Errors

This page is the master reference for how a call to /api/v1/partner or /api/v2/public fails: which HTTP status you get, which of three body shapes carries it, and every machine-readable code the platform raises, with the condition and the remedy. Per-operation pages in the reference repeat the codes that apply to them; this page is derived from the same code paths.

Branch on the HTTP status first

Status Meaning Body shape
400 The request itself is malformed: an X-Tenant-Id header was sent, X-Correlation-Id is invalid, Idempotency-Key is missing or malformed on a mutating call, or the JSON body could not be bound problem+json (shape 3)
401 The API key was not accepted problem+json
403 The key is valid but is not allowed to do this: missing scope or IP outside the allowlist (problem+json), or a policy gate refused the customer — compliance hard block, account not offered (flat ErrorResponse, shape 2) problem+json or shape 2
404 The resource does not exist — or exists but belongs to a customer this key cannot act for. These are indistinguishable on purpose (see Authentication); code is always null shape 2, code: null
409 The request conflicts with current state. Three families: an operation refusal (beneficiary_not_ready, exchange_order_failed, … — shape 1), a compliance HOLD that a review can clear (shape 2), or an Idempotency-Key conflict (problem+json) shape 1, 2 or 3
422 Refused — either the body failed validation (invalid_amount) OR a state precondition is not met with a perfectly valid body (insufficient_funds, beneficiary_not_found). Three 422s occur on GETs with no body at all (unsupported_asset, unknown_level, and invalid_pair on /transfer/price). Read data.code; many are fixable outside the request shape 1
429 Too many requests — one of three causes (below). Honour Retry-After problem+json
500 Unexpected failure. code is absent. Quote X-Correlation-Id to support problem+json
503 A dependency the operation needs is unavailable. On the two crypto reads (…/crypto/wallets, …/crypto/wallets/{asset}/balance) it is a transient custody-provider refresh failure, provider_balance_refresh_failed in shape 1 — retry with backoff; elsewhere the operation's reference names the cause shape 1 or 2

Which check runs first

Shared middleware refuses in a fixed order, and the response only ever reflects the EARLIEST failing check. Fix problems in this order when debugging a client:

  1. X-Correlation-Id malformed or sent twice → 400 correlation_id_invalid
  2. X-Tenant-Id present at all (even empty) → 400, no code — fires even when the key is also wrong
  3. API key → 401 / 403, or 429 if your IP has too many recent failures
  4. Rate limits (per route, then tenant aggregate) → 429 rate_limited
  5. Idempotency-Key missing or malformed (mutating calls) → 400 idempotency_key_invalid
  6. Idempotency-Key conflict → 409 idempotency_key_reused / idempotency_key_in_progress
  7. Body binding → 400 with an errors map, no code
  8. The operation itself → its own 404 / 409 / 422 / 403

The three body shapes

1. The envelope — every 409 and 422 the operation itself raises, and the 503 on the crypto reads:

{
  "success": false,
  "status_code": 422,
  "message": "Invalid conversion pair.",
  "data": { "code": "invalid_pair", "detail": "from_currency and to_currency are required and must be different." }
}

data.code is always present. success and status_code repeat the HTTP status; message is human-readable and not stable.

2. The flat ErrorResponse — every 404, and the 403/409 raised by policy gates:

{ "title": "Customer was not found.", "detail": "No customer exists for the authenticated tenant and customer id.", "status": 404, "code": null }

code is null on every 404 and populated only by the gates listed under Policy-gate codes.

3. RFC 7807 application/problem+json — written by shared middleware for 400, 401, 403 (scope/IP), 409 (idempotency) and 429, and by the framework for the body-binding 400 and for 500:

{ "type": "https://tools.ietf.org/html/rfc9110#section-15.6.4", "title": "Too many requests.", "status": 429, "detail": "Request rate limit exceeded.", "code": "rate_limited" }

Note the content type: a strict application/json parser must accept application/problem+json too. The body-binding 400 adds an errors object (field → messages) and carries no code.

Codes carried by problem+json

