Error reference
Every error type the API can return, with meaning and how to react.
Error reference
All errors are JSON with the same envelope:
{
"error": {
"type": "invalid_request",
"code": "customer_email_invalid",
"message": "customer_email must be a valid email address",
"param": "customer_email",
"request_id": "req_01H…"
}
}
type— high-level family (see table below).code— machine-readable specific error. Always populated for validation and idempotency errors.message— human-readable. Safe to log, do not show verbatim to end customers.param— the offending body field, when applicable.request_id— always log it. Support can look it up instantly.
HTTP status codes
| HTTP | Meaning | Retry? |
|---|---|---|
200 / 201 |
Success | — |
400 |
Client error (bad input) | No — fix and resend |
401 |
Auth failed (missing / bad / revoked key) | No |
402 |
Payment declined at the network | Depends (see decline_code) |
403 |
Key valid but not allowed for this action | No |
404 |
Resource not found | No |
409 |
Conflict (idempotency reuse, merchant not ready) | No — see code |
422 |
Semantically invalid (e.g. session already paid) | No |
429 |
Rate limited | Yes — respect Retry-After |
5xx |
spuke server issue | Yes — with the same Idempotency-Key |
error.type values
| Type | HTTP | When |
|---|---|---|
invalid_request |
400 | Missing / malformed field. param tells you which. |
amount_too_small |
400 | Below the 2.50 EUR equivalent minimum. |
amount_too_large |
400 | Above the per-charge maximum. |
currency_unsupported |
400 | Currency not in the supported list. |
authentication_error |
401 | Key missing, malformed, revoked or wrong mode (test vs live). |
permission_error |
403 | Key valid but scope/role forbids this action. |
card_error |
402 | Buyer's card was declined. See code / decline_code. |
idempotency_error |
409 | Same Idempotency-Key reused with a different request body. code = idempotency_key_reused. |
resource_conflict |
409 | e.g. merchant_not_ready, subscription_already_canceled. |
not_found |
404 | The id does not exist under this merchant. |
invalid_state |
422 | session_expired, session_already_completed, charge_disputed. |
rate_limit_error |
429 | Too many requests. Back off. |
api_error |
5xx | Transient spuke error. Retry with the same Idempotency-Key. |
Idempotency conflict — exact shape
Reusing an Idempotency-Key with a body that differs from the original request always returns:
{
"error": {
"type": "idempotency_error",
"code": "idempotency_key_reused",
"message": "Idempotency-Key reused with a different request body"
}
}
- HTTP status: 409 Conflict
- Uniqueness scope:
(merchant_id, method, path, Idempotency-Key). - Same key + same body → replays the original response (status + body) verbatim.
- Same key + different body → the 409 shown above.
- Keys expire after 24 hours.
Recommended handling
- Never retry
4xxerrors except409 idempotency_errorafter you've fixed the body and rotated the key. - Always retry
5xxwith the same idempotency key, using exponential backoff (1s → 2s → 4s, max 5 attempts). - On
429, sleep for the number of seconds inRetry-After(default 1) before the next attempt. - Show
error.messageto internal operators, not to end customers — use a friendly wrapper.