لم تُترجم هذه الصفحة بعد
تعرض هذه الصفحة النص الإنجليزي حتى تصدر ترجمتها.
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.
flowchart TD
A[Response] --> B{Status}
B -->|429| C[Wait Retry-After, then retry]
B -->|5xx| D[Retry with exponential backoff]
B -->|400 401 403 404 409| E[Do not retry: fix the request, key, plan or id]
B -->|timeout on POST /api/invoices/| F[Poll the invoice list first]
F -->|exists| G[Use it]
F -->|absent| H[Retry once]