USD payouts
Send USD to US bank accounts via ACH (domestic) or wire (international), settled from your USD international account.
See the Payouts overview for common fields + customer attribution, and the NGN page for the shared payout object + management endpoints.
Prerequisites
- International accounts enabled on your merchant account.
- An active USD account to send from. In the individual flow this is the sender’s own virtual account (
sender_account_not_provisionedif missing); in the business/treasury flow it’s your merchant international account (no_active_international_accountif missing). See the error table. - A sender for compliance —
sender_customer_id(preferred) or an inlinesenderblock. - For attribution reads on the customer feed: the API key needs
customer_transaction_view.
POSTSave a USD beneficiary, then pay by reference
A saved USD beneficiary captures the routing method (ACH vs wire), account type, recipient type, and address — everything the receiving network needs.
For an international wire, set bank.method: "wire" and include bank.swift_code.
| Path | Field | Required | Notes |
|---|---|---|---|
| Beneficiary | type | yes | individual | business (top-level). |
| Beneficiary | bank.account_number | yes | US account number. |
| Beneficiary | bank.routing_number | yes | 9-digit ABA routing number. |
| Beneficiary | bank.account_type | yes | checking | savings. |
| Beneficiary | bank.bank_name | yes | Receiving bank name (e.g. Citibank). |
| Beneficiary | bank.method | yes | ach (domestic) | wire (international). |
| Beneficiary | bank.swift_code | wire | Required for wire. |
| Beneficiary | address.street / city / state / zip_code | yes | Receiving-network compliance. |
| Beneficiary | name | yes | Recipient name. |
A USD beneficiary saved before ACH/wire capture has no stored method. Paying it returns usd_beneficiary_missing_method — re-save the beneficiary specifying bank.method to fix.
curl https://api.swappr.me/api/v1/beneficiaries \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"currency": "USD",
"name": "Sarah Williams",
"type": "individual",
"bank": {
"account_number": "1234567890",
"routing_number": "021000089",
"account_type": "checking",
"method": "ach",
"bank_name": "Citibank"
},
"address": {
"street": "1 Park Avenue",
"city": "New York",
"state": "NY",
"zip_code": "10016"
}
}'POSTCreate a USD payout
amount_minor is in cents (250000 = $2,500.00). Prefer the by-beneficiary path; the inline recipient is soft-deprecated for FX.
The inline path is the simpler shape — account_number + routing_number + name. method defaults to ach; pass method: "wire" + swift_code + bank_name for an international wire.
| Path | Field | Required | Notes |
|---|---|---|---|
| Inline | recipient.account_number | yes | US account number. |
| Inline | recipient.routing_number | yes | 9-digit ABA. |
| Inline | recipient.name | yes | Recipient name. |
| Inline | recipient.method | no | ach (default) | wire. |
| Inline | recipient.swift_code / recipient.bank_name | wire | For method: "wire". |
Sender attribution
USD requires the sending customer to be identified — sender_customer_id (preferred) or an inline sender block; the two are complementary (inline fields win per-field, the referenced customer’s KYC backfills gaps). customer_id must match sender_customer_id if both are passed (customer_id_mismatch otherwise).
Response
201 Created with the payout object (currency: "USD"). Status transitions to paid on network confirmation — listen for the payout.paid webhook.
Errors
| Code | HTTP | Cause |
|---|---|---|
fx_features_not_enabled | 403 | International accounts not enabled |
no_active_international_account | 403 | (Business/treasury) No active merchant USD international account |
sender_account_not_provisioned | 422 | (Individual) The sending customer has no active USD virtual account — issue one via POST /v1/customers/{id}/virtual_accounts |
usd_beneficiary_missing_method | 400 | USD beneficiary has no stored bank.method — re-save it |
missing_field | 400/422 | Required recipient field absent (account_number, routing_number, name) |
invalid_account | 400 | Account / routing details failed validation (bad routing number, account-number format, etc.) |
beneficiary_not_found | 404 | beneficiary_id unknown / not yours / wrong env |
beneficiary_currency_mismatch | 400 | beneficiary_id is not a USD beneficiary |
sender_info_required | 422 | No sender_customer_id and no complete inline sender block |
customer_id_mismatch | 400 | customer_id ≠ sender_customer_id |
idempotency_key_conflict | 409 | Same Idempotency-Key, different body |
provider_error | 502 | Downstream rail unreachable; retry-safe with the same key |
Shared management endpoints are on the NGN page.
curl https://api.swappr.me/api/v1/payouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": "250000",
"currency": "USD",
"beneficiary_id": "ben_cmo8x2p9q0...",
"sender_customer_id": "cust_cmoji8...",
"customer_id": "cust_cmoji8...",
"merchant_reference": "PO-9981"
}'// ACH (domestic)
{ "account_number": "1234567890", "routing_number": "021000089", "name": "Sarah Williams", "method": "ach" }
// Wire (international)
{ "account_number": "1234567890", "swift_code": "CITIUS33XXX", "bank_name": "Citibank", "name": "Sarah Williams", "method": "wire" }curl https://api.swappr.me/api/v1/payouts \
-H "Authorization: Bearer sk_test_..." \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount_minor": "250000",
"currency": "USD",
"recipient": { "account_number": "1234567890", "routing_number": "021000089", "name": "Sarah Williams", "method": "ach" },
"sender_customer_id": "cust_cmoji8...",
"merchant_reference": "PO-9981"
}'Webhook events
payout.processing → payout.paid / payout.failed (+ payout.reversed). customer_id / customer_reference included when set.