SWIFT Cross-Border Payouts
Cross-border payouts use the SWIFT destination type when the destination country or currency has no local corridor (no ACH, SEPA, domestic GBP, mobile money, etc.). SWIFT is the fallback wire rail for USD, EUR, HKD, and similar currencies into most countries around the world.
If GET /api/payouts/supported-countries/:country returns a corridor whose destination_types array contains "swift", that country supports SWIFT payouts. SWIFT is typically the only option when the destination currency isn't the country's native currency (e.g. paying USD into the UK, or EUR into Switzerland).
Three-Step Flow
SWIFT payouts are three separate HTTP calls. Beneficiary details go on the initialize call — not on the quote.
A common integration mistake is putting beneficiary/bank details on the quote. The quote body only carries currency/amount/country — all beneficiary information (SWIFT code, bank address, recipient address, sender identity) goes on the initialize call.
Step 1 — Create Quote
Describes the currency, amount, and destination country. No beneficiary info.
Request Body
field | type | required | description |
|---|---|---|---|
from_asset | String | Required | Source asset being debited. Examples: 'USDC', 'USD', 'BTC', 'USDT'. Alphanumeric, max 10 chars. |
to_currency | String | Required | Destination currency the beneficiary receives. Examples: 'USD', 'EUR', 'GBP', 'HKD'. Alphanumeric, max 10 chars. |
source | String | Required | 'offchain' debits your company balance; 'onchain' means the user funds the payout via crypto transfer. |
chain | String | Conditional | Required when source = 'onchain'. Values: 'ETHEREUM', 'TRON', 'BTC', etc. |
amount | String (decimal) | Conditional | Source-asset amount (e.g. '1500.00'). One of amount / settlement_amount / amount_in_base_units must be provided. |
settlement_amount | String (decimal) | Conditional | Destination-currency amount. Use instead of amount when you want to pin the receive side. |
amount_in_base_units | String (integer) | Conditional | Source amount in the asset's smallest unit. Alternative to amount. |
country | String | Required | Destination country (ISO 3166-1 alpha-2 or alpha-3). |
payment_reason | String | Optional | Free-text description of the payment. |
reference | String | Optional | Client-generated idempotency / tracking key. |
client_meta_data | String | Optional | Arbitrary metadata string for your own bookkeeping. |
Example Request
Example Response
Step 2 — Initialize with SWIFT Beneficiary
The initialize call carries the full SWIFT beneficiary payload. The server validates the beneficiary object against the corridor schema registered for (country, destination_type).
Top-Level Fields
field | type | required | description |
|---|---|---|---|
beneficiary | Object | Required | The SWIFT beneficiary payload. Validated against the corridor schema for (country, destination_type). Details below. |
customer_id | UUID | Optional | Link the payout to a persisted customer record. |
reference | String | Optional | Overrides or supplements the quote's reference. |
payment_reason | String | Optional | Free-text description of the payment. |
callback_url | String | Optional | Per-payout webhook override URL. |
client_meta_data | String | Optional | Arbitrary client metadata. |
All fields are snake_case on our API. Enum values (destination_type, remittance_purpose, sender.type) are lowercase on input — the server uppercases them before forwarding to the underlying rail. Sending SWIFT also works (the discriminator lookup is case-insensitive), but swift / family_support / business is the canonical form.
Beneficiary — Core SWIFT Fields
Required on every SWIFT payload, regardless of destination country:
field | type | required | description |
|---|---|---|---|
destination_type | String | Required | Must be 'swift' (lowercase). Explicitly set this — the server will fall back to 'bank' otherwise. |
country | String | Required | ISO 3166-1 alpha-2 destination country. Must match the quote's country. |
account_name | String | Required | Recipient's name. Must contain ≥2 space-separated parts of ≥2 chars each. Honorifics (Mr/Mrs/Dr) and single-letter-plus-dot (e.g. 'J.') are rejected. |
account_number | String | Required | For IBAN countries (GB, DE, AT, AD, BE, BG, CH, FR, …) the IBAN, regex ^[A-Z0-9]{15,34}$. For non-IBAN countries (AR, BW, CN, …) a plain account number, 5–34 chars. |
swift_code | String | Required | BIC/SWIFT code. 8 or 11 uppercase chars. Pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$. Example: 'DEUTDEFF' or 'DEUTDEFF500'. |
bank_name | String | Required | Full legal name of the receiving bank. Example: 'Deutsche Bank AG'. |
bank_address | String | Required | Street address of the receiving bank branch. |
bank_city | String | Required | City of the receiving bank branch. |
bank_post_code | String | Required | Postal/ZIP code of the receiving bank. |
bank_country | String | Required | ISO 3166-1 alpha-2 country of the receiving bank. |
remittance_purpose | String (enum) | Required | Lowercase snake_case enum value — see the Remittance Purpose section below for the full list. |
beneficiary | Object (nested) | Required | Physical address of the recipient — see 'Nested beneficiary' below. |
sender | Object (nested) | Required | Payer identity — discriminated on type. See 'Sender variants' below. |
Nested beneficiary — Recipient's Physical Address
Separate from the bank_* fields — this is the person or company receiving the money, not the bank branch.
field | type | required | description |
|---|---|---|---|
country | String | Required | ISO 3166-1 alpha-2. |
city | String | Required | Recipient's city. |
post_code | String | Required | Recipient's postal/ZIP code. |
address | String | Required | Recipient's street address. |
type | String | Conditional | Only required when the destination country is RW (Rwanda) or SG (Singapore). One of 'business' or 'individual'. Every other SWIFT country omits it. |
Sender — Payer Identity
The payer. Discriminated by type. Both variants share a common set of address fields; each variant adds its own.
Shared Fields (Both Variants)
field | type | required | description |
|---|---|---|---|
type | String | Required | 'business' or 'individual' (case-insensitive — normalized server-side). |
account_name | String | Required | Legal name of the sender. Same name-format rules as beneficiary.account_name. |
country | String | Required | ISO 3166-1 alpha-2 country of the sender. |
city | String | Required | Sender's city. |
address | String | Required | Sender's street address. |
post_code | String | Required | Sender's postal/ZIP code. |
type: "business" Only
field | type | required | description |
|---|---|---|---|
registration_number | String | Required | Company registration number. 2–30 characters. Examples: US EIN '12-3456789', UK Companies House '12345678'. |
type: "individual" Only
field | type | required | description |
|---|---|---|---|
date_of_birth | String | Required | Format YYYY-MM-DD (strict regex — no other formats accepted). |
country_of_birth | String | Required | ISO 3166-1 alpha-2 of the sender's country of birth. |
A type: "individual" sender with a registration_number has that field silently dropped. A type: "business" sender missing registration_number fails validation outright. Always match required fields to the declared variant.
Country-Specific Required Fields
A handful of destinations add extra required fields on top of the 12 core SWIFT fields. GET /api/payouts/supported-countries/:country is always the source of truth — these are the notable quirks:
country | extra required field | format / notes |
|---|---|---|
AU (Australia) | bsb_number | 6-digit Bank State Branch code, regex ^\d{6}$. The corridor placeholder shows '062-000' but the regex does not allow dashes — strip them before sending. |
IN (India) | IFSCode | 11-char IFSC code, regex ^[A-Z]{4}0[A-Z0-9]{6}$. The key is literally camelCase 'IFSCode' — unlike every other snake_case field in the payload. Sending 'ifs_code' or 'ifsc_code' silently fails as 'missing required field'. |
HK (Hong Kong) | swift_code (different regex) | Regex is ^[A-Za-z]{4}HK[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$ — case-insensitive and pins the country bits to literal 'HK'. All other SWIFT-supporting countries use the global 8/11-char BIC regex. |
RW (Rwanda), SG (Singapore) | beneficiary.type | One of 'business' or 'individual'. Required only for these two countries on the nested beneficiary object. |
remittance_purpose Enum
Lowercase snake_case values. The server uppercases them (family_support → FAMILY_SUPPORT) when forwarding to SWIFT.
Step 3 — Finalize
No request body. Just the URL. Finalize settles the payout and returns the final Payout record.
Any body you send is ignored. Finalize uses the state set during initialize.
Full Example — GB Destination, Business Sender
Step 1 — Create Quote
Response → data.quote_id = "qte_01HY2Q..."
Step 2 — Initialize with SWIFT beneficiary
Step 3 — Finalize
Full Example — DE Destination (EUR), Individual Sender
Step 1 — Create Quote
Step 2 — Initialize
Step 3 — Finalize
Discovering SWIFT Support for a Country
Before building the payload, fetch the corridor config for the destination country:
Inspect the corridors[] array for entries whose destination_types includes "swift":
The returned destination_types["swift"] block carries the exact field schema — it is the authoritative source for that country's requirements, including any per-country extras listed in the Country-Specific Extra Fields section above.
Validation Failures
http | error code | cause |
|---|---|---|
400 | INVALID_BODY | Request body isn't valid JSON or doesn't match the expected shape at the HTTP parser. |
400 | INVALID_BENEFICIARY_DATA | `country` missing from beneficiary, OR a required SWIFT field is missing / violates its regex / fails min-max length. The error message lists each failing field. |
422 | UNSUPPORTED_CORRIDOR | The (country, destination_type) pair has no schema — e.g. SWIFT on a country that doesn't support it. |
422 | AMOUNT_OUTSIDE_LIMITS | Quote amount is below or above the corridor's min/max limits. |
422 | INSUFFICIENT_BALANCE | Company balance can't cover the payout (applies when source = 'offchain'). |
410 | QUOTE_EXPIRED | Quote TTL elapsed before you called initialize or finalize. Fetch a fresh quote and start over. |
Common Pitfalls
Putting beneficiary on the quote. It doesn't go there. The quote body has no beneficiary, no destination_type. Both live on initialize.
Casing on enums. Our API is lowercase snake_case for destination_type, remittance_purpose, sender.type. Uppercase is the downstream form — the adapter does the transform. Send swift / family_support / business.
Casing on SWIFT/BIC codes. Must be uppercase — DEUTDEFF, not deutdeff. Length is exactly 8 or 11 characters.
Three separate address blocks. bank_* = destination bank branch. Nested beneficiary.* = recipient's physical address. sender.* = payer's address. All three are independent — don't conflate them.
Finalize body. There is none. Sending a body is silently ignored.
country appears twice. Once at the top of beneficiary (lookup key for the corridor schema), and once nested inside beneficiary.country (recipient's country — often the same value).
Sender variant mismatch. type: "individual" with a registration_number — the field is silently ignored. type: "business" without registration_number — validation fails.
Name format. account_name (and sender.account_name) must have ≥2 space-separated parts, each ≥2 chars. No Mr/Mrs/Dr. No single-letter-plus-dot. J. Doe is rejected; John Doe passes.
IFSCode is the one camelCase field. Every other SWIFT field is snake_case; India's IFSC code field is literally IFSCode. Sending ifs_code or ifsc_code silently fails validation as "missing required field".
AU BSB number has no dashes. The placeholder shows 062-000, but the regex ^\d{6}$ rejects the dash. Submit as 062000.
API callers must supply sender. Dashboard users get it auto-injected from the authenticated company profile; direct API integrations do not.