Отклонённый запрос приходит в общем конверте ошибки, и ветвиться в нём нужно по code — не по HTTP-статусу и никогда не по тексту сообщения. Неудачная генерация — отдельный случай: запрос при этом успешен, и причина лежит в другом поле и в другом словаре.
Три словаря, которые легко спутать
| Поле | Когда приходит | Что называет |
|---|---|---|
| error.code | Запрос отклонён, статус 4xx или 5xx | Почему отклонён весь запрос. Словарь — таблица ниже. |
| error.details[].reason | Рядом с error.code, когда отказ разбирается по полям | Чем именно плохо названное в field значение. Словарь — таблица причин ниже. |
| error.reason | Внутри записи генерации — в любом успешном ответе, который её несёт: 200 у чтений и 202 у повтора отправки | Почему генерация со статусом FAILED не удалась: provider_error, moderation_block, timeout, no_content, invalid_input, unknown. |
Один запрос несёт либо первый словарь, либо третий, никогда оба: успешный статус означает, что API ответил, даже если сама генерация провалилась. Поэтому клиент, который смотрит только на HTTP-статус, неудачных генераций не увидит вовсе.
Ветвиться на 200 здесь мало: повтор POST /v1/generations с тем же Idempotency-Key возвращает ту же самую запись и отвечает 202 независимо от её статуса — в том числе уже FAILED, вместе с её error.reason. Читайте это поле в любом ответе, который несёт запись генерации.
Форма ответа об ошибке
HTTP/1.1 400 Bad Request
{
"error": {
"code": "invalid_limit",
"message": "The requested amount is out of range.",
"user_message": "Запрошено больше допустимого.",
"request_id": "8f3c1d2e-5a47-4b90-9a1e-2f6b0c7d4e11",
"details": [
{
"field": "limit",
"reason": "out_of_range",
"message": "Too big: expected number to be <=100"
}
]
}
}code— стабильный машинный токен. Это единственное поле, по которому клиенту стоит принимать решения.message— всегда английский и предназначен для логов, а не для показа пользователю.user_message— та же ошибка словами для человека. По умолчанию английская; русская приходит только на запрос с заголовкомAccept-Language: ru, а если перевода для кода нет, поле равноmessage.request_id— то, что нужно назвать в поддержке: по нему находится строка лога. Исключение — отказ по лимиту запросов без опознанного вызывающего: такие не пишутся в журнал доступа, и строки по этому идентификатору не будет.details— появляется там, где у отказа есть причины по полям:field— путь до поля через точку,reason— что с этим полем не так, из словаря причин ниже, аmessage— та же причина текстом, для логов.
Ни стека, ни сообщения фреймворка в ответе не бывает. Если вы прислали X-Request-Id, request_id повторит его значение.
Чужой объект отвечает 404
Объект, принадлежащий другому аккаунту, отвечает 404, а не 403: строка, которую вызывающему нельзя видеть, читается как никогда не существовавшая. Поэтому 404 здесь не означает «нет прав» и не лечится повторами: проверяйте идентификатор, а не ключ.
Сообщить о проблеме
Когда генерация вернулась негодной, а error.reason ничего не объясняет, об этом сообщают: POST /v1/generations/{id}/report заводит обращение в поддержку и отвечает 201 с support_request_id и message_id — их и нужно называть в переписке. Маршруту нужна область generations:write.
Денег этот вызов не двигает. Возврат за такую генерацию — решение человека после разбора, и автоматической полосы здесь нет намеренно: баланс после отправки отчёта не меняется, и поля с кредитами в ответе нет.
text— обязателен, до 2000 символов.reason—failureпо умолчанию;moderationвдобавок отзывает саму генерацию — кроме той, что уже удерживается на разборе: её отзывает оператор, а обращение всё равно принимается.client_msg_id— UUID, по желанию: повтор после потерянного ответа вернёт то же обращение, а не заведёт второе. Тот же идентификатор с другим текстом —409с кодомreport_replay_mismatch.
Новое обращение с reason: failure по генерации, за которой не осталось неурегулированного — кредиты вернулись автоматически либо не списывались вовсе, — отвечает generation_already_settled: просить по ней нечего. Это не касается moderation — такое обращение принимается и по рассчитанной генерации — и повтора по тому же client_msg_id, который возвращает прежнее обращение. Чужой идентификатор, как и везде, отвечает 404.
Таблица кодов
Прочерк в последней колонке значит, что отдельного совета у кода нет.
| Код | Статус | message | Что делать |
|---|---|---|---|
| api_disabled | 503 | The public API is currently disabled. | Публичный API отключён целиком — отвечает любой путь /v1, ещё до чтения ключа. Ключ и запрос здесь ни при чём, и повтор ничего не меняет. |
| unauthorized | 401 | Provide an API key or an OAuth access token via Authorization: Bearer, or a session cookie. | Добавьте заголовок Authorization с API-ключом или OAuth-токеном; проверьте, что он не потерялся у прокси. |
| invalid_api_key | 401 | This API key is not valid. | Ключ не разобран или не существует — выпустите новый. |
| api_key_expired | 401 | This API key has expired. | Срок ключа истёк — выпустите новый. |
| api_key_revoked | 401 | This API key has been revoked. | Ключ отозван — выпустите новый и замените его везде. |
| invalid_token | 401 | This access token is not valid, has expired, or its grant was revoked. | OAuth-токен неизвестен, истёк, выпущен для другого ресурса или его разрешение отозвано. Обновите его по refresh-токену; отказал и refresh — проведите пользователя через авторизацию заново. |
| api_access_not_granted | 403 | This account does not have public API access. | Аккаунту не выдан доступ к публичному API. Это не проблема ключа и не лечится повтором — обратитесь в поддержку. |
| account_suspended | 403 | This account is suspended. | Обратитесь в поддержку — вызовы не пройдут ни по одному ключу. |
| access_denied | 403 | This request is not authorized for the current session. | Приходит только на экране согласия OAuth: сессия браузера принадлежит не тому аккаунту, для которого начата авторизация. Начните авторизацию заново. |
| legal_consent_required | 403 | This account has not accepted the current terms. Sign in at coremind.ru and accept them; the same credentials work again afterwards. | — |
| origin_mismatch | 403 | This request did not come from an allowlisted origin. | Отказ бывает только на канале сессионной cookie и только на изменяющем запросе: страница прислала его с Origin, которого нет в списке разрешённых. Вызову по API-ключу этот код не приходит вовсе. Отправляйте такой запрос со страницы самого сервиса — или, если это ваша интеграция, ключом со своего сервера, а не из браузера. |
| insufficient_scope | 403 | This credential is missing a scope required by the endpoint. | Ключу или OAuth-токену не хватает области, которую требует маршрут, — добавьте её в настройках ключа в профиле или запросите новой авторизацией. |
| payment_required | 402 | This account has no active paid plan, so its API credentials may neither start generations — free quotas and promo modes included — nor cancel them. Buy or renew a plan on coremind.ru; nothing else unlocks it. | У аккаунта нет активного тарифа, поэтому через API закрыты и запуск генераций — платный, по бесплатным квотам и промо, — и их отмена. Прошлые оплаты не считаются, ключ здесь ни при чём — оформите или продлите тариф в интерфейсе. Точный повтор уже принятого запроса с тем же Idempotency-Key по-прежнему возвращает сохранённый ответ. |
| insufficient_credits | 402 | The account balance does not cover this request. | Пополните баланс. Сколько стоит запрос, заранее скажет POST /v1/pricing/estimate. |
| insufficient_text_credits | 402 | The chat balance does not cover this message. | — |
| context_limit_exceeded | 400 | The conversation is longer than the chosen model accepts. | — |
| duplicate_submission | 409 | This request was already submitted. Send it again with the confirm token to repeat it. | — |
| duplicate_key_conflict | 409 | This Idempotency-Key was already used with a different request body. | Этот Idempotency-Key уже закреплён за другим телом запроса. Повтор не поможет — отправляйте новый ключ. |
| moderation_blocked | 403 | The request was blocked by content moderation. | Запрос отклонён модерацией. Повтор того же текста или той же картинки даст тот же ответ. |
| submit_unavailable | 503 | This generation type is not submitted through this API. | Этот тип генерации API не принимает. Запрос корректен, ничего не списано, и повтор даст тот же ответ. Модели такого типа не показываются в GET /v1/models, поэтому модель, выбранная оттуда, этот ответ не получит. Оценка цены для такого типа отвечает тем же кодом. |
| upload_unavailable | 503 | This upload kind is not signed through this API. | Такой kind не подписывается: загрузка принимает image. |
| estimate_unavailable | 422 | This request cannot be priced before submitting: a promo_mode submit is charged by entitlement, not by a rate. | Запрос с promo_mode оплачивается правом на промо-режим, а не тарифом, поэтому цену заранее не узнать. Без promo_mode тот же запрос оценивается. |
| key_spend_limit_reached | 402 | This API key has reached its monthly spend limit. | — |
| grant_spend_limit_reached | 402 | This authorized application has reached its monthly spend limit. | — |
| generation_frozen | 409 | This generation is held for review and cannot be settled yet. | Генерация придержана для разбора — ни отменить, ни удалить её нельзя. |
| generation_in_flight | 409 | This generation is already running and can no longer be cancelled. | Генерацию уже забрали в работу: отменять поздно, дождитесь результата. |
| generation_already_settled | 422 | Nothing is owed for this generation: its credits were refunded automatically or were never charged. There is nothing to report. | По этой генерации не осталось ничего неурегулированного: кредиты уже вернулись автоматически либо не списывались вовсе — например на бесплатной квоте или в промо-режиме. Просить нечего. |
| report_replay_mismatch | 409 | This client_msg_id was already used for a different report body. | Этот client_msg_id уже закреплён за другим обращением: сравнивается и text, и reason. Повтор с тем же идентификатором должен нести то же тело; для другого обращения возьмите новый UUID. |
| support_replay_mismatch | 409 | This client_msg_id was already used for a different message. | Этот client_msg_id уже закреплён за другим сообщением в переписке с поддержкой: сравниваются text и число картинок. Повтор с тем же идентификатором должен нести то же тело; для нового сообщения возьмите новый UUID. |
| folder_limit_reached | 409 | This account already holds the maximum number of library folders. Delete one before creating another. | Папок уже столько, сколько разрешено на аккаунт. Удалите ненужную папку — её содержимое уйдёт в корень — и повторите. |
| folder_name_taken | 409 | A library folder with this name already exists. | Папка с таким названием уже есть. Выберите другое название или работайте с существующей папкой. |
| not_found | 404 | No such endpoint or resource. | Проверьте путь и идентификатор. Чужой объект отвечает тем же кодом, что и несуществующий. |
| unreadable_record | 422 | A stored record for this resource could not be read back. | — |
| revision_conflict | 409 | This resource was changed by another request. Reload it before saving again. | — |
| resource_conflict | 409 | This resource could not be saved in its current state. | — |
| gone | 410 | This endpoint has been retired and is no longer available. | Маршрут выведен из эксплуатации — перейдите на его замену. |
| plan_expired | 400 | The paid period has ended; buy the plan again. | — |
| renewal_already_on | 400 | Auto-renewal is already on. | — |
| no_active_plan | 400 | This account has no active plan. | — |
| subscription_not_linked | 409 | No provider subscription is linked to this account. | — |
| card_token_missing | 409 | No saved card is left to renew with; buy the plan again. | — |
| checkout_in_progress | 409 | A plan checkout is still open; finish it first. | — |
| subscription_superseded | 409 | A newer subscription already renews this plan. | — |
| subscription_recancelled | 409 | The subscription was cancelled again; resume it once more. | — |
| subscription_resume_failed | 502 | The subscription could not be resumed. | — |
| subscription_cancel_failed | 502 | The subscription could not be cancelled at the provider. | — |
| subscription_cancel_unsaved | 500 | The subscription was cancelled at the provider but the local state was not saved. | — |
| card_change_unavailable | 409 | This subscription cannot change its card. | — |
| card_check_not_authorized | 409 | The card check did not authorize; try again. | — |
| card_change_in_progress | 409 | This card change is already being completed. | — |
| card_change_failed | 502 | The card could not be changed. | — |
| rate_limited | 429 | Too many requests. Retry after the window resets. | Читайте details[].reason: request_rate — квота HTTP-запросов, submit_rate — отправка генераций, concurrency — занятые места, queue_full — заполненная очередь. Для минутных окон соблюдайте Retry-After; при занятых местах дождитесь завершения задачи. Поля scope, limit и current уточняют ограничение. Заголовки RateLimit всегда описывают HTTP-окно. Другие лимиты маршрутов могут возвращать только общий код — тогда повторяйте с растущей паузой. |
| too_many_streams | 429 | This key already holds the maximum number of open event streams. Close one before opening another. | — |
| request_too_large | 413 | The request body is larger than this API accepts. | — |
| internal_error | 500 | Unexpected server error. | Повторите с выдержкой. Если повторяется — напишите в поддержку и приложите request_id. |
| service_unavailable | 503 | The service is temporarily unavailable. Retry shortly. | — |
| request_aborted | 499 | The client closed the request before it was answered. | — |
| invalid_request | 400 | The request parameters are not valid. | Исправьте запрос по details: там названы поле и причина. |
| invalid_limit | 400 | The requested amount is out of range. | Приведите limit к допустимым границам — целое от 1 до 100 — или сократите список идентификаторов в теле запроса до 50. |
| invalid_cursor | 400 | The cursor parameter is not a valid cursor. | Не правьте и не собирайте курсор сами. Передавайте next_cursor без изменений или начните с первой страницы. |
Причины в details
Это полный словарь error.details[].reason: он собирается из кода, который эти причины и отдаёт, поэтому новая причина появляется в таблице вместе с собой. Ветвиться по нему можно — токены стабильны, — но решение об ошибке в целом всё равно принимается по error.code.
| reason | Что означает |
|---|---|
| wrong_type | У поля не тот тип, который описан в схеме. |
| not_allowed | Значение вне набора допустимых. |
| bad_format | Значение не того формата — идентификатор, URL, дата. |
| out_of_range | Число, длина или количество вне допустимых границ. |
| unknown_field | Такого поля в запросе быть не должно. |
| duplicate | Значение повторяется там, где повторы запрещены. |
| invalid | Общий случай: значение не подошло, а отдельного имени у причины нет. |
| required | Обязательное поле или заголовок не передан. |
| in_use | Значение уже закреплено за другим запросом — например Idempotency-Key. |
| exceeded | — |
| request_rate | — |
| submit_rate | — |
| concurrency | — |
| queue_full | — |
| not_available | Само значение допустимо, но этот путь его не принимает. |
| unknown_model | Этой модели нет в GET /v1/models для вашего ключа: такой модели нет, этот путь не отправляет её тип, она выключена или доступна только в раннем доступе. |
| unsupported_model | Модель есть, но она не подаётся через этот тип запроса. |
| unsupported_resolution | Модель не публикует такое разрешение. |
| unsupported_duration | Модель не публикует такую длительность — в том числе в паре с выбранным разрешением. |
| unsupported_aspect | Модель не публикует такое соотношение сторон. |
| unsupported_quality | У модели нет оси качества или такого значения на ней. |
| unsupported_scale | Модель не публикует такой коэффициент увеличения. |
| unsupported_n_variants | Модель рендерит меньше выходов, чем запрошено. |
| transparency_not_supported | Модель не умеет прозрачный фон. |
| prompt_too_long | Промпт длиннее предела, который публикует модель. |
| image_required | Модели нужны референсы, а их меньше минимума. |
| too_many_images | Референсов больше, чем модель принимает. |
| refused | — |
| extend_not_found | — |
| extend_unavailable | — |
| draft_missing | — |
| draft_not_found | — |
| draft_wrong_model | — |
| draft_not_ready | — |
| draft_cache_missing | — |
| draft_expired | — |
| draft_facts_missing | — |
| untrusted_reference | — |
| invalid_graph | — |
| insufficient_credits | — |
| foreign_reference | — |
| reference_unavailable | — |
| unfetchable_reference | — |
Сколько запросов осталось в окне, как долго ждать после rate_limited и какие границы у limit — на странице лимитов. Статусы каждой отдельной операции перечислены в справочнике.