Ошибки и отладка
У каждой ошибки — тип, сообщение и ID запроса. Неверный запрос, ключ или баланс приходят HTTP-ошибкой. Если модель уже генерирует — статус 200, а сбой приходит событием SSE.
Форма ответа
JSON одной формы на всех методах API, включая публичные (/public/aitunnel/…):
type ErrorResponse = {
error: {
code: number; // HTTP-статус ответа
type: string; // тип ошибки из таблицы ниже
message: string; // что случилось
metadata?: Record<string, unknown>; // детали, если они есть
request_id: string; // ID запроса, тот же в заголовке X-Request-Id
};
};{
"error": {
"code": 402,
"type": "insufficient_balance",
"message": "Недостаточно средств! Доступно 0 руб., а запросу нужно минимум 0.01 руб. Пополните баланс: https://aitunnel.ru/panel/purchase",
"metadata": {
"available_rub": 0,
"required_rub": 0.01
},
"request_id": "chat-0J9fQk2vXc8LrT1mNbZa4e"
}
}error.code— число, совпадает с HTTP-статусом ответа.error.type— тип ошибки. Список закрытый, он в таблице ниже: по нему удобно ветвить код, аmessage— показывать человеку.error.request_id— ID запроса. Он же приходит в заголовкеX-Request-Idкаждого ответа, успешного тоже. Назовите его поддержке — по нему мы находим запрос.
Запрос отклонён до генерации (неверные параметры, ключ, баланс) — ответ с HTTP-статусом ошибки. Если модель уже начала отвечать в потоке, HTTP будет 200, а ошибка придёт событием SSE. Подробнее — стриминг.
const response = await fetch("https://api.aitunnel.ru/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer sk-aitunnel-xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-5.6-sol",
messages: [{ role: "user", content: "Привет" }],
}),
});
if (!response.ok) {
const { error } = await response.json();
console.error(response.status, error.type, error.message);
console.error("ID запроса:", error.request_id); // он же в response.headers.get("x-request-id")
}Типы ошибок
error.type | HTTP | Когда | Повторять? |
|---|---|---|---|
invalid_request | 400 | Запрос составлен неверно: битый JSON, нет обязательного поля, недопустимое значение параметра. Со статусом 413 — тело запроса больше лимита, со статусом 409 — действие сейчас невозможно (например, DELETE пакета, который ещё выполняется) | Нет, исправьте запрос |
authentication_failed | 401 | Ключ не передан ни в Authorization: Bearer sk-aitunnel-…, ни в x-api-key; в Authorization нет префикса Bearer; ключ недействителен, удалён или у него истёк срок | Нет, проверьте ключ |
permission_denied | 403 | Ключу это запрещено: IP не в белом списке, модель не в списке разрешённых, ключ на паузе или аккаунт запланирован к удалению | Нет, измените настройки ключа |
model_not_found | 404 | Такой модели нет в каталоге. Когда модель названа в теле запроса (чат, эмбеддинги, картинки и другие методы генерации), статус — 400; 404 отвечает GET /v1/models/{id} | Нет, возьмите имя из каталога |
insufficient_balance | 402 | Не хватает средств на балансе. В metadata — available_rub и required_rub | После пополнения баланса |
budget_exceeded | 402 | Исчерпан бюджет API-ключа. В metadata — available_rub и required_rub | После изменения бюджета ключа |
sensitive_data_blocked | 400 | Ключ в режиме блокировки нашёл в запросе чувствительные данные. В metadata.detected — найденные типы | Нет, уберите данные из запроса |
not_found | 404 | Нет такого пути, запроса, задачи или сохранённого лога — либо они принадлежат другому ключу или аккаунту | Нет |
rate_limited | 429 | Лимит провайдера, который не прошёл и после наших повторов, или слишком много запросов от вас: одновременных от аккаунта, слишком частых с одного ключа, неудачных попыток авторизации с одного IP, оборванных запросов без стрима. Обычно с заголовком Retry-After | Да, после паузы из Retry-After |
provider_error | 502 | Провайдер модели ответил ошибкой: неверный параметр, модерация, слишком длинный промпт. Статус — тот, что вернул провайдер (400, 403, 404, 408, 422 …); 502, если он не назвал свой | Для 4xx — нет, исправьте запрос |
provider_unavailable | 503 | Ни один провайдер не смог взять запрос после повторов и запасных моделей: 503 — перегружен или недоступен, 502 — ответил ошибкой сервера или соединение оборвалось, 504 — не ответил вовремя | Да; помогают запасные модели |
upstream_invalid_response | 502 | Провайдер ответил успешно, но ответ нельзя использовать: не JSON, пустой поток, нет результата | Да |
service_unavailable | 503 | Временный сбой на стороне AITUNNEL. Обычно с заголовком Retry-After | Да, через несколько секунд |
internal | 500 | Внутренняя ошибка сервиса. Мы получаем о ней уведомление сами | Да; если повторяется — напишите в поддержку с request_id |
HTTP-статус в таблице — обычный для типа. Настоящий всегда в error.code и в статусе ответа: у provider_error это статус провайдера, у provider_unavailable — 502, 503 или 504.
Messages API
POST /v1/messages и POST /v1/messages/count_tokens отвечают в формате Anthropic, чтобы его понимали SDK Anthropic и Claude Code: type: "error" на верхнем уровне, request_id рядом с error, а error.type — из словаря Anthropic. Наш тип лежит в error.aitunnel_type.
{
"type": "error",
"error": {
"type": "billing_error",
"message": "Недостаточно средств! Доступно 0 руб., а запросу нужно минимум 0.01 руб. Пополните баланс: https://aitunnel.ru/panel/purchase",
"code": 402,
"aitunnel_type": "insufficient_balance",
"metadata": {
"available_rub": 0,
"required_rub": 0.01
}
},
"request_id": "msg-0J9fQk2vXc8LrT1mNbZa4e"
}error.aitunnel_type | error.type |
|---|---|
invalid_request | invalid_request_error |
authentication_failed | authentication_error |
permission_denied | permission_error |
model_not_found | not_found_error |
insufficient_balance | billing_error |
budget_exceeded | billing_error |
sensitive_data_blocked | invalid_request_error |
not_found | not_found_error |
rate_limited | rate_limit_error |
provider_error | api_error |
provider_unavailable | overloaded_error |
upstream_invalid_response | api_error |
service_unavailable | overloaded_error |
internal | api_error |
402: баланс и бюджет ключа
Два разных типа с одним статусом:
insufficient_balance— не хватает баланса аккаунта. Пополните его.budget_exceeded— исчерпан бюджет ключа. Баланс при этом может быть любым: увеличьте бюджет или дождитесь его сброса.
В обоих случаях в error.metadata — сколько доступно и сколько нужно запросу, в рублях:
type BalanceErrorMetadata = {
available_rub: number; // сколько доступно: баланс или остаток бюджета ключа
required_rub: number; // сколько нужно этому запросу
};Когда приходит 402:
- Баланс 0 ₽ и ниже (с учётом овердрафта, если он подключён) — любой запрос, который запускает работу (
POST), включая бесплатные модели и/v1/moderations, при любых параметрах. То же с ключом, у которого бюджет исчерпан. Проверка баланса,GET /v1/modelsи получение уже оплаченных результатов (GETзадач видео и batch) работают и при нулевом балансе. - Чат, Responses, Messages — если денег не хватает на вход запроса и короткий ответ. Большой или незаданный
max_tokensсам по себе 402 не вызывает: вывод ограничивается суммой, которая есть, а ответ приходит с заголовкомX-AITunnel-Output-Capped. - Эмбеддинги, rerank, решения, картинки, озвучка, распознавание — если не хватает на оценку стоимости этого запроса.
- Видео и batch — если не хватает на резерв задачи.
Часть баланса на время занята запросами, которые выполняются прямо сейчас, — тогда в сообщении будет «с учётом выполняющихся запросов». Как это устроено — лимит расходов.
413: размер запроса
- JSON-тело — до 50 МБ.
- Загрузка файлов (
multipart/form-data:/audio/transcriptions,/images/edits) — до 100 МБ на весь запрос.
Больше — 413 с типом invalid_request. У отдельных файлов свои, меньшие пределы — см. лимиты.
429: слишком много запросов
Тип rate_limited. Чаще всего это лимит провайдера модели: мы сами повторяем такие запросы и отдаём 429, только если повторы не помогли. Задайте запасные модели.
429 бывает и от AITUNNEL — когда запросов слишком много одновременно или ключ ведёт себя как при сбое. Все случаи — лимиты. Если в ответе есть заголовок Retry-After, повторите запрос не раньше, чем через столько секунд.
Ошибки модерации
Если провайдер пометил ввод, приходит provider_error со статусом 403, а в error.metadata — причина:
type ModerationErrorMetadata = {
reasons: string[]; // почему пометили ввод
flagged_input: string; // фрагмент до 100 символов; длиннее — обрезан посередине через …
provider_name: string; // кто запросил модерацию
model_slug: string;
};Сообщение error.message уже переписано по-русски: какой провайдер отклонил и почему.
Ошибки провайдера
У provider_error и provider_unavailable в error.metadata может быть:
type ProviderErrorMetadata = {
provider_name: string; // провайдер, у которого случилась ошибка
raw?: unknown; // исходный текст ошибки, без внутренних ссылок
};Внутренние ссылки и брендинг из текста ошибки вычищаются. Не полагайтесь на точный вид raw — ориентируйтесь на type, code и message.
504 — провайдер не ответил за отведённое время (для /images/generations и /images/edits — 9 минут). Средства не списываются. Повторите запрос или выберите другую модель.
Когда ответа нет
Иногда модель не генерирует текст: холодный старт или масштабирование у провайдера. Обычно это секунды, иногда минуты.
Если пустые ответы повторяются — простой retry или другая модель / запасные модели.
Ошибки в стриме
До первого токена — обычный JSON и HTTP 4xx/5xx. После начала генерации статус уже 200: ошибка приходит событием SSE с тем же объектом error (code, type, message, request_id). У каждого API своя форма события — Chat Completions, Responses и Messages разобраны на странице стриминг.
Соединение не устанавливается
Таймаут, Connection reset или Failed to fetch без тела ответа и без HTTP-статуса — это не ошибка API: запрос до AITUNNEL не дошёл. У части российских провайдеров блокируется сеть, через которую работает api.aitunnel.ru. Поменяйте base URL на https://ru-api.aitunnel.ru/v1 — ключи и пути те же. Подробнее — зеркало API.
Что проверить
error.typeиerror.message— там сказано, что не так.- 402 — смотрите тип:
insufficient_balance(баланс) илиbudget_exceeded(бюджет ключа), иmetadata.required_rub. - 429 / 502 / 503 — запасные модели и повтор после
Retry-After. - Неизвестная модель — в сообщении часто есть похожее имя из каталога.
- В стриме смотрите
errorв событиях, не только HTTP-статус. - Нет ни статуса, ни JSON — проверьте зеркало
https://ru-api.aitunnel.ru/v1. - Пишете в поддержку — приложите
request_id(или заголовокX-Request-Id).