Skip to main content

Route reference

Every route below is served identically by https://api.sandbox.cashxchain.com and https://api.cashxchain.com, except where marked sandbox only. Guides explain the behaviour; this page is the index of the contract.

Conventions​

  • JSON in and out. Send Content-Type: application/json with a body.
  • Amounts are decimal strings — send "10.00", read "10.0000". Never floats.
  • Currencies are ISO 4217 (EUR), countries ISO 3166-1 alpha-2 (DE).
  • Timestamps are RFC 3339, UTC.
  • Ids: payouts and conversions have UUIDs — a conversion's is listed in GET /v1/banking/payouts, since fx/execute answers with the rail's deal_id. Accounts, beneficiaries, quotes and KYB applications have ids issued by the rail — opaque strings whose shape differs between sandbox and production.
  • Errors use one envelope; framework-level rejections are plain text — see Errors.
  • Every response carries X-Request-Id.

Credentials in the tables: S dashboard session · M master key (mk_) · K secret key (sk_) · R restricted key (rk_) · P publishable key (pk_). R needs the scope shown; K holds every scope; S and M are not subject to key scopes, though S is subject to its seat's permissions. See Authentication.

Headers: Idem = Idempotency-Key required · Step-up = X-Step-Up-Token required of sessions · Delegable = accepts Cxc-Account with the grant scope shown; money marks the routes that need a session or a master key with the money permission when delegated.


