Endpoints

Send a payout

POST/api/v1/me/payout/send

Send 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.

The payout environment is independent from pay-in. The flow:
  1. FundPOST /api/v1/me/payout/transfer moves USD from your pay-in balance to payout (atomic).
  2. Swap (optional)POST /api/v1/me/payout/swap converts USD→local currency at the current rate.
  3. SendPOST /api/v1/me/payout/send debits your balance in the method's currency and creates the payout.
Check your balance per currency at GET /api/v1/me/payout/balance and the rails at GET /api/v1/me/payout/methods.
Validations and errors:
  • payout_method_unavailable (422) — the methodId doesn'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 carries details.availableUsd and details.shortfallUsd.
  • amount_invalid (422) · invalid_request (400) · rate_limited (429).
Send an Idempotency-Key — a retried POST never sends twice.
Beneficiary data (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), bankCode required, documentType RFC|CURP.
  • PIX (BR): account = PIX key, accountType CPF|CNPJ|EMAIL|PHONE|EVP, documentType CPF|CNPJ.
  • Bank transfer (CO/PE/…): account + bankCode required, accountType CHECKING|SAVINGS, documentType by country (CC, DNI, …).
A missing or invalid field is rejected and the debit is reversed automatically — you're never left short on balance by a failed payout.
POST/api/v1/me/payout/sendsecret key

Creates 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.

Body parameters
  • methodIdstringrequired
    Method id (GET /api/v1/me/payout/methods). E.g. po_mxn_spei.
  • amountnumberrequired
    Amount in the method's currency. >0, ≤ your balance in that currency.
  • recipientobject
    Destination data (account, name, …).
Request
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" } }'
Response
{
  "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:

End-to-end flow (fund a real sandbox balance):
  1. DiscoverGET /api/v1/me/payout/currencies to see which currencies you can fund + pay out in (each with its rails + `funded` flag). In sandbox this includes the synthetic test currencies.
  2. FundPOST /api/v1/me/payout/fund-sandbox with { currency, amount } credits your pay-out available balance in that currency with test money (no pay-in / transfer / swap needed). Sandbox only.
  3. SendPOST /api/v1/me/payout/send with a real rail (e.g. po_mxn_spei) debits that balance and creates the payout in processing.
  4. Complete / failPOST /api/v1/me/payout/send/{id}/simulate with { action } drives it to completed (or fails it and reverses the debit). This runs the same money logic a real provider webhook would.
bash
# 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" }'
Quick status testing (no funding): in sandbox, 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.