External bank payouts
Send same-currency fiat from a customer account to an outside bank (SEPA / ACH / CAD EFT / SWIFT). Source is always a customer-owned fiataccount_id (pcus_*). Destination is beneficiary + bank details, not another ledger account_id.
Prerequisite: Customers quickstart. Banking gate:
products.deposit_account.status=ready.
Auth and environments
Gates
Source
account_id must belong to {customer_id}.
If banking routes are called before ready, expect 409 with data such as deposit_account_status and reason: banking_profile_incomplete.
Happy path
1
Confirm banking ready
Poll
GET /partner/customers/{customer_id} until deposit_account.status=ready2
List accounts
Resolve source fiat
account_id via GET …/accounts3
Preview
POST …/payouts/preview with amount, beneficiary, bank → preview_token4
Confirm
POST …/payouts with preview_token + idempotency_key (tokens expire quickly; use a fresh preview)5
Poll
GET …/payouts/{payout_id} until terminal status1. Resolve source account
2. Preview
Preview and confirm must use the same sourceaccount_id. Required bank fields depend on currency / scheme (EUR SEPA: iban + country; USD ACH: routing_number + account_number; CAD EFT: institution + transit + account; SWIFT: scheme=swift + swift_bic).
preview_token, amounts, fee fields (often fee_status=pending_confirm until confirm), and customer_id. Save preview_token.
In sandbox, until Element Pay has credited balance and shared a known-good beneficiary, prefer insufficient-balance preview checks (below) over confirm.
3. Confirm
idempotency_key on retries after network errors. Confirm / GET typically surface final fee status.
4. Get payout
Business rules
- Same currency fiat → external bank only (no FX on this rail).
- Preview → confirm on the same source
account_id; use a fresh preview if the token expired. - Partner-neutral JSON: error
message/datado not expose upstream PSP names. - Balance: like book transfers, require
available >= amount(exact balance is allowed). This differs from stablecoin sends, which need amount strictly belowavailable(1 minor-unit headroom). Insufficient balance returns 422 withdata.available,data.amount, anddata.currencywhen known. - Fees may be
pending_confirmon preview and final on confirm/GET. Fees can dominate small principals (for example a fixed fee larger than a tiny payout). Always read preview / confirm / GET fee fields and do not invent a fee schedule; do not assume net receive equalsamount.
amount above available → 422 + balance fields. Do not confirm unless you have usable balance and intend a bank push.
Legacy routes
The same operations exist under/partner/entities/{entity_id}/accounts/.../payouts. Those paths are legacy. New integrations should use pcus_* customer paths only.