Ошибки и лимиты
Коды ошибок GetVibe API: 401 (ключ), 402 (баланс), 403 (IP), 429 (лимит запросов). Как обрабатывать ошибки и повторять запросы с экспоненциальной задержкой.
Ошибки, порождаемые GetVibe на /v1/*, имеют JSON-конверт с error.message, error.type и error.code. Не полагайтесь на текст сообщения или заголовки сторонних сервисов как на контракт.
Формат ошибки
{
"error": {
"message": "insufficient balance",
"type": "insufficient_quota",
"code": 402,
"request_id": "8d4b6b3a-..."
}
}Коды ответа
| Параметр | Тип | Описание |
|---|---|---|
401 | authentication_error / 401 | Отсутствует или имеет неверный формат ключ, либо ключ отключён. Не повторяйте попытки, пока учётные данные не будут исправлены. |
402 | insufficient_quota / 402 | Нет доступного баланса или лимита — ожидание не поможет. Ответ 402 никогда не содержит Retry-After и не подлежит повтору: пополните баланс или дождитесь сброса окна подписки. |
403 | permission_error or policy type / 403 | IP, модель или политика не разрешают выполнить запрос. Не повторяйте запрос, не изменив сам запрос или настройки. |
429 | rate_limit_exceeded, inflight_client_cap or rate_limit_error / 429 | Ваш лимит скорости, ваш лимит параллелизма, кратковременно удерживаемый резерв баланса вашими же параллельными запросами (причина reserved_in_flight) или лимит поставщика. Повторяйте только после Retry-After; 429 никогда не означает, что ваш баланс исчерпан. |
410 | media_endpoint_retired / media_endpoint_retired | Только /v1/images/edits, /v1/video/generations и /v1/videos (выведен из эксплуатации). Не повторяйте запрос; переключитесь на /api/media/generate. /v1/images/generations обслуживается. |
413 | invalid_request_error / 413 | Только Files API: файл превышает 50 MiB. Уменьшите файл, затем создайте новый запрос. |
503 | overloaded / 503 | Перегрузка глобального узла. Повторите запрос после Retry-After: 2. Другие ошибки 503 могут не быть повторяемыми без изменения модели или запроса. |
Ошибки запросов (400, 413, 422): коды и способы исправления
Каждая ошибка запроса содержит стабильный error.code из таблицы ниже, error.param с именем поля, к которому она относится (null, если модель его не указала), и ссылку на соответствующую строку в конце error.message — Codex и другие CLI показывают только сообщение, поэтому ссылка размещена в нём.
| error.code | error.param | Что делать |
|---|---|---|
unsupported_parameter | имя поля | Выбранная модель или канал не принимает этот параметр. Удалите его из запроса (в Codex/IDE — из конфигурации) и повторите попытку. |
invalid_parameter_value | имя поля | Параметр присутствует, но его значение недопустимо для этой модели: например, temperature отличается от 1 при включённом рассуждении, max_tokens превышает максимум или effort имеет значение "none", когда рассуждение нельзя отключить. Исправьте значение согласно сообщению. |
missing_parameter | имя поля | Обязательное поле отсутствует или пусто. Добавьте его. |
invalid_tool_schema | tools[].function.* | Определение инструмента отклонено: в именах допускаются только латинские буквы, цифры, "_" и "-"; параметры должны быть заданы в виде JSON Schema с корневым типом type: "object". Либо выберите другой вариант модели. |
tool_choice_unsupported | tool_choice | Модель не поддерживает принудительный выбор инструмента. Передайте tool_choice со значением "auto" или "none". |
invalid_tool_history | messages / input | В истории есть вызов инструмента без результата. Добавьте результат с тем же id или при сокращении истории удалите вызов вместе с его результатом. |
invalid_image | — | Не удалось прочитать изображение. Отправьте полный файл PNG, JPEG, WebP или GIF (BMP, TIFF, AVIF и SVG не принимаются). |
unsupported_input_modality | — | Модель не принимает вложения этого типа (PDF, аудио, видео, изображения). Удалите вложение или выберите модель, в описании которой в каталоге указан этот тип входных данных. |
conversation_state_unreadable | — | Рассуждение в истории создано другим каналом. Запрос корректен; повторите его, а если ошибка повторится, начните новый диалог. |
invalid_json | — | Тело запроса не является JSON или поступило в усечённом виде. Для очень длинных диалогов с изображениями начните новый диалог или сожмите историю. |
request_too_large | — | Запрос слишком большой. Уменьшите объём вложений или истории. |
request_rejected_by_channel | — | Канал модели отклонил запрос без указания причины; сам запрос, скорее всего, корректен. Повторите его; если отказ повторится, выберите другой вариант модели (суффикс после двоеточия) или модель без суффикса. Если это не поможет, обратитесь в службу поддержки и укажите request_id. |
model_not_available_on_route | — | Для этой модели не настроен маршрут. Выберите другую модель или вариант. |
context_length_exceeded | messages / input / prompt | Запрос не помещается в контекстное окно. Сожмите историю (в Codex: /compact) или выберите модель с более широким контекстным окном. |
content_moderation | — | Запрос или ответ заблокирован фильтром содержимого провайдера. Переформулируйте запрос или выберите другую модель. |
invalid_request | — | Причина не распознана. Проверьте запрос по справочнику API; если он корректен, обратитесь в службу поддержки и укажите request_id из ответа. |
Заголовки ответа
| Параметр | Тип | Описание |
|---|---|---|
x-pv-cost-rub | string | Приблизительная оценка стоимости в рублях. В стримах x-pv-cost-rub доступен, когда оценка по входу известна; pv_cost_rub в финальном событии usage требует x-pv-cost: 1. Фактическое списание указано в истории расхода. |
x-pv-generation-id | string | Идентификатор записи истории использования (GET /v1/generations/{id}). Возвращается только для нестриминговых запросов chat/completions. |
x-pv-dropped-params | string | Параметры запроса через запятую, которые выбранный маршрут не принимает и которые поэтому не были отправлены в модель (например, temperature,top_p). Присутствует только тогда, когда что-то действительно было отброшено, — как в ответах, так и в ошибках. Мы никогда не отбрасываем параметр молча: отсутствие заголовка означает, что всё отправленное дошло до модели без изменений. |
Retry-After | integer | Секунды ожидания. Edge устанавливает его при 429 rate, 429 concurrency (5), 503 global overload (2) и некоторых временных 402. Не предполагается для каждого 429/503. |
Ошибки до и после начала ответа
Медленный поток (stream: true) получает комментарии 200 и : pv-keepalive после 5 секунд молчания, чтобы соединение не выглядело мёртвым; долгий нестриминговый запрос получает 200 и пробелы после первого токена. С этого момента статус измениться не может, а ошибка приходит в теле. Если ваш клиент при ошибке HTTP переключается на другого поставщика, отправьте x-pv-honest-errors: 1 (или попросите поддержку включить это для аккаунта): тогда нет ни keepalive, ни раннего 200 до первого байта ответа, и каждая ошибка — обычный HTTP-статус с телом JSON. Цена — молчащий сокет до первого токена; ваш тайм-аут заголовков должен это покрывать.
Ошибка после начала ответа — это одно терминальное событие: для chat event: error с {"error":{message,type,code,status},"choices":[{"finish_reason":"error"}]}, за которым следует data: [DONE]; для /v1/responses event: response.failed, несущее тот же error; для /v1/messages — event: error от Anthropic. code — это машинная причина, а status — HTTP-статус, который был бы у ошибки, если бы она возникла до начала ответа.
stream: true) x-pv-generation-id не возвращается, поскольку биллинг завершается после окончания потока. Просматривайте историю запросов через GET /v1/generations.Лимиты частоты и параллелизма
Оба лимита зависят от текущего баланса аккаунта и применяются ко всем его API-ключам в совокупности. Уровень пересчитывается автоматически: при пополнении баланса лимит повышается, а при снижении баланса ниже порогового значения уровень понижается. Лимита токенов в минуту (TPM) нет.
| Баланс от | Запросы в минуту (RPM) | Одновременные запросы |
|---|---|---|
| 0 $ | 300 | 10 |
| 1 $ | 1 100 | 50 |
| 2 $ | 6 600 | 300 |
| 4 $ | 19 800 | 900 |
| 12 $ | 44 000 | 2 000 |
| Параметр | Тип | Описание |
|---|---|---|
429 rate_limit_exceeded | RPM | За последние 60 секунд (скользящее окно) аккаунт отправил столько запросов, сколько допускает его уровень. Retry-After — количество секунд до освобождения слота в окне (не менее 1); x-ratelimit-limit-requests — ваш текущий RPM. Тело ответа: error.cause = key_rate_limited, error.details = { enforced_by: "getvibe", limit, unit: "requests_per_minute", retry_after_seconds }. |
429 inflight_client_cap | concurrency | Уже выполняется столько запросов, сколько допускает уровень. Запрос занимает слот до завершения; поток — до последнего события. Retry-After: 5. Тело ответа: error.cause = account_concurrency_limited, error.details = { enforced_by: "getvibe", limit, unit: "parallel_requests", retry_after_seconds: 5 }. |
Personal limit | RPM | Если для аккаунта задан индивидуальный лимит запросов, он может только снизить RPM уровня, но не повысить его. |
503 overloaded | service | Это не ваш лимит, а защита сервиса: Retry-After: 2 означает достижение общего лимита для всех аккаунтов, а Retry-After: 1 — что один из серверов занят. Повторите запрос после паузы. |
429 from a provider | upstream | error.cause = supplier_rate_limited означает, что провайдер модели ограничил запрос, а ваш собственный лимит не исчерпан; channel_rate_limited — общий лимит канала провайдера (Retry-After: 10, ничего не было отправлено). |
Повторные попытки запросов
Для повторяемого ответа сначала используйте Retry-After, если он присутствует. Без этого заголовка не считайте никакой 429 или 503 автоматически повторяемым: проверьте статус HTTP и публичный error.type, устраните непреодолимую причину и применяйте ограниченный backoff только для временной ошибки.
import time, requests
def call_with_retry(url, headers, payload, max_retries=5):
for attempt in range(max_retries):
r = requests.post(url, headers=headers, json=payload)
if r.status_code == 429:
# honor Retry-After, otherwise exponential backoff
wait = int(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
return r
return r