Errors
HTTP statuses, machine-readable error codes and safe retry behaviour.
Failed requests return an HTTP status and a JSON body with a stable code and
a human-readable message.
HTTP/1.1 400 Bad Request
{
"code": "INVALID_REQUEST",
"message": "currency: Invalid input: expected \"AED\""
}Codes raised by the API
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_REQUEST | A body, query parameter or field combination is invalid. |
| 400 | INVALID_REQUEST_BODY | A webhook URL is malformed, non-HTTPS, private or otherwise unsafe. |
| 400 | INVALID_CURSOR | The pagination cursor is malformed or no longer usable. |
| 400 | INVALID_IDEMPOTENCY_KEY | The key is not 1–255 printable ASCII characters. |
| 400 | PRODUCT_NOT_ACTIVE | A checkout contains a draft or archived product. |
| 400 | INVALID_PRICE | A price is zero or below the smallest chargeable amount. |
| 400 | INVALID_CART | A recurring checkout does not contain exactly one recurring product. |
| 400 | MIXED_CURRENCIES | Products in one checkout use different currencies. |
| 400 | CURRENCY_NOT_SUPPORTED | A checkout's currency is one the API accepts but the card processor has not enabled in this mode. |
| 400 | INVALID_AMOUNT | A refund's amount is not a whole number above zero. |
| 400 | AMOUNT_TOO_LARGE | A refund is more than what is left of the payment. |
| 401 | UNAUTHORIZED | The API key is missing, wrong or revoked. |
| 403 | MERCHANT_NOT_APPROVED | The account is not approved to use the API. |
| 403 | FORBIDDEN | The authenticated key cannot perform the operation. |
| 404 | NOT_FOUND | The endpoint or object does not exist for this merchant and mode. |
| 405 | METHOD_NOT_ALLOWED | The path exists but not for that HTTP method. |
| 409 | CUSTOMER_EXISTS | A customer with this email already exists in this merchant and mode. |
| 409 | IDEMPOTENCY_KEY_IN_USE | Another request with the same key is still running; retry the same request. |
| 409 | TEST_MODE_UNAVAILABLE | Sandbox payments are not available on the platform yet; it is not a per-account setting. |
| 409 | SUBSCRIPTION_CANCELLED | A cancelled subscription cannot be changed. |
| 409 | CANCELLATION_FINAL | Renewal was already stopped at the processor and cannot be resumed. |
| 409 | CHARGE_IN_PROGRESS | A renewal is being charged right now; cancel again in a few minutes. |
| 409 | LIMIT_REACHED | The account reached a resource limit, such as API keys or webhooks. |
| 409 | NOT_REFUNDABLE | The payment has not succeeded, so there is nothing to refund. |
| 409 | ALREADY_REFUNDED | The payment has been refunded in full already. |
| 409 | FULL_REFUND_ONLY | The payment was charged in the customer's own currency and can be refunded only in full. |
| 409 | NOT_SETTLED | The payment was made today and has not settled yet: until tomorrow it can be refunded only in full. |
| 409 | INSUFFICIENT_BALANCE | Your balance in the payment's currency does not cover the refund. |
| 409 | CONFLICT | The request conflicts with the object's current state. |
| 410 | GONE | Reserved. The hosted checkout pages answer it for a checkout already paid or expired; no /api/v1 endpoint raises it today. |
| 413 | PAYLOAD_TOO_LARGE | The request exceeds the body-size limit. |
| 415 | UNSUPPORTED_MEDIA_TYPE | The request content type is unsupported. |
| 422 | IDEMPOTENCY_KEY_REUSED | The key was previously used with different request parameters. |
| 422 | UNPROCESSABLE_ENTITY | The request is syntactically valid but cannot be processed. |
| 429 | RATE_LIMITED | The rate limit was reached; follow Retry-After. |
| 502 | CANCELLATION_FAILED | The card processor did not confirm the cancellation; nothing changed, so try again. |
| 502 | REFUND_FAILED | The card processor did not make the refund; nothing was refunded, so try again later. |
| 503 | PAYMENTS_UNAVAILABLE | Live payment configuration is temporarily unavailable. |
| 503 | REFUNDS_UNAVAILABLE | Refunds are not available for this account right now; contact support. |
| 5xx | INTERNAL_ERROR | An unexpected server error occurred. |
Validation errors produced from a schema use INVALID_REQUEST. The message
names the first invalid field, but integrations should branch on code, not
the English message.
The schema is checked first, so a currency outside the accepted list never
reaches the CURRENCY_NOT_SUPPORTED check: creating a product, or pricing a
cart line inline, in another currency answers INVALID_REQUEST. What is left
for CURRENCY_NOT_SUPPORTED is a checkout whose currency the API accepts while
the card processor has not enabled it — a product saved before the accepted list
narrowed, for instance. Read the enabled list from GET /account instead of
assuming it. See Currencies and amounts.
Limits
| What | Limit | Answer when exceeded |
|---|---|---|
| Request body | 64 KiB | 413 PAYLOAD_TOO_LARGE |
| Webhook endpoints | 16 per mode | 409 LIMIT_REACHED |
| API keys | 10 active per mode; revoked ones don't count | 409 LIMIT_REACHED |
metadata | 50 keys, each name 1–40 and each value up to 500 characters | 400 INVALID_REQUEST |
product_cart | 1–50 lines, quantity 1–100 per line | 400 INVALID_REQUEST |
List limit | 1–100, default 20 | 400 INVALID_REQUEST |
Idempotency-Key | 1–255 printable ASCII characters | 400 INVALID_IDEMPOTENCY_KEY |
The two 409 LIMIT_REACHED limits are counted per mode, so live and test have
their own allowance. POST /webhooks and the dashboard share the endpoint
limit; keys are only created in the dashboard. Neither limit lifts on its own —
delete an endpoint or revoke a key to make room.
Retrying
Retry network errors, 429, IDEMPOTENCY_KEY_IN_USE and 5xx with backoff.
For a POST, reuse the same Idempotency-Key and identical body. Do not retry
other 4xx responses without changing the request or account state.
The libraries map statuses to typed errors. See Node.js and PHP.