code Status Condition Remedy
correlation_id_invalid 400 X-Correlation-Id sent twice, or longer than 128 visible ASCII characters Send one value, ≤128 chars, printable ASCII — or omit it and use the one we return
(none) 400 An X-Tenant-Id header was present. Title Invalid tenant header. Remove the header; the tenant comes from the key
idempotency_key_invalid 400 Mutating call with no Idempotency-Key, two of them, or a value outside 8–256 visible ASCII (no spaces) Send exactly one well-formed key
(none), errors map 400 The JSON body could not be bound (malformed JSON, wrong type, bad UUID) Fix the field named in errors
api_key_authentication_failed 401 Missing, malformed, unknown, revoked, expired, rotated-out, not-yet-active, wrong-environment key; or the tenant is suspended / API access is off. detail is specific only for request-shape mistakes (no key, both headers, non-Bearer scheme, unparseable key) Fix the credential. Do not retry in a loop: repeated failures from one IP become 429
api_key_not_authorized 403 The key lacks a scope this operation requires, or the request came from an IP outside the key's allowlist Compare GET /api/v1/partner/me scopes with the operation's security list; issue a new key with the right scopes or allowlist
idempotency_key_reused 409 The same key was already used by this API key for a different method, path or body Mint a new key for the new action
idempotency_key_in_progress 409 The first attempt with this key is still executing Retry the identical request shortly; you will get its stored result
rate_limited 429 (a) per-route budget for this key, (b) tenant-wide aggregate, or (c) too many failed authentications from your IP — detail is Too many failed authentication attempts. for (c) (a)/(b): wait Retry-After seconds. (c): waiting does not help — fix the credential
(none) 500 Unhandled failure Retry with backoff; a mutating retry with the same Idempotency-Key re-executes (a failed attempt does not lock the key) — check state first

Authentication codes are intentionally one per status: the granular reason (expired vs revoked vs IP-denied) is recorded in our audit trail and never returned, because it would tell a probe how far its guess got.

Idempotency retention and size boundary

Idempotency entries are retained for 24 hours. The shared replay layer hashes known-length JSON request bodies up to 1 MiB (1,048,576 bytes). A chunked request with no known content length, or a request above that size, currently proceeds without middleware replay protection. A successful response above the same 1 MiB boundary is not stored for byte-for-byte replay. A first attempt that returns any non-2xx does not lock the key. The full contract is in Idempotency.

Operational response headers

Policy-gate codes (flat ErrorResponse)

These are raised by shared gates rather than by one operation, on the money-moving routes (withdrawals, conversions, crypto withdrawals) and account opening.

code Status Condition Remedy
compliance_subject_blocked 403 The customer is blocked, suspended, rejected, expired or provider-revoked by compliance Permanent for the current state; contact support
compliance_subject_manual_review 409 The customer is in manual review Retry once the review clears (watch onboarding status)
compliance_subject_lifecycle_unavailable 409 The compliance state could not be read Retry later
compliance_subject_lifecycle_unknown 409 The compliance state is unknown Retry later; contact support if it persists
fiat_unsupported_jurisdiction 403 Fiat account opening: the customer's country is on the blocked list (see the v1 reference introduction) Cannot be changed by configuration
currency_not_available_for_customer_type 403 Fiat account opening: this currency is not offered to this customer type Choose an offered currency
self_serve_provisioning_disabled 403 Fiat account opening: self-serve provisioning is locked for this customer — operations opens accounts on request. Checked first inside the account-opening workflow, before the offer policy Contact support
(null) 404 Unknown id, or an id this key cannot act for Check the id and the key's binding via /me

A platform-wide money-movement pause is NOT reported with its own code on v1. The shared handler defines 409 money_movement_frozen, but the three v1 money routes catch it first and answer with their generic failure code (fiat_withdrawal_failed, crypto_withdrawal_failed, exchange_order_failed, shape 1) whose detail is operator-authored text. Treat those three codes as "retry later" unless detail says otherwise.

Operation codes (envelope, data.code)

Status is 422 unless marked 409. Every customer-scoped route also returns 404 (shape 2, code: null) for an unknown or unreachable customerId, and every money-moving route starts with the shared precondition 409 kyc_or_kyb_not_approved (the customer has not reached an approved verification status — read GET /customers/{customerId}/onboarding/status).

Customers and onboarding

code Operation Condition Remedy
invalid_email PATCH /customers/{customerId} email is not a valid address Fix the field
unsupported_status PATCH /customers/{customerId} status is not a value the platform accepts Use a documented status
jurisdiction_invalid PATCH /customers/{customerId} jurisdiction is not an ISO 3166-1 alpha-2 code Send a two-letter country code
external_reference_in_use (409) PATCH /customers/{customerId} The email or external reference is already assigned to another customer in your tenant — the message never says which Choose a different reference
unknown_level GET /onboarding/requirements The requested verification level does not exist Use a level from the reference
invalid_onboarding_type POST …/onboarding/kyc/initiate, …/kyc/token, …/kyb/initiate, …/kyb/token KYC was requested for a business, or KYB for an individual Use the flow matching customerType
password_not_supported POST /customers (whitelabel programme) password was sent; no identity account exists on this surface, so a credential would be discarded Remove the field — never send a real credential

