Skip to main content

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:

Plain text
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/payouts has has_more, true exactly when next_cursor is set, as on GET /v1/transactions — unlike there, next_cursor stays present, as null, 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_transfer or internal by 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.
  • kind is on currency_accounts. An entry in accounts of GET /v1/banking/accounts has no kind; a digital-asset account's kind digital is on its currency_accounts entry. See Accounts.
  • degraded_partners on GET /v1/banking/accounts is absent — never null — when there is nothing to report.
  • GET /v1/transactions/{id} of a banking payout reads type external (fx_conversion for an FX conversion) and carries bank_details where it paid a bank account. A banking FX conversion reads counterparty.name FX Treasury and carries no destination_amount or destination_currency; a plan or fee payment from your balance reads CashXChain, its note saying which. See the route reference.
  • The Idempotency-Key per route. A payout and the faucet answer a repeat as the first request did, and another body under the key is 409 idempotency_key_reuse; opening an account again is 409 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, and needs_reconciliation, which support resolves. See Lifecycle.
  • quote_id and route are on the 202 answer only: GET /v1/banking/payouts and its detail do not return them. See Sending.
  • No fee figure is returned for an on-chain send: a quote's fees and network_fee read null, 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 0 and 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/faucet takes an API key with payments:write (secret and master keys too), so a key-only integration can fund the EUR account a convert starts from. It takes an optional Idempotency-Key: under one it credits once, and a repeat credits nothing (409 idempotency_key_reuse for another currency or amount). Without one, every call credits again. See Test money.
  • degraded_partners on GET /v1/banking/accounts and GET /v1/banking/beneficiaries no 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 still 400, now naming the account's own asset and where its movements are: GET /v1/transactions and GET /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/balance lists 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/execute with a key) needs Professional or above. Checked on every request, so a plan change applies at once. A refusal is 403 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_reached and plan_limit_exceeded name the cheapest plan that allows the request (plan, reason, self_serve, and current_limit / plan_limit on limits). See Plan limits and upgrades.
  • A live FX payout takes a buy-fixed quote. POST /v1/banking/payouts with an fx_quote_id whose sell side was fixed is refused with 400 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_verified links here. details.doc_url now 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 plus limit.
  • 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.