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.
Want the exact fee before you send? Preview it with POST /v1/payouts/quote, an advisory, read-only fee + cost breakdown for your account.
Prerequisites
- International accounts enabled on your merchant account.
- An active USD account to send from. When you pay from a customer’s account this is the customer’s own account (
sender_account_not_provisionedif missing); when you pay from your own account it’s your merchant international account (no_active_international_accountif missing). If your account can do both, the account checked follows the payout: see sender attribution. See the error table. - A sender for compliance, how you supply it depends on which account the payout leaves from: paying from a customer’s account passes
sender_customer_idon every payout; paying from your own account passes no per-payout sender; an account that can do both passes it or omits it per payout. See below. - 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. |
| Beneficiary | email / country | yes | Recipient email, ISO 3166-1 alpha-2 country. |
| Beneficiary | bank_address.line1 / city / country | yes | Recipient bank’s address. |
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.account_type | yes | checking | savings. |
| Inline | recipient.bank_name | yes | Recipient’s bank name. |
| Inline | recipient.email | yes | Recipient email. |
| Inline | recipient.street / recipient.city / recipient.country | yes | Recipient address (country as ISO 3166-1 alpha-2, e.g. US). |
| Inline | recipient.state / recipient.zip_code | yes | State + ZIP. |
| Inline | recipient.bank_address | yes | Recipient bank’s address { line1, city, country } (country ISO alpha-2). |
| Inline | recipient.method | no | ach (default) | wire. |
| Inline | recipient.swift_code | wire | For method: "wire". |
The full recipient identity + address, account_type, and the recipient bank’s address (bank_address) are now required for USD, regardless of which network routes the payout. This guarantees the payout is deliverable on either rail. A missing field is rejected with a clear missing_field error naming it.
Sender attribution
USD requires the sending customer to be identified for compliance, and how you identify them depends on which account the payout leaves from.
From a customer’s account: pass sender_customer_id on every payout (the id of a customer created via POST /v1/customers); Swappr resolves their stored KYC. It is required: an inline sender block does not substitute for it, and omitting it returns missing_field. You may still send an inline sender alongside it: the two are complementary, inline fields winning per-field while the referenced customer’s stored KYC backfills any you omit.
From your own account: omit sender_customer_id. The sender is your merchant treasury customer, resolved automatically. If your account can only pay from its own account, sending one anyway returns customer_id_mismatch. If your account is configured to require sender identity, supply it with an inline sender block.
Both: if Technest has enabled both on your account, sender_customer_id is optional, and it selects the source per payout. Supply it and the payout is attributed to that end customer and leaves the customer’s own account (sender_account_not_provisioned if they have none). Omit it and the payout is attributed to your treasury customer and leaves your merchant international account (no_active_international_account if you have none). customer_id_mismatch does not apply, supplying a sender is a valid instruction for this configuration, not an error. This is a Technest-configured capability, not a per-request option.
customer_id (optional, for attribution) 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.
Paying a saved beneficiary. A payout by beneficiary_id uses the details saved on the beneficiary, including its email, and checks them against the same requirements as an inline recipient. If anything is missing, the payout is refused with 422 beneficiary_details_incomplete and error.missing_fields lists every missing field. Add contact or address details with PATCH /v1/beneficiaries/{id}; for missing bank details, create a new beneficiary. Some payout rails need an extra recipient field for a particular recipient (for example recipient.state for an address in the US or Canada); when a rail enabled on your account needs one, it is required on both paths.
Errors
| Code | HTTP | Cause |
|---|---|---|
fx_features_not_enabled | 403 | International accounts not enabled |
no_active_international_account | 403 | (Paying from your own account) No active merchant USD international account |
sender_account_not_provisioned | 422 | (Paying from a customer’s account) 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: recipient.account_number, recipient.routing_number, recipient.name, recipient.account_type, recipient.bank_name, recipient.email, recipient.street, recipient.city, recipient.country, recipient.state, recipient.zip_code, recipient.bank_address.line1/city/country |
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 |
beneficiary_details_incomplete | 422 | (Paying by beneficiary_id) The saved beneficiary is missing fields this payout needs; error.missing_fields lists every one. Add contact or address details with PATCH /v1/beneficiaries/{id}; for missing bank details, create a new beneficiary |
missing_field | 400 | (Paying from a customer’s account) sender_customer_id absent, it is required on FX payouts and an inline sender block does not substitute. Returned before sender_info_required. |
sender_info_required | 422 | Your account is configured to require sender identity and the merged sender details (inline sender block + the referenced customer’s stored KYC) are still incomplete |
customer_id_mismatch | 400 | customer_id ≠ sender_customer_id, or an account that can only pay from its own account sent a per-payout sender_customer_id (omit it; the sender is your treasury customer). The second cause does not apply to an account that can do both |
no_treasury_customer | 400 | (Paying from your own account) Your treasury customer isn’t provisioned yet, contact Technest to set up your international payouts |
idempotency_key_conflict | 409 | Same Idempotency-Key, different body |
idempotency_conflict | 409 | Same Idempotency-Key as a prior payout that differs in amount or recipient account: narrower than idempotency_key_conflict, which compares the whole request body |
merchant_reference_duplicate | 409 | merchant_reference was already used on a recent payout; each reference is single-use within the cool-down window |
insufficient_funds | 402 | Wallet balance is below the payout amount plus fees |
wallet_frozen | 403 | The source wallet is frozen; no debits are permitted |
beneficiary_blacklisted | 403 | Recipient is on your merchant blacklist, un-blacklist them on the Beneficiaries page, or send to a different recipient |
beneficiary_globally_blocked | 403 | Recipient is on the platform-wide blacklist and cannot be overridden, contact support |
provider_not_entitled | 403 | Your account isn’t enabled to send this currency through any configured route, contact Technest to enable it. Distinct from routing_failed: routes exist, your account is not enabled for them |
provider_paused | 403 | Sending this currency is temporarily paused on your account, contact Technest. Distinct from provider_not_entitled: your account is enabled for a route, but Technest has paused it |
routing_failed | 422 | No disbursement route is available for this payout at all, or the destination bank could not be identified |
beneficiary_cooldown | 429 | Same recipient paid recently, pass allow_duplicate: true if intentional |
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"
}'// USD requires the full recipient identity + address, account_type, and the
// recipient bank's address — regardless of ACH vs wire. `method` selects the
// rail: "ach" (default) or "wire" (add "swift_code").
{
"name": "Sarah Williams",
"account_number": "1234567890",
"routing_number": "021000089",
"account_type": "checking",
"bank_name": "Citibank",
"email": "sarah.williams@example.com",
"street": "1 Park Avenue",
"city": "New York",
"state": "NY",
"zip_code": "10016",
"country": "US",
"bank_address": { "line1": "388 Greenwich St", "city": "New York", "country": "US" },
"method": "ach"
}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": {
"name": "Sarah Williams",
"account_number": "1234567890",
"routing_number": "021000089",
"account_type": "checking",
"bank_name": "Citibank",
"email": "sarah.williams@example.com",
"street": "1 Park Avenue",
"city": "New York",
"state": "NY",
"zip_code": "10016",
"country": "US",
"bank_address": { "line1": "388 Greenwich St", "city": "New York", "country": "US" }
},
"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.