PlaidEx — Production-grade Plaid API client for Elixir/OTP.
Features
- Full Plaid API coverage — Link, Transactions, Auth, Identity, Investments, Liabilities, Transfer, Signal, Beacon, Assets, Income, Statements, Institutions, Sandbox, and more
- Typed schemas — all responses are typed structs, never raw maps
- Resilient HTTP — exponential backoff, full jitter, circuit breakers, idempotency, per-tenant rate limiting
- Webhook orchestration — signature verification, deduplication, typed events, Oban integration
- Cursor-based sync — durable transaction sync with OTP-supervised workers, pluggable cursor persistence
- Multi-tenant — runtime credential injection, per-tenant process isolation
- OpenTelemetry — distributed tracing across sync workers and webhook handlers
- Telemetry — deep metrics for all operations
Quick start
# config/config.exs
config :plaid_ex,
client_id: System.get_env("PLAID_CLIENT_ID"),
secret: System.get_env("PLAID_SECRET"),
environment: :sandbox
# Create a Link token (server-side only)
{:ok, link} = PlaidEx.create_link_token(
user: %{client_user_id: "user-abc123"},
client_name: "Acme Finance",
products: ["transactions"],
country_codes: ["US"],
language: "en"
)
# Exchange public token after Link completes
{:ok, result} = PlaidEx.exchange_public_token("public-sandbox-...")
access_token = result.access_token
# Start continuous transaction sync
{:ok, _pid} = PlaidEx.start_transaction_sync(access_token,
handler: fn page ->
MyApp.Transactions.upsert_batch(page.added)
:ok
end
)Multi-tenant quick start
# Register each tenant's credentials at runtime
config = PlaidEx.Config.new!(
client_id: vault.get("tenant/plaid/client_id"),
secret: vault.get("tenant/plaid/secret"),
environment: :production,
tenant_id: "acme_corp"
)
TenantRegistry.register("acme_corp", config)
# Use tenant config for all API calls
{:ok, token} = PlaidEx.create_link_token(config, user: %{...})Webhook setup
# In your Phoenix router
forward "/webhooks/plaid", PlaidEx.Webhooks.Plug,
config: PlaidEx.Config.load!(),
handler: MyApp.PlaidWebhookHandler
# Handler module
defmodule MyApp.PlaidWebhookHandler do
use PlaidEx.Webhooks.Handler
@impl true
def on_transactions_sync(%{item_id: item_id}) do
TransactionSync.trigger_sync(
MyApp.Items.get_access_token!(item_id)
)
:ok
end
endConfiguration reference
See PlaidEx.Config for all available configuration options.
API modules
| Module | Plaid Product |
|---|---|
PlaidEx.API.Link | Link Token lifecycle |
PlaidEx.API.Items | Item management |
PlaidEx.API.Accounts | Account data |
PlaidEx.API.Transactions | Transactions |
PlaidEx.API.Auth | ACH routing numbers |
PlaidEx.API.Identity | Account owner identity |
PlaidEx.API.Investments | Investment holdings/transactions |
PlaidEx.API.Liabilities | Loans, mortgages, credit cards |
PlaidEx.API.Transfer | ACH/RTP transfers |
PlaidEx.API.Signal | ACH return risk |
PlaidEx.API.Beacon | Fraud network |
PlaidEx.API.Assets | Asset reports |
PlaidEx.API.Income | Income verification |
PlaidEx.API.Statements | Bank statements |
PlaidEx.API.Institutions | Institution search/metadata |
PlaidEx.API.Monitor | Watchlist screening |
PlaidEx.API.Processor | Processor token operations |
PlaidEx.API.Sandbox | Sandbox test utilities |
Summary
Functions
Attaches the default structured logging telemetry handler.
Returns the status of all circuit breakers.
Completes an OAuth flow after the user is redirected back.
Returns the current application-level PlaidEx config.
Creates a Plaid Link token using application config.
Creates a Link token with explicit config (for multi-tenant).
Exchanges a Link public_token for a permanent access_token.
Returns accounts for an Item.
Returns real-time balances.
Retrieves a tenant configuration.
Returns the health status of the PlaidEx subsystem.
Initiates an OAuth Link flow for OAuth-required institutions.
Registers a tenant configuration at runtime.
Removes an Item (revokes access token, disconnects accounts).
Manually resets a circuit breaker (use with caution).
Rotates a tenant's API secret without full re-registration.
Starts a continuous cursor-based transaction sync worker.
Stops a running transaction sync worker.
Returns all Telemetry.Metrics definitions for PlaidEx.
Returns status of a transaction sync worker.
Triggers an immediate sync cycle for an access token.
Functions
@spec attach_telemetry(keyword()) :: :ok
Attaches the default structured logging telemetry handler.
Call this in your application start to get automatic logging of all PlaidEx operations.
def start(_type, _args) do
PlaidEx.attach_telemetry()
# ...
end
@spec circuit_breaker_status() :: map()
Returns the status of all circuit breakers.
@spec complete_oauth(keyword()) :: {:ok, map()} | {:error, PlaidEx.Error.t() | atom()}
Completes an OAuth flow after the user is redirected back.
@spec config() :: PlaidEx.Config.t()
Returns the current application-level PlaidEx config.
For multi-tenant usage, use PlaidEx.Config.TenantRegistry.get/1 instead.
@spec create_link_token( keyword() | map(), keyword() ) :: {:ok, PlaidEx.Schemas.LinkToken.t()} | {:error, PlaidEx.Error.t()}
Creates a Plaid Link token using application config.
Example
{:ok, %{link_token: token}} = PlaidEx.create_link_token(
user: %{client_user_id: "user-123"},
client_name: "My App",
products: ["transactions"],
country_codes: ["US"],
language: "en"
)
@spec create_link_token(PlaidEx.Config.t(), keyword() | map(), keyword()) :: {:ok, PlaidEx.Schemas.LinkToken.t()} | {:error, PlaidEx.Error.t()}
Creates a Link token with explicit config (for multi-tenant).
@spec exchange_public_token( String.t(), keyword() ) :: {:ok, PlaidEx.Schemas.AccessToken.t()} | {:error, PlaidEx.Error.t()}
Exchanges a Link public_token for a permanent access_token.
Example
{:ok, %{access_token: token, item_id: id}} =
PlaidEx.exchange_public_token("public-sandbox-abc123")
@spec exchange_public_token(PlaidEx.Config.t(), String.t(), keyword()) :: {:ok, PlaidEx.Schemas.AccessToken.t()} | {:error, PlaidEx.Error.t()}
@spec get_accounts( String.t(), keyword() ) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
Returns accounts for an Item.
@spec get_balances( String.t(), keyword() ) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
Returns real-time balances.
@spec get_tenant_config(String.t()) :: {:ok, PlaidEx.Config.t()} | :not_found
Retrieves a tenant configuration.
@spec health() :: map()
Returns the health status of the PlaidEx subsystem.
Useful for health check endpoints and monitoring dashboards.
GET /health/plaid -> PlaidEx.health()
@spec initiate_oauth(keyword()) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
Initiates an OAuth Link flow for OAuth-required institutions.
See PlaidEx.OAuth.Flow for full documentation.
@spec initiate_oauth( PlaidEx.Config.t(), keyword() ) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
@spec register_tenant(String.t(), PlaidEx.Config.t()) :: :ok
Registers a tenant configuration at runtime.
@spec remove_item( String.t(), keyword() ) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
Removes an Item (revokes access token, disconnects accounts).
@spec remove_item(PlaidEx.Config.t(), String.t(), keyword()) :: {:ok, map()} | {:error, PlaidEx.Error.t()}
@spec reset_circuit_breaker(atom()) :: :ok
Manually resets a circuit breaker (use with caution).
Rotates a tenant's API secret without full re-registration.
@spec start_transaction_sync( String.t(), keyword() ) :: {:ok, pid()} | {:error, :already_started | term()}
Starts a continuous cursor-based transaction sync worker.
The worker runs in a supervised process, polling for new transactions
every sync_poll_interval_ms (default 30s). Handles all retry and
error logic automatically.
The handler function MUST:
- Accept a
PlaidEx.Schemas.TransactionSyncPagestruct - Return
:okon success - Return
{:error, reason}on failure - Be idempotent — may be called multiple times for the same page
Example
{:ok, _pid} = PlaidEx.start_transaction_sync(access_token,
handler: fn page ->
MyApp.Transactions.upsert_batch(page.added)
MyApp.Transactions.update_batch(page.modified)
MyApp.Transactions.remove_batch(Enum.map(page.removed, & &1.transaction_id))
:ok
end,
tenant_id: "acme_corp"
)
@spec start_transaction_sync(PlaidEx.Config.t(), String.t(), keyword()) :: {:ok, pid()} | {:error, :already_started | term()}
@spec stop_transaction_sync(String.t()) :: :ok | {:error, :not_found}
Stops a running transaction sync worker.
@spec telemetry_metrics() :: [Telemetry.Metrics.t()]
Returns all Telemetry.Metrics definitions for PlaidEx.
Returns status of a transaction sync worker.
@spec trigger_transaction_sync(String.t()) :: :ok | {:error, :not_found}
Triggers an immediate sync cycle for an access token.