Limits & errors
HTTP status codes
| Code | When |
|---|---|
| 200 | Success: reads, PATCH, closing / reopening / rating a ticket, POST /messages, webhook test |
| 201 | Created: POST /tickets, POST /tickets/{id}/messages, POST /contacts (even when the contact already existed), POST /webhooks, POST /media/upload |
| 204 | Success with no body: DELETE /webhooks/{id} |
| 302 | GET /media/{id} when the project keeps files in its own S3 bucket: a redirect to the object |
| 400 | POST /tickets or POST /contacts without telegram_id and email |
| 401 | No Authorization header, or the key is wrong, disabled or deleted, or the project is suspended. The response carries WWW-Authenticate: Bearer |
| 403 | TICKET_LIMIT_REACHED: the plan’s monthly ticket limit is used up; PLAN_FEATURE_UNAVAILABLE: the plan has no knowledge base; POST /tickets for a blocked contact |
| 404 | Ticket, contact, message, webhook, file or article not found |
| 409 | TICKET_ALREADY_CLOSED: closing a ticket that is already closed |
| 413 | MEDIA_TOO_LARGE: the file is larger than 200 MB |
| 422 | The body or parameters failed validation (for example an unknown status or priority), VALIDATION_ERROR (a file from another project in media_ids, an unknown webhook event, editing a system message), MESSAGE_EMPTY, TICKET_INVALID_STATUS_TRANSITION |
| 429 | RATE_LIMIT_EXCEEDED: too many requests; the Retry-After header says in how many seconds to retry |
| 500 | Unexpected server error |
| 502 | MEDIA_UPLOAD_FAILED: the file couldn’t be written to storage |
| 503 | MAINTENANCE_MODE: the platform is under maintenance |
Error format
An error body comes in one of three shapes. Business-logic errors (ticket not found, plan limit, empty message, rate limit) use the common envelope:
{
"error_code": "TICKET_NOT_FOUND",
"error_message": "Ticket not found",
"detail": { "ticket_id": "8a3f..." },
"request_id": "0b5c..."
}error_code: a stable UPPER_SNAKE_CASE string to branch on in code.error_message: a short description in English.detail: context specific to the error, for example{"field": "contact", "reason": "at least one of telegram_id, email is required"}, ornull.request_id: the request’s identifier, the same as in theX-Request-IDresponse header.
API-level checks (the key, the required contact identifiers, a webhook or file that doesn’t exist) answer briefly, with no request_id in the body:
{ "detail": "contact must include at least one of telegram_id, email" }When the body or the parameters don’t match the schema (a required field is missing, a wrong type, page_size above the maximum, an unknown status or priority), FastAPI returns 422 with a list of problems:
{
"detail": [
{ "type": "missing", "loc": ["body", "contact"], "msg": "Field required", ... }
]
}error_code, branch on it; otherwise look at detail (a string or a list). Responses carry an X-Request-ID header (only a 500, or the 503 during maintenance, may lack it). You can send your own value in the request: up to 200 visible ASCII characters with no spaces, otherwise the server issues a new one. Quote that id when you contact support.Rate limit
The limit is per client and covers the whole API, not a single path or endpoint:
- 20,000 requests per minute per API key for requests with a working key. All the servers of your integration that use one key share this budget.
- 1,000 requests per minute per client IP address for the rest: no key, a wrong or disabled key. A new key’s first request in each API process counts here too, until the key has been checked there.
The window is fixed: one minute from the first request in it. Over the limit you get 429 with RATE_LIMIT_EXCEEDED in the common envelope and a Retry-After header: the number of seconds until the window reopens. There are no X-RateLimit-* headers.
A request to /api/v1/updates counts once, however long it waits for events.
The counters live in Redis and are shared by all API servers. If Redis is down, requests aren’t limited.
When to retry
- 429: after the number of seconds in
Retry-After. - 500, 502, 503: with a growing pause (for example 1, 2, 4, 8 seconds).
- A retry won’t fix any other 4xx: change the request.
During maintenance the API answers 503 with the body {"error_code": "MAINTENANCE_MODE", "error_message": "Platform is under maintenance"}.

