Errors
One envelope, a stable code to branch on, and a message you can show a human.
The shape
{
"error": {
"type": "permission_error",
"code": "scope_missing",
"message": "This key is missing the `customers:write` permission.",
"request_id": "req_1f4e2b0b9f5a2c1d0f7a41c8"
}
}Branch on code. It is stable and will not change wording underneath you. type is the coarse family, useful for generic handling. message is written to be readable by whoever ends up staring at it.
Always keep the request id
Every response — success or failure — carries x-kleos-request-id, and every failure repeats it in the body. Log it. It is the single fastest way for Kleos support to find the exact request you are asking about, and the same id appears in your console's activity log.
Codes
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | The key is malformed, unknown, or not a Kleos key. |
| 401 | key_revoked | The key was revoked. Create a new one in the console. |
| 401 | key_rotated | The key was rotated and its grace window has closed. Deploy the replacement. |
| 401 | key_expired | The key passed its expiry date. |
| 403 | scope_missing | The key is valid but lacks the permission this endpoint needs. |
| 403 | ip_not_allowed | The request came from an address outside the key's allowlist. |
| 403 | emporos_suspended | The account is suspended. Your Kleos contact can say why. |
| 403 | live_not_enabled | A live key was used before the account was approved for live traffic. |
| 404 | customer_not_found | No such customer in this mode. |
| 409 | idempotency_key_reused | The same idempotency key arrived with a different body. |
| 429 | rate_limit_exceeded | Too many requests. `Retry-After` says how many seconds to wait; the response also carries the window's limit and reset. |
| 400 | invalid_limit | A spending or concurrency limit was negative, fractional, or not a number. |
| 400 | invalid_json | The request body was not valid JSON. |
| 500 | api_error | Something failed on our side. Nothing was charged. |
Refusals are logged, not hidden
Every refusal is written to your account's activity log with its reason code, visible at console.kleos.click. “Kleos said no at 14:02 because the key was missing calls:write” should be a two-second answer, not a support thread.
Retrying
5xx and network timeouts are worth retrying with backoff — send an idempotency key and a retry cannot duplicate anything. 4xx is not worth retrying: the same request will be refused the same way until you change something.
When a 500 comes back, nothing was charged. Kleos does not bill for a request it could not complete.