# Response codes and errors

> HTTP codes, error body formats, the asynchronous response envelope.

## HTTP codes

| Code  | When it is returned                                                                                                         | What to do                                                  |
| ----- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `200` | success                                                                                                                     | —                                                           |
| `201` | object created                                                                                                              | —                                                           |
| `400` | input validation error                                                                                                      | fix the request, do not retry                               |
| `401` | token missing, expired or invalid                                                                                           | refresh the token pair and retry once                       |
| `403` | no permission for the route, company blocked or key not activated                                                           | grant the permission; check the company and key status      |
| `404` | route or object not found                                                                                                   | check the path and the identifier                           |
| `408` | waiting for the asynchronous response timed out (600 s; for [report export to Excel](/en/reference/report-export/) — 100 s) | check the object state with a separate request              |
| `422` | business validation error                                                                                                   | fix the data                                                |
| `429` | the rate limiter triggered on a heavy route                                                                                 | retry with exponential backoff                              |
| `500` | internal error                                                                                                              | 1–2 retries, then contact support with the `correlation_id` |

## Body formats

**Classic envelope** — this is how most methods and the access check respond

```json
{ "code": 401, "message": "token expired", "error": "token has been expired", "data": null }
```

**Error object**

```json
{ "error": { "code": "BAD_REQUEST", "message": "shop_id is required" } }
```

**Asynchronous response envelope**

```json
{
  "session_id": "00000000-0000-0000-0000-000000000000",
  "company_id": "00000000-0000-0000-0000-000000000000",
  "id": "00000000-0000-0000-0000-000000000000",
  "status_code": 400,
  "error": { "code": "BAD_REQUEST", "message": "..." },
  "correlation_id": "00000000-0000-0000-0000-000000000000"
}
```

In this case the final HTTP code is taken from the `status_code` field, and the ID of the created or modified object — from `id`.

Empty envelope fields are omitted from the response (`omitempty`); `data` in an error envelope is usually absent.

**Timeout body** — the same channel, but shorter: it has **no** `status_code` field.

```json
{ "correlation_id": "...", "error": { "code": "TIMEOUT", "message": "Timeout waiting for response" } }
```

`error.code` is checked first.

## Semantics of key codes

### 401 — token

The cause is distinguished by the `message` + `error` pair:

| `message`       | `error`                                        | What happened                                                    |
| --------------- | ---------------------------------------------- | ---------------------------------------------------------------- |
| `token error`   | JWT parsing text                               | the token is truncated, corrupted or signed with a different key |
| `token expired` | `token has been expired`                       | expired — a refresh is required                                  |
| `unauthorized`  | `refresh token used as access`                 | a refresh token was passed in `Authorization`                    |
| `unauthorized`  | `session not found` / `session revoked`        | the session is closed or revoked — a new sign-in is required     |
| `unauthorized`  | `mismatch platform-id with token info`         | `platform-id` differs from the one the token was issued with     |
| `unauthorized`  | `user is not active` / `user has been expired` | the key is disabled or has expired                               |

The token pair is refreshed via `POST /v2/auth/refresh` (the `platform-id` header is required). If the refresh token has also expired, a new token is issued via `POST /v1/auth/login`. Errors of the refresh itself come with code `400` and a description of the cause.

### 403 — permissions

The method exists and the token is valid, but the “method + route template” pair is not in the list allowed for the key.

Two more states respond with the same code: the company is blocked (`"error": "company is blocked"`) and the key is not activated (`"error": "user hasn't been activated"`).

### 429 — rate

Overall limit — 2 requests per second per IP. On top of it, dedicated limiters stand on individual heavy routes: search routes and [report exports to Excel](/en/reference/report-export/) (5 requests per second). These counters are shared by all clients of the platform.

### 408 — asynchronous response timeout

Occurs when the result of an asynchronous operation has not arrived within 600 seconds; for [report export to Excel](/en/reference/report-export/) the wait is shorter — 100 seconds. The operation may still have completed — **do not retry a write blindly**.

## Business codes

### String codes

The `error` / `error.code` field carries string codes: `BAD_REQUEST`, `TIMEOUT`, `TOO_MANY_REQUESTS`, `rate_limited`, and token refresh responses use the RFC 6749 §5.2 codes: `invalid_request`, `invalid_grant`, `unauthorized_client`, `server_error`.

### Numeric codes — in sales

Sale methods respond with `400`; the exact cause is a number **in `data.error_code`**:

```json
{
  "status_code": 400,
  "error": { "code": "", "message": "status: 400 - errors: Bad request - causes: ..." },
  "data": {
    "error_code": 20084,
    "message": "payment is possible only for calculated order",
    "data": null
  }
}
```

`error.code` here is an empty string.

The most common codes in an online store integration:

| Code    | Meaning                                                 | What to do                                                                          |
| ------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `20084` | payment is possible only for a recalculated cart        | the items were added with the wrong method — use `POST /v2/order-product/:order_id` |
| `20082` | payment with the shift closed                           | open a shift in the shop of the sale                                                |
| `20010` | the user has no current cash register                   | open a shift; the system picks the cash register, passing it in the body is useless |
| `20009` | the cash register’s shop and the sale’s shop differ     | the sale was created in the wrong shop                                              |
| `20015` | the sale has no items                                   | add products before payment                                                         |
| `20016` | insufficient stock                                      | check the stock in the shop of the sale                                             |
| `20017` | the draft has been deleted                              | create a new one                                                                    |
| `20020` | wrong payment amount                                    | the sum of payments must match the receipt total                                    |
| `20033` | shift not found                                         | open a shift                                                                        |
| `20036` | product not found                                       | check `product_id`                                                                  |
| `20037` | the product is archived                                 | an archived product cannot be sold                                                  |
| `20063` | the sale was not sent to the fiscal data operator (OFD) | removed with the `skip_ofd` flag                                                    |
| `20085` | the seller has no access to the shop                    | check `seller_id`                                                                   |

The primary reference is the HTTP code; the numeric and string codes serve for precise diagnostics and logs.

## Parsing the error body

```python
def parse_error(resp):
    try:
        body = resp.json()
    except ValueError:
        return {"code": resp.status_code, "message": resp.text[:300]}
    err = body.get("error")
    if isinstance(err, dict):
        return {"code": body.get("status_code", resp.status_code),
                "message": err.get("message") or err.get("code")}
    return {"code": body.get("code", resp.status_code),
            "message": body.get("message") or (err if isinstance(err, str) else "unknown")}
```

`correlation_id` from the response is written to the log — support uses it to find the request in the trace.