A complete, production-grade Elixir client for the Mercury Banking API — accounts, transactions, recipients, invoices, payments, treasury, webhooks, and more.
Ported from and kept in parity with the
reference Go SDK, adapted to
idiomatic Elixir: {:ok, _} | {:error, _} tuples with ! raising
variants, typed exception structs, lazy auto-paginating Streams, and a
Req-based transport with automatic retry.
Installation
def deps do
[
{:mercury, "~> 1.0"}
]
endQuick start
client = Mercury.Client.new("secret-token:mercury_production_...")
{:ok, account} = Mercury.Accounts.get(client, "account-id")
{:ok, %Mercury.Page{items: accounts}} = Mercury.Accounts.list(client, limit: 25)
# Lazy, auto-paginating stream across every page:
client
|> Mercury.Accounts.stream()
|> Enum.each(&IO.inspect/1)
# Every function has a `!` variant that raises instead of returning a tuple:
account = Mercury.Accounts.get!(client, "account-id")Configuration
Mercury.Client.new("secret-token:mercury_production_...",
base_url: "https://api.mercury.com/api/v1", # override for a sandbox
timeout: 30_000, # per-request timeout, ms
retry: %Mercury.Retry{max_retries: 3}, # see Mercury.Retry
on_request: fn method, url, request_id ->
Logger.debug("mercury request #{method} #{url} #{request_id}")
end,
on_response: fn method, url, request_id, status, duration_ms ->
Logger.debug("mercury response #{method} #{url} #{status} #{duration_ms}ms")
end,
req_options: [plug: {Req.Test, MyStub}] # escape hatch for tests, proxies, etc.
)Errors
Every error is one of the typed exceptions in Mercury.*Error:
Mercury.AuthenticationError, Mercury.NotFoundError,
Mercury.ValidationError, Mercury.ConflictError,
Mercury.RateLimitError, Mercury.ServerError, Mercury.NetworkError,
Mercury.TimeoutError, and the catch-all Mercury.APIError.
case Mercury.Accounts.get(client, id) do
{:ok, account} ->
account
{:error, error} ->
cond do
Mercury.Error.not_found_error?(error) -> :not_found
Mercury.Error.rate_limit_error?(error) -> :try_again_later
true -> reraise error, __STACKTRACE__
end
endRate limit (429) and server (5xx) errors are automatically retried with
exponential backoff and jitter — see Mercury.Retry.
Resources
| Module | Covers |
|---|---|
Mercury.Accounts | accounts, cards, statements, sending money |
Mercury.Transactions | cross-account transaction lookup, updates, attachments |
Mercury.Recipients | payment recipients, tax form attachments |
Mercury.Invoices | accounts-receivable invoices |
Mercury.Payments | internal transfers, send-money approval requests |
Mercury.Categories | expense categories |
Mercury.Customers | accounts-receivable customers |
Mercury.Treasury | treasury accounts, transactions, statements |
Mercury.Webhooks | webhook endpoints + inbound signature verification |
Mercury.Events | audit log |
Mercury.Organization | org info, partner onboarding |
Mercury.Users | platform users |
Mercury.SafeRequests | SAFE (equity) requests |
Mercury.Credit | credit accounts |
Mercury.Attachments | file attachment metadata |
Mercury.OAuth2 | partner OAuth2 authorization flow |
Webhooks
defmodule MyAppWeb.MercuryWebhookController do
use MyAppWeb, :controller
def create(conn, _params) do
raw_body = conn.assigns.raw_body # captured by a raw-body-preserving plug
signature = conn |> Plug.Conn.get_req_header("mercury-signature") |> List.first()
if Mercury.Webhooks.verify_signature(raw_body, signature, signing_secret()) do
# handle the event
send_resp(conn, 200, "")
else
send_resp(conn, 401, "invalid signature")
end
end
endConfirm the exact header name and signing scheme against the current Mercury webhooks documentation — see the
Mercury.Webhooks.verify_signature/3docs for details.
Naming conventions
- Response structs use snake_case fields (
account.legal_business_name), converted from the API's camelCase. create/updatefunctions accept snake_case keys in a map or keyword list and camelize them automatically before sending.- Enum-like values (payment methods, wire types, statuses) should be
given as atoms or strings that already match Mercury's wire format
exactly, e.g.
:domesticWire,"businessChecking". - Deeply nested, provider-specific objects (routing info, merchant data, currency exchange info) are returned as plain maps rather than fully modeled structs — consult the Mercury API reference for their shape.
Mercury.Transactiongroups its least-frequently-used nested fields (glAllocations,attachments,relatedTransactions,categoryData,merchant,currencyExchangeInfo,details) under a singletransaction.extramap with their original camelCase keys, to keep the struct under BEAM's 32-field threshold.
Development
mix deps.get
mix test
mix credo --strict
mix dialyzer
mix docs
License
MIT — see LICENSE.