Ошибки API: коды, тексты и что делать
Каждая ошибка возвращается в конверте OpenAI с соответствующим HTTP-статусом - совместимо с обработкой ошибок в OpenAI SDK. Ниже - раздел на каждый код: точный текст, причина, проверки по шагам и ответ на вопрос, имеет ли смысл повтор.
Форма ошибки
Ответ с ошибкой всегда JSON. Если в теле пришёл HTML - это ответил не наш API, а промежуточный фильтр или прокси; такой случай разобран ниже.
Разбирать ошибку следует по error.code - он точнее статуса: за одним 402 стоят и пустой баланс, и упёршийся в потолок ключ.
Все коды разом
400 - invalid_request_error
Тело запроса не прошло проверку до того, как его увидела модель: не хватает обязательного поля, значение вне допустимого диапазона или запрошенный режим недоступен у этой модели. Поле param в ответе называет виновника.
Что вы видите
Что проверить
- Прочитайте error.param - он указывает точное поле, а не «где-то в теле».
- Проверьте, что messages - непустой массив, а model - строка из каталога.
- Для изображений и видео сверьтесь с режимами модели: редактирование картинки и image-to-video есть не у всех.
- Если type равен content_policy_violation, дело не в форме запроса: контент отклонила модерация - переформулируйте промпт.
Ретраить или нет
401 - authentication_error
Ключ не пришёл, не существует или отозван. Ключи хранятся как SHA-256-хеш, поэтому «посмотреть» существующий ключ нельзя - только выпустить новый.
Что вы видите
Что проверить
- Заголовок должен быть Authorization: Bearer ar-… (принимается и x-api-key).
- Проверьте, что ключ не обрезан при копировании и не содержит переносов строки.
- Убедитесь, что ключ не отозван в кабинете: отозванный ключ отвечает тем же 401.
- Проверьте base URL: https://api.altrouter.ai/v1 - ключ ar-… не примет чужой шлюз.
Ретраить или нет
402 - insufficient_quota
Денег не хватает, чтобы зарезервировать стоимость запроса, - либо на балансе организации, либо в рамках потолка трат конкретного ключа. Это разные коды, и лечатся они по-разному.
Что вы видите
Что проверить
- Посмотрите error.code: insufficient_quota - это баланс, spend_limit_exceeded - потолок ключа.
- При insufficient_quota пополните баланс в кабинете (карта РФ, СБП, крипта).
- При spend_limit_exceeded баланс может быть полным: поднимите или снимите лимит ключа. Окна - день, неделя, месяц и «за всё время».
- Учтите резервы: перед запросом удерживается оценочная стоимость, поэтому доступная сумма меньше баланса на размер незакрытых холдов.
Ретраить или нет
403 - permission_error
Ключ настоящий, но обращается к поверхности вне своих скоупов. Второй, редкий случай - адрес клиента в блок-листе: такой запрос отбивается до маршрутизации и до аутентификации.
Что вы видите
Что проверить
- Сверьте скоупы ключа в кабинете с поверхностью запроса: chat, images, videos.
- Ключ без списка скоупов имеет доступ ко всему; ограниченный ключ - только к перечисленным.
- Если нужен доступ к другой поверхности, выпустите новый ключ с нужным скоупом, а не правьте запрос.
- Код access_denied означает блокировку по адресу, а не проблему с ключом.
Ретраить или нет
404 - not_found_error
Запрошенной модели нет в каталоге либо она относится к другой поверхности - например, видеомодель в /v1/chat/completions. Тот же статус отдаётся на неизвестный путь.
Что вы видите
Что проверить
- Возьмите id ровно из GET /v1/models или из каталога - идентификаторы это слаги вида gpt-5.4, gemini-2.5-flash.
- Проверьте поверхность: чат-модели идут в /v1/chat/completions, изображения - в /v1/images/generations, видео - в /v1/videos.
- При коде unknown_endpoint проверьте путь и базовый адрес: https://api.altrouter.ai/v1.
Ретраить или нет
429 - rate_limit_error: превышен лимит запросов
Ключ вышел за свои запросы в минуту. Лимит по умолчанию - 600 rpm на ключ, у отдельного ключа может стоять свой. Окно фиксированное, длиной 60 секунд, и считается по ключу, а не по организации.
Что вы видите
Что проверить
- Прочитайте заголовок Retry-After - в нём число секунд до конца окна.
- Смотрите x-ratelimit-limit и x-ratelimit-remaining в каждом ответе: остаток виден до того, как лимит кончится.
- Разнесите пиковую нагрузку по нескольким ключам либо запросите повышение rpm для ключа.
- Не наращивайте параллелизм в ответ на 429 - это только ускоряет исчерпание окна.
Ретраить или нет
500, 502, 503, 504 - сбои на нашей стороне и у провайдера
500 - внутренняя ошибка шлюза; её текст всегда один и тот же, детали остаются в наших логах и клиенту не показываются. 502 - провайдер ответил ошибкой. 503 - живого маршрута для модели не нашлось: маршрутизатор уже перебрал резервные провайдеры, прежде чем вернуть этот статус. 504 - запрос не уложился в отведённое время.
Что вы видите
Что проверить
- Повторите с экспоненциальной задержкой - это единственная категория, где повтор действительно меняет исход.
- Если 503 держится, попробуйте другую модель: статус относится к маршрутам конкретной модели, а не ко всему шлюзу.
- При 504 сократите ожидаемую длину ответа (max_tokens) или включите стриминг: SSE отдаёт первые токены, не дожидаясь конца генерации.
- Для /v1/images и /v1/videos повторяйте с заголовком Idempotency-Key, чтобы не создать вторую генерацию и второе списание.
Ретраить или нет
Какие коды ошибок можно ретраить
Короткий ответ: повторять имеет смысл 429 и пятисотые. Клиентские четырёхсотые описывают состояние вашего запроса, ключа или счёта - оно само не изменится.
Forbidden: access denied by security policy - и другие ошибки не от API
Часть сообщений, с которыми приходят в поддержку, наш API не возвращает вовсе. Отличить их просто: в теле ответа HTML-страница, а не JSON, и в ней нет ни error.type, ни error.code.
Так отвечает защитный фильтр сайта (WAF, чаще всего Cloudflare), когда не пропускает соединение по адресу клиента или его стране. До API дело не доходит, поэтому ни ключ, ни баланс, ни тело запроса на результат не влияют, а повтор ничего не меняет. Выхода два: другой сетевой маршрут - либо шлюз, который ходит к моделям сам. У altrouter запросы идут на api.altrouter.ai, а к OpenAI, Anthropic и Google ходим мы; VPN не нужен ни для API, ни для кабинета, ни для оплаты.
Коды, которые видны только в логах
Статус 499 с кодом client_closed - это не сбой сервиса: так помечается запрос, соединение по которому разорвал сам клиент, например при отмене стрима. Код stream_error означает обрыв уже начавшегося стрима: заголовки со статусом 200 к тому моменту ушли клиенту, поэтому HTTP-кода у такой ошибки нет - она видна в журнале запросов.