Skip to main content

Sending from a digital-asset account

Sandbox only. Every send is POST /v1/banking/payouts (and its quote) with a digital-asset account as source_account_id and exactly one destination: a destination object, or a destination_account_id naming another account of yours. A beneficiary_id beside either is 400.

SendDestinationcurrency and amount
On chain, same chain or across chainsdestination.type onchainthe account's asset; what leaves, exactly
To an IBAN (EUR over SEPA)destination.type ibanUSDC or EURC; what leaves, exactly. The EUR that arrives is an estimate
To a bank account outside SEPAdestination.type us_bank, gb_bank, clabe, pixthe asset; what leaves, exactly. What arrives is an estimate
By @handle to another CashXChain customerdestination.type cxc_handlethe asset; what leaves, exactly
Swap USDC ↔ EURCdestination_account_id: your account in the other assetthe asset sold; what leaves, exactly
Send back to your own EUR accountdestination_account_id: your EUR accountEUR; what arrives, exactly
Convert EUR into your USDC accounta payout from your EUR account to the USDC accountEUR; what leaves, exactly

Every send on chain, to an IBAN or a bank account, by @handle, and every swap:

  • is fixed at the source — amount is what leaves your wallet — with at most two decimal places;
  • names source_network, the chain of your wallet it leaves: solana, base, ethereum, tron or tempo, where the asset is offered on it (digital_actions.send_onchain lists them);
  • needs an Idempotency-Key;
  • answers 202 with the payout, which you then follow to a final status.

A send-back is the exception among sends from a digital-asset account: its amount is the EUR that arrives, and it takes no source_network. A convert is a payout from your EUR account, not from a digital-asset account.

digital_actions on GET /v1/banking/accounts says what each account offers — see Accounts and wallets.


Who sends, and how​

CallerDestinationStep-up
An API key (cxc_sk_, cxc_mk_, or cxc_rk_ with payments:write)A third party only as a trusted beneficiary: {"type": "…", "trusted_beneficiary_id": "…"}. Your own accounts (swap, send-back, convert) directlyNone
A person's session (the web app)Inline, as on this page, or a trusted beneficiaryX-Step-Up-Token bound to the send — see Step-up

The examples below set up once:

Bash
export CXC_BASE=https://api.sandbox.cashxchain.com
export CXC_KEY=cxc_sk_sandbox_... # your secret key
export CXC_SESSION=eyJ... # a person's session token, for inline sends — see below
export RUN=$(date +%s) # makes this run's Idempotency-Keys unique

USDC=$(curl -s "$CXC_BASE/v1/banking/accounts" -H "Authorization: Bearer $CXC_KEY" \
| jq -r '.accounts[] | select(.currency == "USDC") | .id')
EURC=$(curl -s "$CXC_BASE/v1/banking/accounts" -H "Authorization: Bearer $CXC_KEY" \
| jq -r '.accounts[] | select(.currency == "EURC") | .id')

The inline examples are a person's sends, as the web app makes them: $CXC_SESSION is that person's session token and $STEP_UP a step-up token bound to the send. To make them by hand in the sandbox, get the first as in Authentication and mint the second as in Minting a token, bound to the step-up recipient each example names. From an API key, send the same destination as a trusted beneficiary instead — Sending with an API key.


On chain​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onchain-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {
"type": "onchain",
"network": "solana",
"address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"beneficiary": {
"is_self": false,
"type": "business",
"name": "Muster Handels GmbH",
"address": {"street_line_1": "Hauptstr. 1", "city": "Berlin", "postal_code": "10115", "country": "DE"},
"business_identifier": {"type": "vat", "value": "DE123456789", "issuing_country": "DE"},
"wallet_type": "external"
}
}
}'

Step-up recipient: solana:9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM.

Field
destination.networkThe chain the address is on: source_network, or another for a cross-chain send
destination.addressChecked for that chain and taken verbatim (base58 is case-sensitive): EVM chains 0x and 40 hex digits; Solana base58, 32 to 44 characters; Tron T and 33 base58 characters; Stellar G and 55 base32 characters with a valid checksum; Tempo an EVM address. One of your own wallets is refused (422 destination_is_own_wallet)
destination.memoOptional, and only on Stellar (at most 28 bytes). A memo on any other chain is 400 — Tron included, for now
destination.beneficiaryRequired: the travel rule
destination_network (top level)Optional; must equal destination.network

