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. In the individual flow this is the sender’s own virtual account (sender_account_not_provisioned if missing); in the business/treasury flow it’s your merchant international account (no_active_international_account if missing). If your account is configured for both flows, the account checked follows the payout — see sender attribution. See the error table.
  • A sender for compliance — how you supply it depends on your flow: the individual flow passes sender_customer_id on every payout; the business/treasury flow passes no per-payout sender; an account configured for both passes it or omits it per payout. See below.
  • For attribution reads on the customer feed: the API key needs customer_transaction_view.
POST/v1/beneficiaries

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.

PathFieldRequiredNotes
Beneficiarytypeyesindividual | business (top-level).
Beneficiarybank.account_numberyesUS account number.
Beneficiarybank.routing_numberyes9-digit ABA routing number.
Beneficiarybank.account_typeyeschecking | savings.
Beneficiarybank.bank_nameyesReceiving bank name (e.g. Citibank).
Beneficiarybank.methodyesach (domestic) | wire (international).
Beneficiarybank.swift_codewireRequired for wire.
Beneficiaryaddress.street / city / state / zip_codeyesReceiving-network compliance.
BeneficiarynameyesRecipient 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.

Request
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

POST/v1/payouts

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.

PathFieldRequiredNotes
Inlinerecipient.account_numberyesUS account number.
Inlinerecipient.routing_numberyes9-digit ABA.
Inlinerecipient.nameyesRecipient name.
Inlinerecipient.account_typeyeschecking | savings.
Inlinerecipient.bank_nameyesRecipient’s bank name.
Inlinerecipient.emailyesRecipient email.
Inlinerecipient.street / recipient.city / recipient.countryyesRecipient address (country as ISO 3166-1 alpha-2, e.g. US).
Inlinerecipient.state / recipient.zip_codeyesState + ZIP.
Inlinerecipient.bank_addressyesRecipient bank’s address { line1, city, country } (country ISO alpha-2).
Inlinerecipient.methodnoach (default) | wire.
Inlinerecipient.swift_codewireFor 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 your flow.

Individual flow — 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.

Business / treasury flow — omit sender_customer_id. The sender is your merchant treasury customer, resolved automatically. If only this flow is enabled on your 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 flows enabled — if Technest has enabled both flows on your account, sender_customer_id is optional, and it selects the flow per payout. Supply it and the payout is attributed to that end customer and leaves the sender’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.

Errors

CodeHTTPCause
fx_features_not_enabled403International accounts not enabled
no_active_international_account403(Business/treasury) No active merchant USD international account
sender_account_not_provisioned422(Individual) The sending customer has no active USD virtual account — issue one via POST /v1/customers/{id}/virtual_accounts
usd_beneficiary_missing_method400USD beneficiary has no stored bank.method — re-save it
missing_field400/422Required 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_account400Account / routing details failed validation (bad routing number, account-number format, etc.)
beneficiary_not_found404beneficiary_id unknown / not yours / wrong env
beneficiary_currency_mismatch400beneficiary_id is not a USD beneficiary
missing_field400(Individual) 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_required422Your 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_mismatch400customer_idsender_customer_idor a business/treasury-only 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 configured for both flows
no_treasury_customer400(Business/treasury) Your treasury customer isn’t provisioned yet — contact Technest to set up your international payouts
idempotency_key_conflict409Same Idempotency-Key, different body
idempotency_conflict409Same 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_duplicate409merchant_reference was already used on a recent payout; each reference is single-use within the cool-down window
insufficient_funds402Wallet balance is below the payout amount plus fees
wallet_frozen403The source wallet is frozen; no debits are permitted
beneficiary_blacklisted403Recipient is on your merchant blacklist — un-blacklist them on the Beneficiaries page, or send to a different recipient
beneficiary_globally_blocked403Recipient is on the platform-wide blacklist and cannot be overridden — contact support
provider_not_entitled403Your 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_paused403Sending 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_failed422No disbursement route is available for this payout at all, or the destination bank could not be identified
beneficiary_cooldown429Same recipient paid recently — pass allow_duplicate: true if intentional
provider_error502Downstream rail unreachable; retry-safe with the same key

Shared management endpoints are on the NGN page.

Request — by beneficiary (preferred)
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"
  }'
Inline recipient shapes
// 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"
}
Request — inline recipient (soft-deprecated for FX)
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.processingpayout.paid / payout.failed (+ payout.reversed). customer_id / customer_reference included when set.