Send a payout
/api/v1/me/payout/sendSend money to your beneficiaries from your payout balance. Separate environment from pay-in: you fund it, optionally swap to local currency, and send the payout.
- Fund —
POST /api/v1/me/payout/transfermoves USD from your pay-in balance to payout (atomic). - Swap (optional) —
POST /api/v1/me/payout/swapconverts USD→local currency at the current rate. - Send —
POST /api/v1/me/payout/senddebits your balance in the method's currency and creates the payout.
GET /api/v1/me/payout/balance and the rails at GET /api/v1/me/payout/methods.payout_method_unavailable(422) — themethodIddoesn't exist. List the methods at GET /api/v1/me/payout/methods.currency_not_funded(422) — you have no balance in the method's currency. Do a USD→that-currency swap first.balance_insufficient(422) — the amount exceeds your balance in that currency. The response carriesdetails.availableUsdanddetails.shortfallUsd.amount_invalid(422) ·invalid_request(400) ·rate_limited(429).
Idempotency-Key — a retried POST never sends twice.recipient) — for a REAL disbursement (provider configured) the recipient object carries the destination's bank details. Fields by rail:- All:
name(beneficiary),account(account/CLABE/PIX key/CCI),documentId(identification). Optional:documentType,accountType,phone,email. - SPEI (MX):
account= CLABE (18),bankCoderequired,documentTypeRFC|CURP. - PIX (BR):
account= PIX key,accountTypeCPF|CNPJ|EMAIL|PHONE|EVP,documentTypeCPF|CNPJ. - Bank transfer (CO/PE/…):
account+bankCoderequired,accountTypeCHECKING|SAVINGS,documentTypeby country (CC, DNI, …).
/api/v1/me/payout/sendsecret keyCreates a payout. The shop is identified via the Bearer token. The `amount` is in the method's currency (e.g. MXN for po_mxn_spei), and is debited from your payout balance in that currency. Returns the payout with its `status` (processing → completed/failed). When a REAL payout provider is configured (Pagsmile), the disbursement is dispatched to it and stays `processing`; its webhook finalizes it (PAID → completed; REJECTED/REFUNDED → failed and the debit is reversed to your balance). Without a real provider configured, a simulated driver completes it instantly.
methodIdstringrequiredMethod id (GET /api/v1/me/payout/methods). E.g. po_mxn_spei.amountnumberrequiredAmount in the method's currency. >0, ≤ your balance in that currency.recipientobjectDestination data (account, name, …).
curl -X POST "https://sandbox.key2pay.ai/api/v1/me/payout/send" \
-H "Authorization: Bearer <accessToken>" \
-H "Idempotency-Key: po-001" \
-H "Content-Type: application/json" \
-d '{ "methodId": "po_mxn_spei", "amount": 1850, "recipient": { "account": "012180012345678901", "name": "Juan Perez" } }'{
"payout": {
"id": "po-202606-ab12cd34",
"methodId": "po_mxn_spei",
"methodName": "SPEI",
"currency": "MXN",
"amount": 1850,
"status": "completed",
"provider": "simulated",
"providerRef": "SIM-po-202606-ab12cd34",
"environment": "sandbox",
"createdAt": "2026-06-20T18:00:00.000Z",
"completedAt": "2026-06-20T18:00:00.100Z"
},
"methodName": "SPEI",
"logoUrl": "https://api.key2pay.ai/api/payment-method-logo/payout_spei?v=2026-07-04T00:00:00.000Z",
"iconUrl": "https://api.key2pay.ai/api/payment-method-logo/payout_spei?v=2026-07-04T00:00:00.000Z"
}Testing payouts in sandbox
Sandbox payouts do NOT auto-settle and the Sandbox-Simulate header is pay-in only — it does NOT affect payouts. The POST /api/v1/me/payout/send/{id}/simulate endpoint is the completion signal: you drive each payout to its terminal status yourself. There are two ways to test:
- Discover —
GET /api/v1/me/payout/currenciesto see which currencies you can fund + pay out in (each with its rails + `funded` flag). In sandbox this includes the synthetic test currencies. - Fund —
POST /api/v1/me/payout/fund-sandboxwith{ currency, amount }credits your pay-outavailablebalance in that currency with test money (no pay-in / transfer / swap needed). Sandbox only. - Send —
POST /api/v1/me/payout/sendwith a real rail (e.g.po_mxn_spei) debits that balance and creates the payout inprocessing. - Complete / fail —
POST /api/v1/me/payout/send/{id}/simulatewith{ action }drives it tocompleted(or fails it and reverses the debit). This runs the same money logic a real provider webhook would.
# 1) Fund the sandbox pay-out balance (test money — sandbox only):
curl -X POST https://sandbox.key2pay.ai/api/v1/me/payout/fund-sandbox \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{ "currency": "MXN", "amount": 5000 }'
# → { "currency": "MXN", "amount": 5000,
# "balances": [ { "currency": "USD", "available": 0, "pending": 0, "reserved": 0 },
# { "currency": "MXN", "available": 5000, "pending": 0, "reserved": 0 } ] }
# 2) Send a payout from that balance (lands as "processing"):
curl -X POST https://sandbox.key2pay.ai/api/v1/me/payout/send \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{ "methodId": "po_mxn_spei", "amount": 1850,
"recipient": { "account": "012180012345678901", "name": "Juan Perez" } }'
# → { "payout": { "id": "po-202606-ab12cd34", "status": "processing", … } }
# 3) Complete it (the completion signal — sandbox payouts don't auto-settle):
curl -X POST https://sandbox.key2pay.ai/api/v1/me/payout/send/po-202606-ab12cd34/simulate \
-H "Authorization: Bearer <accessToken>" \
-H "Content-Type: application/json" \
-d '{ "action": "completed" }'
# → { "payout": { "id": "po-202606-ab12cd34", "status": "completed",
# "completedAt": "2026-06-27T18:00:00.000Z", … } }
# Drive a failure instead (reverses the debit back to your balance):
curl -X POST https://sandbox.key2pay.ai/api/v1/me/payout/send/po-202606-ab12cd34/simulate \
-H "Authorization: Bearer <accessToken>" \
-d '{ "action": "failed" }'GET /api/v1/me/payout/methods prepends synthetic test rails (sbx_po_mx_spei, sbx_po_br_pix, sbx_po_co_bank, sbx_po_pe_bank) — 4 countries. A payout created with an sbx_po_* id is SYNTHETIC: it lands in processing with NO balance debit and no real provider, so you don't even need to fund first. Then move it to any status with POST /api/v1/me/payout/send/{id}/simulate.Simulate actions: completed / paid → completed · failed / rejected / refunded / returned → failed (the debit is reversed on a real-debited payout) · processing → no-op. Both endpoints are sandbox-only — they return fund_sandbox_only / simulate_sandbox_only (400) in production.
Other payout endpoints
GET /api/v1/me/payout/balance (balance per currency + transferable from pay-in) · GET /api/v1/me/payout/methods (available rails) · GET /api/v1/me/payout/currencies (distinct currencies you can pay out in, with funded + rails) · POST /api/v1/me/payout/transfer (fund from pay-in) · GET·POST /api/v1/me/payout/swap (quote/execute swap) · GET /api/v1/me/payout/send (list) · GET /api/v1/me/payout/send/{id} (get one). All in the OpenAPI viewer.