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
413 A JSON request body exceeded the 1 MiB bound the replay layer can fingerprint safely (idempotency_request_too_large); the operation did not run and no key state was written problem+json
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. Replay layer: a JSON body over 1 MiB → 413 idempotency_request_too_large; then an Idempotency-Key conflict → 409 idempotency_key_reused / idempotency_key_in_progress / idempotency_response_unavailable
  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 and 413 (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

The shared replay layer supplies the idempotency 409/413 codes below. The public end-customer POST .../internal-transfers and POST .../consolidate operations bypass it and use their own transactional recovery and JSON conflict codes; common header validation still applies. See Sub-customers & parent access.

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 key was already used with a different method, path, body, or caller — a second API key of the same tenant counts as a different caller 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. A refusal that persists means the original completion could not be recorded — treat it as a support case, not a cue to mint a new key
idempotency_response_unavailable 409 The original request returned 2xx, but its response was too large to retain for replay — while the completed entry exists, an identical retry is refused rather than re-executed. This is not proof of payment settlement Confirm the original result by reading the resource (see Idempotency); use a new key only for a genuinely new action
idempotency_request_too_large 413 A JSON body above 1 MiB on an idempotency-protected route. The operation did not run and no key state was written Split the payload or use a documented bulk operation
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 Read the resource's state first, then follow its retry contract. Once failure is recorded, shared replay allows identical bytes and key to execute again; changed bytes conflict while retained. Transactional commands use their own recovery rules

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

Shared replay entries are retained for 24 hours. Except for the transactional commands above, on JSON requests the shared replay layer fingerprints bodies up to 1 MiB (1,048,576 bytes) — including chunked bodies, which are read through a bounded buffer rather than let through unprotected. A JSON body above that bound is refused with 413 idempotency_request_too_large before the operation runs. A successful response above the same 1 MiB boundary is not retained for byte-for-byte replay, so an identical retry is refused with 409 idempotency_response_unavailable instead of being re-executed while that completed entry remains retained. After expiry the shared replay layer can let the operation run again; payment workflows may have additional durable checks. A first attempt whose non-2xx failure is recorded does not lock the key — but only the identical bytes may retry it; a changed body under a failed key is refused with 409 idempotency_key_reused. 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
insufficient_network_funds_for_gas (409) POST …/banking/conversion-requests Selling a network's own coin (TRX on TRON, BTC on Bitcoin) would leave nothing to pay that network's fee; detail states the most that can be converted Lower the amount to at most the figure in detail
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 code — 202 means "accepted for review", not "sent".

Practical guidance