Если что-то пошло не так

Поиск ошибки

#Вставьте ошибку

Разбирайте ошибки в одном порядке: HTTP-статус, затем error.type, затем error.code. Статус всегда информативен; type показывает, кто отклонил вызов; code стабилен только для ошибок самого шлюза.

#Как обрабатывать

  1. 1

    Сначала статус

    402 — пополните баланс, 403 — проверьте ограничения ключа, 429 — подождите, 5xx — повторите. Этот уровень всегда точен и никогда не меняется.

  2. 2

    Затем error.type

    xiaoxiang_error — наша ошибка. Всё остальное — openai_error, claude_error, gemini_error — тело ошибки самого провайдера, переданное без изменений.

  3. 3

    Потом error.code

    Ориентируйтесь на code только для xiaoxiang_error. Коды провайдеров принадлежат им и могут меняться без предупреждения.

Не у каждой ошибки есть JSON-тело

Отказы из-за ограничения частоты и слишком больших тел приходят с одной строкой статуса. Парсер, который рассчитывает, что response.json() всегда сработает, выбросит исключение вместо обработки ошибки.
Форматы ошибок

До клиента доходят ошибки пяти форматов. Обрабатывайте все пять — последние три в интеграциях обычно упускают.

Ошибка шлюза

Вызов отклонил сам шлюз: авторизация, квота, маршрутизация, формат запроса.

http
{
  "error": {
    "message": "insufficient account balance, please top up to continue (request id: 20260910...)",
    "type": "xiaoxiang_error",
    "code": "insufficient_user_quota"
  }
}

Ошибка вышестоящего провайдера, передаётся как есть

Провайдер вернул собственную ошибку. Тело передаётся без изменений, поэтому type и code — от провайдера.

http
{
  "error": {
    "message": "This model's maximum context length is 128000 tokens.",
    "type": "invalid_request_error",
    "code": "context_length_exceeded"
  }
}

Отказ предварительной проверки

Проверка идемпотентности и ещё несколько проверок перед проксированием отвечают раньше, чем формируется обёртка ответа. Плоская структура, код в верхнем регистре.

http
{
  "code": "IDEMPOTENCY_IN_PROGRESS",
  "error": "a request with this Idempotency-Key is still processing"
}

Отказ асинхронной задачи

Каждый эндпоинт задач — видео, изображения, музыка, 3D — сообщает об ошибках в собственном плоском формате. Вложенного объекта error здесь нет, поэтому парсер, написанный под /v1/chat/completions, прочитает null.

http
{
  "code": "task_not_exist",
  "message": "task does not exist",
  "data": null
}

Только статус, пустое тело

Отказы из-за ограничения частоты запросов и слишком большие тела запросов завершаются одним кодом статуса.

http
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Исходный ответ нельзя безопасно воспроизвести (например, поток или слишком большое тело).Прекратите повторы с этим ключом. Вместо этого сверьтесь с эндпоинтом задачи или своими записями.
log-the-request-id.ts
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-ключи.нет
401signature_requiredДля ключа включена подпись запросов, а в запросе нет X-Timestamp, X-Nonce или X-Signature.Подписывайте каждый запрос с этим ключом (Аутентификация и ключи → Подпись запросов) или отключите подпись для ключа.нет
401signature_expiredX-Timestamp не является 10-значным временем в Unix-секундах или отличается от времени сервера больше чем на 5 минут. В ответе передаётся X-Server-Timestamp.Скорректируйте часы клиента на разницу с X-Server-Timestamp и подпишите запрос заново с новым nonce.да
401signature_invalidX-Signature не соответствует секрету подписи ключа, или X-Nonce некорректен.Подписывайте timestamp, nonce, метод и путь (без query-строки), соединённые переводами строки, текущим секретом из раздела Консоль → API-ключи.нет
401signature_replayedЭтот X-Nonce уже использовался с этим ключом за последние 10 минут.Генерируйте новый случайный nonce для каждого запроса, включая повторные.да
403access_deniedIP вызывающей стороны не входит в белый список IP ключа.Добавьте исходящий адрес в разделе Консоль → API-ключи или отключите белый список для ключей без фиксированного IP.нет
403нетАккаунт приостановлен, либо ключу запрещено использовать эту модель или канал.Сначала проверьте белый список моделей ключа; если ограничений нет, создайте обращение в поддержку — заблокирован сам аккаунт.нет
400unsupported_endpointМодель существует, но не обслуживает этот путь. В сообщении перечислены эндпоинты, которые она поддерживает.Перенесите вызов на один из указанных эндпоинтов — модели для задач генерации изображений не отвечают на /v1/chat/completions.нет
400read_request_body_failedНе удалось прочитать тело запроса: загрузка оборвалась, указан неверный Content-Type или поток завершился раньше времени.Отправьте полное тело с подходящим Content-Type. Повторная отправка того же повреждённого запроса завершится той же ошибкой.нет
402insufficient_user_quotaБаланса кошелька не хватает на сумму, резервируемую под этот вызов.Пополните баланс в разделе Консоль → Кошелёк и повторите запрос.нет
429insufficient_user_quotaДостигнут дневной лимит расходов аккаунта. Статус тот же, что при ограничении частоты запросов, но смысл обратный — задержка перед повтором не поможет.Повысьте или снимите лимит в настройках аккаунта либо продолжите завтра. Ориентируйтесь на code, а не только на 429.нет
429нетОграничение RPM/RPD для ключа или лимит параллельных запросов. Тело пустое; время ожидания передаётся в Retry-After.Подождите Retry-After секунд, затем повторяйте с экспоненциальной задержкой и случайным разбросом (jitter).да
413read_request_body_failedТело запроса превышает допустимый размер. Лимит транспортного уровня обрывает запрос с пустым телом ответа.Уменьшите размер запроса: загружайте файлы через /v1/media/upload-policy и передавайте URL вместо Base64.нет
422invalid_requestНи один канал не поддерживает этот режим генерации для модели — например, видео по изображению на маршруте, который умеет только видео по тексту.Посмотрите поддерживаемые режимы в карточке модели и смените модель или режим.нет
503model_not_foundНеизвестное имя модели, или сейчас её не обслуживает ни один исправный канал. Обратите внимание на статус: для неизвестной модели это 503, а не 404.Сверьте имя со списком /v1/models и повторите запрос — при следующей попытке переключение обычно находит исправный канал.да
500sensitive_words_detectedПромпт заблокирован фильтром контента ещё на шлюзе.Измените промпт. Повтор с тем же текстом всегда завершится ошибкой.нет
500count_token_failedШлюзу не удалось подсчитать токены для расчёта стоимости — обычно из-за некорректного массива сообщений.Проверьте массив messages; если он корректен, повторите запрос и создайте обращение в поддержку с ID запроса.да
500invalid_requestЗапрос не удалось привести к формату, который принимает этот эндпоинт. Обратите внимание на статус: тело, которое шлюз не смог прочитать, возвращается как 500, а не 400.Перед повтором проверьте JSON по справочнику эндпоинта.нет
502do_request_failedПровайдер недоступен или вернул ошибку, которую шлюз не смог обойти переключением на другой канал.Повторите с увеличивающейся задержкой — следующая попытка обычно попадает на другой канал.да
504do_request_failedПровайдер не ответил за отведённое шлюзом время.Повторите запрос с увеличивающейся задержкой: длинные промпты и перегруженные провайдеры чаще упираются в тайм-аут.да
499do_request_failedВаш клиент закрыл соединение до получения ответа — обычно это срабатывает ваш собственный HTTP-тайм-аут посреди потока.Увеличьте тайм-аут чтения на клиенте. Долгая генерация занимает минуты — больше, чем по умолчанию отводит большинство HTTP-библиотек.нет
Ошибки проверки идемпотентности

