API fundamentals
Errors
The error envelope, every error code, request ids and what to do about each kind of error.
Every response that is not a success has the same envelope, whatever went wrong. The HTTP status tells you the class of problem, type how to react, and code exactly what happened.
The error envelope
{
"error": {
"type": "invalid_request_error",
"code": "amount_too_small",
"message": "The minimum amount is 50.00 DA (5000 centimes).",
"param": "amount",
"request_id": "req_8f3a1c9d2b7e4a60"
}
}typestringThe category of the error. It decides how you react.
codestringA stable, machine-readable identifier. Branch on this, never on the message.
messagestringAn explanation for developers. Do not show it to payers, and do not parse it.
paramstringnullableThe request parameter the error is about, such as amount or customer.email. Nested names use dots and brackets: items[0].quantity.
request_idstringThe id of the request, also in the Rais-Request-Id response header.
Request ids
Every response, errors included, carries a Rais-Request-Id header such as req_8f3a1c9d2b7e4a60c5d1, and the Rais-Version header. Log the request id of every call that fails, and quote it when you contact support: it finds the exact request on our side. Raïs logs each request with its id but never its headers, query string or body. The id is the same in the header and in error.request_id.
Types
| Type | Status | What to do |
|---|---|---|
authentication_error | 401 | Fix the key. See Authentication. |
permission_error | 403 | The key is valid but not allowed: a missing scope. (A suspended store, or live mode before verification, answer 401 instead: see Authentication.) |
invalid_request_error | 400, 405, 409 | Fix the request. The param names the field. Do not retry as it is. |
not_found_error | 404 | The object does not exist for this key. See Authentication. |
idempotency_error | 409, 422 | The Idempotency-Key conflicts with another request. See Idempotency and retries. |
rate_limit_error | 429 | Wait for the Retry-After header, then retry. See Rate limits. |
payment_error | 402, 502 | The payment gateway or the bank refused. Nothing was changed on your side of the line. |
api_error | 500, 503 | A problem on our side or the gateway's. Retry later with the same Idempotency-Key. |
Error codes
| Code | HTTP | Type | Meaning |
|---|---|---|---|
amount_mismatch | 400 | invalid_request_error | The amount is not an integer, is outside 5000...999999999999 centimes, or disagrees with `items`. |
amount_too_large | 400 | invalid_request_error | The amount is not an integer, is outside 5000...999999999999 centimes, or disagrees with `items`. |
amount_too_small | 400 | invalid_request_error | The amount is not an integer, is outside 5000...999999999999 centimes, or disagrees with `items`. |
currency_not_supported | 400 | invalid_request_error | Only `DZD`. |
customer_not_found | 400 | invalid_request_error | A field is invalid (see `param`). |
insufficient_balance | 400 | invalid_request_error | The refund is more than what is left on the payment, or than your balance can cover (the refund plus its fee). |
invalid_amount | 400 | invalid_request_error | The amount is not an integer, is outside 5000...999999999999 centimes, or disagrees with `items`. |
invalid_cursor | 400 | invalid_request_error | The header or the `starting_after` value is invalid. |
invalid_email | 400 | invalid_request_error | A field is invalid (see `param`). |
invalid_idempotency_key | 400 | invalid_request_error | The header or the `starting_after` value is invalid. |
invalid_json | 400 | invalid_request_error | The request body or query is malformed. |
invalid_metadata | 400 | invalid_request_error | A field is invalid (see `param`). |
invalid_parameter | 400 | invalid_request_error | The request body or query is malformed. |
invalid_url | 400 | invalid_request_error | A field is invalid (see `param`). |
parameter_missing | 400 | invalid_request_error | The request body or query is malformed. |
parameter_unknown | 400 | invalid_request_error | The request body or query is malformed. |
refund_exceeds_balance | 400 | invalid_request_error | The refund is more than what is left on the payment, or than your balance can cover (the refund plus its fee). |
invalid_api_key | 401 | authentication_error | The key is missing, unknown, revoked, expired, not allowed from this IP address, or its store cannot use it. |
refund_declined | 402 | payment_error | The bank refused the refund. |
account_disabled | 403 | permission_error | The store is suspended or disabled. |
insufficient_scope | 403 | permission_error | The key lacks the scope the endpoint needs. |
live_mode_not_enabled | 403 | permission_error | Complete business verification to take live payments. |
event_not_found | 404 | not_found_error | Nothing there (or it belongs to another store or environment). |
payment_not_found | 404 | not_found_error | Nothing there (or it belongs to another store or environment). |
refund_not_found | 404 | not_found_error | Nothing there (or it belongs to another store or environment). |
route_not_found | 404 | not_found_error | Nothing there (or it belongs to another store or environment). |
method_not_allowed | 405 | invalid_request_error | Wrong HTTP method. |
idempotency_in_progress | 409 | idempotency_error | The first request with this key is still running. |
payment_not_cancellable | 409 | invalid_request_error | The payment is not in a state that allows it. |
payment_not_refundable | 409 | invalid_request_error | The payment is not in a state that allows it. |
idempotency_key_reused | 422 | idempotency_error | The key was used with different parameters. |
rate_limit_exceeded | 429 | rate_limit_error | Too many requests (60 per second, bursts of 120, per key) or too many failed authentications. |
internal_error | 500 | api_error | Our fault. Quote the `request_id`. |
gateway_rejected | 502 | payment_error | The payment gateway refused to create the payment. |
gateway_unavailable | 503 | api_error | The gateway cannot be reached; retry later. Nothing was created or refunded. |
More codes can appear while a request is being read. They are produced by the validation of the request itself, so they are not tied to one endpoint:
| Code | HTTP status | Meaning |
|---|---|---|
invalid_request_body | 400 | The body is missing or is not a JSON object, for example an array. |
parameter_out_of_range | 400 | A number is below or above what the parameter accepts, for example limit=0 or expires_in: 299. param names it. |
parameter_too_long | 400 | A text or a list is longer than allowed, for example a reference of 65 characters or 51 items. param names it. |
parameter_too_short | 400 | A text or a list is shorter than required. |
invalid_request | 400 | A request that is invalid in a way no other code describes. Read message. |
Handling errors
async function rais(path, init) {
const response = await fetch(`https://api.rais.app/v1${path}`, init);
const body = await response.json();
if (response.ok) return body;
const { error } = body;
console.error("Raïs error", error.code, error.request_id); // always log the request id
if (error.type === "rate_limit_error" || error.type === "api_error") {
// transient: retry with backoff, reusing the same Idempotency-Key
throw Object.assign(new Error(error.code), { retryable: true, retryAfter: response.headers.get("Retry-After") });
}
throw Object.assign(new Error(error.code), { retryable: false, param: error.param });
}Retry guidance
| Situation | Retry? |
|---|---|
| Network error or timeout | Yes, with the same Idempotency-Key. The first request may have succeeded. |
429 rate_limit_exceeded | Yes, after Retry-After seconds. |
500 internal_error | Yes, with backoff, and the same Idempotency-Key. |
503 gateway_unavailable | Yes. Nothing was created or refunded. Wait the Retry-After seconds. |
409 idempotency_in_progress | Yes, after Retry-After seconds: the first request is still running. |
502 gateway_rejected | Not as it is: the gateway refused the payment. Read message. |
402 refund_declined | No. The bank refused. Do not send it again unchanged. |
Other 4xx | No. Fix the request. |
Back off exponentially (1 s, 2 s, 4 s ...) with some random jitter, and give up after a few attempts. Never retry a POST without an Idempotency-Key.