Регистрация

Коды ошибок

Ошибка всегда приходит одним и тем же объектом. По коду понятно, чинить запрос или повторять его.

Формат ошибки

Любой ответ с кодом 4xx или 5xx содержит объект error и идентификатор запроса. Тело всегда JSON — даже когда ошибку вернул шлюз, а не модель.

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 — имя параметра, из-за которого запрос отклонён.

json · ошибка валидации
{
  "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 приходит заголовок со временем до открытия окна — используйте его вместо собственного расчёта.
  • Стрим не переигрывается автоматически. Если поток оборвался после первых токенов, решение о повторе принимает приложение — и обычно запрос стоит отправить заново целиком.
python · ретраи с бэкоффом
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 любого ответа, включая успешный. По нему поддержка находит запрос в логах: модель, токены, тайминги и код ответа.

bash · достать 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.

Написать в поддержку