PlaidEx (plaid_ex v1.0.0)

Copy Markdown View Source

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
end

Configuration reference

See PlaidEx.Config for all available configuration options.

API modules

ModulePlaid Product
PlaidEx.API.LinkLink Token lifecycle
PlaidEx.API.ItemsItem management
PlaidEx.API.AccountsAccount data
PlaidEx.API.TransactionsTransactions
PlaidEx.API.AuthACH routing numbers
PlaidEx.API.IdentityAccount owner identity
PlaidEx.API.InvestmentsInvestment holdings/transactions
PlaidEx.API.LiabilitiesLoans, mortgages, credit cards
PlaidEx.API.TransferACH/RTP transfers
PlaidEx.API.SignalACH return risk
PlaidEx.API.BeaconFraud network
PlaidEx.API.AssetsAsset reports
PlaidEx.API.IncomeIncome verification
PlaidEx.API.StatementsBank statements
PlaidEx.API.InstitutionsInstitution search/metadata
PlaidEx.API.MonitorWatchlist screening
PlaidEx.API.ProcessorProcessor token operations
PlaidEx.API.SandboxSandbox 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

attach_telemetry(opts \\ [])

@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

circuit_breaker_status()

@spec circuit_breaker_status() :: map()

Returns the status of all circuit breakers.

complete_oauth(opts)

@spec complete_oauth(keyword()) :: {:ok, map()} | {:error, PlaidEx.Error.t() | atom()}

Completes an OAuth flow after the user is redirected back.

config()

@spec config() :: PlaidEx.Config.t()

Returns the current application-level PlaidEx config.

For multi-tenant usage, use PlaidEx.Config.TenantRegistry.get/1 instead.

exchange_public_token(public_token, opts \\ [])

@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")

exchange_public_token(config, public_token, opts)

@spec exchange_public_token(PlaidEx.Config.t(), String.t(), keyword()) ::
  {:ok, PlaidEx.Schemas.AccessToken.t()} | {:error, PlaidEx.Error.t()}

get_accounts(access_token, opts \\ [])

@spec get_accounts(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, PlaidEx.Error.t()}

Returns accounts for an Item.

get_balances(access_token, opts \\ [])

@spec get_balances(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, PlaidEx.Error.t()}

Returns real-time balances.

get_tenant_config(tenant_id)

@spec get_tenant_config(String.t()) :: {:ok, PlaidEx.Config.t()} | :not_found

Retrieves a tenant configuration.

health()

@spec health() :: map()

Returns the health status of the PlaidEx subsystem.

Useful for health check endpoints and monitoring dashboards.

GET /health/plaid -> PlaidEx.health()

initiate_oauth(opts)

@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.

initiate_oauth(config, opts)

@spec initiate_oauth(
  PlaidEx.Config.t(),
  keyword()
) :: {:ok, map()} | {:error, PlaidEx.Error.t()}

register_tenant(tenant_id, config)

@spec register_tenant(String.t(), PlaidEx.Config.t()) :: :ok

Registers a tenant configuration at runtime.

remove_item(access_token, opts \\ [])

@spec remove_item(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, PlaidEx.Error.t()}

Removes an Item (revokes access token, disconnects accounts).

remove_item(config, access_token, opts)

@spec remove_item(PlaidEx.Config.t(), String.t(), keyword()) ::
  {:ok, map()} | {:error, PlaidEx.Error.t()}

reset_circuit_breaker(environment)

@spec reset_circuit_breaker(atom()) :: :ok

Manually resets a circuit breaker (use with caution).

rotate_tenant_secret(tenant_id, new_secret)

@spec rotate_tenant_secret(String.t(), String.t()) :: :ok | :not_found

Rotates a tenant's API secret without full re-registration.

start_transaction_sync(access_token, opts)

@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:

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"
)

start_transaction_sync(config, access_token, opts)

@spec start_transaction_sync(PlaidEx.Config.t(), String.t(), keyword()) ::
  {:ok, pid()} | {:error, :already_started | term()}

stop_transaction_sync(access_token)

@spec stop_transaction_sync(String.t()) :: :ok | {:error, :not_found}

Stops a running transaction sync worker.

telemetry_metrics()

@spec telemetry_metrics() :: [Telemetry.Metrics.t()]

Returns all Telemetry.Metrics definitions for PlaidEx.

transaction_sync_status(access_token)

@spec transaction_sync_status(String.t()) :: {:ok, map()} | {:error, :not_found}

Returns status of a transaction sync worker.

trigger_transaction_sync(access_token)

@spec trigger_transaction_sync(String.t()) :: :ok | {:error, :not_found}

Triggers an immediate sync cycle for an access token.