Ошибки и лимиты
Коды ошибок PlusVibe API: 401 (ключ), 402 (баланс), 403 (IP), 429 (лимит частоты). Как обрабатывать ошибки и повторять запросы с экспоненциальной задержкой.
Ошибки, сформированные PlusVibe на /v1/*, имеют JSON-envelope error.message, error.type и error.code. Не используйте текст сообщения или заголовки стороннего сервиса как контракт.
Формат ошибки
{
"error": {
"message": "insufficient balance",
"type": "insufficient_quota",
"code": 402,
"request_id": "8d4b6b3a-..."
}
}Коды ответов
| Параметр | Тип | Описание |
|---|---|---|
401 | authentication_error / 401 | Нет, формат неверен или ключ отключён. Не повторять до исправления credentials. |
402 | insufficient_quota / 402 | Нет доступного баланса или лимита — ожидание его не исправит. 402 никогда не приходит с Retry-After и повторять его не нужно: пополните баланс или дождитесь обновления окна подписки. |
403 | permission_error или policy type / 403 | IP, модель или политика не разрешают запрос. Не повторять без изменения запроса или настроек. |
429 | rate_limit_exceeded, inflight_client_cap или rate_limit_error / 429 | Ваш лимит, лимит одновременных запросов, временно занятый резерв баланса (cause reserved_in_flight) или ограничение поставщика. Повторять только после Retry-After; 429 никогда не означает, что исчерпан ваш баланс. |
410 | media_endpoint_retired / media_endpoint_retired | Только для retired media routes. Не повторять; перейдите на /api/media/generate. |
413 | invalid_request_error / 413 | Только Files API: файл больше 50 MiB. Уменьшите файл, затем создайте новый запрос. |
503 | overloaded / 503 | Глобальная перегрузка edge. Повторять после Retry-After: 2. Другие 503 могут быть не retryable без изменения модели или запроса. |
403 (:vibe) | permission_error / 403, cause vibe_requires_claude_code | Запрос с суффиксом :vibe к модели Claude пришёл не из Claude Code. Запрос никуда не отправлен и ничего не стоит. Повтор того же запроса не поможет — уберите суффикс или переключитесь на другого поставщика. |
403 (:vibe) | permission_error / 403, cause own_pool_not_admitted | Запрос из Claude Code, но ключу полоса :vibe недоступна. Запрос никуда не отправлен и ничего не стоит. Уберите суффикс или переключитесь на другого поставщика. |
503 (:vibe) | upstream_error / 503, cause own_pool_no_capacity | У полосы :vibe сейчас нет свободной ёмкости. Запрос не переведён на другого поставщика. Повторять после Retry-After или сразу уйти на другого поставщика. |
Заголовки ответа
| Параметр | Тип | Описание |
|---|---|---|
x-pv-cost-rub | string | Best-effort оценка стоимости в рублях. В потоках заголовок доступен всегда, когда известна входная оценка; поле pv_cost_rub в финальном usage-событии появляется только при x-pv-cost: 1. Фактическое списание — в истории usage. |
x-pv-generation-id | string | ID записи в истории использования (GET /v1/generations/{id}). Возвращается только для не-потоковых (non-streaming) запросов chat/completions. |
x-pv-dropped-params | string | Список параметров запроса через запятую, которые выбранный маршрут не принимает и которые поэтому не были отправлены модели (например temperature,top_p). Появляется только когда что-то действительно снято, на ответах и на ошибках. Мы никогда не снимаем параметр молча: если заголовка нет — всё, что вы прислали, ушло в модель как есть. |
Retry-After | integer | Секунды ожидания. Edge ставит его на 429 частоты, 429 concurrency (5), 503 global overload (2), 503 own_pool_no_capacity и некоторых временных 402. Не предполагается для каждого 429/503. |
Ошибка до и после начала ответа
Медленный поток (stream: true) через 5 секунд тишины получает 200 и комментарии : pv-keepalive, чтобы соединение не считалось мёртвым; длинный не-потоковый запрос после первого токена получает 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.Лимиты частоты и параллелизм
| Параметр | Тип | Описание |
|---|---|---|
0 / 50 / 100 / 300 / 1 000 ₽ | RPM | 300 / 1 100 / 6 600 / 19 800 / 44 000 запросов в минуту соответственно. Публикуется только исполняемый RPM: TPM не ограничивается как клиентский runtime-лимит. |
RPM account-wide | edge | RPM считается по клиентскому аккаунту сразу для всех его API-ключей. Индивидуальный лимит клиента может только уменьшить tier RPM. |
0 / 50 / 100 / 300 / 1 000 ₽ | concurrency | 10 / 50 / 300 / 900 / 2000 одновременных запроса соответственно. Лимит одновременных запросов определяется тем же тиром баланса, что и RPM, и считается по клиентскому аккаунту. |
Превышение параллелизма | edge | Превышение лимита аккаунта - 429 и Retry-After: 5. Кроме него действует общий защитный предел сервиса на все аккаунты сразу: 503 и Retry-After: 2. Эти значения могут быть настроены сервисом. |
Повтор запросов (retry)
Для retryable ответа сначала используйте Retry-After, если он есть. Без заголовка не считайте любой 429 или 503 автоматически retryable: проверьте HTTP status и публичный error.type, устраните не-retryable причину и используйте ограниченный 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:
# уважаем Retry-After, иначе экспоненциальная задержка
wait = int(r.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
continue
return r
return r