Они возникают в защите от повторов перед проксированием, поэтому используют плоский формат {code, error} с кодом в верхнем регистре вместо формата OpenAI.

HTTPКодЗначениеЧто делатьПовторять?
400IDEMPOTENCY_STREAM_UNSUPPORTEDIdempotency-Key передан в потоковом запросе.Уберите заголовок из потоковых вызовов или задайте stream:false, если нужна защита от повторов.нет
400IDEMPOTENCY_BODY_UNREADABLEНе удалось вычислить хеш тела для журнала идемпотентности.Отправляйте обычное тело JSON или multipart — chunked-тела, которые нельзя буферизовать, отклоняются.нет
409IDEMPOTENCY_PAYLOAD_CONFLICTЭтот ключ уже использовался с другим телом, путём или query-параметрами.Сгенерируйте новый ключ. Ключ обозначает один конкретный запрос, а не цикл повторов.нет
409IDEMPOTENCY_IN_PROGRESSПервый запрос с этим ключом ещё выполняется.Подождите Retry-After (2 с) и повторите запрос с тем же ключом — вы получите исходный ответ.да
409IDEMPOTENCY_RESULT_UNREPLAYABLEИсходный запрос завершён, но его ответ закрыт и не может быть воспроизведён.Не отправляйте запрос повторно вслепую. Сначала сверьтесь с эндпоинтом задачи или своими записями.нет
503IDEMPOTENCY_UNAVAILABLEЖурнал идемпотентности недоступен, поэтому проверка отклоняет запрос, чтобы не рисковать двойным списанием.Повторите с увеличивающейся задержкой — запрос не выполнялся.да

Как работают ключи идемпотентности →

Ошибки асинхронных задач

Эндпоинты видео, задач изображений, музыки и 3D отвечают в собственном плоском формате — {code, message, data} — и при отправке, и при опросе, и при отмене. Учтите одну особенность: опрос неизвестной задачи возвращает 400, а её отмена — 404.

HTTPКодЗначениеЧто делатьПовторять?
400task_not_existОпрос или ремикс задачи с несуществующим ID или ID другого аккаунта. Обратите внимание на статус: опрос неизвестной задачи возвращает 400, а отмена — 404.Сохраняйте task_id при отправке и опрашивайте именно по этому значению.нет
400invalid_requestОтсутствует обязательное поле — prompt при ремиксе, task_id при получении результата.Перед повтором исправьте тело или путь.нет
400task_channel_disableПоследующая задача (ремикс, продление) привязана к каналу исходной задачи, а этот канал сейчас отключён.Отправьте новую задачу вместо продолжения старой.нет
400invalid_source_taskИсходная задача не подходит для этой операции — например, ID видео передан в продолжение 3D-задачи.Убедитесь, что исходный ID относится к тому же семейству возможностей.нет
404task_not_existОтмена задачи с несуществующим или чужим ID.Отменять нечего — считайте, что задачи уже нет.нет
409task_not_cancellableЗадача уже в конечном состоянии, отменять нечего.Опросите задачу ещё раз и используйте результат, за который вы уже заплатили.нет
501task_cancel_not_supportedПровайдер этой задачи не предоставляет API отмены. У большинства его нет — отмена скорее исключение, чем правило.Дайте задаче завершиться. Проектируйте интерфейс так, чтобы отмена была попыткой, а не гарантией.нет
502cancel_task_failedПровайдер был недоступен во время отмены. Задача не изменилась.Повторите один раз, а если не выйдет — дайте задаче завершиться.да

Жизненный цикл асинхронной задачи →

Проблема не решена?

Пришлите нам ID запроса или текст ошибки, и мы разберёмся.