# Errors and limits

The error format, every code with its HTTP status, request and size limits, and which requests to retry.

## Error format {#format}

Every error comes as the same JSON:

```json Response 409
{
  "error": {
    "code": "sold_out",
    "message": "Билеты этого тарифа закончились",
    "details": {"tariff_id": "0193e0b2-4a7d-7a21-8c44-6e2f9a0b3c71"}
  }
}
```

- `code` never changes. Base your logic on it.
- `message` is written in Russian for a person, and its text may change. You can show it to an operator, but don't parse it.
- `details` isn't always there. It holds a field, a tier, a seat or a reason: the set of keys depends on the error.

## Codes {#codes}

| Code | HTTP | When | What to do |
|---|---|---|---|
| `invalid_request` | 400 | Bad JSON, field or parameter. The reason is in `message`, sometimes the field is in `details` | Fix the request. The same request won't pass again |
| `unauthorized` | 401 | No key, a wrong or revoked key, or a suspended organization | Check the key |
| `forbidden` | 403 | The action is dashboard-only: `details.reason: "session_only"` | Do it in the dashboard |
| `not_found` | 404 | No such object or endpoint. An object of another organization or mode isn't found either | Check the id and the key's mode |
| `event_not_on_sale` | 409 | The event isn't published, or sales of the tier and seats are still opening after publishing | Publish the event. After publishing, wait a few seconds |
| `sold_out` | 409 | The tier is sold out, `details.tariff_id` | Offer another tier or refund |
| `seat_taken` | 409 | The seat is held by another order or sold, `details.seat_id` and `details.seat` | Offer another seat |
| `reservation_expired` | 409 | The reservation expired and no tickets are left | Refund the buyer |
| `idempotency_conflict` | 409 | The `Idempotency-Key` belongs to another request, is still running, or its outcome is unknown | See [idempotency](https://budu.events/en/docs/idempotency/#outcomes) |
| `limit_exceeded` | 422 | More than 20 tickets in an order, or more than the tier allows per order | Make the order smaller or split it |
| `rate_limited` | 429 | Too many requests | Wait `Retry-After` seconds |
| `internal` | 500 | A failure on our side | Retry with the same `Idempotency-Key` |

While the service restarts, the proxy may answer `502`, `503` or `504` without JSON. Retry such a request with the same `Idempotency-Key`.

## Limits {#limits}

| What | How much |
|---|---|
| Requests per key | 600 per minute |
| JSON body | 1 MB |
| Seat file | 5 MB, up to 50,000 seats |
| Guest file | 1 MB, up to 2,000 lines |
| Cover | 5 MB, up to 25 megapixels |
| Tickets per order | 20 |
| Reservation | 60 to 1,800 seconds, 900 by default |
| `Idempotency-Key` | up to 255 characters, kept 24 hours |
| Ticket email resend | once a minute per ticket |
| List page | tickets up to 200, seats up to 5,000 |

The request limit counts calendar minutes. Going over gives `429 rate_limited` with the `Retry-After` header and `details.retry_after_s`: the seconds left until the next minute.

## Which requests to retry {#retries}

- `429`: after `Retry-After` seconds.
- `5xx`, a timeout or a dropped connection: with the same `Idempotency-Key`, with a pause that grows with each attempt (1, 2, 4 seconds…).
- `400`, `401`, `403`, `404`, `422`: don't retry, fix the request first.
- `409`: depends on the code. `sold_out`, `seat_taken` and `reservation_expired` are final. `event_not_on_sale` right after publishing passes in a few seconds. For `idempotency_conflict` see [idempotency](https://budu.events/en/docs/idempotency/#outcomes).
