Errors and rate limits¶
Two response shapes¶
Clix returns errors in one of two shapes depending on which layer refused the call. Handle both.
Middleware — throttling and the fair-use guard — uses detail. Almost everything else uses message. Validation failures put an array in message.
Validation errors are 400, not 422
Clix replaces the framework default. A malformed body returns 400 with a list, one string per error, each prefixed with the path to the field. On the invoice endpoints the path starts with invoice_schema_data:
A business rule refused after validation returns 400 with one string in message. Some of those also carry rule_id, the ZATCA business rule that failed.
Status codes¶
| Code | Meaning | Retry? |
|---|---|---|
400 |
Validation failed, or a business rule refused it | No — fix the request |
401 |
Missing, invalid or revoked key | No |
403 |
Permission, inactive org, plan without API access, or fair use | No |
404 |
Not found, or not yours | No |
409 |
PATCH …/clearance-reporting/ on an invoice ZATCA has already answered, or one already queued |
No |
429 |
Rate limited | Yes, after Retry-After |
5xx |
Server side | Yes, with backoff |
The 401s and 403s worth distinguishing¶
| Body | Cause |
|---|---|
{"status":"false","message":"Missing or invalid token (use Authorization: Bearer <token> or accessToken cookie)"} |
No Authorization header |
{"status":"false","message":"Invalid or revoked API key"} |
Unknown, revoked, or a deactivated organisation |
{"status":"false","message":"API access is not included in your current plan."} |
Plan without API access — checked on every request |
{"message":"Your account does not have permission to perform this action."} |
The key's role or scopes do not cover this endpoint. Some endpoints word it their own way: POST /api/invoices/ says User is not authorized to create invoices |
{"message":"Your organization's account is inactive. Please contact support."} |
Organisation suspended |
{"message":"Monthly invoice limit exceeded. You have used your grace period. Please upgrade your plan.", "current_usage": …, "limit": …, "plan_name": …, "upgrade_url": …} |
POST /api/invoices/ over the plan's invoice allowance plus its 10% grace. It has no code: branch on the status and limit. See Usage and quotas |
Machine-readable codes¶
Most errors carry prose only. These are the stable code values:
code |
Status | Meaning |
|---|---|---|
fair_use_limit |
403 | The organisation hit its fair-use ceiling for the month |
invalid_request |
400 | Malformed key-management request |
not_found |
404 | No such key |
invalid_role, invalid_scopes |
400 | Bad role or scope at key creation |
key_id_unavailable |
503 | Could not allocate a key id — retry |
Branch on code where one exists. Never parse the prose.
A person signed in to the app with several organisations can also get 409 org_context_required. An API key never does: it belongs to one organisation, and Clix ignores X-Active-Org on a key.
Rate limits¶
A fixed 60-second window. The window starts with your first request and resets 60 seconds later, whatever happened in between.
| Caller | Limit |
|---|---|
| An API key | 200 requests a minute, for that key |
| A person signed in to the app | 200 requests a minute, shared across all their organisations |
| No credentials | 100 requests a minute per IP address |
These are the defaults. Each limit and the window are environment settings that Clix can change, so read X-RateLimit-Limit rather than hard-coding 200.
Each API key has its own count. Two keys in one organisation do not share the 200, and a key that hits its limit does not slow down people using the app.
Responses carry the state:
On a successful response, X-RateLimit-Reset is always 60, the window length, not the time left. Count down with X-RateLimit-Remaining instead. On a 429, X-RateLimit-Reset and Retry-After both give the seconds actually left in the window.
The headers are missing on the documentation paths (/api/docs, /api/redocs, /api/integrations/docs, the OpenAPI files) and on /health/. Those paths are not rate limited.
The 201st request in a window gets a 429 with Retry-After:
{ "detail": "Rate limit exceeded. Please try again later.",
"limit": 200, "window": 60, "retry_after": 12 }
Respect Retry-After. Read X-RateLimit-Remaining and slow down before you hit zero rather than after.
Fair use¶
API calls are not metered against a monthly API quota. What can refuse a call is the fair-use guard on writes (POST, PUT, PATCH and DELETE). A write counts when it succeeds with a 2xx. Clix switches the guard on for the platform or for one organisation; it is off by default.
When it is on, an organisation is refused once its counted writes this month pass its fair-use allowance plus 10%, rounded up. The allowance is set per plan, and support can override it for one organisation. No API response shows it; -1 means unlimited.
The month is the Asia/Riyadh calendar month. Once the guard is on, the refusal looks like this:
{ "code": "fair_use_limit",
"detail": "Your organisation has reached its fair-use limit for this month. Viewing and downloading still work. Contact support to continue.",
"current_usage": 1234, "limit": 1000 }
It is a 403, not a 429, and retrying will not clear it. Reads keep working. Talk to support.
Not counted: reads, failed requests, previews, PDF downloads, sample invoices, sign-in and subscription calls, search, and key management.
api_calls_used is always 0
GET /api/usage/current/ returns api_calls_used: 0 and api_calls_limit: 0 permanently, kept so older clients keep parsing. The meaningful figures there are the invoice counters. See Usage and quotas.
What to retry¶
Retry 429 after Retry-After, and 5xx with exponential backoff.
Do not retry 400, 401, 403, 404, 409.
A 202 from POST /api/invoices/ that says the submission to ZATCA failed is not an error to retry with a new create. The invoice exists; send PATCH /api/invoices/{invoice_id}/clearance-reporting/ for it.
Never blind-retry POST /api/invoices/. Identical payloads are refused for five minutes with a 400, and a timeout does not mean the invoice was not created. Poll first. A cleared invoice cannot be deleted — only credited.