No reference on chain (400). The provider's minimum is 1 of the asset on chain. While the send is in flight, the wallet holds its amount plus its chain's headroom — 1 %, 5 % on Ethereum — so the wallet must cover both; from a minute after its debit is read, only the headroom, until about a minute after the send finishes (holds).

The travel rule​

beneficiary says who receives the money:

  • A third party (is_self: false): type (individual or business), name, address with country (ISO 3166-1 alpha-2), street_line_1 and city (postal_code optional), wallet_type — external (an exchange or another custodian) or self_custodied — and, for a business, a business_identifier.
  • Your own wallet elsewhere: {"is_self": true, "wallet_type": "external"} (or "self_custodied") and nothing else.

Anything missing is 422 travel_rule_incomplete, with every missing field in details.fields, before your step-up is spent. The provider refuses a third party's address without a street and a city only once the transfer is created, so they are required here first.

Your own self-custodied wallets (is_self: true, wallet_type: self_custodied) may receive at most 1,000 of each asset (EURC, USDC) over a rolling 30 days, this send included. Past that the provider needs the wallets' ownership attested, which is not offered yet: 422 wallet_attestation_required with details.sent and details.remaining. Sends to third parties' wallets, and to your own wallets at a custodian, are not counted.

A business identifier​

Required for a business beneficiary on chain, and for a business holding an IBAN or bank account that is not your own business:

JSON
{"type": "lei", "value": "506700GE1G29325QX363"}
typevalueissuing_country
leiA Legal Entity Identifier, ISO 17442: 20 characters, mod-97 checkOptional
vatA VAT number: a two-letter prefix and 2 to 13 letters and digits; spaces and dots are ignoredRequired
company_registrationA commercial-register number, 1 to 64 charactersRequired

Missing, it is 422 travel_rule_incomplete naming destination.beneficiary.business_identifier (or destination.owner.business_identifier), or the missing part; malformed, it is 400 bad_request.

Across chains​

Name a destination.network other than source_network. The same asset arrives on the other chain — never a swap. The routes the provider offers from a wallet:

AssetDestination chainsMinimum
USDCarbitrum, avalanche_c_chain, base, celo, ethereum, optimism, polygon, solana, stellar1
EURCbase, ethereum, solana, stellar1
USDTethereum, solana (20); tron, tempo (5) — not for a business in the EEAas shown

digital_actions.send_onchain_to lists the chains your account reaches; anything else is 422 corridor_unsupported.

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: crosschain-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {
"type": "onchain",
"network": "base",
"address": "0x1111111111111111111111111111111111111111",
"beneficiary": {
"is_self": false,
"type": "individual",
"name": "Jane Doe",
"address": {"street_line_1": "923 Folsom Street", "city": "San Francisco", "postal_code": "94107", "state": "CA", "country": "US"},
"wallet_type": "external"
}
}
}'

Step-up recipient: base:0x1111111111111111111111111111111111111111 — the chain the address is on. The payout reads network: "solana" (the wallet it left) and destination_network: "base".

A memo (Stellar)​

A Stellar address that an exchange credits by memo takes memo, and the step-up binds it too: stellar:{address}:{memo}.

JSON
"destination": {
"type": "onchain",
"network": "stellar",
"address": "GBRPYHIL2CI3FNQ4BXLFMNDLFJUNPU2HY3ZMFSHONUCEOASW7QC7OX2H",
"memo": "4711",
"beneficiary": { "is_self": false, "type": "business", "name": "Muster Handels GmbH",
"address": {"street_line_1": "Hauptstr. 1", "city": "Berlin", "country": "DE"},
"business_identifier": {"type": "lei", "value": "506700GE1G29325QX363"},
"wallet_type": "external" }
}
A Stellar account needs a trust line for the asset

The provider refuses a send to a Stellar account that holds no trust line for the asset it receives, when the transfer is created: 422 destination_not_payable, provider_refused, the payout failed. Check the account before you send — see Sandbox behaviour.


To an IBAN​

USDC or EURC paid out as EUR over SEPA, to a third party's IBAN or your own.

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: iban-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "10.00",
"currency": "USDC",
"reference": "Invoice 4711",
"destination": {
"type": "iban",
"iban": "DE89 3704 0044 0532 0130 00",
"bic": "COBADEFFXXX",
"owner": {
"type": "individual",
"first_name": "Erika",
"last_name": "Mustermann",
"address": {"street_line_1": "Hauptstr. 1", "city": "Berlin", "postal_code": "10115", "country": "DE"}
}
}
}'

Step-up recipient: the IBAN without spaces, upper case — DE89370400440532013000.

