Skip to main content
The Payouts API delivers money outside the US over two rails. They look similar in the API and are different products underneath, so the choice is made once — when you create the receiving account — and everything afterwards follows from it. Both rails require the same two things before you can even create the receiving account: the rail has to be enabled for your partner (otherwise 403 INTERNATIONAL_NOT_ENABLED), and the business has to have completed international onboarding (otherwise 409 ENTITY_ONBOARDING_INCOMPLETE, listing what is outstanding). See International onboarding. All calls below are against https://api.melio.com/v2, with your api-key header and a Melio-Entity-Id header naming the business (ent_<uuid>, or me if you have a single entity).

1. Create the receiving account

international-fx

An FX account is described by its currency, its destination country and the bank identifiers that corridor uses. The shapes differ per corridor — an IBAN for EUR, a sort code for GBP, an IFSC code for INR, a CLABE for MXN, and so on — so POST /accounts accepts twelve variants of CreateExternalInternationalFxAccountRequest. The full currency → destination → bankDetails table lives in the OpenAPI description of that schema; each currency also has a ready-to-send request example on POST /accounts, which is the fastest way to get the identifiers right.
Two things to know:
  • USD is not accepted on this type. A dollar payment converts nothing, so there is no FX rail to put it on — use a domestic account type, or international-gusd. A currency of USD here is rejected with 400 VALIDATION_ERROR.
  • A currency outside the documented table is 400 UNSUPPORTED_CURRENCY. Even a listed currency can be refused for a given destination: the international-payments service is the authority on what it can deliver today.

international-gusd

A GUSD account is just a wallet:
Only the last four characters of the wallet address are stored, and the full address is never returned — so keep your own copy if you need to show it back to the payer.

2. Check the business can pay internationally

GET /limitations reports payment.domestic:write and payment.international:write independently. A business may be able to pay domestically and be blocked internationally, or the reverse, so check the international one rather than assuming:
reasons[].code is worth branching on: MissingInformation is something you can fix (see onboarding), while LocationRestricted is not — the FX provider will not onboard a business registered in Nevada, the US Virgin Islands, Guam or the Northern Mariana Islands, and its reason carries an empty missingFields because there is no data that would lift it. Creating a payment for a blocked business returns 403 BUSINESS_NOT_ELIGIBLE.

3. Create the payment

The request body differs by rail, and deliveryPreference decides which one you are sending. POST /payments also requires an Idempotency-Key header (a UUID is fine) so the call is safe to retry — omitting it is 400 IDEMPOTENCY_KEY_REQUIRED, and reusing one with a changed body is 409 IDEMPOTENCY_KEY_REUSED.

international-fx

  • amount is what the counterparty receives, in minor units of currency — 100000 is €1,000.00. The business is debited the equivalent in USD at the applied rate, plus fees.
  • currency must equal the receiving account’s currency. A mismatch is 400 VALIDATION_ERROR.
  • fxQuoteToken is optional. With it you are charged exactly what was quoted; without it the payment is priced at creation. See FX quotes and rates.
  • compliance.purpose is required, and a few corridors want a code rather than prose (below).
  • Some destinations additionally require a supporting document — see Payment attachments.

international-gusd

GUSD is dollar-denominated, so it takes the ordinary USD payload: currency defaults to USD and can be omitted, and there is nothing to quote and no purpose to declare. Sending fxQuoteToken or compliance.purpose on this rail is rejected with 400 VALIDATION_ERROR rather than accepted and ignored — a purpose that never reaches a provider should not look like one that did.

Payment purpose

compliance.purpose is what the currency provider is told about why the money is moving. It is usually free text, but three corridors take a code from a fixed set and refuse anything else: Send free text to one of those and the request is rejected with 400 VALIDATION_ERROR listing the values it accepts, so you can surface them without hard-coding the list. The provider owns these sets: treat the table as examples and read the error for the current set.

Delivery speed

international-fx is the rail’s normal speed. fast-international-fx is the expedited variant and is subject to a per-payment eligibility check — an ineligible request is rejected with 400, never silently downgraded. POST /tools/fast-payment-eligibility answers for the prospective payment before you create it, and returns a decision per fast preference valid for the accounts involved, fast-international-fx among them. GUSD has a single speed.

Pricing and timing

Both rails are supported by the two estimate endpoints, and both are independent of the FX quote:
  • POST /tools/fee-calculator — the fees for a given amount, originating account and preference.
  • POST /tools/delivery-eta — the expected delivery date.
An FX quote covers the conversion only; fees are never inside it.

Errors worth handling

See also