Exchange (conversions)

code Operation Condition Remedy
invalid_pair GET /transfer/price, POST …/banking/conversion-requests pair is not SOURCE/TARGET, or the two currencies are missing or identical Send exactly one slash and two different assets
exchange_quote_unavailable (409) GET /transfer/price The pricing provider did not return a quote Retry shortly
invalid_amount POST …/banking/conversion-requests amount_minor is not a positive integer Send minor units as an integer > 0
below_minimum_trade_size POST …/banking/conversion-requests The amount is under the minimum trade size for the source asset Raise the amount (the detail names the floor)
insufficient_onchain_balance POST …/banking/conversion-requests Selling crypto: the on-chain balance is lower than the amount Wait for pending deposits to settle, or lower the amount
exchange_account_missing / exchange_wallet_missing (409) POST …/banking/conversion-requests The customer has no fiat account / crypto wallet on one side of the pair Open the account or wallet first
exchange_funding_account_ambiguous (409) POST …/banking/conversion-requests More than one funding account matches Contact support
exchange_ledger_prerequisites_missing (409) POST …/banking/conversion-requests Ledger accounts for this customer are not provisioned Contact support
exchange_workflow_unavailable (409) POST …/banking/conversion-requests The exchange workflow is not enabled on this host Contact support
crypto_balance_unverifiable (409) POST …/banking/conversion-requests The custody provider could not confirm the balance Retry later
crypto_network_unavailable (409) POST …/banking/conversion-requests No active network for the asset Retry later; contact support if it persists
insufficient_network_funds (409) POST …/banking/conversion-requests Not enough balance on the specific network Wait for delivery, or lower the amount
exchange_order_failed (409) POST …/banking/conversion-requests Any other failure while placing the order, including a platform money-movement pause; detail is free text Retry later; do not retry in a tight loop

Fiat accounts

code Operation Condition Remedy
unsupported_currency POST …/banking/accounts Not a platform fiat currency Use GBP, EUR or USD (see the operation)
unsupported_provider POST …/banking/accounts A named provider the platform does not know Omit the provider to be routed, or name a supported one
provider_unavailable POST …/banking/accounts The named provider is not enabled for this tenant Omit the provider
missing_provider_prerequisites POST …/banking/accounts The customer record lacks data the provider requires (the detail names it) Complete the customer profile
provider_routing_unavailable (409) POST …/banking/accounts No provider can be routed for this currency and customer right now Retry later
onboarding_prerequisite_not_met (409) POST …/banking/accounts A provider onboarding step has not completed Retry after onboarding completes
duplicate_currency_account (409) POST …/banking/accounts The customer already holds an active account in this currency at ANOTHER provider (the detail names it) Close the existing account first

Beneficiaries

code Operation Condition Remedy
unsupported_currency POST, PATCH …/banking/beneficiaries Not a platform fiat currency Use an offered currency
unsupported_provider POST, PATCH …/banking/beneficiaries A named provider that is not Fiat Republic or OpenPayd Omit it
unsupported_payment_scheme POST, PATCH …/banking/beneficiaries A USD scheme the platform does not support Use a listed scheme
beneficiary_details_incomplete POST, PATCH …/banking/beneficiaries Bank details do not satisfy the scheme, or an OpenPayd payee lacks the name attributes (the detail lists the fields) Supply the fields
beneficiary_provider_validation_failed POST, PATCH …/banking/beneficiaries The banking provider pre-flight definitively rejected the details Correct the details named
bank_account_required (409) POST, PATCH …/banking/beneficiaries The customer has no completed fiat account in the beneficiary's currency Open the account first
beneficiary_creation_failed / beneficiary_update_failed (409) POST / PATCH …/banking/beneficiaries Provider-side failure; detail is the provider's message Retry later

Fiat withdrawals

