One customer, many accounts
An account belongs to exactly one customer and holds exactly one currency. There is no multi-currency account. A customer can hold several accounts in the same currency — useful when you want separate coordinates per payer, per site or per business line. Customers can hold up to 10 accounts per currency, counting active and pending accounts combined. Requests beyond this limit are refused with409 ERRACC_1018.
The two categories
Every account carries acategory on the account object.
FIAT — holds what it receives
- Available for AED only at this time
- No
walletIdneeded — nothing settles onward - The response has no
destinationblock - Deposit fees arrive as a display string, not a structure
ONRAMP — converts on arrival
- Settles into the wallet you name as
walletId— required - The response carries
destinationwith asset and chain - Deposit fees arrive per rail, as structured numbers
- Required for every currency except AED FIAT
Choosing between them
Where accounts are available
Six currencies can be opened today, across two providers. Which rails a given account exposes is decided by the provider when it is created — treat the rail list as data to iterate, not a constant to hardcode.Creating an account
Opening an account is one call, but the call is not always the end of the story.Make sure the customer exists
cus_… reference id — that is the {userId} in every accounts path. A
customer belonging to another partner is a 403, not a 404.Make sure they are eligible
ONRAMP account the customer also needs a
wallet, because walletId is required
and the settlement asset, chain and address are taken from it. A FIAT account
needs no wallet.Ask what they can open
Open it
currency, accountName and accountCategory are always required, and
walletId as well when the category is ONRAMP.displayName is worth setting, because it is the one label you control — and
it cannot be changed afterwards through this API.Handle both possible answers
200, but one of two bodies. If the response has an id,
the account exists. If it has no id and status: PENDING, it is still
being created — do not treat the missing id as an error.Wait for it to be ready
id, the account already exists and an
account.opened webhook is emitted for it.If it did not, poll List accounts
until the account appears. A queued open emits no webhook at all — the
response returns before the account exists, so there is nothing to fire an
event about.For AED, polling Get account is
additionally what causes the IBAN to appear, so once the account exists poll
that rather than List.Fetch the funding instructions
fundingInstructions, and the only one that
returns deposit fees at all. Open account may include a partial block, but
never the fees — do not build your payer-facing screen from it.Funding an account
Once an account is active,fundingInstructions.source holds everything a payer
needs. It contains a list of payoutRails — one entry per way the account can be
paid — and each entry carries only the coordinate fields that rail actually uses.
Render rails, do not index them. A USD account typically exposes three rails;
a GBP account typically exposes one. Which fields are populated differs per
currency: a routing number for USD, an IBAN for EUR and AED, a sort code for GBP,
a CLABE for MXN, a BR Code for BRL. Fields that do not apply are omitted
entirely, not returned as null — so iterate the array and render whichever keys
are present.
paymentReference and
bank.addressDetails are part of the response model but are not currently
populated. Do not build deposit attribution on paymentReference, and read
bank.address rather than the structured address.Deposit fees come in two shapes
They are mutually exclusive, and which one you get follows the account type.depositFee: the percentage is trimmed
("0.5" means 0.5%) while the flat amount keeps its decimals ("10.00").
depositFeeNote is free text — show it verbatim and do not try to parse a
number out of it.
Lifecycle
An account reports one of seven statuses. They are deliberately not collapsed into fewer, because only one of them is something you can undo yourself.Turning an account off and on
Deactivating is a soft switch, not a deletion — there is no delete on this API at all. The change is queued rather than applied inline, so the response tells you what you asked for (requested) alongside what the account currently says
(status). They differ until the change lands. Poll
Get account until status reaches
requested.
Reading balances and history
Get account balance gives you one number plus anasOf timestamp saying when it was accurate. For AED that value
is served from storage and refreshed behind the scenes, so it can lag; every
other currency is fetched live on each call. Respect asOf rather than assuming
“now”.
Get account activity lists the
account’s movements, newest first. Two things to plan for:
- No pagination and no date filter, so a busy account returns its whole history in one response.
- Amounts have no sign — a credit and a debit of the same size look identical
except for
type.
status uses mixed casing and a hyphen — Completed, Pending,
In-Progress — and unrecognised values pass through unchanged. Compare
case-insensitively.Error handling
Every error returns the same envelope, so you only need one parser. Branch onerrors[0].code, not on the message text — the codes are frozen and will never
be renumbered or reused, while messages may be reworded.
When the failure is about one specific input, errors[0].field names it, so you
can bind the message straight to a form field. When it is not about an input —
authentication, rate limits, a provider outage — it is null rather than filled
with prose.
Webhooks
The accounts surface produces exactly two events. Delivery, signing, replay protection and the envelope shape are covered in the Webhooks overview — this is only what is specific to accounts.deposit.received event, not an account event.
iban.assigned against an acct_ id. Handle it alongside these two if you
subscribe to account activity.What never produces an event
- A queued open never fires
account.opened. If Open account returned noid, nothing emits an event when the account later appears — poll List accounts. - Status changes Endl makes on its own emit nothing. An account closed or
frozen by the banking partner or by compliance sends no
account.updated. Re-read accounts you care about on a schedule. - Funding instructions becoming available emits nothing. An AED account whose IBAN is issued after opening produces no event. Poll Get account.
Common pitfalls
I am parsing data from a successful response and getting nothing
I am parsing data from a successful response and getting nothing
200 body is the object itself —
there is no data key to unwrap. Only errors use the five-key envelope.I passed a UUID as {userId} and got a 404
I passed a UUID as {userId} and got a 404
cus_…, accounts are
acct_…. A wrongly-shaped id is a 400; a well-formed but unknown one is a
404.Open account returned 200 but there is no id
Open account returned 200 but there is no id
I am waiting on account.opened and it never arrives
I am waiting on account.opened and it never arrives
id, the account was queued and no
webhook is emitted for it — the response returned before the account existed.
Poll GET /api/v0/accounts/{userId} until it appears. account.opened fires
only for accounts created during the call itself.My AED account is stuck on PENDING with no IBAN
My AED account is stuck on PENDING with no IBAN
I asked for a FIAT GBP account and got ONRAMP back
I asked for a FIAT GBP account and got ONRAMP back
accountCategory. Always read category off
the response.An account vanished — Get account returns 409
An account vanished — Get account returns 409
My balances do not add up to the accounts I can see
My balances do not add up to the accounts I can see
status filter, balances covers only active accounts even
though deactivated ones appear in data. Pass status explicitly so the two
agree.Glossary
Api-Version: 2026-09.1. Rail sets and bank details are supplied
by the banking partner behind each currency and can change — read them from the
API rather than hardcoding them.