Last updated

Errors & rate limits

One envelope, stable codes, and the retry rules that keep you out of trouble.

The error envelope

Every error carries the same four fields — message, code, statusCode and requestId. errors is added only on field-level validation failures. Branch on statusCode first, then refine on code: it is always there, so you never have to test for its presence.

{
  "message": "Validation failed.",
  "code": "validation_failed",
  "statusCode": 400,
  "requestId": "7186288b-2f41-4c6a-9d85-1b0e3a7c5f92",
  "errors": [
    { "field": "amount", "code": "invalid_format", "message": "amount has an invalid format." }
  ]
}

Each errors entry is exactly field, code and message. The field code is one of required, invalid_format, unsupported_value, out_of_range, unknown_field or duplicate — and unknown_field means what it says: a field that isn't part of the request is rejected, not ignored. duplicate means a record with the same value already exists, for example a beneficiary with the same name, bank country and bank currency.

On the beneficiary and payment-method endpoints, a 422 validation_failed rebuilds errors[] with a field that names where the problem is, such as bankingInformation.accountNumber or contactInformation.emailAddress. The 422 on POST /v1/beneficiaries for a name field that doesn't match entityType is the exception: it lists those fields in errorData.errors[] instead.

Keep the requestId. It's the fastest way for Jeeves support to find what happened, and it echoes your own X-Request-Id when you send one.

message is localized and may be reworded without notice — never branch on it.

Status codes and stable codes

StatuscodeMeaning
400invalid_request_bodyThe body could not be parsed at all — malformed JSON, or a JSON value that isn't an object. No errors array, because nothing could be read.
400validation_failedThe body parsed, but field values were rejected. Offending fields listed in errors. Also returned for an unrecognised query parameter.
401unauthorizedMissing, invalid, or expired bearer token. Get a new token and retry.
403forbiddenToken lacks the scope, client certificate missing/invalid, or source IP not allowlisted.
404payment_not_foundUnknown payment, or one belonging to another customer (existence is never confirmed).
404beneficiary_not_foundUnknown, deleted, malformed or other-customer beneficiary. The body is identical in every case, and it is never a 403.
409idempotency_conflictSame Idempotency-Key, different body. Nothing created.
409payment_method_already_existsThe beneficiary already has a payment method on this rail. The check uses the payment method only, not the account number.
422quote_expiredQuote consumed or expired with onQuoteExpiry: fail.
422validation_failedWell-formed but invalid for the corridor — purpose code not in the corridor's list, or a beneficiary detail failing corridor rules.
422insufficient_fundsBalance below quoted amount plus fees.
422invalid_schedulequoteId and scheduledDate together, or a scheduled payment without amount.
422unsupported_pairNo rate for this currency pair on the selected account type.
422not_cancelableThe payment can't be cancelled: it is past in-review or scheduled, or it was already cancelled.
429rate_limitLimit exceeded. Nothing was executed, not even partially.
500service_errorUnexpected error.

Note the token endpoint is the one exception: POST /oauth/token errors follow the OAuth 2.0 format (error / error_description per RFC 6749), not this envelope.

Scope note: 409, 422, and 429 are Payments API semantics. The cards/boletos/webhooks endpoints in the main reference return the 400/401/403/404/500 subset documented there, and their auth failures carry the older code: AuthError rather than unauthorized / forbidden — branch on statusCode if you call both APIs.

Rate limits

Every response carries the window state:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window (e.g. 300/minute per credential).
X-RateLimit-RemainingRequests left.
X-RateLimit-ResetUnix time the window resets.
Retry-AfterOn 429 only: seconds to wait.

On 429, back off exponentially with jitter, honoring Retry-After. Webhook deliveries to you don't count against your limits.

Retry rules

SituationDo
429Wait Retry-After, back off with jitter, retry. Nothing was executed.
Timeout or 5xx on POST /v1/paymentsRetry with the same Idempotency-Key — the safe path. A new key risks a duplicate payment.
Timeout or 5xx elsewhereIdempotent reads are safe to retry as-is.
401Request a new token, retry once.
409 idempotency_conflictDon't retry — use a fresh key for what is genuinely a new payment.
422Don't blind-retry — the request was rejected deterministically; fix the cause (new quote, valid purpose code, funded account).
Timeout on POST /v1/payments/{paymentId}/cancel, POST /v1/beneficiaries or POST /v1/payment-methodsThese calls are not idempotent and read no Idempotency-Key. A retry after a success returns 422 not_cancelable, 422 with errors[].code: duplicate, or 409 payment_method_already_exists. Read the resource first to see whether the first call went through.