Если что-то пошло не так
Поиск ошибки
#Вставьте ошибку
Разбирайте ошибки в одном порядке: HTTP-статус, затем error.type, затем error.code. Статус всегда информативен; type показывает, кто отклонил вызов; code стабилен только для ошибок самого шлюза.
#Как обрабатывать
- 1
Сначала статус
402 — пополните баланс, 403 — проверьте ограничения ключа, 429 — подождите, 5xx — повторите. Этот уровень всегда точен и никогда не меняется.
- 2
Затем error.type
xiaoxiang_error — наша ошибка. Всё остальное — openai_error, claude_error, gemini_error — тело ошибки самого провайдера, переданное без изменений.
- 3
Потом error.code
Ориентируйтесь на code только для xiaoxiang_error. Коды провайдеров принадлежат им и могут меняться без предупреждения.
Не у каждой ошибки есть JSON-тело
Форматы ошибок
До клиента доходят ошибки пяти форматов. Обрабатывайте все пять — последние три в интеграциях обычно упускают.
Ошибка шлюза
Вызов отклонил сам шлюз: авторизация, квота, маршрутизация, формат запроса.
{
"error": {
"message": "insufficient account balance, please top up to continue (request id: 20260910...)",
"type": "xiaoxiang_error",
"code": "insufficient_user_quota"
}
}Ошибка вышестоящего провайдера, передаётся как есть
Провайдер вернул собственную ошибку. Тело передаётся без изменений, поэтому type и code — от провайдера.
{
"error": {
"message": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded"
}
}Отказ предварительной проверки
Проверка идемпотентности и ещё несколько проверок перед проксированием отвечают раньше, чем формируется обёртка ответа. Плоская структура, код в верхнем регистре.
{
"code": "IDEMPOTENCY_IN_PROGRESS",
"error": "a request with this Idempotency-Key is still processing"
}Отказ асинхронной задачи
Каждый эндпоинт задач — видео, изображения, музыка, 3D — сообщает об ошибках в собственном плоском формате. Вложенного объекта error здесь нет, поэтому парсер, написанный под /v1/chat/completions, прочитает null.
{
"code": "task_not_exist",
"message": "task does not exist",
"data": null
}Только статус, пустое тело
Отказы из-за ограничения частоты запросов и слишком большие тела запросов завершаются одним кодом статуса.
HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Length: 0Заголовки ответа
Логируйте ID запроса в каждом ответе, а не только при ошибках. По этому идентификатору поддержка находит вызов — с ним ответ займёт пять минут, а без него — день догадок.
| Заголовок | Когда передаётся | Что с ним делать |
|---|---|---|
| X-Oneapi-Request-Id | В каждом ответе — и успешном, и с ошибкой. | Логируйте его рядом со своим trace id. Поддержка разбирает обращения по этому значению, и тот же ID добавляется к сообщениям об ошибках шлюза. |
| Retry-After | При 429, а также при 409, пока запрос с этим Idempotency-Key ещё выполняется. | Подождите не меньше указанного числа секунд. Это единственная подсказка о задержке, которую отправляет шлюз. |
| X-Idempotent-Replayed | При повторном Idempotency-Key возвращается сохранённый ответ. | true означает, что ничего не выполнялось заново и повторно не списывалось, — считайте тело ответа исходным результатом. |
| X-Idempotency-Sealed | Исходный ответ нельзя безопасно воспроизвести (например, поток или слишком большое тело). | Прекратите повторы с этим ключом. Вместо этого сверьтесь с эндпоинтом задачи или своими записями. |
const response = await fetch(url, init);
const requestId = response.headers.get("X-Oneapi-Request-Id");
if (!response.ok) {
// Bodies are absent on 429 and 413 — never assume JSON here.
const detail = await response.text();
logger.error({ status: response.status, requestId, detail });
throw new GatewayError(response.status, requestId, detail);
}#Таблица кодов ошибок
Ошибки, которые возвращает сам шлюз, всегда с type "xiaoxiang_error". У двух строк одинаковый статус 429, но противоположный смысл — прочитайте code, прежде чем делать паузу.
| HTTP | Код | Значение | Что делать | Повторять? |
|---|---|---|---|---|
| 401 | нет | Bearer-токен отсутствует, некорректен, удалён или отключён. Этот уровень не задаёт code — ориентируйтесь на статус. | Проверьте заголовок Authorization и состояние ключа в разделе Консоль → API-ключи. | нет |
| 401 | signature_required | Для ключа включена подпись запросов, а в запросе нет X-Timestamp, X-Nonce или X-Signature. | Подписывайте каждый запрос с этим ключом (Аутентификация и ключи → Подпись запросов) или отключите подпись для ключа. | нет |
| 401 | signature_expired | X-Timestamp не является 10-значным временем в Unix-секундах или отличается от времени сервера больше чем на 5 минут. В ответе передаётся X-Server-Timestamp. | Скорректируйте часы клиента на разницу с X-Server-Timestamp и подпишите запрос заново с новым nonce. | да |
| 401 | signature_invalid | X-Signature не соответствует секрету подписи ключа, или X-Nonce некорректен. | Подписывайте timestamp, nonce, метод и путь (без query-строки), соединённые переводами строки, текущим секретом из раздела Консоль → API-ключи. | нет |
| 401 | signature_replayed | Этот X-Nonce уже использовался с этим ключом за последние 10 минут. | Генерируйте новый случайный nonce для каждого запроса, включая повторные. | да |
| 403 | access_denied | IP вызывающей стороны не входит в белый список IP ключа. | Добавьте исходящий адрес в разделе Консоль → API-ключи или отключите белый список для ключей без фиксированного IP. | нет |
| 403 | нет | Аккаунт приостановлен, либо ключу запрещено использовать эту модель или канал. | Сначала проверьте белый список моделей ключа; если ограничений нет, создайте обращение в поддержку — заблокирован сам аккаунт. | нет |
| 400 | unsupported_endpoint | Модель существует, но не обслуживает этот путь. В сообщении перечислены эндпоинты, которые она поддерживает. | Перенесите вызов на один из указанных эндпоинтов — модели для задач генерации изображений не отвечают на /v1/chat/completions. | нет |
| 400 | read_request_body_failed | Не удалось прочитать тело запроса: загрузка оборвалась, указан неверный Content-Type или поток завершился раньше времени. | Отправьте полное тело с подходящим Content-Type. Повторная отправка того же повреждённого запроса завершится той же ошибкой. | нет |
| 402 | insufficient_user_quota | Баланса кошелька не хватает на сумму, резервируемую под этот вызов. | Пополните баланс в разделе Консоль → Кошелёк и повторите запрос. | нет |
| 429 | insufficient_user_quota | Достигнут дневной лимит расходов аккаунта. Статус тот же, что при ограничении частоты запросов, но смысл обратный — задержка перед повтором не поможет. | Повысьте или снимите лимит в настройках аккаунта либо продолжите завтра. Ориентируйтесь на code, а не только на 429. | нет |
| 429 | нет | Ограничение RPM/RPD для ключа или лимит параллельных запросов. Тело пустое; время ожидания передаётся в Retry-After. | Подождите Retry-After секунд, затем повторяйте с экспоненциальной задержкой и случайным разбросом (jitter). | да |
| 413 | read_request_body_failed | Тело запроса превышает допустимый размер. Лимит транспортного уровня обрывает запрос с пустым телом ответа. | Уменьшите размер запроса: загружайте файлы через /v1/media/upload-policy и передавайте URL вместо Base64. | нет |
| 422 | invalid_request | Ни один канал не поддерживает этот режим генерации для модели — например, видео по изображению на маршруте, который умеет только видео по тексту. | Посмотрите поддерживаемые режимы в карточке модели и смените модель или режим. | нет |
| 503 | model_not_found | Неизвестное имя модели, или сейчас её не обслуживает ни один исправный канал. Обратите внимание на статус: для неизвестной модели это 503, а не 404. | Сверьте имя со списком /v1/models и повторите запрос — при следующей попытке переключение обычно находит исправный канал. | да |
| 500 | sensitive_words_detected | Промпт заблокирован фильтром контента ещё на шлюзе. | Измените промпт. Повтор с тем же текстом всегда завершится ошибкой. | нет |
| 500 | count_token_failed | Шлюзу не удалось подсчитать токены для расчёта стоимости — обычно из-за некорректного массива сообщений. | Проверьте массив messages; если он корректен, повторите запрос и создайте обращение в поддержку с ID запроса. | да |
| 500 | invalid_request | Запрос не удалось привести к формату, который принимает этот эндпоинт. Обратите внимание на статус: тело, которое шлюз не смог прочитать, возвращается как 500, а не 400. | Перед повтором проверьте JSON по справочнику эндпоинта. | нет |
| 502 | do_request_failed | Провайдер недоступен или вернул ошибку, которую шлюз не смог обойти переключением на другой канал. | Повторите с увеличивающейся задержкой — следующая попытка обычно попадает на другой канал. | да |
| 504 | do_request_failed | Провайдер не ответил за отведённое шлюзом время. | Повторите запрос с увеличивающейся задержкой: длинные промпты и перегруженные провайдеры чаще упираются в тайм-аут. | да |
| 499 | do_request_failed | Ваш клиент закрыл соединение до получения ответа — обычно это срабатывает ваш собственный HTTP-тайм-аут посреди потока. | Увеличьте тайм-аут чтения на клиенте. Долгая генерация занимает минуты — больше, чем по умолчанию отводит большинство HTTP-библиотек. | нет |
Ошибки проверки идемпотентности
Они возникают в защите от повторов перед проксированием, поэтому используют плоский формат {code, error} с кодом в верхнем регистре вместо формата OpenAI.
| HTTP | Код | Значение | Что делать | Повторять? |
|---|---|---|---|---|
| 400 | IDEMPOTENCY_STREAM_UNSUPPORTED | Idempotency-Key передан в потоковом запросе. | Уберите заголовок из потоковых вызовов или задайте stream:false, если нужна защита от повторов. | нет |
| 400 | IDEMPOTENCY_BODY_UNREADABLE | Не удалось вычислить хеш тела для журнала идемпотентности. | Отправляйте обычное тело JSON или multipart — chunked-тела, которые нельзя буферизовать, отклоняются. | нет |
| 409 | IDEMPOTENCY_PAYLOAD_CONFLICT | Этот ключ уже использовался с другим телом, путём или query-параметрами. | Сгенерируйте новый ключ. Ключ обозначает один конкретный запрос, а не цикл повторов. | нет |
| 409 | IDEMPOTENCY_IN_PROGRESS | Первый запрос с этим ключом ещё выполняется. | Подождите Retry-After (2 с) и повторите запрос с тем же ключом — вы получите исходный ответ. | да |
| 409 | IDEMPOTENCY_RESULT_UNREPLAYABLE | Исходный запрос завершён, но его ответ закрыт и не может быть воспроизведён. | Не отправляйте запрос повторно вслепую. Сначала сверьтесь с эндпоинтом задачи или своими записями. | нет |
| 503 | IDEMPOTENCY_UNAVAILABLE | Журнал идемпотентности недоступен, поэтому проверка отклоняет запрос, чтобы не рисковать двойным списанием. | Повторите с увеличивающейся задержкой — запрос не выполнялся. | да |
Ошибки асинхронных задач
Эндпоинты видео, задач изображений, музыки и 3D отвечают в собственном плоском формате — {code, message, data} — и при отправке, и при опросе, и при отмене. Учтите одну особенность: опрос неизвестной задачи возвращает 400, а её отмена — 404.
| HTTP | Код | Значение | Что делать | Повторять? |
|---|---|---|---|---|
| 400 | task_not_exist | Опрос или ремикс задачи с несуществующим ID или ID другого аккаунта. Обратите внимание на статус: опрос неизвестной задачи возвращает 400, а отмена — 404. | Сохраняйте task_id при отправке и опрашивайте именно по этому значению. | нет |
| 400 | invalid_request | Отсутствует обязательное поле — prompt при ремиксе, task_id при получении результата. | Перед повтором исправьте тело или путь. | нет |
| 400 | task_channel_disable | Последующая задача (ремикс, продление) привязана к каналу исходной задачи, а этот канал сейчас отключён. | Отправьте новую задачу вместо продолжения старой. | нет |
| 400 | invalid_source_task | Исходная задача не подходит для этой операции — например, ID видео передан в продолжение 3D-задачи. | Убедитесь, что исходный ID относится к тому же семейству возможностей. | нет |
| 404 | task_not_exist | Отмена задачи с несуществующим или чужим ID. | Отменять нечего — считайте, что задачи уже нет. | нет |
| 409 | task_not_cancellable | Задача уже в конечном состоянии, отменять нечего. | Опросите задачу ещё раз и используйте результат, за который вы уже заплатили. | нет |
| 501 | task_cancel_not_supported | Провайдер этой задачи не предоставляет API отмены. У большинства его нет — отмена скорее исключение, чем правило. | Дайте задаче завершиться. Проектируйте интерфейс так, чтобы отмена была попыткой, а не гарантией. | нет |
| 502 | cancel_task_failed | Провайдер был недоступен во время отмены. Задача не изменилась. | Повторите один раз, а если не выйдет — дайте задаче завершиться. | да |
Проблема не решена?
Пришлите нам ID запроса или текст ошибки, и мы разберёмся.