Multi-currency
The /api/mca routes: your currency balances, their account details, rate
quotes and conversions. Each currency is its own balance. A currency balance
can be in US dollars too, and it's still separate from your USD business
account. For how it fits together, see the
multi-currency guide.
Balances, account details and activity work with test money. Live balances
aren't part of our first launch. Converting is coming soon: it isn't
switched on for beta accounts yet (features.convert is false), so quotes
and conversions answer 422 capability_unavailable. Paying from a balance
is coming soon too, and its routes answer the same.
Conventions
- Two ways in. An
Authorization: Bearer apk_…header means an API key: the route's scope must be on the key itself (an old*key gets403). Without the header, the request uses your dashboard session. Routes marked dashboard only refuse an API key with403 session_required, and their writes are for workspace owners and admins. - Amounts are whole minor units as strings (
amountMinor: "10000"is EUR 100.00), never decimals. Balances carry theirexponent. - Errors always carry
error; most refusals also carry a plainmessage. Another workspace's ids answer404. - Writes check availability first: a step that isn't available answers
422 capability_unavailablebefore anything is held or written. Balance and activity reads are never refused that way; account details and the rate estimate answer422 capability_unavailablewhile multi-currency isn't connected. - Live (not switched on): opening a balance and converting need the
workspace's business verification approved, else
403 kyb_required. Sandbox skips this check. - Rate limits:
429 rate_limited(withRetry-After).
Balances
GET /api/mca/accounts
Bearer apk_ · read:treasury · or dashboard session
Your currency balances, read from SorenPay's ledger.
| Field | What it is |
|---|---|
preview | true only for a workspace that has never been set up while multi-currency isn't connected; the dashboard then shows sample details |
kyb | Business verification for these balances |
accounts[] | One per currency: currency, status, balance (availableMinor, pendingMinor, exponent), scopeStatus and detailsStatus |
coverage.currencies[] | What this workspace can open: currency, receive, convert, and a reason when it can't hold that currency |
features | convert, estimate and send: which balance actions your account has right now (left out when unknown) |
scopeStatus is ok, checking or paused. While it isn't ok we're
double-checking that balance with our partner, and it can't be converted from.
GET /api/mca/accounts/{currency}/details
Bearer apk_ · read:treasury · or dashboard session
The account details to get paid in that currency:
{currency, status, details: [{rail, holderName, holderKind, status, fields}]}.
Errors: 400 invalid_currency, 404 not_found, and
422 capability_unavailable in preview.
POST /api/mca/accounts/{currency}
Dashboard only (owner or admin) · Idempotent
Open a balance in a currency ("Add currency"). Idempotency-Key is required.
Errors: 400 invalid_currency, idempotency_key_required,
invalid_idempotency_key; 409 currency_account_exists;
422 currency_unavailable, currency_restricted, currency_not_eligible,
capability_unavailable.
GET /api/mca/activity?currency=EUR
Bearer apk_ · read:treasury · or dashboard session
What happened in one currency balance: money in, conversions, charges and
holds, as {data}. Error: 400 currency_required.
Onboarding
GET /api/mca/onboarding
Bearer apk_ · read:treasury · or dashboard session
{kybStatus, actionRequired, resubmissionAllowed, sandboxAutoAccepted}.
POST /api/mca/onboarding
Dashboard only (owner or admin)
Start or continue onboarding. In sandbox your workspace is set up and accepted in one call, with no verification step.
Quotes and conversions
POST /api/mca/quotes
Bearer apk_ · write:treasury · or dashboard session (owner or admin) · Idempotent
Lock a rate. Idempotency-Key is required. Both the sell and the buy
balance must be open and ready before you quote: to buy a currency you don't
hold yet, open its balance first. Buying USD credits a USD currency balance,
not your USD business account.
{
"sellCurrency": "EUR",
"buyCurrency": "GBP",
"amountMinor": "10000",
"fixedSide": "sell"
}fixedSide says which amount is fixed (sell by default). Answers 201:
{id, status, sell, buy, rate, rateDisplay, includesOurFee, hold, lockExpiresAt, serverNow}.
sell, buy and hold are {currency, amountMinor, display}. hold is
what converting sets aside, which your balance must cover. Count the lock
down from lockExpiresAt − serverNow.
Errors: 400 invalid_body, same_currency; 404 currency_account_not_found
(you don't hold the sell or the buy currency); 409 account_not_ready (a
balance is still being set up), onboarding_required (multi-currency isn't
set up for your workspace yet); 422 capability_unavailable,
currency_unavailable, account_suspended; 429 rate_limited (quotes per
workspace per minute); 503 quote_lifetime_too_short.
POST /api/mca/conversions
Bearer apk_ · write:treasury · or dashboard session (owner or admin) · Idempotent
Convert at a quote's locked rate: {"quoteId": "…"}. Idempotency-Key is
required. Answers 202 {orderId, status} with status held or
provider_submitted: accepted, not finished.
Errors: 404 quote_not_found; 422 quote_used, quote_expired,
insufficient_funds, balance_paused, limit_exceeded, account_suspended,
capability_unavailable; live only: 403 kyb_required.
GET /api/mca/conversions/{id}
Bearer apk_ · read:treasury · or dashboard session
{id, status, label, sell, buy, createdAt}, with label in plain words.
status is one of held, provider_submitted, outcome_unknown,
completed, rejected, failed, cancelled or abandoned. Only
completed means the money has moved; buy then shows the amount that
arrived. Error: 404 not_found.
GET /api/mca/rates?from=EUR&to=GBP&amount=100.50
Dashboard only
An indicative estimate while someone types:
{from, to, fixedSide, indicative: true, rate, rateDisplay, asOf, sell, buy}.
Nothing is locked or held; converting still needs a quote. amount is a
plain decimal in the fixed side's currency (leave it out for the rate of one
unit), and fixedSide is sell (default) or buy.
Errors: 400 invalid_currency, invalid_amount; 422 with a currency_…
code (a currency you can't use), same_currency, invalid_amount,
rate_unavailable or capability_unavailable (multi-currency isn't
connected).
Payouts from a balance
No partner serves payouts from a currency balance yet, so the two POST
routes below answer 422 capability_unavailable and
GET /api/mca/accounts reports features.send: false.
POST /api/mca/payouts/quotes
Dashboard only (owner or admin) · Idempotent
Lock the rate for paying a saved recipient in their currency from a balance:
{counterpartyId, sourceCurrency, recipientCurrency, amountMinor}, where
amountMinor is what the recipient gets.
POST /api/mca/payouts
Dashboard only (owner or admin) · Idempotent
Pay a saved recipient from a balance:
{counterpartyId, sourceCurrency, recipientCurrency, amountMinor, quoteId?, reference?}.
GET /api/mca/payouts
Bearer apk_ · read:treasury · or dashboard session
Payouts from your currency balances, newest first (?status=in_progress for
the ones still in progress): {data: [{id, status, inProgress, label, recipient, from, to, rate, createdAt}]}.
Sandbox only
Add test money is a button on a balance in the dashboard of a sandbox
workspace (POST /api/dev/mca/simulate-payin, dashboard only). The test money
arrives in the balance the way a real payment would: a little later, not in
the response.