code Operation Condition Remedy
unsupported_currency POST …/banking/withdrawals Not a platform fiat currency Use an offered currency
invalid_amount POST …/banking/withdrawals amount_minor is not a positive integer Send minor units as an integer > 0
beneficiary_not_found POST …/banking/withdrawals No beneficiary with that id for this customer Check the id
beneficiary_currency_mismatch POST …/banking/withdrawals The beneficiary's currency differs from the withdrawal currency Use a matching beneficiary
beneficiary_not_ready (409) POST …/banking/withdrawals The beneficiary has not finished provider provisioning Poll the beneficiary until ready
beneficiary_not_approved (409) POST …/banking/withdrawals The beneficiary awaits staff approval Wait for approval (approval_status)
source_account_not_ready (409) POST …/banking/withdrawals The customer's account in this currency is not completed Wait for the account
withdrawal_client_money_blocked (409) POST …/banking/withdrawals Funds are client money that cannot be withdrawn by this route Contact support
source_account_pot_missing (409) POST …/banking/withdrawals Ledger pot for the account is missing Contact support
fiat_withdrawal_prerequisites_missing (409) POST …/banking/withdrawals The customer's or the provider's LEDGER account does not exist yet (internal ledger accounts, not provider references) Contact support
unsupported_rail POST …/banking/withdrawals, POST …/payout-batches The rail is not valid for this currency Use a rail from the operation's list
rail_not_supported_by_beneficiary POST …/banking/withdrawals The beneficiary's bank details do not support that rail Choose another rail or beneficiary
invalid_payment_reference POST …/banking/withdrawals The reference is too long or contains disallowed characters Shorten or clean it
fiat_withdrawal_failed (409) POST …/banking/withdrawals Any other failure while submitting, including a platform money-movement pause; detail is free text Retry later

Payout batches

code Operation Condition Remedy
currency_required POST …/payout-batches No currency Send one
unsupported_currency POST …/payout-batches Not a platform fiat currency (GBP, EUR, USD) — AUD included Use an offered currency
items_required POST …/payout-batches No items Send at least one
too_many_items POST …/payout-batches More than 500 items Split the batch
unsupported_rail POST …/payout-batches The rail is not valid for the currency Use a listed rail
subject_not_active POST …/payout-batches The customer is not active Check onboarding status

A payout batch can be refused per item while the request returns 200 — inspect each item's status and failure_reason; see the operation.

Crypto

code Operation Condition Remedy
unsupported_asset POST …/crypto/wallets/{asset}/address, GET …/crypto/wallets/{asset}/balance, POST …/crypto/withdrawals Not a supported asset (the detail lists them) Use a listed asset
crypto_wallet_workflow_unavailable (409) POST …/crypto/wallets/{asset}/address Wallet opening is not configured on this host Contact support
invalid_amount POST …/crypto/withdrawals amount is not positive Send a positive amount
whitelisted_address_required POST …/crypto/withdrawals This customer must withdraw to a whitelisted address and none was named Whitelist the address first
whitelisted_address_not_found POST …/crypto/withdrawals The whitelisted-address id is unknown Check the id
whitelisted_address_not_active (409) POST …/crypto/withdrawals The whitelisted address is not yet active (pending approval or screening) Wait for it to become active
whitelisted_address_asset_mismatch POST …/crypto/withdrawals The whitelisted address is for another asset or network Use a matching address
invalid_destination_address POST …/crypto/withdrawals The address is not valid for the asset's network Correct the address
crypto_withdrawal_prerequisites_missing (409) POST …/crypto/withdrawals Wallet or ledger configuration is missing Open the wallet first; contact support if it persists
crypto_withdrawal_workflow_unavailable (409) POST …/crypto/withdrawals Withdrawals are not configured on this host Contact support
crypto_withdrawal_failed (409) POST …/crypto/withdrawals Any other failure while submitting, including a platform money-movement pause or a same-key race; detail is free text Retry later

The two crypto operations also pass workflow decision codes through verbatim as 422 — the vocabulary is open and new codes ship without notice. Seen today on POST …/crypto/withdrawals: insufficient_funds, awaiting_crypto_delivery (bought but not yet delivered to the wallet — detail says how much you can send now), insufficient_network_funds, ledger_available_balance_insufficient, withdrawal_fee_unavailable, asset_network_policy_unavailable, crypto_withdrawals_disabled, crypto_withdrawal_below_minimum, provider_submission_failed. On POST …/crypto/wallets/{asset}/address: crypto_asset_network_policy_not_configured, crypto_deposits_disabled. A crypto withdrawal parked for review answers 202 with the normal withdrawal body and no code202 means "accepted for review", not "sent".

Practical guidance