Currencies, rails and providers

Fiat money on this API moves through two banking providers — Fiat Republic and OpenPayd — and every rail field you will meet (rail on account create, on beneficiary create, on withdrawal create and on payout batches) means something slightly different. This page is the one table for all of them, plus the rule that ties an account, its beneficiaries and its withdrawals to one provider.

Currencies

The fiat surface supports GBP, EUR and USD. Fiat account create, beneficiary create and update, and fiat withdrawal create all refuse anything else with 422 unsupported_currency (payout batches enforce their own currency rule — see that route). Conversions use a different list: GBP, USD and EUR plus BTC, ETH, USDC and USDT — see Conversions.

Two refusals sit ahead of everything: a customer in a blocked jurisdiction gets 403 fiat_unsupported_jurisdiction on account create for every currency, and a currency not offered to the customer's type gets 403 currency_not_available_for_customer_type.

Which provider holds an account

Currency Fiat Republic OpenPayd
GBP Yes Yes
EUR Yes Yes
USD Yes No — OpenPayd issues GBP and EUR only, so a USD account is always Fiat Republic.

You do not pick OpenPayd. POST .../banking/accounts accepts an explicit provider of FIAT_REPUBLIC only (422 unsupported_provider for anything else, OPENPAYD included); when you omit provider, the tenant's routing rules choose, and that is the only way an account is placed with OpenPayd. GET .../banking/accounts/form-schema describes what can be named — it therefore advertises Fiat Republic alone, while its currency and rail lists describe what the platform can do.

Read the account's provider field to learn where it landed. It decides everything downstream.

One account per currency. A customer may hold at most one active fiat account per currency (Pending, PendingProvider, ManualReview or Completed), at any provider. A second request in the same currency at the same provider returns the existing account; at a different provider it is refused with 409 duplicate_currency_account — the detail names the provider and status of the account in the way. To move a currency to the other bank, the existing account has to be closed first.

The provider chain

Provider identity is a chain, not a per-call choice:

  1. Account — issued at Fiat Republic or OpenPayd (above).
  2. BeneficiaryPOST .../banking/beneficiaries creates the payee at each provider where the customer holds an account in that currency that the provider has accepted (provider_account_id set). No such account → 409 bank_account_required. Naming provider narrows the choice to that provider's accounts; it cannot reach a provider the customer has no account with.
  3. WithdrawalPOST .../banking/withdrawals is funded from the customer's Completed account at the beneficiary's provider in the withdrawal currency. If there is none (for example the account was routed to OpenPayd but the beneficiary is an old Fiat Republic row, or the account is not Completed yet) the call is refused with 409 source_account_not_ready.

The practical consequence: check provider on the account first, then create beneficiaries only after provider_account_id is set, and expect beneficiaries and withdrawals to follow the account's provider. Details of step 2 are in Beneficiaries.

Identifiers a receiving bank can recognise (OpenPayd EUR/GBP). destination on a withdrawal row is the provider's own internal reference — never quote it to a bank. Once an OpenPayd payout is accepted, the withdrawal read also carries transaction_id (the provider's own transaction id), end_to_end_reference (the SEPA/SWIFT end-to-end reference actually quoted to the receiving bank) and provider_short_id (a short, human-readable reference such as 20220922-K3QY65) — use provider_short_id when reading a reference out over the phone, and transaction_id when escalating to support. All three are null until the provider accepts the payment, null for a provider that does not report one, and null on rows created before this field shipped (except transaction_id, which is recovered from the stored OpenPayd reference where possible).

Rails

Two vocabularies exist, one per provider:

Which one applies is decided by the provider of the beneficiary (withdrawals) or of the routed account (accounts), never by the value you send.

Currency Fiat Republic withdrawal / payout-batch rails Default when rail is omitted (Fiat Republic) OpenPayd withdrawal rails Default when omitted (OpenPayd)
GBP FPS, CHAPS, BACS FPS FASTER_PAYMENTS, CHAPS, SWIFT when enabled FASTER_PAYMENTS
EUR SCT SCT SEPA, SEPA_INSTANT, SWIFT when enabled SEPA_INSTANT
USD ACH, ACH_SAME_DAY, DOMESTIC_WIRE, INTERNATIONAL_WIRE ACH, unless the payee's confirmed schemes are known, in which case the first of ACH, ACH_SAME_DAY, DOMESTIC_WIRE, INTERNATIONAL_WIRE it supports — (no USD at OpenPayd)

What each route does with rail:

Route Meaning of rail Validated?
POST .../banking/accounts A provider-routing selector, matched against the tenant's routing rules; stored on the account, ignored by the account adapters. Omitting it substitutes the Fiat Republic default (FPS / SCT / ACH) before routing, so a rule keyed on an OpenPayd code is reached only by sending that code. No — never against a scheme list.
POST .../banking/beneficiaries On USD it selects the payee shape and routing-code type; use schemes to register several USD schemes. On OpenPayd EUR/GBP, explicit SWIFT selects the international beneficiary contract above. SWIFT: currency, destination and fields checked before creation.
POST .../banking/withdrawals The scheme the payment goes out on, in the beneficiary provider's vocabulary (table above). Fiat Republic: 422 unsupported_rail outside the currency's set, 422 rail_not_supported_by_beneficiary when the payee's confirmed USD schemes exclude it, both before any funds are reserved. OpenPayd: translated to its payment type; a value it cannot translate fails after the hold and returns 200 with status Failed. Fiat Republic: yes. OpenPayd: at the adapter.
POST .../payout-batches Rail for every item, validated against the Fiat Republic scheme set for the batch currency (422 unsupported_rail) and stored as the canonical code — you send FASTER_PAYMENTS, you read back FPS. Yes.

Per-rail limits and scheme notes

Conversions

POST .../banking/conversion-requests converts between the customer's balances. The asset list is GBP, USD, EUR, BTC, ETH, USDC and USDT, with three rules:

  1. from_currency and to_currency must differ.
  2. A BTC or ETH source converts to fiat only — BTC→USDC, ETH→USDT, BTC→ETH and the like are refused with 422 invalid_pair. Fiat, USDC and USDT sources may target any other listed asset.
  3. Fiat, USDC and USDT sources have a 20 major-unit minimum (422 below_minimum_trade_size).

The customer must already hold a Completed fiat account (fiat side) or an active wallet (crypto side) for both assets — 409 exchange_account_missing / exchange_wallet_missing otherwise; this route opens neither.

GET /api/v1/partner/transfer/allowed-destinations lists the assets the exchange can deliver for your tenant; it is derived from the tenant's exchange fee configuration and is empty when no exchange fee is configured. Pass source_currency to have the pair rules applied for you. GET /api/v1/partner/transfer/price is indicative only: the conversion executes at the live rate and references nothing from the quote.