Platforms
A platform is a business that runs CashXChain for other businesses — a marketplace paying out its sellers, an ERP paying its customers' suppliers. Each of those businesses keeps its own verified account, its own bank accounts and its own money; the platform sends instructions on their behalf and never holds their funds.
The pieces:
| Term | Meaning |
|---|---|
Master key (cxc_mk_…) | The platform's credential. Issued by CashXChain. |
| Managed account | A business you have attached. It signed up and is verified in its own right. |
| Grant | The record that lets you act for one managed account, with a set of scopes. Either side can revoke it. |
Cxc-Account header | Names the managed account a request acts for. |
Master keys
The developer console calls them Platform keys; they are the same thing. Master keys are not self-service: a master key reaches accounts other than your own, so we issue it deliberately. Ask from the console's API keys page or at [email protected], and say what you are building and whether you need to move money for the businesses you serve.
- A master key can do everything a secret key can, plus what a secret key
cannot: key management (
/v1/api-keys) and the dashboard surface (/v1/accounts, billing) — see what each credential can reach. - It skips scope checks for itself. When it acts for someone else, the grant's scopes are checked instead — see below.
- Moving a managed account's money is a separate permission, off when the key is issued and enabled per key by CashXChain. Without it, a master key can read for and onboard the businesses it serves, but not open accounts, register beneficiaries, pay out or convert for them.
- A master key never steps up and never reaches
/internal/*. - It cannot be created through
POST /v1/api-keys. Only CashXChain staff can issue or revoke one, and revocation takes effect on the next request. If one is exposed, tell us immediately.
Attach a business
The business signs up for CashXChain itself first. Then you attach to it by its email:
POST /v1/accounts
Authorization: Bearer cxc_mk_…
Idempotency-Key: attach-acme-gmbh
Content-Type: application/json
{"contact_email": "[email protected]", "display_name": "Acme GmbH", "country_code": "DE"}
201 returns the account and a platform_permission_id — the grant. The grant
starts with accounts:read, accounts:write, payments:read and fx:read;
payments:write is added by CashXChain.
| Error | When |
|---|---|
409 conflict | No CashXChain account has that email — the business signs up first; or it has never signed in; or it already belongs to another platform |
200 | It is already yours — the same account is returned |
A platform attaches to an identity whose owner is already in control of it;
it cannot reserve one. The business can see your grant at
GET /v1/me/platform-permissions and end it.
The business then completes verification — you can drive it for them
with Cxc-Account. They are approved by our banking partner in their own right.
See who you can act for
GET /v1/platform/accounts
[
{
"account_id": "6f1c2b9e-0d1a-4c3e-9f7a-2b8d4e6a1c00",
"display_name": "Acme GmbH",
"kyb_status": "active",
"can_move_money": true,
"grant": {"id": "…", "scopes": ["accounts:read", "accounts:write", "payments:read", "fx:read"], "kyc_mode": "direct", "granted_at": "…"}
}
]
grant.scopes are exactly the scopes checked when you act for that account.
In the sandbox can_move_money can read false for a pending account that
the sandbox would in fact let pay out.
End the relationship
DELETE /v1/platform-permissions/{id} — callable by the platform or the
managed business. The record is kept; your authority ends at once. The
business keeps its account, its bank accounts and its money.
Act for a managed account
Add one header:
POST /v1/banking/payouts
Authorization: Bearer cxc_mk_…
Cxc-Account: 6f1c2b9e-0d1a-4c3e-9f7a-2b8d4e6a1c00
Idempotency-Key: acme-payout-INV-0042
The request then acts entirely as the managed account: the payout, the debit, the limits and the verification check are theirs. Without the header, your key acts as you. Naming your own account is the same as not sending the header.
Who may send it. Your account must be a platform, you must hold a live grant for the named account, and the grant must carry the route's scope. Beyond that, it depends on the route:
| Route kind | Allowed credentials |
|---|---|
| Reads, quotes, KYB, primary-currency change | Session, mk_, sk_, or an rk_ key carrying the route's scope. Never pk_. |
| Money: open an account, register a beneficiary, payout, FX execute | Session, or mk_ with the money permission |
What you can call. Only these routes accept Cxc-Account; every other
route refuses it with 403 delegation_not_supported:
| Scope in the grant | Routes |
|---|---|
accounts:read | GET /v1/banking/accounts, …/{account_id}/payin-instructions, …/{account_id}/balance, …/plan-impact, GET /v1/banking/beneficiaries, GET /v1/banking/corridors, GET /v1/banking/settlement/*, GET /v1/banking/kyb/precheck-records, GET /v1/banking/kyb/referrals/{external_id} |
accounts:write | Every POST /v1/banking/kyb/* route, PUT /v1/banking/accounts/primary, money: POST /v1/banking/accounts |
beneficiaries:write | money: POST /v1/banking/beneficiaries. Every grant that held accounts:write before October 2026 was given this scope too, so no platform lost payee registration. |
payments:read | GET /v1/banking/payouts, GET /v1/banking/payouts/{payout_id}, POST /v1/banking/payouts/quote, GET /v1/banking/payout-options, GET /v1/transactions* |
payments:write | money: POST /v1/banking/payouts, POST /v1/banking/fx/execute |
fx:read | GET /v1/fx/rates, POST /v1/banking/fx/quote |
Digital assets (sandbox). Digital-asset onboarding
(GET/POST /v1/banking/kyb/digital-assets) is delegable under
accounts:read / accounts:write as above, and GET /v1/banking/capabilities
with a grant carrying accounts:read. Acting for a managed account, a
platform cannot send from its digital-asset account to a third party or back
to its EUR account (403 session_required: the payment needs the customer's
own person or key), nor approve its trusted beneficiaries
(403 delegation_not_supported). actions[].allowed_by_policy in
Capabilities says, route by route, what a
delegated caller is admitted to. The earlier /v1/casp/* routes were removed.
Not delegable, among others: beneficiary verification and
accept-verification, account close, statements, settlement policy changes,
API keys, request logs, usage, billing, webhooks.
Refusals
Every refusal except the last row carries details, telling you what to
fix.
| Status | Code | Cause | details |
|---|---|---|---|
| 400 | delegation_malformed | The header is not a UUID | discover |
| 400 | delegation_conflict | ?account_id= sent as well | header, query_param |
| 403 | delegation_not_supported | This route cannot be delegated | method, path |
| 403 | delegation_not_permitted | A publishable key or public session token, or your account is not a platform | credential or api_tier, required_api_tier |
| 403 | delegation_no_grant | No live grant for that account — or no such account | account_id, discover |
| 403 | delegation_scope_denied | The grant lacks the route's scope | required_scope, granted_scopes, grant_id |
| 403 | delegation_requires_session | A money route with a secret or restricted key, or a master key without the money permission | credential, capability, grant_id |
| 403 | account_not_verified | A money route for a managed account that is not active (production), refused before anything is sent — the same answer your own account gets, with delegated: true. The sandbox lets a pending managed account through and refuses a suspended one the same way. | account_id, kyb_status, next_action, delegated, referral |
| 503 | service_unavailable | The decision could not be recorded; nothing was done | — |
discover points at GET /v1/platform/accounts. Every decision — allowed or
refused — is recorded before the request runs, except a malformed or
conflicting header, which is refused before we know whom it concerns.
Things to know
- Request logs record a delegated call under your account, the one whose credential made it, not the managed account's.
- Idempotency keys belong to the account the request acts for. Two managed accounts can use the same key without colliding.
- Step-up, for a dashboard session, is bound to the person signed in, not to the managed account.
- The header is checked on the developer, money and dashboard surfaces, where only the routes in the table accept it. On the public and sandbox routes it is ignored, and the request acts as you.