Limits & errors
For Developers

Limits & errors

HTTP status codes

CodeWhen
200Success: reads, PATCH, closing / reopening / rating a ticket, POST /messages, webhook test
201Created: POST /tickets, POST /tickets/{id}/messages, POST /contacts (even when the contact already existed), POST /webhooks, POST /media/upload
204Success with no body: DELETE /webhooks/{id}
302GET /media/{id} when the project keeps files in its own S3 bucket: a redirect to the object
400POST /tickets or POST /contacts without telegram_id and email
401No Authorization header, or the key is wrong, disabled or deleted, or the project is suspended. The response carries WWW-Authenticate: Bearer
403TICKET_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
404Ticket, contact, message, webhook, file or article not found
409TICKET_ALREADY_CLOSED: closing a ticket that is already closed
413MEDIA_TOO_LARGE: the file is larger than 200 MB
422The 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
429RATE_LIMIT_EXCEEDED: too many requests; the Retry-After header says in how many seconds to retry
500Unexpected server error
502MEDIA_UPLOAD_FAILED: the file couldn’t be written to storage
503MAINTENANCE_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:

HTTP 404
{
  "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"}, or null.
  • request_id: the request’s identifier, the same as in the X-Request-ID response 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:

HTTP 400
{ "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:

HTTP 422
{
  "detail": [
    { "type": "missing", "loc": ["body", "contact"], "msg": "Field required", ... }
  ]
}
Parse a response like this: if there is an 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"}.

Was this page helpful?