# Ошибки и лимиты

> Форма отказа REST API Строфи, коды ошибок и их статусы, признак повторяемости, частота запросов и одновременность, потолки размеров и машинное описание API.

Источник: https://strophe.app/docs/product/developers/errors-and-limits
Обновлено: 2026-08-26

Отказ приходит в одной и той же форме на всех ручках, а по заголовкам ответа видно, сколько запросов осталось и когда повторять. Эта статья описывает то и другое.

## Форма отказа

```json
{
  "error": {
    "code": "cost_above_limit",
    "message": "The generation costs more than the maxCredits ceiling in the request.",
    "retryable": false,
    "details": { "credits": 62, "maxCredits": 40 }
  }
}
```

`code` — машинный идентификатор, единственное, на что стоит программировать. `message` написан для человека, читающего лог, и может меняться. `retryable` отвечает, имеет ли смысл повторить тот же запрос без изменений. `details` есть не всегда: там лежит машинная диагностика — перечень проблем валидации, незакрытые входы, числа.

Локализации нет: ответ читает программа, а не человек в интерфейсе.

Каждый ответ несёт `X-Request-Id`. Он же записан в наших логах, поэтому разбор происшествия начинается с одной строки — приложите его к обращению в поддержку.

## Коды и статусы

Список закрыт: код, которого нет в таблице, наружу не уходит. Новые значения могут появляться — незнакомый код обрабатывайте по числовому статусу и признаку `retryable` из ответа.

| Код                         | HTTP | Когда приходит                                                               |
| --------------------------- | ---- | ---------------------------------------------------------------------------- |
| `access_denied`             | 403  | Внешний доступ этому счёту закрыт                                            |
| `account_blocked`           | 403  | Аккаунт заблокирован                                                         |
| `cancel_conflict`           | 409  | Состояние генерации изменилось во время отмены                               |
| `cost_above_limit`          | 402  | Запрос дороже переданного `maxCredits`                                       |
| `credit_reservation_failed` | 402  | Резерв кредитов не удался                                                    |
| `family_not_found`          | 400  | Такого семейства в каталоге нет                                              |
| `family_unavailable`        | 503  | Семейство временно недоступно                                                |
| `file_fetch_failed`         | 502  | Файл по ссылке не забрался                                                   |
| `file_metadata_missing`     | 400  | Длительность входного видео ещё не измерена, а модель считается посекундно   |
| `file_not_found`            | 404  | Такого идентификатора файла или генерации нет                                |
| `file_not_ready`            | 409  | Файл ещё обрабатывается                                                      |
| `file_processing_failed`    | 422  | Обработка файла не удалась                                                   |
| `generation_not_found`      | 404  | Генерации с таким идентификатором у вас нет                                  |
| `idempotency_conflict`      | 409  | `Idempotency-Key` уже использован с другими параметрами                      |
| `idempotency_in_progress`   | 409  | Запрос с этим `Idempotency-Key` ещё выполняется                              |
| `insufficient_credits`      | 402  | Кредитов не хватает — на балансе или после вычета занятого другими запусками |
| `insufficient_scope`        | 403  | У ключа нет нужного права                                                    |
| `internal_error`            | 500  | Внутренний сбой сервиса                                                      |
| `invalid_cursor`            | 400  | Метка страницы не из предыдущего ответа                                      |
| `invalid_parameters`        | 400  | Параметры не подходят выбранному варианту                                    |
| `invalid_request`           | 400  | Тело или параметр не прошли проверку; перечень проблем в `details.issues`    |
| `invalid_token`             | 401  | Ключ отозван или не существует                                               |
| `not_cancellable`           | 409  | Генерация запущена в редакторе — такую отменяют там                          |
| `payload_too_large`         | 413  | Тело запроса больше допустимого                                              |
| `price_unavailable`         | 400  | Стоимость для таких параметров не складывается                               |
| `queue_unavailable`         | 500  | Очередь генерации недоступна                                                 |
| `rate_limited`              | 429  | Превышена частота запросов                                                   |
| `service_unavailable`       | 503  | Сервис временно недоступен                                                   |
| `spend_limit_exceeded`      | 403  | Задет потолок ключа; какой именно — в `details.limitKind`                    |
| `subscription_required`     | 403  | Семейство требует действующей подписки                                       |
| `team_expired`              | 403  | Срок плана команды истёк                                                     |
| `team_required`             | 403  | Доступ идёт вместе с командой; чего не хватает — в тексте ошибки             |
| `too_many_concurrent`       | 429  | Слишком много запросов в работе одновременно                                 |
| `unauthorized`              | 401  | Ключа в запросе нет                                                          |
| `unsupported_content_type`  | 415  | Такой тип содержимого ручка не принимает                                     |
| `unsupported_file_format`   | 415  | Содержимое не опознано или такой формат не принимается                       |
| `variant_not_resolved`      | 400  | Ни один вариант не подходит к такому набору входов                           |

Повторять или нет, говорит `retryable` в самом ответе: у части кодов это зависит от причины, а не от кода. У всех отказов по частоте и временной недоступности есть заголовок `Retry-After`.

## Отказ самой генерации

Провалившаяся генерация — это ответ 200 с состоянием `failed`, а не отказ запроса: запрос выполнен, не удалась работа модели. Причина лежит внутри генерации и устроена иначе, чем отказ ручки:

