Changelog
This changelog tracks public documentation and API-facing product updates.
2026-11-04
Digital assets (sandbox): sends settle on time without the provider's webhooks
The public sandbox receives no webhooks from the provider, and its sends
waited on two slow reads: in the run of 2026-11-03 the sends the sandbox
settles itself settled 2.8 to 3.5 minutes after acceptance. The sandbox now reads them sooner. No field, route
or status changes. See
When a send reads settled.
With the default one-minute wait, sends the sandbox settles itself settle 60 to 75 seconds after acceptance — to an IBAN, the send-back to your EUR account, over Faster Payments, SPEI or Pix, a swap — where the provider has listed the debit by about 45 seconds, and otherwise within about 15 seconds of the first read of the wallet after it is listed. The wallet is read whenever a read of the payout's status finds the provider has moved it, and — while the debit is not seen — 15 seconds before the payout's wait is over, at the wait, and 30 seconds, 1½, 3½ and 7½ minutes after it.
Sends the provider's sandbox completes read settled at the next
15-second read of their status — on chain, across chains, by @handle —
where they took up to about two minutes more. The payouts accepted in the
last ten minutes are read every 15 seconds, up to 10 a read across the
sandbox, besides the two-minute read of every payout in flight. That read
pauses, for up to two minutes, while the provider throttles it or does not
answer.
A us_bank send is left to the provider for five minutes before the
sandbox settles it itself; it was one minute. The provider's sandbox was seen to
complete an ACH send itself, within 168 seconds of accepting it, and such a
send reads settled with no simulated. One still in flight after five
minutes — a wire, not yet observed, or an ACH send it has not completed — is
settled by the sandbox (simulated: true), and simulate-completion still
settles one at once, whatever its wait. The docs said a us_bank send had
not been observed and might stay processing. See
Which sends finish.
A payout support is resolving is never settled by the sandbox. One the
provider reports in a state only a person can resolve stays
needs_reconciliation (simulate-completion answers 409,
needs_reconciliation), and one the provider completes meanwhile reads
settled with no simulated.
A send's hold stops counting its debited part sooner, the wallet being
read soon after the provider moves the send: a send made right after another
is refused insufficient_balance for a shorter time. The hold rule is
unchanged. See Holds.
A send-back's source_amount is worked out when the payout is read, on a
send-back the sandbox settled: every debit naming its transfer, also an entry
for its gas read after the settlement. A conversion the provider reports
after the settlement is shown as it reported it. See
Sends the sandbox settles.
Digital assets (sandbox): fixes from an end-to-end sandbox run
A swap's arrival is internal. What a swap bought — the credit it shows
as its arrival — is listed by GET /v1/transactions and
GET /v1/transactions/{id} with type and rail internal and no
counterparty: your own money, moved inside the provider. Its
payin.received names rail internal. Where the provider's record of the
deposit named a chain, as its sandbox's does, it was listed as
onchain_transfer on that chain with an empty counterparty. Amounts,
balances and the swap's arrival are unchanged; arrivals booked before keep
the label they were booked with. See Swap.
A send-back the sandbox settles shows what left. source_amount and
source_currency — the USDC the wallet was seen to give for its transfer —
and exchange_rate, the EUR that arrived per USDC that left (six places),
where the provider's receipt puts them in production. The provider's sandbox
never sends a receipt, so they never appeared there. A conversion the
provider did report is shown as it reported it. Sends to an IBAN or a bank
account outside SEPA and swaps settled by the sandbox still carry none:
only a receipt states what the bank was paid, and the provider states no
rate for a swap. Production shows the provider's receipt, as before. See
Sends the sandbox settles.
insufficient_balance names the amount and the headroom apart. A send
that holds headroom on top of its amount said requested 9090.0000 for a
9,000 USDC send. It now says:
Insufficient balance: available 138.924857 USDC, needed 9090.00 USDC: 9000.00 USDC for the payout plus 90.00 USDC headroom, held while it is in flight
The code, the status and details are unchanged, and a payout without
headroom reads as before. See
Holds.
next_steps is empty once the USDC account is open.
GET /v1/banking/kyb/digital-assets, its POST and the sandbox approval
said "Open a USDC account…" for good. See
Onboarding.
API
GET /v1/banking/payoutshashas_more,trueexactly whennext_cursoris set, as onGET /v1/transactions— unlike there,next_cursorstays present, asnull, on the last page. Additive. The list's paging (before,next_cursor) is documented now too. See Listing.
Documentation
The behaviour below is unchanged; the pages now say what it is.
- New: API transfer guide.
Stablecoin transfers with an API key, end to end in the sandbox: the key
and the
Idempotency-Key, capabilities, a USDC account and its wallets, test funds, trusted beneficiaries and who may approve them, the quote and the route lock, the send, polling, timings and errors. Then bank to wallet — the pay-in details, the faucet and the convert, a send to a trusted wallet — and what does not exist: no on-ramp into an outside wallet, no BRL pay-in, no send from a wallet held elsewhere. - A swap's bought asset that waited too long for its swap's transfer —
over 30 minutes, after a
503 payout_outcome_unknown— is booked as an ordinary deposit,onchain_transferorinternalby the rail the provider's record of it names. See Swap. - A finished send's hold lasts about a minute. It is released for a send whose balance is read a minute or more after it finished, not at once: a send read sooner still counts what is left of it, as the read may not show its last debit yet. A send-back's balance check counts every USDC payout from the same wallet — sends and swaps as well as send-backs. See Holds.
kindis oncurrency_accounts. An entry inaccountsofGET /v1/banking/accountshas nokind; a digital-asset account'skinddigitalis on itscurrency_accountsentry. See Accounts.degraded_partnersonGET /v1/banking/accountsis absent — nevernull— when there is nothing to report.GET /v1/transactions/{id}of a banking payout readstypeexternal(fx_conversionfor an FX conversion) and carriesbank_detailswhere it paid a bank account. A banking FX conversion readscounterparty.nameFX Treasuryand carries nodestination_amountordestination_currency; a plan or fee payment from your balance readsCashXChain, itsnotesaying which. See the route reference.- The
Idempotency-Keyper route. A payout and the faucet answer a repeat as the first request did, and another body under the key is409 idempotency_key_reuse; opening an account again is409 conflict; opening a wallet does not use the key; another test-funds amount or chain under the same key is a new deposit. See the API transfer guide. - Two more payout statuses in the lifecycle:
created, while a payout is being confirmed, andneeds_reconciliation, which support resolves. See Lifecycle. quote_idandrouteare on the202answer only:GET /v1/banking/payoutsand its detail do not return them. See Sending.- No fee figure is returned for an on-chain send: a quote's
feesandnetwork_feereadnull, and the payout reports none. - Approving a recipient in the web app: Manage on the
Approved recipients card, then Approve a recipient, with a name; an
address of one of your own wallets is refused
422 destination_is_own_wallet. See Trusted beneficiaries. - Sandbox defaults are stated as defaults — the one-minute settle wait,
the cooldown of
0and the per-asset caps — with the capability fields that give a deployment's own values.
2026-11-03
Digital assets (sandbox): every send settles
Sends the provider's sandbox never finishes are settled. A send to an IBAN
or to a bank account outside SEPA, a send-back and a swap are accepted by the
provider's sandbox and never finished: they stayed processing. The sandbox
now settles each itself about a minute after the provider accepts it, once
the money has been seen leaving the wallet; a swap's bought asset is deposited
first and shown as its arrival. Such a payout reads simulated: true. New:
POST /v1/sandbox/banking/payouts/{payout_id}/simulate-completion does it
without the one-minute wait, once the debit has been seen; before that it
answers 409 not_simulatable (details.reason: debit_not_seen) and re-reads
the wallet: ask again in a few seconds. A session, or a key with
payments:write; 30 an hour per account, in a window of its own, refused
requests (a debit_not_seen included) counted, so poll it sparingly. Sends on chain, across chains and by @handle are unchanged. See
Sends the sandbox settles.
A refusal when the transfer is created is final. A send the provider
refuses outright when its transfer is created (its own explained 400,
401, 403, 404 or 422, in answer to the only attempt sent) — a Stellar
account with no trust line for the asset, say — is now
422 destination_not_payable with details.reason: provider_refused,
details.payout_id and details.idempotency_key_spent: true. The payout is
failed at once, holds nothing, and the next send is not held behind it. It
used to be 503 payout_outcome_unknown, with every other send refused
409 earlier_payout_unconfirmed for seven minutes or more. A validation
refusal carries the provider's sentence, and names the field it refused in
details.provider_field when it named one: change it and send again under a
new key. Any other refusal (permission, compliance, a rail it will not use) is
in our words and says to contact support; for a rail, details.provider_field
is destination.payment_rail. The provider refusing our own credentials there
is 503 service_unavailable with details.idempotency_key_spent: true and
details.payout_id: the payout is failed, the next send is not held behind
it; send again later under a new key. Anything else at the create (no answer,
an outage, a rate limit, a conflict, or a refusal after a retried attempt) is
still 503 payout_outcome_unknown: send it again only under the same key. See
A refusal when the transfer is created.
Test funds are always credited. A simulate-deposit is booked as yours
whatever you sent before. Test funds of exactly the amount of an @handle send
still on its way into that wallet may be shown as that send, and the send as
the test funds; use amounts unlike any @handle send in flight. Other credits
naming no transfer of ours (not EUR through the account's own IBAN, not a
convert) at 90–105 % of a send from the same wallet in the last 14 days are
still held as a possible refund: a bank send's settled or not, an on-chain,
@handle or swap send's until it settles. See
Credits held as a possible refund.
Holds. A send in flight now holds its amount and its chain's headroom (a
send-back its max_source_amount) only until the wallet's history shows the
debit naming its transfer; from a minute after that, only the headroom. It
used to hold the whole amount until it finished. See Holds.
A send by @handle arrives as internal, from the sender by the name their
handle shows, also where the provider's sandbox records it as a plain
deposit: it is matched to the send by wallet, asset and exact amount, each
send once.
Bank-send rails. The sandbox approval also requests and approves GBP
(Faster Payments), MXN (SPEI) and BRL (Pix); call it again for an account
approved earlier. digital_actions.send_bank on GET /v1/banking/accounts
(and digital_assets[].send_bank in GET /v1/banking/capabilities) lists a
bank-account kind only over a rail enabled for you; bank_send in
capabilities names the kinds still waiting in its reason, and is
requires_onboarding while none is enabled. A send over a rail that is not
enabled is 409 digital_assets_onboarding_required with details.rail,
before the step-up is spent. rails on GET /v1/banking/kyb/digital-assets
lists each rail once (BRL (Pix) was listed up to three times), in every
environment.
API: the faucet takes an API key
POST /v1/sandbox/banking/faucettakes an API key withpayments:write(secret and master keys too), so a key-only integration can fund the EUR account a convert starts from. It takes an optionalIdempotency-Key: under one it credits once, and a repeat credits nothing (409 idempotency_key_reusefor another currency or amount). Without one, every call credits again. See Test money.degraded_partnersonGET /v1/banking/accountsandGET /v1/banking/beneficiariesno longer lists a bank rail you have no relationship with: it is not asked.- Statements of a digital-asset account
(
POST /v1/banking/accounts/{account_id}/statements) are still400, now naming the account's own asset and where its movements are:GET /v1/transactionsandGET /v1/banking/payouts.
Digital assets (sandbox)
Digital-asset accounts. In the sandbox a business can hold accounts in
USDC, EURC, USDT, USDB, PYUSD and PATHUSD beside its bank accounts, on the
same /v1/banking/* API: one account per asset, a wallet per chain (Solana,
Base, Ethereum, Tron, Tempo), held at our crypto-asset service provider.
Onboarding is GET/POST /v1/banking/kyb/digital-assets, approved in the
sandbox in one call (POST /v1/sandbox/banking/kyb/digital-assets/approve);
accounts open with POST /v1/banking/accounts, wallets with
POST /v1/banking/digital-assets/wallets, and test funds come from
POST /v1/sandbox/banking/accounts/{account_id}/simulate-deposit. Every
digital-asset account carries digital_actions, and its balance
by_network. Production offers none of it yet. See
Digital assets.
Sends. POST /v1/banking/payouts from a digital-asset account sends on
chain — on its own chain or across chains, with a memo on Stellar — to an IBAN
over SEPA, to US (ACH or wire), UK (Faster Payments), Mexican (SPEI) and
Brazilian (Pix) bank accounts, and to another CashXChain business by @handle;
it swaps USDC and EURC, and USDC goes back to your own EUR account
(simulate-arrival books its EUR in the sandbox). Each send carries the
travel-rule data its destination needs: a third party's street and city, and a
business's identifier. A US account names its bank (bank_name, required).
GBP, MXN and BRL are for businesses outside the EEA. See
Sending.
API keys and trusted beneficiaries. Every digital-asset action works with
an API key. A key sends to a third party only to a trusted beneficiary that a
person of the account approved with their second factor
(/v1/banking/digital-assets/trusted-beneficiaries); no key can approve or
revoke one. The sandbox's cooldown before a key may use an approval is 0. See
API keys and trusted beneficiaries.
New codes, among others: travel_rule_incomplete,
trusted_beneficiary_required, beneficiary_not_trusted, wallet_not_open,
amount_below_minimum, recipient_cannot_receive,
wallet_attestation_required, digital_send_limit_exceeded,
payout_outcome_unknown, earlier_payout_unconfirmed,
digital_assets_onboarding_required, digital_assets_unavailable. See
Digital-asset errors.
API
Payout routes and the route lock. POST /v1/banking/payouts/quote with a
destination now answers the route the payout would take — its steps,
schemes, chains and countries, never a provider's name — and locks it under a
single-use quote_id for 15 minutes. A payout sent with quote_id runs on
exactly that route or is refused: 409 route_changed,
422 route_quote_mismatch, 422 route_quote_expired, 409 route_quote_used.
The lock holds the route, never a rate. See
Quotes and the route lock.
GET /v1/banking/capabilities. What the deployment offers and what your
account may do now, in one answer: flows and methods with their status, the
digital assets and their routes, the send caps, the route lock, and every
banking route with whether this caller is admitted. See
Capabilities.
/v1/casp/* removed. The earlier crypto-asset routes answer 404;
digital assets are on /v1/banking/*.
payin.received in the sandbox. Sent for every credit booked on a
digital-asset account and for a send-back's simulated arrival; on chain,
rail is the chain.
Documentation
- New section Digital assets: overview, quickstart, accounts and wallets, sending, quotes and the route lock, trusted beneficiaries, step-up, capabilities, errors, sandbox behaviour.
- Product, compliance and API pages no longer say stablecoin accounts and sends do not exist: they are in the sandbox, not yet in production.
- Travel Rule describes the data a digital-asset send carries.
GET /v1/accounts/me/balancelists a digital asset under its own code; it is never counted into EUR or USD (the page said 1:1).
2026-11-02
API
- The plan's API terms are enforced in production. Live API keys and
webhooks need Starter or above; moving money through the API
(
POST /v1/banking/payouts,POST /v1/banking/fx/executewith a key) needs Professional or above. Checked on every request, so a plan change applies at once. A refusal is403 api_not_in_plan. The sandbox is unchanged: every API feature stays open on every plan. - Plan refusals carry
details.upgrade—api_not_in_plan,currency_limit_reached,named_account_limit_reachedandplan_limit_exceededname the cheapest plan that allows the request (plan,reason,self_serve, andcurrent_limit/plan_limiton limits). See Plan limits and upgrades. - A live FX payout takes a buy-fixed quote.
POST /v1/banking/payoutswith anfx_quote_idwhose sell side was fixed is refused with400 bad_request: the bank pays out the quote's own buy amount, which would not be the amount the payout states. Quote with"fixed_side": "buy"and the payout amount. account_not_verifiedlinks here.details.doc_urlnow points to the error reference in these docs.
Documentation
- The error reference and this changelog are the single source; the developer portal links here instead of keeping copies.
2026-10-19
API
One money API. Every payout, conversion and account operation is on
/v1/banking/*: accounts, beneficiaries, payouts, FX quotes and conversions,
KYB. The same routes and request bodies serve the sandbox, where the rail is
simulated, and production, where it is our banking partner; responses and a few
error codes differ where the rail does. Switching environment is a base URL
and a key.
Removed. The previous generation of transfer, payment, FX-quote and
sandbox-faucet routes, and the per-customer routes for virtual accounts,
partner links and statements, no longer exist. Their replacements are the payout, FX, account and statement routes on
/v1/banking/* and POST /v1/sandbox/banking/faucet. GET /v1/fx/rates is
unchanged. The /v1/claims/* routes and the claims:read and claims:write
scopes are gone too.
Every key, every route, one table. Which scope a route needs is decided in
one place, published per scope by GET /v1/meta/scopes (routes, includes)
and mirrored in the OpenAPI spec. Secret keys hold every scope; restricted
keys reach any route — the money API included — with the scope it needs; a
write scope includes its read. Two scopes are new: beneficiaries:write for
registering and verifying payees (and CASP payout and deposit accounts), and
refunds:write for Collect refunds. Collect's merchant routes now take keys.
Publishable keys keep only publishable scopes — today fx:read. Scope
strings outside this vocabulary were stripped from existing keys.
New refusal codes. A missing scope is 403 insufficient_scope with
details.required_scope and details.granted_scopes (it used to be
forbidden). A key type a route does not admit is key_not_admitted — 403
inside a surface, 401 when the money or dashboard surface turns the key away
at the door. A route no key may call is 403 route_not_available_to_api_keys.
Roles are enforced. A dashboard session needs its seat's permission:
payouts.create to send a payout, fx.execute to convert, collect.manage
to onboard to Collect, collect.links.create for the other Collect writes,
api_keys.manage to create, list or revoke keys. Owners and admins hold all
of them, members all but Collect onboarding and key management, viewers none.
The refusal is 403 permission_denied. A key never exceeds the seat that
minted it: a secret key needs payouts.create and fx.execute, a restricted
key the permission behind each of its scopes.
The rail is not named. Responses no longer carry a partner field, and a
partner query or body field is 400 bad_request. Where a client read the
rail's name to decide whether a price was real, a quote and a rate now say
rate_source: live, or reference for a sandbox table price.
corridor_unsupported. A currency, country or pair nobody offers is
422 corridor_unsupported with details.corridor, from the corridor lookup,
beneficiary registration, the payout and the FX quote alike — a standing
answer. 503 service_unavailable now means only that a rail that offers it is
down.
Payee-name check. In production, payouts to GBP and EUR destinations are
checked against the name the receiving bank holds, before any money moves.
POST /v1/banking/beneficiaries/{id}/verify runs the check. A refused payout
says what clears it (details.resolve_by, details.accept_url):
POST /v1/banking/beneficiaries/{id}/accept-verification records that the
payer saw the verdict, scoped to one payment by its Idempotency-Key, after
which the payout is resent under the same key. It replaces the
payee_name_confirmed field and the acknowledge-mismatch route, which never
shipped. An invalid destination cannot be accepted.
Stranded payouts are reconciled. A payout or conversion whose call to the
bank failed or timed out stays created until a reconciler has matched it
against the bank's own notification, then becomes submitted, settled or
failed. It is never closed as failed on a guess, and a replay under the
same key returns it as it stands.
Webhooks. kyb.updated is sent when the banking partner approves a
business, and payin.received when money arrives by bank transfer; neither
names the partner. A delivery is attempted up to 14 times. The dashboard
event stream no longer relays notifications whose signature failed.
Verification gate. In production, every route that moves money or needs
the bank relationship refuses an account that is not active with one
answer, 403 account_not_verified, before anything is sent to the bank;
details.next_action says what comes next. It replaces the bare 503, the
403 forbidden and the 409 managed_account_not_verified those routes used
to answer. The sandbox allows pending, and Cxc-Simulate: account_not_verified shows the refusal there.
Documentation
Rewritten from the backend source, with every quickstart call run against the sandbox:
- A Quickstart from a sandbox key to a settled payout.
- Environments: what is identical, and the timing and checks that are not.
- One page per resource: KYB, Accounts, Beneficiaries, Payouts, FX, Webhooks and the event stream, Platforms.
- A complete route reference and error reference, and pages on idempotency, rate limits and request logs.
- Authentication documents the
cxc_key format and its checksum, which surface each credential reaches, every scope with the routes it opens, and the seat permissions behind a dashboard session. - Master-key revocation is no longer described as needing database access;
CashXChain revokes master keys through its own tooling. Master keys do not
reach
/internal/*, and the documentation no longer says they do. - The Platform section and the per-topic integration guides, which repeated the API pages with different answers, were folded into them. Old URLs redirect.
2026-08-28
Documentation
Reconciled the API reference of the time against the backend source: corrected scope names, error codes, the class-based route gates, step-up authentication, and the request conventions. That reference described the previous generation of money routes and has been replaced by the 2026-10-19 documentation above.
Site
- Redesigned code examples: one flat panel per block with a single border and header, replacing the stacked border, drop shadow, inset shadow and left accent stripe that overlapped the code. Increased the surface contrast against the page so a block reads as a distinct panel.
- Narrowed the syntax palette to the brand families — navy and teal for syntax, one warm accent for literals, grey for punctuation — replacing a nine-colour per-language scheme that included non-brand pink, purple and amber.
- Removed the glowing per-language colour dots in favour of a shell/data/code distinction on the header label.
- Fixed the copy button, which was styled with selectors that match nothing in the current Docusaurus version and floated over the first line of code. It is now an icon button docked in the block header.
- Added tabbed cURL / TypeScript / Python examples on the getting-started and FX pages.
2026-06-06
Documentation
- Reconciled the API reference and platform pages with the sandbox OpenAPI
(
/v1/openapi.json). - Corrected conventions: keys sent as
Authorization: Bearer; resource ids are UUIDs; lists are cursor pluslimit. - Labelled not-yet-available capabilities as planned rather than live.
2026-05-08
Documentation
- Rewrote the public documentation structure for CashXChain.
- Added full introduction, product, platform, API, security, compliance, integration, use case, and legal pages.
- Established CashXChain as a payment orchestration and technology layer with regulated partner execution.
- Added future-state API examples for accounts, wallets, FX quotes, payments, webhooks, statements, and errors.
- Added security, webhook, idempotency, reconciliation, and QA guidance.
Notes
- API examples are intended as product documentation targets and may evolve before production launch.
- Legal pages are documentation drafts and require counsel review before publication.