Field
ibanSpaces allowed
bicOptional
owner{"type": "individual", "first_name", "last_name", "address"} or {"type": "business", "business_name", "business_identifier", "address"}. The whole name is 3 to 35 characters
owner.addresscountry always. For a holder that is not you (the name is not your legal name), also street_line_1 and city; postal_code optional. A business that is your own needs no business_identifier
referenceOptional: 6 to 140 letters, digits, spaces and & - . /, sent as the SEPA reference. Without one the transfer carries CXC and the payout's id

The minimum is 2 of the asset. EURC to an IBAN is one for one; from USDC, the EUR that arrives is the provider's conversion when it executes — the quote estimates it. The IBAN is registered with the provider as a payee the first time you pay it under that holder's name, once your step-up has been checked, and reused after.

In the sandbox

The provider's sandbox accepts an IBAN send and never finishes it, so the sandbox settles it itself from a minute after acceptance, once the money has been seen leaving the wallet (simulated: true) — see Sandbox behaviour.


To a bank account outside SEPA​

A third party's US account (USD over ACH or wire), UK account (GBP over Faster Payments), Mexican CLABE (MXN over SPEI) or Brazilian Pix key (BRL over Pix). Built like a send to an IBAN: fixed at the source, the holder in owner, an estimate of what arrives.

destination.typeFromPaid in, overMinimumFields
us_bankUSDC, EURCUSD, ACH or wire1routing_number — 9 digits, valid ABA check digit; account_number — 4 to 17 digits; account_type — checking (default) or savings; bank_name — required, the name of the account's bank, 1 to 256 characters; rail — ach (default) or wire
gb_bankUSDCGBP, Faster Payments3sort_code — 6 digits (hyphens allowed); account_number — 8 digits
clabeUSDC, EURCMXN, SPEI2 (USDC), 1 (EURC)clabe — 18 digits, valid check digit
pixUSDCBRL, Pix2pix_key — a random key (UUID), a CPF (11 digits), a CNPJ (14), a phone number (+55 and 11 digits) or an email; document_number — optional, the holder's CPF or CNPJ, digits only

Your region decides which. The provider closes GBP, MXN and BRL to businesses in the EEA, so a business registered in the EEA is offered us_bank alone; gb_bank, clabe and pix are for businesses outside it. A kind closed to your region is 422 corridor_unsupported with details.corridor {"type": "bank", "currency", "destination_type", "destination_currency"}. digital_actions.send_bank lists what your account offers.

And the rail must be enabled for you. A kind is offered only over a rail the provider has enabled for your account — rails in GET /v1/banking/kyb/digital-assets: USD (ACH, wire, FedNow) for us_bank, GBP (Faster Payments), MXN (SPEI) and BRL (Pix) for the others. A send over a rail not enabled — or to an IBAN while your EUR (SEPA) rail is not — is 409 digital_assets_onboarding_required with details.rail, before your step-up is spent. In the sandbox the approval (POST /v1/sandbox/banking/kyb/digital-assets/approve) enables all of them. An account approved before GBP, MXN and BRL were added holds USD and EUR (SEPA) only: call the approval again to add the others.

The holder (owner) is built as for an IBAN. A third party needs address.street_line_1 and address.city, and a business its business_identifier. A US account needs the holder's whole address: street_line_1 (4 to 35 characters; with a street number for a wire), city, postal_code, country, and state for a US address (CA).

reference (optional)
us_bank, ACHAt most 10 letters, digits and spaces
us_bank, wireAt most 140 printable characters
gb_bankAt most 18 letters, digits, spaces and - . / &
clabeAt most 40 letters, digits and spaces
pixAt most 100 characters

Without one the payout carries CXC and its id, cut to the rail's length.

US account (ACH or wire)​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: us-bank-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"reference": "INV 4711",
"destination": {
"type": "us_bank",
"routing_number": "101019644",
"account_number": "123456789",
"account_type": "checking",
"bank_name": "Lead Bank",
"rail": "ach",
"owner": {
"type": "business",
"business_name": "Acme Supplies Inc",
"business_identifier": {"type": "lei", "value": "506700GE1G29325QX363"},
"address": {"street_line_1": "923 Folsom Street", "city": "San Francisco",
"postal_code": "94107", "state": "CA", "country": "US"}
}
}
}'

Step-up recipient: us:101019644:123456789:checking. The rail and the bank name are not part of it.