```json
{
  "generationId": "8f14e45f-ceea-467a-9a3e-4b1c2d5e7f80",
  "state": "failed",
  "error": {
    "code": "prompt_validation",
    "reason": "prompt_too_long",
    "message": "The prompt is longer than this model accepts.",
    "retryable": false,
    "hint": "The model could not work with this prompt. Rewrite it, or pick another family.",
    "details": { "limit": 8000 }
  }
}
```

`code` — класс отказа, список закрыт. `hint` называет действие, а не диагноз: что менять перед следующей попыткой. `details` появляется там, где из отказа удалось достать числа — например предел длины промпта у конкретной модели.

| Класс                  | Что случилось                                 | Что делать                                                    |
| ---------------------- | --------------------------------------------- | ------------------------------------------------------------- |
| `content_policy`       | Модель отказала по содержанию                 | Менять промпт или входные файлы: тот же запрос откажет так же |
| `prompt_validation`    | Модель не приняла промпт                      | Переписать промпт или взять другую модель                     |
| `parameter_validation` | Параметр или вход не подходит модели          | Свериться со схемой семейства и поправить запрос              |
| `file_size`            | Входной файл не укладывается в пределы модели | Взять файл другого размера                                    |
| `image_size`           | Размер входной картинки не подходит модели    | Взять картинку другого размера                                |
| `image_format`         | Файл не прочитался как изображение            | Загрузить заново в поддерживаемом формате                     |
| `feature_limitation`   | Модель не умеет того, о чём просили           | Выбрать другое семейство                                      |
| `model_unavailable`    | Модель недоступна                             | Взять другое семейство или повторить позже                    |
| `insufficient_credits` | Кредиты кончились до старта                   | Проверить баланс перед повтором                               |
| `authentication`       | Доступ к модели временно закрыт               | Повторить позже; запрос ни при чём                            |
| `timeout`              | Модель не ответила вовремя                    | Повторить: тот же запрос может пройти                         |
| `provider_error`       | Временный сбой модели                         | Повторить: тот же запрос может пройти                         |
| `internal_error`       | Внутренний сбой сервиса                       | Повторить: тот же запрос может пройти                         |
| `user_cancellation`    | Генерацию отменили                            | Ничего — это ответ на отмену                                  |

`reason` — причина внутри класса, и программировать разное поведение стоит по ней. Один класс покрывает несколько разных отказов: `prompt_too_long`, `prompt_failed` и `prompt_images_failed` — всё это `prompt_validation`, но первый чинится обрезкой промпта, а последний — заменой входных изображений. Список причин, в отличие от списка классов, открыт и пополняется. Опубликованную причину мы не переименовываем, а незнакомую обрабатывайте по `code`.

Дословную формулировку модели мы наружу не отдаём — в `message` приходит наш текст по причине отказа. Чтобы подняли оригинал, назовите `generationId` в обращении в поддержку.

Причина `unclassified` при классе `internal_error` означает, что отказ не удалось отнести ни к одной известной причине. Обрабатывайте его по `code`; со временем такие отказы получают точную причину.

## Частота и одновременность

Ограничений два, и считаются они на пользователя, а не на ключ: сорвавшийся скрипт одного участника команды не должен закрывать API остальным.

**Частота.** Сейчас это 600 запросов в минуту. Превышение — 429 `rate_limited` с заголовком `Retry-After`.

**Одновременность.** До 32 запросов в работе одновременно. Потолок рассчитан на ожидание через `wait`, где запрос висит до минуты; больше тридцати двух открытых ожиданий — повод перейти на историю или на вебхуки. Превышение — 429 `too_many_concurrent`.

Точные числа приходят в заголовках каждого ответа, и опираться стоит на них, а не на значения в тексте:

```http
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587
X-RateLimit-Reset: 1785974160
```

`X-RateLimit-Reset` — момент обнуления счётчика в секундах Unix.

Отдельно от этого действуют потолки траты самого ключа — суточный и разовый. Они описаны в статье [Ключ и права](https://strophe.app/docs/product/developers/authentication).

## Потолки размеров

| Что                     | Предел                                                       | При превышении                      |
| ----------------------- | ------------------------------------------------------------ | ----------------------------------- |
| Тело запроса            | 256 КиБ                                                      | 413 `payload_too_large`             |
| Файл                    | 25 МБ — одинаково для `multipart/form-data`, ссылки и base64 | Отказ приёма файла                  |
| Промпт                  | 20 000 символов                                              | Отказ проверки запроса              |
| Медиа-входов в запросе  | 20                                                           | Подбор варианта отказывает          |
| Страница истории        | 50 записей                                                   | Значение больше не принимается      |
| Ожидание внутри запроса | 60 секунд                                                    | Ответ приходит с `pollAfterSeconds` |

Две последние границы — заслон транспорта, а не пределы моделей: те заметно строже. Сколько входов принимает вариант, видно в подробностях семейства (`inputs.images.max` и соседние поля), а предел длины промпта — в описании параметра, если модель его объявляет. Лишний файл отвергается подбором варианта с кодом `variant_not_resolved`, слишком длинный промпт — самой моделью с классом `prompt_validation`, и там же в `details` приходит настоящее число.

## Машинное описание

`GET /api/v1/openapi.json` отдаёт спеку OpenAPI: пути, схемы запросов и ответов, коды отказа. Она собирается из тех же схем, которыми проверяется вход, поэтому разойтись с поведением не может.

Спека закрыта тем же ключом, что и остальные ручки, и требует права `catalog:read`: документ описывает ручки, но не даёт ими пользоваться.