Configuration Reference

Copy Markdown View Source

PlaidEx is configured using NimbleOptions-validated structs. Every option is validated at startup with clear error messages for invalid values.

Application config (single-tenant)

# config/runtime.exs
config :plaid_ex,
  client_id: System.fetch_env!("PLAID_CLIENT_ID"),
  secret: System.fetch_env!("PLAID_SECRET"),
  environment: :production,
  region: :us,
  webhook_secret: System.fetch_env!("PLAID_WEBHOOK_SECRET"),
  pool_size: 30,
  pool_count: 4,
  request_timeout_ms: 30_000,
  connect_timeout_ms: 5_000,
  retry_max_attempts: 3,
  retry_base_delay_ms: 500,
  retry_max_delay_ms: 30_000,
  circuit_breaker_threshold: 5,
  circuit_breaker_reset_ms: 30_000,
  sync_poll_interval_ms: 30_000,
  oban_queue: :plaid_webhooks,
  oban_max_attempts: 10,
  telemetry_prefix: [:plaid_ex]

Runtime struct construction (multi-tenant)

config = PlaidEx.Config.new!(
  client_id: vault.get("tenant/plaid/client_id"),
  secret: vault.get("tenant/plaid/secret"),
  environment: :production,
  region: :us,
  tenant_id: "acme_corp",
  metadata: %{plan: "enterprise", onboarded_at: "2024-01-15"}
)

Option reference

Required

OptionTypeDescription
client_idstringYour Plaid client_id from the Dashboard
secretstringPlaid secret — differs per environment

Environment

OptionTypeDefaultDescription
environment:sandbox | :development | :production:sandboxPlaid environment
region:us | :eu | :uk:usAPI region for endpoint routing

Base URLs by environment and region:

EnvironmentRegionURL
:production:ushttps://production.plaid.com
:production:eu or :ukhttps://production.eu.plaid.com
:developmentanyhttps://development.plaid.com
:sandboxanyhttps://sandbox.plaid.com

HTTP / connection pool

OptionTypeDefaultDescription
pool_sizepos_integer20Finch connections per endpoint
pool_countpos_integer4Parallel pools per endpoint
request_timeout_mspos_integer30_000Request timeout (30s)
connect_timeout_mspos_integer5_000TCP connect timeout (5s)

Total connections = pool_size * pool_count

Rule of thumb for production:

connections_needed = peak_rps × avg_latency_seconds × 1.5 (safety factor)

For 100 req/s with 200ms average:

connections_needed = 100 × 0.2 × 1.5 = 30 connections
pool_size: 8, pool_count: 4  # = 32 connections

Retry

OptionTypeDefaultDescription
retry_max_attemptsnon_neg_integer3Max retries (0 = no retries)
retry_base_delay_mspos_integer500Backoff base delay
retry_max_delay_mspos_integer30_000Backoff cap

Retry only occurs for errors classified as retryable: true:

  • RATE_LIMIT_EXCEEDED
  • INTERNAL_SERVER_ERROR
  • INSTITUTION_DOWN
  • INSTITUTION_NOT_RESPONDING
  • PRODUCT_NOT_READY
  • PLANNED_MAINTENANCE
  • Network timeouts and connection errors

Backoff formula (full jitter):

delay = random_uniform(min(base_ms * 2^attempt, max_delay_ms))
AttemptJitter range (base=500ms, max=30s)
10–500ms
20–1s
30–2s
40–4s
50–8s
N≥90–30s (capped)

Circuit breaker

OptionTypeDefaultDescription
circuit_breaker_thresholdpos_integer5Failures to open circuit
circuit_breaker_reset_mspos_integer30_000Recovery probe interval

Errors that trigger the circuit breaker:

  • All status >= 500 responses
  • error_type in [:institution_error, :api_error]

Errors that do NOT trigger the circuit breaker:

  • ITEM_LOGIN_REQUIRED (user error)
  • INVALID_INPUT (developer error)
  • RATE_LIMIT_EXCEEDED (handled by retry)

Webhooks

