Accounts and balances
A business holds money in currency accounts. Four currencies — EUR, GBP,
USD and CAD — are local accounts with their own bank details (an IBAN, a sort
code and account number, a routing number); the others are held over SWIFT.
Every account is identified by an opaque id such as
mockacct_7d2f19c4e8a04b6f9e1c3a5b7d9f0e21_eur, which you pass as
source_account_id on payouts and account_id on conversions. Treat it as a
string — its shape differs between the sandbox and production rails.
All routes on this page are on the money API: secret key, master key or dashboard session.
In the sandbox you can also open accounts in USDC, EURC, USDT, USDB, PYUSD
and PATHUSD with the same POST /v1/banking/accounts. They are listed here
with kind digital in currency_accounts and offered_currencies, carry
on-chain identifiers and digital_actions, and their balance adds
by_network. See Accounts and wallets.
List accounts
GET /v1/banking/accounts
{
"accounts": [
{
"id": "mockacct_7d2f19c4e8a04b6f9e1c3a5b7d9f0e21_eur",
"account_holder_name": "Kettlewick Trading GmbH",
"display_name": "EUR account",
"currency": "EUR",
"status": "active",
"identifiers": [{"scheme": "iban", "iban": "DE39500000002907995833", "bic": "MOCKDEFFXXX"}],
"payin_rails": ["sepa", "sepa_instant"],
"opened_at": "2026-01-15T09:00:00Z",
"bank_details": {"local": {"…": "…"}, "international": [{"…": "…"}]},
"standard_qr": [{"scheme": "epc", "block": "local", "payload": "BCD\n002\n1\nSCT\n…"}]
}
],
"refreshing": false,
"primary_currency": "EUR",
"currency_accounts": [
{"currency": "EUR", "kind": "local", "status": "active", "is_primary": true, "created_at": "2026-10-15T17:14:05Z"}
],
"plan": {"tier": "free", "max_named_accounts": 2, "max_currencies": 5, "named_accounts_used": 1, "currencies_used": 1},
"offered_currencies": [{"currency": "EUR", "kind": "local"}, {"currency": "CHF", "kind": "swift"}]
}
accounts— the bank accounts in the currencies you hold, with everything a payer needs:identifiers,bank_details(local and international details, ornullif the rail gave none), and for EUR an EPC QR payload. The rail may hold more — in production our partner opens all four local currencies on approval — but an account stays out of this list until you hold its currency. At first you hold one: the local currency for the country the business is registered in. Add another withPOST /v1/banking/accounts.refreshing—truewhenaccountsis our last known copy and a fresh read from the rail is under way; ask again in a few seconds. In production the list is read from the partner and kept for up to 30 days, refreshed after five minutes.currency_accounts— the currencies you hold or have asked for.statusisactiveorrequested;kindislocal,swiftor, in the sandbox,digital. An entry inaccountshas nokind: match it to its entry here bycurrency.plan— how many local accounts and currencies your plan allows and how many you use.offered_currencies— what you can open: EUR, GBP, USD and CAD aslocal; CHF, AUD, JPY, NZD, SEK, DKK, NOK, PLN, RON, HUF, SGD, HKD, CNH, THB, AED, SAR, BHD, ILS, TRY, MXN and ZAR asswift. In the sandbox, the digital assets offered asdigitaltoo.own_account_payoutson each account — the currencies it may pay to another of your own accounts in, withdestination_account_id:["EUR"]on a USDC account that can send back to your EUR account, and on an EUR account that can convert into your USDC account;[]everywhere else. See Sending.degraded_partners— present only when a rail could not be reached and we held no copy of its accounts. That rail's accounts are then missing fromaccounts.reasonis for diagnosis, not display. With nothing to report, the key is absent — nevernullor[]. When no rail answers at all, the call fails with503instead — with production's single rail, that is what you will see. A rail you have no relationship with is not asked, so it is never listed.closing_accounts— accounts being closed:currency,reason(customerorplan_limit),since,convert_toandlast_error. They are no longer incurrency_accounts.
status on an account is pending, active, suspended or closed.
In production, an account that has not been approved yet has nothing at the
rail, and this call can answer 503 service_unavailable rather than an empty
list until verification completes.
Opening an account
POST /v1/banking/accounts
Idempotency-Key: open-eur-2026-10
Content-Type: application/json
{"currency": "EUR"}
201 Created:
{
"currency_account": {"currency": "EUR", "kind": "local", "status": "active", "is_primary": false, "created_at": "…"},
"account": {"id": "…", "currency": "EUR", "identifiers": ["…"], "…": "…"}
}
What happens depends on the rail:
- Sandbox: the account is opened on the spot and
accountcarries its bank details. - Production: our partner opens EUR, GBP, USD and CAD when it approves the
business, so adding one of those returns the existing account at once. For
any other currency the answer is
status: "requested"with noaccount— it appears inGET /v1/banking/accountsonce the partner has opened it.
| Error | When |
|---|---|
409 conflict | "you already hold a EUR account". Also what a retry with the same Idempotency-Key returns — there is no replay of the original 201 on this route. |
403 currency_limit_reached | Your plan's currency limit is used up |
403 named_account_limit_reached | Your plan's local-account limit is used up (local currencies only) |
400 bad_request | "XYZ is not a currency we can open accounts in" |
Idempotency-Key is required.
Primary currency
PUT /v1/banking/accounts/primary
Content-Type: application/json
{"currency": "GBP"}
200 {"primary_currency": "GBP"}. Only an active local account can be primary —
otherwise 422 primary_not_eligible.
Closing an account
POST /v1/banking/accounts/close
Content-Type: application/json
{"currency": "GBP"}
200 with status closed or closing, a step (closed, converting,
waiting_for_pending, retrying) and convert_to — any remaining balance is
converted into the primary currency before the account closes. You cannot
close the primary account (422 primary_account); an account you do not hold
is 404. This route is stopped by a platform lockdown.
GET /v1/banking/accounts/plan-impact?plan=starter shows which accounts a
plan change would close: {plan, max_named_accounts, max_currencies, accounts_to_close}.
plan is one of free, starter, professional, business, enterprise.
Bank details for incoming payments
GET /v1/banking/accounts/{account_id}/payin-instructions
{
"instructions": [
{
"rail": "sepa",
"currency": "EUR",
"account_holder_name": "Kettlewick Trading GmbH",
"identifier": {"scheme": "iban", "iban": "DE39500000002907995833", "bic": "MOCKDEFFXXX"},
"bank_name": "Mock Bank AG",
"bank_address": "Sandboxplatz 1, 80331 Munich, DE"
}
]
}
One entry per rail that can pay into the account. This is what you give a customer who pays you. In the sandbox the details are simulated and no real payment can reach them.
Balances
GET /v1/banking/accounts/{account_id}/balance
{
"balances": [
{
"account_id": "mockacct_7d2f19c4e8a04b6f9e1c3a5b7d9f0e21_eur",
"currency": "EUR",
"available": "60.0000",
"pending": "0",
"as_of": "2026-10-15T20:02:01Z"
}
],
"refreshing": false
}
- Sandbox: the test ledger, exact at the moment of the call.
- Production: read from the partner and cached. A figure younger than 15
seconds is served as is. An older one is served at once with its
as_oftime,refreshing: true, and a fresh read started in the background — ask again a moment later. A copy older than a day is not served: that read waits for the partner. A payout, a conversion or an incoming payment drops our copy, but another of our servers may still answer from its own for up to 15 seconds.
Show as_of to anyone who will act on the number.
GET /v1/accounts/me/balance (developer surface, payments:read) is not a
bank balance. It is the dashboard's summary, read from our own ledger:
{"totals": [{"currency", "amount", "available", "pending"}]} with an EUR
and a USD entry, and one per digital asset our ledger holds, under its own
code — never folded into EUR or USD; every other currency, GBP and CAD
included, is left out, and
pending is always 0. A digital-asset account's balance is read from its
wallets, not this ledger. Use the per-account balance above.
Statements
POST /v1/banking/accounts/{account_id}/statements
Content-Type: application/json
{"from": "2026-10-01", "to": "2026-10-15"}
201 with a statement: statement_id, the period, the account and holder,
opening_balance, total_credits, total_debits, closing_balance, and
lines — each with booked_at, kind (deposit, payment, transfer,
conversion, test_funds, fee, reversal, adjustment, other),
counterparty, reference, amount and balance_after. Statements carry a
checksum and sandbox: true|false.
The period may be at most 366 days and may not end in the future
(400 bad_request otherwise).
Anyone holding a printed statement — an auditor, a tax advisor, a bank — can check it against our record without credentials:
GET /v1/public/statements/{statement_id}
It returns the statement's summary figures and checksum, never its lines.