A send without bank_name — or with one blank, over 256 characters or with a control character — is 400 bad_request naming destination.bank_name, before your step-up is spent. The name is sent when the account is first registered with the provider; a later send to the same account and holder pays that registration, whatever bank name it gives. "rail": "wire" pays the same account by wire.

UK account (Faster Payments)​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gb-bank-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {
"type": "gb_bank",
"sort_code": "123456",
"account_number": "34567890",
"owner": {"type": "individual", "first_name": "Maria", "last_name": "Silva",
"address": {"street_line_1": "10 Downing Street", "city": "London", "country": "GB"}}
}
}'

Step-up recipient: gb:123456:34567890.

Mexican CLABE (SPEI)​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: clabe-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {
"type": "clabe",
"clabe": "032180000118359719",
"owner": {"type": "individual", "first_name": "Maria", "last_name": "Silva",
"address": {"street_line_1": "Av. Reforma 222", "city": "Ciudad de Mexico", "country": "MX"}}
}
}'

Step-up recipient: clabe:032180000118359719. The CLABE's check digit is enforced: 032180000118359719 passes it.

Brazilian Pix key​

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pix-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "10.00",
"currency": "USDC",
"destination": {
"type": "pix",
"pix_key": "recipient@example.com",
"document_number": "52998224725",
"owner": {"type": "individual", "first_name": "Maria", "last_name": "Silva",
"address": {"street_line_1": "Av. Paulista 1000", "city": "Sao Paulo", "country": "BR"}}
}
}'

Step-up recipient: pix:recipient@example.com|52998224725 — without a document, pix:{key}. A CPF or CNPJ Pix key's check digits are enforced (529.982.247-25 passes); an email key takes letters, digits and . _ % + - before the @.

The payout reads kind us_bank, gb_bank, clabe or pix, destination_currency USD, GBP, MXN or BRL, and rail ach, fedwire, fps, spei or pix.

In the sandbox

The provider's sandbox accepts gb_bank, clabe and pix sends and never finishes them, so the sandbox settles each itself from a minute after acceptance, once the money has been seen leaving the wallet (simulated: true). A us_bank send is left to the provider's sandbox for five minutes: it was seen to complete one over ACH itself, which then reads settled with no simulated. One still in flight after five minutes is settled by the sandbox like the others — see Sandbox behaviour.


By @handle​

To another CashXChain customer, by their handle: your wallet into theirs, inside the provider — no address, no chain fee, no travel rule.

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_SESSION" \
-H "X-Step-Up-Token: $STEP_UP" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: handle-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"source_network": "solana",
"amount": "5.00",
"currency": "USDC",
"destination": {"type": "cxc_handle", "handle": "@their_handle"}
}'

Step-up recipient: @their_handle (lower case).

  • handle — with or without @, as GET /v1/usernames/{username} finds it. An unknown handle is 404; your own is 400.
  • Which of their wallets it goes into is ours to choose: their wallet on source_network where they hold the asset there, else one on another chain the provider routes to (USDC: base, ethereum, solana; EURC: base, solana). digital_actions.send_internal_to lists the chains it may be. It is never shown to you: no destination_network on the quote or the payout, and no tx_hash.
  • No destination.network and no top-level destination_network (400), and no reference (400).
  • They must be able to receive the asset: an open account in it, an active verification, and the asset open to their region as well as yours (USDT never reaches a business in the EEA). Otherwise 422 recipient_cannot_receive with details.asset — ask them to open one.
  • A send from a chain you have no wallet on is 409 wallet_not_open, before the handle is looked up.

The payout reads kind: "internal" and rail: "internal". In the sandbox it settles in well under a minute. It arrives as theirs: a pay-in with rail internal, from you by the name your handle shows — see An @handle arrival.


Swap​

USDC ↔ EURC, inside your wallet on one chain (solana, base or tempo). Name your account in the other asset:

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: swap-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"destination_account_id": "'"$EURC"'",
"source_network": "solana",
"destination_network": "solana",
"amount": "10.00",
"currency": "USDC"
}'
  • destination_network, when given, must equal source_network.
  • An API key needs no step-up. A session's is bound to (amount, the asset sold, destination_account_id).
  • Minimums: 2 USDC into EURC, 1 EURC into USDC.
  • No reference. A swap into the asset it sells is 400.
  • The bought asset arrives in the same wallet and is booked into your account in it; the swap shows it as arrival with matched_by: "transfer". It is your own money moved inside the provider: GET /v1/transactions lists it with type and rail internal and no counterparty, and its payin.received names rail internal.
  • A swap that ended 503 payout_outcome_unknown is confirmed later. Its bought asset, arriving before the swap's transfer is known, waits unbooked until the transfer is found and is then booked as the swap's arrival — for at most 30 minutes, and only while the swap is being confirmed. One that waited longer, or whose swap was never made, is booked as an ordinary deposit, not linked to the swap: type onchain_transfer where the provider's record of it names a chain, internal where it names the provider's own wallet.
  • digital_actions.swap_to lists the other asset where you hold both accounts on a chain both share.
