Skip to main content
Every error is the same envelope, with a stable code your integration may branch on and a human-readable message that may change copy over time:
{ "error": { "code": "insufficient_funds", "message": "..." } }
Codes are published: never renamed, never removed — only added. Branch on code, not on message or status text.

Two namespaces

There are two different kinds of “it didn’t work”, and they behave differently with money:
  • Rejections (this page’s first table) — the request itself was refused. No payout was created and nothing was held. Fix the request (or wait, where retryable) and submit again.
  • Failure codes (second table) — a payout was accepted, held funds, and later died. The payout is terminal, failure: { code, message } is set, and the hold is always released back to your available balance. Resubmission is always a new payout with a new Idempotency-Key.

Rejection codes

CodeHTTPMeaningRetry?
validation_error400Malformed request: bad JSON, missing field, or a missing Idempotency-Key on create.After fixing the request.
unauthorized401Missing, malformed, revoked, or unknown API key.No — fix the key.
forbidden403Org suspended, or request IP not on your allowlist.No — resolve with Jenzy / fix the allowlist.
not_found404No such resource in your org. Unknown, foreign, and malformed ids look identical.No.
insufficient_funds402The amount exceeds your available balance.After funding (book a trade).
conflict409This Idempotency-Key was used with a different body, or its original request is still in progress.Different body → new key. In progress → same key, shortly.
unknown_bank422bank_uuid doesn’t name a payable bank.After re-fetching GET /banks.
unknown_operator422operator_ref_id doesn’t name a payable operator.After re-fetching GET /operators.
invalid_mobile_number422The mobile number doesn’t parse for the target operator.After fixing the number.
invalid_account_number422The account number doesn’t pass validation for the target bank.After fixing the number.
amount_limit_exceeded422This payout is above the per-payout maximum.With a smaller amount.
rate_limited429Too many requests.Yes — honor Retry-After.
org_limit_exceeded429Your org’s rolling daily cap or payout-velocity check tripped.Yes — later.
internal_error500Something broke on our side.Yes — and on create, reuse the same Idempotency-Key: you’ll get the original outcome if one was reached, never a double payout.
rail_disabled503This rail is administratively paused.Yes — later.
service_unavailable503A dependency is unavailable (e.g. reference data has never synced).Yes — later.

Failure codes

Carried in failure.code on a failed payout — on GET /payouts/{id} and in the payout.failed webhook, identically. failure.message is fixed per-code copy, safe to show to your own users.
CodeMeaningCan a resubmission succeed?
recipient_account_invalidThe provider rejected the destination (account doesn’t resolve, invalid number).Only after the destination is corrected.
declinedThe provider declined the payout, or failed it without a reason. The catch-all.Unlikely unchanged — investigate before resubmitting.
provider_unavailableTimeout or outage at the downstream provider.Yes — a fresh payout may plausibly succeed; retry shortly.
temporarily_unavailableA temporary condition on the Hermes side.Yes — retry later; no funds were deducted.
A failed payout is never retried automatically, and the four codes above are the complete failure vocabulary — anything unmappable lands in declined.