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.