Ошибки и ретраи

Единый формат, коды, стратегия повторов

Ошибки всегда возвращаются с HTTP-статусом и единым JSON-телом. Сохраняйте request_id — он поможет нам и вам найти запрос в логах.

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

Контракт ошибок OpenAI-совместимый: error.message, type, param и строковый code. Точный текст, дополнительные поля и HTTP-статус могут отличаться от OpenRouter. Неизвестная или недоступная через каталог/allowlist модель возвращает 404 not_found. Ошибки валидации тела возвращают 400 invalid_request с указанием поля в param. При stream: true ошибка до открытия SSE приходит обычным JSON; после открытия потока ошибку нужно обрабатывать внутри SSE, поскольку HTTP-статус уже отправлен.

json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Please retry after 2 seconds.",
    "type": "rate_limit_error",
    "request_id": "sr_req_7k3m9x2p",
    "param": null,
    "details": []
  }
}

Справочник кодов

Проверка баланса
Для платной модели нулевой или отрицательный баланс блокируется с 402 insufficient_credits. При положительном балансе шлюз не резервирует стоимость всего max_tokens: списание идёт по фактическому использованию, и баланс может стать отрицательным. Следующий платный запрос будет заблокирован. Для бесплатных моделей нулевой баланс допустим; лимиты ключа проверяются отдельно.
invalid_request400retry: noОтсутствуют поля, неверные типы, слишком длинный prompt
invalid_api_key401retry: noНет/невалидный/отозванный/просроченный ключ
forbidden403retry: noДоступ запрещён — чужая организация или недостаточно прав
not_found404retry: noМодель или ресурс не найдены
content_filter400retry: noЗапрос/ответ заблокирован фильтром безопасности
rate_limited429retry: yes (backoff)RPM или квота кредитов исчерпана
insufficient_credits402retry: noНулевой баланс, пополните счёт
provider_error502retry: yesОшибка на стороне upstream-провайдера
timeout504retry: yesПровайдер не ответил в разумное время
service_unavailable503retry: yesВременная недоступность — повторите позже

Стратегия ретраев

Повторяйте только безопасные коды (429, 502, 503, 504) с экспоненциальным backoff и джиттером.

python
import time, random
from openai import OpenAI, APIError, RateLimitError

client = OpenAI(base_url="https://api.kodikrouter.ru/v1", api_key=...)

def chat_with_retry(messages, model, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            return client.chat.completions.create(model=model, messages=messages)
        except RateLimitError as e:
            wait = min(2 ** attempt, 30) + random.random()
            time.sleep(wait)
        except APIError as e:
            if e.status_code in (502, 503, 504) and attempt < max_attempts - 1:
                time.sleep(min(2 ** attempt, 30) + random.random())
                continue
            raise
    raise RuntimeError("exhausted retries")

Идемпотентность

Для гарантированно-однократной записи (например, списания) отправляйте заголовок Idempotency-Key: <uuid>. Повторный запрос с тем же ключом вернёт тот же ответ без дополнительной тарификации.

http
POST /v1/chat/completions HTTP/1.1
Authorization: Bearer sk-sr_live_...
Content-Type: application/json
Idempotency-Key: 9a1b2c3d-4e5f-6789-abcd-ef0123456789
Клиенты openai и anthropic SDK автоматически добавляют экспоненциальный backoff и Idempotency-Key. Если используете их — ретраи уже в комплекте.