In the sandbox

The provider's sandbox accepts a swap and never completes it. From a minute after acceptance, once the sold asset has been seen leaving the wallet, the sandbox has the provider's sandbox deposit the bought asset into the same wallet (the receipt's figure, else the quote's estimate, cut to the cent) and settles the swap (simulated: true). The deposit shows as the swap's arrival once the wallet is read, within seconds, so arrival can still be null just after settled. It is listed as internal, not as a deposit on chain, though the provider's sandbox records it like one. See Sandbox behaviour.


Send back to your EUR account​

USDC from the USDC account to your own EUR account — never anyone else's. Name the EUR account from GET /v1/banking/accounts:

Bash
EUR=$(curl -s "$CXC_BASE/v1/banking/accounts" -H "Authorization: Bearer $CXC_KEY" \
| jq -r '.accounts[] | select(.currency == "EUR") | .id')

curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: send-back-$RUN" \
-d '{
"source_account_id": "'"$USDC"'",
"destination_account_id": "'"$EUR"'",
"amount": "10.00",
"currency": "EUR",
"reference": "Back to the bank"
}'
  • amount is what arrives, in EUR, exactly (two decimal places). What leaves the USDC account is the provider's conversion at its rate when it executes, plus its fees. The quote estimates it and says max_source_amount — what the account must have free, and holds while the send is in flight and for about a minute after it finishes. Once the provider reports the execution, the payout reads what left: source_amount and source_currency (USDC), and exchange_rate — the EUR that arrived per USDC that left, the provider's buffer and fees included, so not the quote's rate.
  • No beneficiary_id and no fx_quote_id (the provider's rate cannot be locked); rail, if sent, must be sepa.
  • An API key of the account sends it with no step-up (a payment to self). A session's step-up is bound to (amount, EUR, destination_account_id). A session or key acting for another account (Cxc-Account) is 403 session_required.
  • Offered where the USDC account's own_account_payouts reads ["EUR"]. Otherwise 422 corridor_unsupported.
  • The destination must be held in your company's legal name (422 destination_not_payable, holder_not_customer, otherwise).
  • The provider registers the EUR account under that name, which must be 3 to 35 characters. A legal name outside that is 422 destination_not_payable, holder_name_length, on every send-back from the account, before your step-up is spent: contact support.

In the sandbox the destination is your sandbox EUR account, and its arrival is simulated: the EUR is booked on it within about two minutes of the provider accepting the send-back, or at once with POST /v1/sandbox/banking/payouts/{payout_id}/simulate-arrival. The payout then shows arrival with matched_by: "reference". The provider's sandbox never settles a send-back, so the sandbox settles it itself from a minute after acceptance, once the USDC has been seen leaving the wallet (simulated: true). simulate-completion skips the minute but not that check: before the debit is seen it answers 409 not_simulatable (debit_not_seen), so ask again in a few seconds. The provider's sandbox never reports the execution, so a send-back the sandbox settles shows what the sandbox saw instead: source_amount and source_currency, the USDC the wallet has been seen to give for its transfer, worked out each time the payout is read (an entry for its gas read after the settlement counts too), and exchange_rate, amount ÷ source_amount to six places — "5.640000" USDC for 5.00 EUR reads "0.886525". See Sends the sandbox settles.

Bash
curl -s -X POST "$CXC_BASE/v1/sandbox/banking/payouts/$PAYOUT_ID/simulate-arrival" \
-H "Authorization: Bearer $CXC_KEY"

200 — {"payout_id", "payin_id", "booked": true, "linked": true}; asking again answers the same credit with booked: false. It is refused 422 not_eligible with details.reason: not_a_send_back, not_accepted, returning, too_old (over 30 days), not_to_a_sandbox_account, no_mock_rail or no_digital_rail. The send-back's own status is not changed by it: that is simulate-completion's.


Convert EUR into USDC​

EUR from your EUR account into your own USDC account, where it arrives as USDC. A payout from the EUR account, naming the USDC account:

Bash
curl -s -X POST "$CXC_BASE/v1/banking/payouts" \
-H "Authorization: Bearer $CXC_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: convert-$RUN" \
-d '{
"source_account_id": "'"$EUR"'",
"destination_account_id": "'"$USDC"'",
"amount": "20.00",
"currency": "EUR"
}'
  • amount is what leaves the EUR account, in EUR, at least 1.00. What arrives is the provider's conversion at its rate, less its fees.
  • Leave out beneficiary_id, fx_quote_id and reference: the payout carries a reference of ours, which is how its arrival is recognised.
  • Whoever may make an ordinary payout from the EUR account may convert: an API key with no step-up; a session with a step-up bound to (amount, EUR, destination_account_id).
  • Offered where the EUR account's own_account_payouts reads ["EUR"]. Otherwise 422 corridor_unsupported.
  • Once the USDC is credited and recognised, the payout shows it as arrival (matched_by: "converted_amount").

