Errors
The uniform error envelope and all eight error codes: when each fires and how to handle it.
Every failure — validation, auth, throttling, internal — returns the same JSON envelope. There are exactly eight error codes; nothing else will ever appear in error.code, so you can switch on it safely.
The error envelope
{
"error": {
"code": "rate_limited",
"message": "rate limit exceeded (invoices: 30/min); retry later",
"doc_url": "https://zerokyc-payments.com/docs/errors#rate_limited"
}
}
code— one of the eight machine-readable codes below.message— human-readable, safe to log and show in your own dashboards. For validation failures it carries the first failed check, e.g.ttl_minutes must be between 10 and 4320.doc_url— deep link to the matching section on this page.
Codes at a glance
| Code | HTTP | Fires when |
|---|---|---|
invalid_request | 400 | Request body or query fails validation |
unauthorized | 401 | Missing, malformed or unknown API key |
account_suspended | 402 | Billing suspension — creating invoices is blocked |
forbidden | 403 | Key revoked or account archived |
not_found | 404 | Object does not exist (or belongs to another merchant/mode) |
idempotency_conflict | 409 | Same Idempotency-Key reused with a different body |
rate_limited | 429 | Per-key rate limit exceeded |
internal_error | 500 | Unexpected server-side failure — retry with backoff |
invalid_request
HTTP 400. Body validation, out-of-range values, or an operation the invoice's state does not allow (e.g. canceling an already confirmed invoice). Fix the request; retrying unchanged will fail the same way.
unauthorized
HTTP 401. The Authorization: Bearer … header is missing, malformed, or the key is unknown. Check the prefix: pk_live_… / pk_test_…. Note that 401 also covers sandbox/live mismatches only implicitly — a valid pk_test_… key simply will not see live invoices (they resolve as not_found).
account_suspended
HTTP 402. Your subscription is in suspension (unpaid platform invoice past its grace period). Creating new invoices returns this code until the platform invoice is settled; existing invoices, checkout pages and webhooks keep working. Details: Direct checkout — billing.
forbidden
HTTP 403. The API key has been revoked in the console, or the account was archived after a long suspension. Rotate to a live key in API keys.
not_found
HTTP 404. The object id does not exist, belongs to another merchant, or lives in the other mode: pk_test_… keys see only sandbox invoices and pk_live_… only live ones. Treat a unexpected 404 on a known invoice as a mode mismatch first.
idempotency_conflict
HTTP 409. You reused an Idempotency-Key with a different request body within the 24-hour window. Either send the identical body, or use a new key. Full semantics: Rate limits & idempotency.
rate_limited
HTTP 429. The per-key limit is exceeded for this scope. The response carries Retry-After: <seconds> plus the standard X-RateLimit-* headers — respect them instead of hammering:
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1694007937
internal_error
HTTP 500. Unexpected failure on our side. Safe to retry the same request after a short backoff — if you sent an Idempotency-Key, the retry cannot create a duplicate. If it persists, check GET /v1/ping and contact support.