Errors

What each code means and which errors are worth retrying.

Every error has the same shape.

{
  "error": {
    "type": "invalid_request",
    "code": "route_unsupported",
    "message": "ARG/ARS/BANK_ACCOUNT/SPEI is not a live route.",
    "param": "rail",
    "request_id": "req_..."
  }
}

type is the category. code is the specific reason and the thing to branch on. param names the offending field where one applies. request_id is what to quote when you ask us about it, so log it.

Branch on the code field. The message field is written for humans and will change, so don't depend on it.

Types

typeHTTPMeans
invalid_request400, 422Something about the request is wrong
authentication_error401Credentials missing or bad
permission_error403Authenticated but not allowed to do this
not_found404No such resource, or not on this host
conflict409Valid request, wrong state
rate_limited429Too many requests
api_error5xxA fault on our side

Codes worth knowing before you hit them

Permission and eligibility

CodeWhat went wrongDo this
endorsement_not_enabledThe customer doesn't hold the endorsement this needs, or it isn't APPROVED yetRequest it, wait for the event, then retry. See Endorsements.
customer_type_ineligibleThis customer type can't do this at all. A SUB_CUSTOMER opening a Corporate USD Account, for instanceNothing to retry.
third_party_not_permittedPaying a destination the customer doesn't own, without stablecoin_payout.third_partyRequest the endorsement.

Routes and rails

CodeWhat went wrongDo this
route_unsupportedThe combination of country, currency, destination type and rail isn't LIVERead GET /v1/countries/{country}. Don't retry the same request.
rail_mismatchThe rail doesn't match the destination's supportedRails[]. A SPEI quote can't fund a PIX accountRe-quote on a rail the destination supports.
currency_mismatchThe destination's currency isn't the quote's toCurrencyFix one or the other.

All three are refused before anything is locked, so no balance is held while you work out what to change.

Quotes

CodeWhat went wrongDo this
quote_expiredExecution landed outside expiresAtRe-quote. Don't retry.
quote_consumedThis quote already funded somethingGet a new one.
pair_shape_violationA conditional field was supplied where the pair forbids it, omitted where it requires it, or both amounts were setCheck param. See Quotes and pricing.

State

CodeWhat went wrongDo this
insufficient_balanceNot enough in availableRead the balance. Don't retry blind.
parent_terminalYou raised an action against a transaction that's COMPLETE, FAILED, CANCELLED or BLOCKEDNothing reaches back into a terminal transaction. A new transaction in the opposite direction is the only route.
not_returnedYou raised a correction against a payout whose leg isn't RETURNEDUse /reprocess if it failed without returning.
recipient_not_linkedThe destination isn't VERIFIEDWait on linked_account.updated.
submission_frozenThe customer's draft is already under verificationUse /reevaluate.
preflight_failedThe draft wouldn't pass. error.details lists what's outstandingFix what it names, then submit.

Requests

CodeWhat went wrong
idempotency_conflictSame key, different body
validation_errorA field is missing, the wrong type, or not in the enum. param names it
file_too_largeOver the 10 MB per-file limit
unsupported_file_typeNot PDF, JPEG or PNG

What to retry

Retry: a 429, waiting for the interval in the Retry-After header. A 5xx, with backoff.

Don't retry: anything 4xx other than 429. The request will fail the same way until you change it.

Careful: replaying a POST needs the same Idempotency-Key as the original. A new key on a retry can double-execute. The same key with a changed body returns idempotency_conflict instead of executing.

Rate limits

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 carries Retry-After.

Read the headers instead of hardcoding a number. X-RateLimit-Remaining is the only figure that's true at the moment you read it, and a limit copied into your code stops being accurate the first time yours is adjusted.

Simulator paths on production

Simulator paths under /v1/test/ exist on the sandbox host only. On production they return 404 not_found. That is intended behavior and doesn't indicate a missing route.


Did this page help you?