In the sandbox, where the sandbox EUR account converts, the payout is settled in the 202; the estimated USDC is deposited into your Solana wallet and booked within seconds, and linked as arrival within about ten minutes. Two converts within ten minutes whose estimates lie within about 5 % of each other cannot be told apart: space them out or make their sizes clearly different. At most 30 sandbox converts an hour per account (429 rate_limited).

A convert from an account at a real bank registers the USDC account as a payee first: the first one answers 409 destination_not_ready with details.ready_at. Send the same request, under the same Idempotency-Key, again from then (about a minute later).


After the send​

202 Accepted with the payout. Beside the usual payout fields, every payout from or to a digital-asset account carries:

Field
kindonchain, iban, us_bank, gb_bank, clabe, pix, internal (by @handle), swap, own_eur (sent back to your EUR account), convert (EUR into your USDC account)
networkThe chain of the wallet the money left
destination_networkOn chain: the chain the address is on — network, or another across chains
destination_currencyWhat the destination receives: the asset on chain, EUR to an IBAN or sent back, USD/GBP/MXN/BRL to a bank account, the asset bought by a swap, USDC for a convert
tx_hashOn chain: the transaction hash, once the provider reports the send completed
source_account_id, destination_account_idThe accounts it was requested with; destination_account_id is null on a send to a third party
arrivalA send-back, convert or swap: the credit it became on your other account, once linked; else null
quote_id, routeOn the 202 answer of a payout sent under a route lock; GET does not return them
simulatedSandbox only: true on a payout the sandbox settled itself because the provider's sandbox left it in flight — see Sends the sandbox settles. Absent otherwise — on a us_bank send the provider's sandbox completed itself, too — and always in production

On a send from a digital-asset account, amount and currency are the asset that left, at its six places ("5.000000"); on a send-back and a convert they are the EUR, as requested. rail is the chain on chain, sepa to an IBAN, the bank scheme to a bank account, internal by @handle.

Bash
curl -s "$CXC_BASE/v1/banking/payouts/$PAYOUT_ID" \
-H "Authorization: Bearer $CXC_KEY" | jq '{status, kind, network, destination_network, tx_hash, arrival}'

The status moves as any payout's — submitted, processing, settled, or failed, cancelled, returned — see the lifecycle. There are no webhooks for sends: poll GET /v1/banking/payouts/{id}. In the sandbox a send's status is read from the provider every 15 seconds for its first ten minutes and about every two minutes after — see when a send reads settled. GET /v1/banking/payouts lists them, newest first.

The same Idempotency-Key and body answer 200 with the payout; another body under the key is 409 idempotency_key_reuse. A refusal without details.idempotency_key_spent: true recorded nothing: correct the request and send it again under the same key. With it, the key is spent — see Errors.

Minimums and caps​

SendMinimum, in the asset
On chain, same chain1
Across chains1 (USDC, EURC); USDT 20 to ethereum and solana, 5 to tron and tempo
To an IBAN2
us_bank1
gb_bank3
clabe2 from USDC, 1 from EURC
pix2
By @handleThe largest the provider sets over the chains the asset could reach from your wallet
Swap2 USDC → EURC, 1 EURC → USDC
Convert1.00 EUR

Below it: 422 amount_below_minimum with details.minimum. The quote answers minimum for the send you describe.

Sends are capped per asset, in the asset: in the sandbox 10,000 a send, 50,000 a day and 250,000 a month (422 digital_send_limit_exceeded, with details.cap, used and remaining). GET /v1/banking/capabilities gives the caps the deployment runs with. Digital sends do not count against your plan's USD payout limits.

Every error code is on Errors.