Money API — /v1/banking/*​

Credentials for every route in this section: S, M, K, and R with the scope shown. pk_ keys get 401 key_not_admitted. A partner query or body field, which older clients used to pin a rail, is 400 bad_request on every route here.

Accounts​

GET /v1/banking/accounts​

Your accounts, currency accounts, plan usage and the currencies you can open.

  • 200 {accounts[], refreshing, primary_currency, currency_accounts[], plan, offered_currencies[], degraded_partners?[], closing_accounts[]}.
  • Errors: 503 service_unavailable (no rail answered).
  • Delegable: accounts:read. → Accounts

POST /v1/banking/accounts​

Open a currency account. Idem.

  • Body {currency}.
  • 201 {currency_account: {currency, kind, status, is_primary, created_at}, account?} — status: "requested" and no account when the rail opens it later.
  • Errors: 400 bad_request, 403 currency_limit_reached, 403 named_account_limit_reached, 409 conflict.
  • Delegable: accounts:write, money. → Accounts

PUT /v1/banking/accounts/primary​

Set the primary currency.

  • Body {currency}. 200 {primary_currency}.
  • Errors: 422 primary_not_eligible.
  • Delegable: accounts:write.

POST /v1/banking/accounts/close​

Close a currency account, converting any balance to the primary currency.

  • Body {currency}. 200 {currency, status: closed|closing, step, convert_to}.
  • Errors: 404 not_found, 422 primary_account, lockdown codes.
  • Not delegable.

GET /v1/banking/accounts/plan-impact​

Which accounts a plan change would close.

  • Query: plan (required: free, starter, professional, business, enterprise).
  • 200 {plan, max_named_accounts, max_currencies, accounts_to_close[{currency, kind, balance, convert_to}]}.
  • Errors: 400 bad_request. Delegable: accounts:read.

GET /v1/banking/accounts/{account_id}/payin-instructions​

Bank details for paying into the account.

  • 200 {instructions[{rail, currency, account_holder_name, identifier, bank_name?, bank_address?, reference?}]}.
  • Errors: 404 not_found. Delegable: accounts:read. → Accounts

GET /v1/banking/accounts/{account_id}/balance​

  • 200 {balances[{account_id, currency, available, pending, as_of}], refreshing}.
  • Errors: 404 not_found. Delegable: accounts:read. → Accounts

POST /v1/banking/accounts/{account_id}/statements​

Issue a statement.

  • Body {from, to} (dates, ≤ 366 days, not in the future).
  • 201 statement with statement_id, period, account, customer, balances, totals, lines[], checksum, sandbox.
  • Errors: 400 bad_request, 404 not_found, 503 service_unavailable. Not delegable. → Accounts

Beneficiaries​

GET /v1/banking/corridors​

What a destination needs.

  • Query: currency, country (required); entity_type (business/individual), payment_method (bank_account/e_transfer).
  • 200 {currency, country_code, accepted_schemes[], rails[], fields[], payment_method?, payment_methods[], entity_type?, rail_field?, reference_max_length?, payment_purposes[]}.
  • Errors: 400 bad_request, 422 corridor_unsupported, 503 service_unavailable. Scope accounts:read; delegable. → Beneficiaries

GET /v1/banking/beneficiaries​

  • 200 {beneficiaries[], degraded_partners?[]}.
  • Scope accounts:read; delegable.

POST /v1/banking/beneficiaries​

Register a destination. Idem.

  • Body {beneficiary_type, name, currency, country_code, account: {scheme, …}, bank_name?, address?, corridor_fields?}.
  • 201 {id, beneficiary_type, name, currency, country_code, account, bank_name?, usable_at?}; 200 for an existing cxc_handle payee.
  • Errors: 400 bad_request, 404 username_not_found, 409 conflict, 422 corridor_unsupported, 503 service_unavailable, lockdown codes.
  • Scope beneficiaries:write; delegable, money. → Beneficiaries

POST /v1/banking/beneficiaries/{beneficiary_id}/verify​

Run the payee-name check.

  • 200 {outcome: matched|close_match|no_match|not_checked|invalid, may_pay, account_holder?, check?}.
  • Errors: 404 not_found. Scope beneficiaries:write; not delegable. → Beneficiaries

POST /v1/banking/beneficiaries/{beneficiary_id}/accept-verification​

Record that the payer saw the bank's verdict and chose to pay — scoped to one payment.

  • Body {payment_idempotency_key}.
  • 200 {outcome, accepted, verification_id?, acceptance_id?, account_holder?, partner_acknowledged}.
  • Errors: 400 bad_request, 404 not_found, 422 payee_name_mismatch (an invalid destination). Scope beneficiaries:write; not delegable. → Beneficiaries

Payouts​

POST /v1/banking/payouts​

Send a payout. Idem. Step-up.

  • Body {source_account_id, beneficiary_id, amount, currency, reference?, fx_quote_id?, purpose_of_payment?, rail?, destination_country?, quote_id?} — or, instead of beneficiary_id, destination_account_id (one of your own accounts) or destination with source_network / destination_network (a send from a digital-asset account, sandbox).
  • 202 payout {id, status, partner_payment_id?, amount, currency, direction, rail?, partner_status?, created_at, updated_at, submitted_at?, settled_at?, source_account_id, destination_account_id, arrival, quote_id?, route?, kind?, network?, destination_network?, destination_currency?, tx_hash?}; 200 on a replay.
  • Errors: 400 bad_request, 400 insufficient_balance, 403 account_not_verified, 403 permission_denied, 404 not_found, 409 idempotency_key_reuse, 409 quote_expired, 409 quote_unrecorded, 409 route_changed, 409 route_quote_used, 422 corridor_unsupported, 422 payee_name_mismatch, 422 plan_limit_exceeded, 422 no_rate, 422 route_quote_expired, 422 route_quote_mismatch, 503 limit_unmeasurable, 503 fee_unpriceable, 503 service_unavailable, step-up and lockdown codes. From a digital-asset account, its own codes — Digital-asset errors.
  • Scope payments:write; delegable, money. → Payouts, Sending from a digital-asset account

GET /v1/banking/payouts​

  • Query: limit (1–200, default 50), before (the previous page's next_cursor).
  • 200 {payouts[], has_more, next_cursor} — newest first, conversions included. has_more is true exactly when next_cursor is set; on the last page next_cursor is null.
  • Delegable: payments:read. → Payouts

GET /v1/banking/payouts/{payout_id}​

  • 200 payout. Errors: 404 not_found, 400 (plain text) for a non-UUID id.
  • Delegable: payments:read. → Payouts

POST /v1/banking/payouts/quote​

Price a payout, and disclose and lock its route. Moves nothing.

  • Body {source_account_id, currency, amount, beneficiary_id?, rail?, destination_country?} — or the payout's other destinations, as for POST /v1/banking/payouts.
  • 200 {source_currency, source_amount, fee, payment_fee, rate, currency, amount, simulated, rate_source?, quote_id, expires_at, route, rate_locked} (quote_id null without a destination); a digital-asset send answers its own estimate — Quotes.
  • Errors: 404 not_found, 422 no_rate, 503 fee_unpriceable, 503 service_unavailable. Delegable: payments:read. → Payouts

GET /v1/banking/capabilities​

What this deployment offers and what your account may do now.

  • 200 {environment, flows[], digital_assets[], digital_rules, payout_rails[], route_lock, account, caller, actions[]}.
  • Scope accounts:read. → Capabilities

GET /v1/banking/payout-options​

  • 200 {currencies[], countries[{country_code, default_currency}]}. Delegable: payments:read.

FX​

POST /v1/banking/fx/quote​

  • Body {sell_currency, buy_currency, amount, fixed_side?: sell|buy}.
  • 200 {quote_id, sell_currency, buy_currency, sell_amount, buy_amount, rate, tier, unit_fee?, unit_fee_currency?, expires_at, tradable?, rate_source: live|reference}.
  • Errors: 400 bad_request, 422 corridor_unsupported, 422 market_closed, 503 service_unavailable. Scope fx:read; delegable. → FX

POST /v1/banking/fx/execute​

Convert between your accounts. Idem. Step-up for sessions from 10,000 USD.

  • Body {quote_id, account_id}.
  • 200 {deal_id, status, raw}.
  • Errors: 400 account_currency_mismatch, 400 quote_expired, 400 quote_consumed, 400 insufficient_balance, 400 invalid_amount, 400 bad_request, 403 account_not_verified, 403 permission_denied, 409 conflict, 409 idempotency_key_reuse, 409 quote_unrecorded, step-up and lockdown codes.
  • Scope payments:write; delegable, money. → FX

Verification (KYB)​

None takes an Idempotency-Key. All are delegable — reads with accounts:read, writes with accounts:write. → KYB

RouteBodySuccessErrors
POST /v1/banking/kyb/precheck/preview{legal_name, country_code, registration_number?, industry_code?, business_description?}200 {blocking_findings[]}—
POST /v1/banking/kyb/precheckapplication200 {precheck_record_id, decision, findings[], existence[], sanctions_hits[], unavailable_sources?, missing_onboarding_fields?}400
GET /v1/banking/kyb/precheck-recordsquery limit (1–100, default 20)200 {records[]}—
POST /v1/banking/kyb/referralsapplication201 referred · 202 manual_review · 200 already_referred — {outcome, referral?, precheck?, message?}400, 422 kyb_precheck_blocked, 503
GET /v1/banking/kyb/referrals/{external_id}—200 {external_id, status, raw}404
POST /v1/banking/kyb/referrals/{external_id}/personsperson201 {person_external_id}400, 404
POST /v1/banking/kyb/referrals/{external_id}/documents/slots{document_type, content_type, other_document_type?}200 partner slot payload400, 404
POST /v1/banking/kyb/referrals/{external_id}/persons/{person_external_id}/documents/slots{id_type, id_number, issued_country, id_expiry_date?, address_type, id_content_type, address_content_type, document_name?}200 partner slot payload400, 404
POST /v1/banking/kyb/referrals/{external_id}/parent-entities{role, entity_category, legal_name, registration_id, date_of_incorporation, email, website?, trading_name?, address, owners[]}201 {parent_entity_external_id, owner_external_ids[]}400, 404
POST /v1/banking/kyb/referrals/{external_id}/parent-entities/{parent_entity_external_id}/documents/slots{document_type, content_type}200 partner slot payload400, 404
POST /v1/banking/kyb/referrals/{external_id}/finalise—200 {external_id, status: "submitted"}400, 404

Settlement​

Automatic conversion of incoming money into another currency.

RouteBody / querySuccessNotes
GET /v1/banking/settlement/policies—200 {policies[{source_currency, target_currency, enabled, min_amount, updated_at}]}Delegable: accounts:read
PUT /v1/banking/settlement/policies{source_currency, target_currency, enabled?, min_amount?}200 the policy400; lockdown codes; not delegable
GET /v1/banking/settlement/intentslimit (1–200, default 50)200 {intents[]} — status: pending, quoted, settled, failed, needs_reconciliation, no_railDelegable: accounts:read

Developer API​

Rate-limited at 600 requests a minute per key (per account for sessions), counted per server. → Rate limits

FX rates​

GET /v1/fx/rates​

Indicative rates for display — not a quote.

  • Credentials: S, M, K, R, P · scope fx:read.
  • Query: base, currencies (comma-separated, ≤ 30).
  • 200 {base, rates[{currency, rate, rate_source: live|reference}], unavailable[], as_of} — rate is units of base per unit of currency.
  • Errors: 400 bad_request, 403 insufficient_scope. Delegable: fx:read. → FX

Webhooks​

Credentials: S, M, K, R, P (P on GET only). → Webhooks

RouteScopeRequestSuccessErrors
GET /v1/webhooksaccounts:read—200 {object: "list", data[{id, object, url, description, events, status, success_rate, disabled_reason, created_at}]}—
POST /v1/webhooksaccounts:write{url, description?, events?[]}201 endpoint with secret400, 409 conflict
GET /v1/webhooks/eventsaccounts:read—200 {object: "list", data[{name, description}]}—
GET /v1/webhooks/deliveriesaccounts:readendpoint_id?, limit (1–100, default 20)200 {object: "list", data[{id, object, endpoint_id, event, event_id, status, http_status?, attempts, latency_ms?, last_error?, created_at, delivered_at?}]}—
DELETE /v1/webhooks/{id}accounts:write—204404
POST /v1/webhooks/{id}/rotate-secretaccounts:write—200 endpoint with new secret404

API keys​

Credentials: S (a seat holding api_keys.manage: owner, admin, personal account) and M only — any API key gets 403 key_not_admitted, a member or viewer session 403 permission_denied. Not delegable. → Managing API keys

RouteRequestSuccessErrors
GET /v1/api-keys—200 [{id, object, name, key_id, prefix, last4, legacy, type, scopes, last_used_at, created_at}]—
POST /v1/api-keys{name, type?, scopes?[]}201 key with token, shown once400, 403 permission_denied (a key beyond the creator's seat)
DELETE /v1/api-keys/{id}—204404

Request logs and usage​

Credentials: S, M, K — rk_/pk_ get 403 key_not_admitted. Not delegable. → Request IDs and logs

RouteQuerySuccess
GET /v1/request-logslimit (1–500, default 100), before200 {logs[{id, method, path, status_code, duration_ms, api_key_id?, ip_address?, idempotency_key?, created_at}], has_more, next_cursor?}
GET /v1/request-logs/{id}—200 the entry plus request_body, response_body; 404
GET /v1/usage/summary—200 {requests_7d, requests_7d_delta_pct, error_rate_pct, total_volume, avg_settlement_secs}
GET /v1/usage/timeserieswindow: 7d, 30d, 90d200 {window, data[{date, count}]}

Account and platform reads​

RouteCredentials · scopeSuccessNotes
GET /v1/meta/scopesAny credential200 {object: "list", data[{scope, group, description, publishable, includes[], routes[{method, path}]}]} — every route each scope opens, and the read a write includesNot in the OpenAPI spec
GET /v1/accounts/me/balanceS, M, K, R, P · payments:read200 {totals[{currency, amount, available, pending}]}EUR and USD only, from our ledger — not a bank balance. → Accounts
GET /v1/platform/accountsS, M, K, R · accounts:read (pk_ refused)200 array of {account_id, display_name, kyb_status, can_move_money, grant}→ Platforms
GET /v1/utility/validate-addressAny credential200 {valid, reason?, ata_exists, asset?, estimated_rent}Query address, chain (solana, ethereum, polygon, base, arbitrum), asset?. Syntax check; on Solana, also whether the token account exists — read from Solana devnet by default, in every environment, so do not rely on ata_exists for mainnet.

Activity feed and contacts​

GET /v1/transactions lists money coming in — bank transfers received into your accounts, sandbox test funds, payments to your handle — and transfers made in the dashboard. It is the only list of incoming payments. It does not list payouts or conversions made through /v1/banking/*; those are in GET /v1/banking/payouts. GET /v1/transactions/{id} resolves an id from either list: the list's row plus completed_at and, where they apply, reference and bank_details.

A payout or conversion id from GET /v1/banking/payouts reads direction outgoing and type external — fx_conversion for an FX conversion, the one an account closing makes included — whatever its kind: a payout to a beneficiary, or, in the sandbox only, a send-back, a convert, a swap, or a send on chain, to an IBAN, to a bank account outside SEPA or by @handle (kind on GET /v1/banking/payouts/{payout_id} says which). rail is its rail, counterparty.name the payee's name as the payout recorded it — FX Treasury on a banking FX conversion (type fx_conversion), and CashXChain on a plan or fee payment from your balance, whose note says which — status completed for settled, rolled_back for returned, failed for failed or cancelled and pending otherwise, completed_at its settled_at, and reference the reference it was sent with, when it has one. Such a banking FX conversion carries no destination_amount or destination_currency here: its bought leg is recorded on its quote, not on the payout.

bank_details — account_holder, bank_name and country where recorded, account_number, and routing_number (the BIC where one was given, the sort code or the routing number) — is served for a payout paid to a bank account, as the payout recorded the account: an IBAN in full (a send-back to your own EUR account included); a US or UK account or a CLABE paid from a digital-asset account (sandbox only) as **** and its last four. It is absent for a payout on chain, a swap, a send by @handle or to a Pix key, an Interac e-Transfer and a cxc_handle payee.

RouteCredentials · scopeRequestSuccessErrors
GET /v1/transactionsS, M, K, R, P · payments:readlimit (1–100, default 25), before, direction (incoming, outgoing, all)200 {transactions[], has_more, next_cursor?}403
GET /v1/transactions/{id}as above—200 transaction detail404
GET /v1/contactsS, M, K, R, P · accounts:readlimit (1–100, default 50)200 {contacts[], has_more}403
POST /v1/contactsS, M, K, R · accounts:write{kind, name, email?, username?, country?, bank_name?, account_number?, routing_number?, iban?, swift?, currency?, network?, asset?, wallet_address?, note?}201 contact400, 404 username_not_found, 422 username codes
PATCH /v1/contacts/{id}as aboveany of {name, email, username, country, bank_name, currency, note}200 contact403, 404
DELETE /v1/contacts/{id}as above—204403, 404

/v1/transactions* is delegable with payments:read; contacts are not.

Digital assets (sandbox only)​

Digital-asset accounts are on the money API: opened with POST /v1/banking/accounts, listed by GET /v1/banking/accounts, and sent from with POST /v1/banking/payouts. The routes of their own:

RouteCredentials · scopeBody2xxErrors
GET /v1/banking/kyb/digital-assets?refresh=S, M, K, R · accounts:read—200 {state, can_open_account, blocked_reason, next_steps[], rails[], confirmed_at}503 digital_assets_unavailable
POST /v1/banking/kyb/digital-assets IdemS, M, K, R · accounts:write{redirect_url, account_type?}201/200 {outcome, verification_url, terms_url, onboarding}503 digital_assets_unavailable
GET /v1/banking/digital-assets/walletsS, M, K, R · accounts:read—200 {wallets[], networks[]}503 digital_assets_unavailable
POST /v1/banking/digital-assets/wallets IdemS, M, K, R · accounts:write{network}201/200 wallet403 account_not_verified, 409 digital_assets_onboarding_required, 409 digital_assets_onboarding_incomplete, 422 network_unsupported
GET /v1/banking/digital-assets/trusted-beneficiariesS, M, K, R · accounts:read—200 {trusted_beneficiaries[]}503 digital_assets_unavailable
POST /v1/banking/digital-assets/trusted-beneficiaries Idem. Step-up.S only — a person of the account{kind, label, …}201 entry; 200 on a replay403 session_required, 409 trusted_beneficiary_exists, 422 travel_rule_incomplete, 422 corridor_unsupported, …
DELETE /v1/banking/digital-assets/trusted-beneficiaries/{id} Step-up.S only—200 entry, revoked403 session_required, 404
POST /v1/sandbox/banking/kyb/digital-assets/approveS, M, K, R · accounts:write—200 onboarding status409 conflict, 429 rate_limited, 503 digital_assets_unavailable
POST /v1/sandbox/banking/accounts/{account_id}/simulate-deposit IdemS, M, K, R · payments:write{amount, network?}202 {account_id, currency, network, amount, status}409 wallet_not_open, 422 account_not_digital, 422 asset_not_on_network, 429 rate_limited
POST /v1/sandbox/banking/payouts/{payout_id}/simulate-arrivalS, M, K, R · payments:write—200 {payout_id, payin_id, booked, linked}422 not_eligible, 429 rate_limited
POST /v1/sandbox/banking/payouts/{payout_id}/simulate-completionS, M, K, R · payments:write—200 the payout (simulated: true once the sandbox settled it)409 not_simulatable, 422 not_eligible, 429 rate_limited, 503 service_unavailable

→ Digital assets. Every code: Digital-asset errors.

The earlier /v1/casp/* routes were removed: they answer 404. Use the routes above.

Customers​

POST /v1/customers​

CashXChain staff only — secret, restricted and master keys get 403, a publishable key 401, and a request without Idempotency-Key gets 400 before either. Businesses join by signing up, not through this route.


Sandbox tools — sandbox only​

The faucet takes a session or an API key with payments:write (S, M, K, R) and is logged like a money route; its Idempotency-Key is optional — without one every call credits again. adopt-corporate and /v1/sandbox/admin/whoami take a dashboard session (S) only: every API key gets 401; they are not rate-limited and not logged. The spec routes take no credential. None is stopped by a lockdown.

RouteRequestSuccessErrors
POST /v1/sandbox/banking/faucet{currency, amount} — whole units, 1–100,000200 {currency, amount, balance}400, 409, 409 idempotency_key_reuse, 422 asset_unsupported, 429 rate_limited
POST /v1/sandbox/banking/adopt-corporate{partner, external_id}200 {customer_id, partner, external_id, kyb_status, accounts_mirrored}400, 404, 409
GET /v1/sandbox/admin/whoami—200 your account as the sandbox sees it403, 404
GET /v1/openapi.json, GET /v1/docs—The spec, Swagger UINo credential

The remaining /v1/sandbox/admin/* routes are internal sandbox tooling, not part of the API.


Public routes​

No credential.

RouteReturns
GET /health{ok, service, version, environment, mail}
GET /ready{ok, db} — always HTTP 200; read ok
GET /internal/version{service, version, commit, commit_short, built_at?, environment}
GET /v1/platform-status{paused, message?, since?, confirmed, enforced} — see lockdown
GET /v1/public/statements/{id}A statement's summary and checksum, for third parties — Accounts
GET /v1/public/pay/{handle}A business's public pay page. 300 requests per hour per IP.
POST /webhooks/banking/{partner_id}, POST /webhooks/casp/…, POST /webhooks/resend, POST /v1/webhooks/stripe/…Inbound notifications from our partners, each verified by its own signature. Not for integrators.
GET /v1/pay/{slug}, GET /v1/a/{slug}, POST /v1/a/{slug}/…Payer-facing pages of Collect

Dashboard routes​

These serve the console and the web app. They take a dashboard session (S) or a master key (M); every other key gets 401. They are listed so that master-key holders know what exists; their request and response schemas are in the OpenAPI spec, and their error codes in Errors. None accepts Cxc-Account.

FamilyRoutes
AccountPOST /v1/accounts (attach a managed business — Platforms; Idem), GET /v1/accounts, GET /v1/accounts/{id}, GET /v1/accounts/me, GET /v1/accounts/me/vibans, GET /v1/accounts/me/closure-check, GET /v1/me, GET /v1/customers, GET /v1/customers/{id}
GrantsGET /v1/me/platform-permissions, DELETE /v1/platform-permissions/{id}
Event streamPOST /v1/notifications/ticket — then GET /v1/notifications with the cookie (Webhooks)
BillingGET/POST /v1/billing/subscription, POST /v1/billing/subscription/preview, …/cancel, …/change-now, POST/DELETE /v1/billing/promotion-code, GET /v1/billing/invoices, GET /v1/billing/invoices/{id}/pdf, GET/POST/PUT /v1/billing/payment-method, GET /v1/billing/catalogue, GET /v1/billing/entitlements
Usernames and pay pageGET/PUT /v1/accounts/me/username, GET /v1/usernames/check, …/search, …/suggest, GET /v1/usernames/{username}, GET/PUT /v1/pay-page, POST /v1/pay-page/links, DELETE /v1/pay-page/links/{id}, GET /v1/crypto-addresses/me
Collect, people-onlyPOST /v1/collect/accounts (Idem; seat collect.manage), POST /v1/collect/account-session (collect.manage), GET/POST /v1/collect/groups, POST /v1/collect/groups/preview, …/csv, POST /v1/collect/anlaesse, GET /v1/collect/anlaesse/{anlass_id}, …/export

Collect's merchant routes are on the developer surface instead, so a secret key or a restricted key with the scope reaches them: GET /v1/collect/status, GET /v1/collect/payment-links, GET …/{slug}/payments, GET /v1/collect/payments and GET /v1/collect/refunds with collect:read; POST /v1/collect/payment-links (Idem), PATCH …/{slug} and POST /v1/collect/payments with collect:write; POST /v1/collect/payments/{payment_intent_id}/refunds (Idem) with refunds:write. A dashboard session needs the seat permission collect.links.create for those writes. The /v1/claims/* routes and the claims:* scopes were removed in October 2026.


Not for integrators​

/internal/* — CashXChain staff and service-to-service routes. No API key of any type reaches them, master keys included. GET /internal/version (above) is the only public one.


OpenAPI coverage​

The spec is served in the sandbox only, at https://api.sandbox.cashxchain.com/v1/openapi.json (Swagger UI at /v1/docs). It describes production too. Mounted routes it does not describe:

  • GET /v1/meta/scopes
  • POST /v1/banking/kyb/referrals/{external_id}/parent-entities and its …/documents/slots
  • GET /v1/billing/invoices, GET /v1/billing/invoices/{id}/pdf, POST /v1/billing/subscription/change-now
  • /v1/collect/groups* and /v1/collect/anlaesse*
  • GET /health, GET /ready, GET /internal/version, /v1/a/*, and the inbound webhook receivers

Where the spec and this page disagree, this page was written from the code. Each operation's security requirement in the spec comes from the same route-policy table that GET /v1/meta/scopes publishes, so which credential and scope a route takes is accurate there. Known differences:

  • Its description says only /health and /ready need no credential — the public routes need none either.
  • It gives 422 for a beneficiary that fails corridor validation — the API answers 400 bad_request.
  • It lists /internal/admin/* routes, which no integrator credential can call.