Errors
Error envelope shape and the full catalogue of error codes returned by the payzum API.
The interactive API reference at /api/docs lets you try every endpoint in the browser and see live error responses alongside the schema documentation.
All error responses share a single JSON envelope shape. Your error-handling code only needs to inspect one structure regardless of which endpoint returned the error.
Error envelope
{
"statusCode": 401,
"code": "API_KEY_MISSING",
"message": "The x-api-key header is required for this endpoint."
}| Field | Type | Description |
|-------|------|-------------|
| statusCode | number | Mirrors the HTTP status code. |
| code | string | Machine-readable constant — use this for programmatic handling. |
| message | string | Human-readable description, suitable for logging. |
Always branch on code, not on message — the message text may change across releases.
Error code reference
| HTTP | code | When it occurs |
|------|--------|----------------|
| 400 | INVALID_REQUEST | The request body failed JSON parsing or Zod schema validation. The message field contains the first validation error. |
| 400 | CURRENCY_NOT_SUPPORTED | The pay_currency value is unknown or the underlying chain is not enabled for this deployment. |
| 401 | API_KEY_MISSING | The x-api-key header was not sent on an endpoint that requires authentication. |
| 401 | API_KEY_MALFORMED | The header was present but the value is not a 64-character lowercase hex string. |
| 401 | API_KEY_NOT_FOUND | The key format is valid but no merchant record matches the SHA-256 hash of the supplied key. |
| 403 | MERCHANT_SUSPENDED | The merchant account exists and the key is valid, but the account has been suspended by an admin. Existing open invoices continue to accept funds and fire IPNs; new invoices cannot be created. |
| 404 | PAYMENT_NOT_FOUND | No invoice matching the supplied invoice ID or merchant order_id was found for this merchant. |
| 429 | RATE_LIMIT_EXCEEDED | The per-merchant request quota of 60 requests per 60 seconds was exceeded. The response includes a Retry-After: 60 header. |
| 429 | QUOTA_EXCEEDED | The merchant has reached the maximum number of simultaneously open invoices. Close or let existing invoices expire before creating new ones. |
| 500 | INTERNAL_ERROR | An unhandled server-side failure — encryption error, missing master key, or unexpected exception. Retry with exponential backoff; if the error persists, contact support. |
| 503 | RATE_PROVIDER_DOWN | The upstream market rate provider is unreachable. The estimate and invoice-creation endpoints require a live price feed. Retry after a short delay. |
Handling errors in code
const res = await fetch(`${PAYZUM_BASE}/v1/payment`, {
method: "POST",
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!res.ok) {
const err = await res.json() as { statusCode: number; code: string; message: string };
switch (err.code) {
case "RATE_LIMIT_EXCEEDED":
// honour Retry-After header
await sleep(Number(res.headers.get("Retry-After") ?? 60) * 1000);
break;
case "CURRENCY_NOT_SUPPORTED":
// show user a currency picker
break;
default:
throw new Error(`Payzum error ${err.code}: ${err.message}`);
}
}MERCHANT_SUSPENDED (403) will not resolve on its own — creating new invoices is blocked until an admin lifts the suspension. Handle it by surfacing a clear message to the merchant rather than retrying.