OptionTypeDefaultDescription
webhook_secretstring | nilnilWebhook signing secret
oban_queueatom:plaid_webhooksOban queue name
oban_max_attemptspos_integer10Max Oban job retries

Sync

OptionTypeDefaultDescription
sync_poll_interval_mspos_integer30_000Poll interval when caught up

Plaid recommends polling no more frequently than every 30 seconds. Use webhooks (SYNC_UPDATES_AVAILABLE) to trigger immediate syncs.

Caching

OptionTypeDefaultDescription
cache_institutions_ttl_mspos_integer86_400_000Institution cache TTL (24h)

Observability

OptionTypeDefaultDescription
telemetry_prefix[atom][:plaid_ex]Prefix for all telemetry events

Change this if you have naming conflicts:

telemetry_prefix: [:my_app, :plaid]
# Events become: [:my_app, :plaid, :http, :stop], etc.

Multi-tenant

OptionTypeDefaultDescription
tenant_idstring | nilnilTenant identifier
metadatamap%{}Arbitrary metadata for tracing

Pluggable backends

Cursor store

# config/config.exs
config :plaid_ex, cursor_store: MyApp.PlaidCursorStore

# Implement the behaviour:
defmodule MyApp.PlaidCursorStore do
  @behaviour PlaidEx.Sync.CursorStore.Behaviour

  @impl true
  def get(access_token), do: # fetch from DB
  @impl true
  def put(access_token, cursor), do: # persist to DB
  @impl true
  def delete(access_token), do: # delete from DB
end

Webhook handler (for Oban)

config :plaid_ex, webhook_handler: MyApp.PlaidWebhooks

Used by PlaidEx.Webhooks.ObanWorker to look up the handler module when processing jobs asynchronously.

config :plaid_ex, app_name: "Acme Finance"

Used in OAuth flow Link token creation as the client_name.

OAuth state TTL

config :plaid_ex, oauth_state_ttl_seconds: 600  # 10 minutes (default)

Webhook dedup window

config :plaid_ex, webhook_dedup_window_seconds: 3600  # 1 hour (default)

Environment-specific patterns

Development

# config/dev.exs
config :plaid_ex,
  environment: :sandbox,
  pool_size: 4,
  pool_count: 1,
  retry_max_attempts: 0,   # fail fast in dev
  webhook_secret: nil      # skip verification in dev

Test

# config/test.exs
config :plaid_ex,
  client_id: "test_client_id",
  secret: "test_secret",
  environment: :sandbox,
  pool_size: 2,
  pool_count: 1,
  retry_max_attempts: 0,    # no retries in tests
  request_timeout_ms: 5_000 # fast timeout

Production

# config/runtime.exs
config :plaid_ex,
  client_id: System.fetch_env!("PLAID_CLIENT_ID"),
  secret: System.fetch_env!("PLAID_SECRET"),
  environment: :production,
  region: :us,
  webhook_secret: System.fetch_env!("PLAID_WEBHOOK_SECRET"),
  pool_size: 25,
  pool_count: 4,
  retry_max_attempts: 3,
  circuit_breaker_threshold: 5,
  circuit_breaker_reset_ms: 30_000,
  sync_poll_interval_ms: 30_000,
  oban_queue: :plaid_webhooks,
  oban_max_attempts: 10

Validation errors

PlaidEx raises ArgumentError on invalid config with a detailed message:

PlaidEx.Config validation failed:

  invalid value for :environment option:
  expected one of [:sandbox, :development, :production], got: :prod

See `PlaidEx.Config` documentation for all valid options.

Config introspection

# Get the current loaded config
config = PlaidEx.config()
config.environment  # :production
config.pool_size    # 25

# Get scrubbed config (safe for logging)
PlaidEx.Config.scrub(config)
# %{client_id: "your-id", secret: "[REDACTED]", webhook_secret: "[REDACTED]", ...}

# Check if production
PlaidEx.Config.production?(config)  # true

# Get the base URL
PlaidEx.Config.base_url(config)  # "https://production.plaid.com"

# Rotate secret (returns new config, does not mutate)
new_config = PlaidEx.Config.rotate_secret(config, new_secret)