Ошибки LLM API: справочник кодов и решений
Ошибка LLM API — это HTTP-статус плюс JSON-объект error с полями type, code, message и request_id. Статус говорит, кто виноват: 4xx — запрос или ключ, 5xx — поставщик модели. Ниже разобраны коды, которые go2llm и официальные API Anthropic и OpenAI возвращают чаще всего, и почти для каждого есть отдельная страница с воспроизведением и починкой.
Справочник составлен 2026-09-11, обновлён 2026-09-22. Машиночитаемая версия той же таблицы лежит в документации: go2llm.tech/docs/errors.
Таблица всех ошибок
| HTTP | error.code | Что делать | Страница |
|---|---|---|---|
| 400 | invalid_request | Проверить валидность JSON и обязательные поля model, messages, max_tokens | 400: изображения и PDF в текстовом endpoint |
| 400 | unsupported_content_type | Убрать image, audio, video и PDF из тела текстового запроса | 400 unsupported_content_type |
| 400 | model_protocol_mismatch | Взять правильный endpoint для модели из GET /v1/models | 404 model_not_found |
| 400 | stateful_responses_unsupported | Убрать previous_response_id / store: true из /v1/responses, слать всю историю в input | 400 stateful_responses_unsupported |
| 400 | background_unsupported | Убрать background: true из /v1/responses — вызовы синхронные | 400 stateful_responses_unsupported |
| 400 | idempotency_key_required | Привести Idempotency-Key к 8–128 печатным ASCII-символам | 409 idempotency_conflict |
| 401 | no-key | Передать действующий ключ gk_sk_… в x-api-key или Authorization: Bearer | 401 authentication_error |
| 402 | insufficient_balance | Пополнить баланс от $1 или уменьшить max_tokens | 402 insufficient_balance |
| 403 | model / inactive / expired / ip | Проверить список моделей, срок и IP allow-list ключа в кабинете | 403 region not supported |
| 404 | model_not_found | Скопировать точный public ID модели из GET /v1/models | 404 model_not_found |
| 404 | — (на /v1/responses) | Сменить клиента: у go2llm нет Responses API | 404 на /v1/responses в Codex CLI |
| 409 | idempotency_conflict / request_in_progress | Не переиспользовать Idempotency-Key для другого тела запроса | 409 idempotency_conflict |
| 413 | request_too_large | Уменьшить тело запроса: лимит 20 MiB | 413 request_too_large |
| 429 | rate_limit | Дождаться Retry-After, лимит по умолчанию — 20 RPM на ключ | 429 rate_limit_error |
| 429 | quota | Поднять квоту ключа в долларах или создать второй ключ | 429 rate_limit_error |
| 502 | upstream_unavailable | Повторить безопасный текстовый запрос с экспоненциальной задержкой | 502 и 503: как правильно ретраить |
| 503 | product_paused / no_eligible_route | Проверить GET /status, повторить позже | 502 и 503: как правильно ретраить |
Отдельная категория — ошибки, которые возвращает не go2llm, а официальные API вендоров: authentication_error с текстом invalid x-api-key у Anthropic, insufficient_quota и your credit balance is too low у OpenAI и Anthropic, unsupported_country_region_territory у OpenAI, overloaded_error с кодом 529 у Anthropic — разобран отдельно на странице 529 overloaded_error, потому что у go2llm такого статуса нет вовсе.
Как читать ответ об ошибке
go2llm возвращает ошибку одним JSON-объектом с четырьмя полями. Разбирать нужно все четыре: HTTP-статуса недостаточно, потому что под одним 400 и одним 429 живут разные причины с разной починкой.
{
"error": {
"type": "insufficient_balance",
"code": "insufficient_balance",
"message": "not enough spendable balance",
"request_id": "gw_4f1c8a2e9b7d40c3a15e6f2b8c9d0e1a"
}
}
error.type — класс ошибки. По нему принимается решение «ретраить или нет». Ретраить безопасно только upstream_unavailable, rate_limit и product_paused. Всё остальное при повторе даст тот же ответ.
error.code — машинный код для ветвления в вашем обработчике. В go2llm code совпадает с type для ошибок шлюза, но разделять их всё равно стоит: коды авторизации (no-key, inactive, expired, ip, model) приходят из подсистемы ключей и различают четыре разные причины отказа под общим статусом 403.
error.message — человекочитаемое пояснение: missing or unknown api key, model not allowed for this key, Only text input is supported; rejected image at messages.0.content.1.source. Не парсите message регулярками, текст меняется; парсите code.
error.request_id — идентификатор запроса с префиксом gw_. Это единственное, что нужно приложить к обращению в support@go2llm.tech или Telegram @go2llm_support. Детальные request logs go2llm хранит до 90 дней, промпты и ответы в продуктовые логи не пишутся — по request_id восстанавливается маршрут, usage, стоимость и статус, но не содержимое.
Дополнительно go2llm отдаёт заголовок Retry-After в секундах на статусах 429 и 503. Для product_paused это 60 секунд.
Универсальная стратегия ретраев
Правильный ретрай для LLM API строится на трёх правилах. Первое: повторять только 429, 502, 503 и сетевые таймауты — 400, 401, 402, 403, 404 и 409 повтор не лечит. Второе: уважать Retry-After, если заголовок пришёл. Третье: обязательно добавлять jitter, иначе десять параллельных воркеров после общего 429 синхронно ударят в шлюз через ровно 1, 2 и 4 секунды.
import random, time
import httpx
RETRYABLE = {429, 502, 503}
BASE_URL = "https://go2llm.tech/v1/chat/completions"
def call_with_retry(payload, api_key, max_attempts=5, base=1.0, cap=32.0):
with httpx.Client(timeout=120.0) as client:
for attempt in range(max_attempts):
r = client.post(
BASE_URL,
headers={"Authorization": f"Bearer {api_key}"},
json=payload,
)
if r.status_code < 400:
return r.json()
if r.status_code not in RETRYABLE or attempt == max_attempts - 1:
err = r.json().get("error", {})
raise RuntimeError(
f"{r.status_code} {err.get('code')}: {err.get('message')} "
f"[request_id={err.get('request_id')}]"
)
server_hint = r.headers.get("retry-after")
if server_hint and server_hint.isdigit():
delay = float(server_hint)
else:
delay = min(cap, base * (2 ** attempt))
time.sleep(delay * (0.5 + random.random() / 2)) # full-range jitter
Пять попыток с базой 1 секунда и потолком 32 секунды дают суммарное ожидание около 23 секунд в худшем случае — это разумный предел для интерактивного запроса. Для фоновых задач поднимайте max_attempts до 8 и cap до 60 секунд.
Два исключения, о которых не пишут в туториалах. Первое: стрим нельзя повторить после первого валидного SSE-события — go2llm может подменить маршрут на совместимый fallback только до первого байта ответа, после этого переключение не выполняется, и ретрай на уровне клиента приведёт к дублю. Второе: ambiguous_upstream_result в media-запросах повторять автоматически нельзя — поставщик мог принять запрос, и повтор спишет деньги дважды.
Сколько стоит ошибка
Неудачный запрос к go2llm не списывает деньги: шлюз резервирует оценочную стоимость до вызова, затем списывает фактическую и освобождает остаток резерва, а баланс не уходит ниже нуля. Фактическая стоимость каждого успешного запроса приходит в usage.cost_usd, в том числе в финальном usage-блоке SSE-стрима. Цены по всем 38 опубликованным моделям — на странице тарифов; claude-sonnet-5 стоит $0.33 за 1M входных токенов против официальных $2.00.
FAQ
Чем error.type отличается от error.code в go2llm? type описывает класс ошибки и используется для решения о ретрае, code — точный машинный код для ветвления. У ошибок шлюза go2llm оба поля совпадают; у ошибок авторизации code конкретнее и принимает значения no-key, inactive, expired, ip, model, quota.
Какие коды безопасно ретраить автоматически? 429, 502, 503 и сетевые таймауты. Коды 400, 401, 402, 403, 404 и 409 ретраить бессмысленно: go2llm вернёт тот же ответ, пока не изменится запрос, ключ или баланс.
Где взять request_id, если ответ не парсится? request_id лежит в теле ответа в поле error.request_id и начинается с gw_. Если тело пустое, а статус 502 или 503 — соединение оборвалось до формирования ответа шлюза; приложите к обращению точное время запроса с точностью до секунды и модель.
Почему ошибка пришла на английском? go2llm отдаёт error.message на английском, чтобы текст совпадал с официальными API Anthropic и OpenAI и не ломал существующие обработчики при миграции. Полный список сообщений с переводом — в документации.
Получить ключ и сделать первый запрос за 3 минуты → go2llm.tech. При регистрации на баланс начисляется $1 — этого хватает на первый запрос без пополнения.