Skip to content

Error Handling & Retries

Every error returned by the API is classified as retryable or non-retryable so that your integration can decide, deterministically, whether to retry a failed request or treat it as final and compensate.

All errors follow the standard error envelope. This page adds the retry classification and organizes it by flow (quote & intent, off-ramp, payout).

Is an error retryable?

The rule follows the HTTP status class:

ResponseCauseRetryableWhy
5xxServer / infrastructure — transientYesTime resolves it — retry with backoff
422 with status: "rejected"Business rejection — liquidity, constraints, policyNoYou must change the request itself. Some subCodes are conditional — see Policy subCodes
other 4xxClient — request, credentials, or stateNoSame request yields the same result until you change it

Two exceptions4xx that is retryable:

  • 429 (rate limited) — retry after Retry-After with exponential backoff.
  • 409 (in progress) — the original request is still being processed. Retry with the same Idempotency-Key to re-query the result — never resend as a new request (see Payout).

One HTTP rule for rejections: a request that cannot produce a result never returns 200. Synchronous business rejections — no liquidity, constraints not met, policy denial — return 422 with status: "rejected" and the error envelope. 200/202 always means the request produced a usable result (or was accepted for processing). Terminal failures of an already-accepted asynchronous flow (e.g. a settlement that later fails) surface as resource state on status reads and webhooks — not as an HTTP error on the original request.

One-line test: "Could the same request produce a different result if sent again unchanged?" Yes → retryable; No → not retryable.

Common errors (all endpoints)

CodeHTTPRetryableRecommended action
INVALID_API_KEY401NoCheck credentials / rotate key
INVALID_PARAMETER, INVALID_PAIR400NoFix the request
IDEMPOTENCY_KEY_REQUIRED, IDEMPOTENCY_KEY_INVALID400NoSupply a valid Idempotency-Key
IDEMPOTENCY_KEY_CONFLICT409NoSame key reused with a different body — use a new key
IDEMPOTENCY_KEY_PROCESSING409Yes (re-query)Retry the same key to fetch the in-flight result
RATE_LIMIT_EXCEEDED429YesBack off, honor Retry-After
SERVICE_UNAVAILABLE, INTERNAL_ERROR5xxYesRetry with exponential backoff

Quote & Intent

Errors raised while pricing a quote or placing an intent. Business rejections return 422 with status: "rejected" and a reason — never 200 (see the rule above).

CodeHTTPRetryableRecommended action
INSUFFICIENT_LIQUIDITY422 rejectedNoNo route available — a valid business outcome, not a request fault; surface to the user. Resubmitting later is a new request, not an automatic retry
CONSTRAINTS_NOT_MET422 rejectedNoOutput falls below your minimum — adjust amount or minimum (a new request)
INVALID_PARAMETER400NoMissing beneficiary / recipient details — complete the request
AMOUNT_OUT_OF_RANGE400NoAmount outside the allowed min/max — adjust the amount (a new request)
INTENT_NOT_FOUND404NoNo intent exists with that ID — check the identifier
POLICY_DENIED422 rejectedNoBlocked by policy (limits / compliance). Carries a machine-readable subCode — see Policy subCodes. When the policy check runs at the asynchronous order stage, the same denial surfaces as a rejected state instead of an HTTP error

The following are reserved for the confirm / cancel flows (coming soon), and are all non-retryable: INVALID_STRATEGY (400), INTENT_EXPIRED (409), INTENT_ALREADY_EXECUTING (409).

Off-ramp

Errors while converting and confirming the settlement leg (USDC → fiat). Reconciliation detail is internal; failures surface on the intent as a terminal failed status.

CodeHTTPRetryableRecommended action
SETTLEMENT_FAILED200 intent failedNoTerminal conversion/confirmation failure (mismatch, short, or timeout) — handle by compensation; do not blind-retry
PROVIDER_UNAVAILABLE5xxYesDependency (oracle / provider) transiently down — retry with backoff

Payout

The recipient payout leg moves money, so it is at-most-once: an unconfirmed failure is never resent — the result is re-queried instead. Payout exposes a small, stable set of codes; how they are produced internally is not part of the contract:

CodeHTTPRetryableRecommended action
IDEMPOTENCY_KEY_PROCESSING409Yes (re-query)Payout in progress / result unconfirmed. Retry with the same Idempotency-Key to re-query — never create a new order or new key
PAYOUT_UNAVAILABLE5xxYesPayout temporarily unavailable — back off and retry
PAYOUT_REJECTED400NoPayout rejected — read the message, fix, and resubmit (an accepted payout returns 201)

Money-safety: on a timeout or dropped connection during payout, do not build a new request. Query the original with the same Idempotency-Key and the API returns the settled state safely — this guarantees no double payout. See Idempotency.

Policy subCodes

POLICY_DENIED always carries a machine-readable subCode. It is a closed enum — branch on these values, not on message. New values may be added over time; treat any unknown subCode as non-retryable.

subCodeMeaningRetryableRecommended action
limits_not_configuredNo trading limits are configured for this accountNoContact onboarding — resending cannot succeed until limits are set
single_trade_cap_exceededThe order notional exceeds the per-trade capNoSplit or reduce the amount (a new request); the cap does not change with time
currency_limit_exceededThe currency exposure limit is exhaustedConditionalSee Conditional retries
compliance_blockedBlocked by KYC / sanctions / Travel Rule policyNoResolve the compliance finding out of band, then submit a new request

Conditional retries

Most POLICY_DENIED results are terminal, but subCode: "currency_limit_exceeded" is conditional: the same request may succeed once conditions change.

  • Signaled as retryable: false + subCode: "currency_limit_exceeded" — it is not an automatic-backoff case, because resending immediately yields the same denial.
  • Retry only after (a) the currency limit recovers capacity (other trades settle or roll off), or (b) you reduce the amount.

How retryability is signaled

A dedicated retryable field is planned for the standard response envelope. Until it ships, infer the classification from the HTTP status as described above:

HTTPRetryable
5xxYes
429Yes
409 (in progress)Yes (re-query)
422 status: "rejected"No (branch on subCode — see Policy subCodes; currency_limit_exceeded is conditional)
other 4xxNo

Once the retryable field is exposed, its value matches the classification on this page.