Коды ошибок
Ошибка всегда приходит одним и тем же объектом. По коду понятно, чинить запрос или повторять его.
Формат ошибки
Любой ответ с кодом 4xx или 5xx содержит объект
error и идентификатор запроса. Тело всегда JSON — даже когда ошибку вернул шлюз, а не
модель.
{
"error": {
"message": "Модель openai/gpt-4.1-nano не найдена в каталоге.",
"type": "model_not_found",
"code": "model_not_found"
},
"request_id": "req_td_8f31c0a94e2b"
} - error.message
- Человекочитаемое описание на русском. Годится для лога, не годится для сравнения в коде — текст может меняться.
- error.type
- Класс ошибки. Семь значений, полный список — ниже. По нему принимают решение о ретрае.
- error.code
- Уточнение внутри типа: например model_not_found, context_length_exceeded, insufficient_balance.
- request_id
- Идентификатор запроса в логах TokenDock. Его же отдаёт заголовок X-Request-Id.
У ошибок валидации добавляется поле error.param — имя параметра, из-за которого запрос
отклонён.
{
"error": {
"message": "Значение max_tokens (200000) больше контекста модели (128000).",
"type": "invalid_request_error",
"code": "context_length_exceeded",
"param": "max_tokens"
},
"request_id": "req_td_2b70ce41ad99"
} Если ошибка случилась после начала SSE-потока, HTTP-код уже отдан как 200 — описание придёт событием в потоке, а соединение закроется. Обрабатывайте ошибки не только по коду ответа, но и внутри стрима.
HTTP-коды
Тринадцать кодов покрывают всё, что может вернуть API. Колонка «Ретрай» отвечает на главный вопрос: имеет ли смысл повторять запрос без изменений.
| Код | Тип | Причина | Что делать | Ретрай |
|---|---|---|---|---|
| 400 | invalid_request_error | Тело не разбирается как JSON, отсутствует обязательное поле или значение поля недопустимо. | Исправьте запрос. Имя поля приходит в error.param — повтор без правок даст тот же ответ. | нет |
| 401 | authentication_error | Заголовок Authorization отсутствует, ключ отозван или передан без префикса Bearer. | Проверьте заголовок и значение переменной окружения. Если ключ утерян — выпустите новый в консоли. | нет |
| 402 | insufficient_quota | Недостаточно средств: баланс аккаунта на нуле или исчерпан бюджет, заданный на ключ. | Пополните счёт или поднимите бюджет ключа. После зачисления запросы идут сразу, ключи не отзываются. | нет |
| 403 | authentication_error | Ключ действителен, но ему не разрешён доступ к запрошенному ресурсу. | Сверьте ограничения ключа в консоли. | нет |
| 404 | model_not_found | Модели с таким id нет в каталоге или неверен путь эндпоинта. | Возьмите точный id из ответа GET /models — вместе с вендором, например openai/gpt-4.1-mini. | нет |
| 408 | timeout_error | Клиент не дослал тело запроса за отведённое время или разорвал соединение на приёме. | Повторите запрос. Если тело большое — проверьте сеть и таймауты HTTP-клиента. | да |
| 413 | invalid_request_error | Тело запроса больше 8 МБ — обычно это батч эмбеддингов или изображение в base64. | Разбейте батч на части, изображения передавайте ссылкой вместо base64. | нет |
| 422 | invalid_request_error | JSON корректен, но значения не проходят проверку: пустой messages, max_tokens больше контекста, строка длиннее контекста модели. | Приведите значения к ограничениям модели. Длину контекста отдаёт GET /models. | нет |
| 429 | rate_limit_error | Превышен лимит RPM, TPM или число конкурентных запросов. | Подождите время из заголовка Retry-After и повторите с экспоненциальным бэкоффом. | да |
| 500 | upstream_error | Необработанная ошибка на стороне TokenDock. | Повторите запрос. Если повторяется — пришлите request_id в поддержку. | да |
| 502 | upstream_error | Апстрим вернул некорректный ответ. | Повторите с бэкоффом — ошибка обычно временная. | да |
| 503 | upstream_error | Модель временно недоступна на стороне апстрима — провайдеры перегружены. | Повторите с бэкоффом или отправьте запрос на другую модель. | да |
| 504 | timeout_error | Провайдер не отдал ответ за отведённое время, бюджет времени запроса исчерпан. | Повторите. Сократите промпт и max_tokens — длинные генерации упираются в таймаут чаще. | да |
Кода 200 достаточно, чтобы считать запрос успешным и тарифицируемым: списание идёт по
полю usage из тела ответа. Ошибочные ответы не тарифицируются — подробности в разделе
баланс и тарификация.
Типы ошибок
Поле error.type стабильнее текста сообщения — стройте обработку на нём. Один тип может
приходить с разными HTTP-кодами.
| type | HTTP | Что означает |
|---|---|---|
| invalid_request_error | 400 · 413 · 422 | Запрос не соответствует контракту API. Ошибка на стороне клиента, повтор не поможет. |
| authentication_error | 401 · 403 | Ключ не принят или ему не разрешён доступ к запрошенному ресурсу. |
| insufficient_quota | 402 | Денег на балансе или в бюджете ключа не хватает на выполнение запроса. |
| rate_limit_error | 429 | Превышена частота запросов, объём токенов в минуту или конкурентность. |
| model_not_found | 404 | Модели с таким id нет в каталоге. |
| upstream_error | 500 · 502 · 503 | Сбой на стороне провайдера или шлюза. Обычно временный, лечится повтором. |
| timeout_error | 408 · 504 | Ответ не уложился в отведённое время — на приёме запроса или на генерации. |
Типы совпадают с типами OpenAI везде, где это возможно, — код, написанный под их SDK, продолжает работать. Собственные значения появляются только там, где у OpenAI нет аналога, — например для бюджетов на ключ.
Ретраи
Повторять имеет смысл ровно шесть кодов: 408, 429, 500, 502, 503, 504. Остальные ошибки — про сам запрос: пока его не исправить, ответ не изменится.
- Экспоненциальный бэкофф. Пауза удваивается на каждой попытке: 1 с, 2 с, 4 с, 8 с. Пять попыток — разумный потолок, дальше ошибку лучше показать пользователю.
- Джиттер обязателен. Без случайной добавки параллельные воркеры вернутся к API одновременно и снова упрутся в 429.
- Retry-After сильнее формулы. На 429 приходит заголовок со временем до открытия окна — используйте его вместо собственного расчёта.
- Стрим не переигрывается автоматически. Если поток оборвался после первых токенов, решение о повторе принимает приложение — и обычно запрос стоит отправить заново целиком.
import os
import random
import time
import requests
ENDPOINT = "https://api.tokendock.cloud/v1/chat/completions"
RETRIABLE = {408, 429, 500, 502, 503, 504}
MAX_ATTEMPTS = 5
HEADERS = {
"Authorization": f"Bearer {os.environ['TOKENDOCK_API_KEY']}",
"Content-Type": "application/json",
}
def chat(payload: dict) -> dict:
for attempt in range(MAX_ATTEMPTS):
response = requests.post(ENDPOINT, json=payload, headers=HEADERS, timeout=120)
if response.ok:
return response.json()
last = attempt == MAX_ATTEMPTS - 1
if response.status_code not in RETRIABLE or last:
body = response.json()
raise RuntimeError(
f"{body['error']['type']}: {body['error']['message']} "
f"(request_id={body.get('request_id')})"
)
# Retry-After важнее собственного расчёта: сервер знает, когда откроется окно
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
# Джиттер разводит параллельные воркеры, иначе они вернутся все разом
time.sleep(delay + random.uniform(0, 0.5))
raise RuntimeError("unreachable")
Официальные SDK умеют это сами: у openai-python и openai-node
есть параметр max_retries с бэкоффом по умолчанию. Ручной цикл нужен, когда вы ходите в
API напрямую или хотите свою логику очереди.
request_id
Каждый запрос получает идентификатор вида req_td_8f31c0a94e2b. Он приходит в теле
ошибки и в заголовке X-Request-Id любого ответа, включая успешный. По нему поддержка
находит запрос в логах: модель, токены, тайминги и код ответа.
curl -i https://api.tokendock.cloud/v1/chat/completions \
-H "Authorization: Bearer $TOKENDOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "openai/gpt-4.1-mini", "messages": [{"role": "user", "content": "ping"}]}' \
| grep -i "^x-request-id"
# x-request-id: req_td_8f31c0a94e2b - Логируйте его всегда. Пишите
request_idрядом с ошибкой — без него разбор превращается в поиск по времени и модели. - Прикладывайте к обращению. Один идентификатор экономит переписку: по нему видно всё, что произошло с запросом на стороне шлюза.
- Тела запроса в логах нет. Мы храним только метаданные, поэтому промпт при обращении придётся описать словами — что именно отправляли и что ожидали получить.
Для обращения нужны три вещи: request_id, время запроса с часовым поясом и id модели.
Напишите в поддержку через форму на сайте или на
support@tokendock.cloud.