Ошибки
HTTP-коды статуса Half Off Dev, тела ошибок в форматах Anthropic, OpenAI и Gemini, ошибки в потоке и код повторов, который можно скопировать.
Токены за полцены дёшевы, только если ваш код грамотно обрабатывает сбои. Half Off Dev делает это просто: ошибки используют обычные HTTP-коды статуса, а тело написано в формате того API, которое вы вызывали, поэтому SDK Anthropic, OpenAI и Google выбрасывают те же исключения, что и при обращении к вендору. Как правило, 4xx означает «измените запрос» (или пополните баланс), а 429 и 5xx временные, и их стоит повторять.
Коды статуса вкратце#
| Статус | Название | Типичная причина | Повторять? |
|---|---|---|---|
400 | Bad request | Тело не является корректным JSON. Модель отклонила параметр, например max_tokens выше её предела. Неизвестный x-halfoff-group. Фильтр содержимого модели отклонил запрос. | Нет, исправьте запрос |
401 | Unauthenticated | Ключ отсутствует, содержит опечатку, отозван или просрочен | Нет, проверьте ключ |
402 | Payment required | Баланс $0 или ключ достиг лимита расходов | Только после пополнения или повышения лимита |
403 | Forbidden | Модели нет в списке разрешённых для ключа | Нет |
404 | Not found | Неизвестный id модели, модель, не обслуживаемая в этом формате API, неизвестный эндпоинт или неверный HTTP-метод | Нет |
413 | Payload too large | Промпт слишком длинный для модели | Нет, сократите его |
422 | Unprocessable | Модель отказалась обрабатывать содержимое запроса | Нет |
429 | Too many requests | Лимит запросов или параллелизма ключа, либо ёмкость модели занята | Да, после retry-after |
500 | Internal error | Непредвиденная ошибка в Half Off Dev | Да, с задержкой |
502 | Bad gateway | Модель дала сбой на каждом канале, который попробовал Half Off Dev | Да, с задержкой |
Любые другие 5xx обрабатывайте так же, как 502. Запросы, отклонённые до обращения к модели, не тарифицируются: 400 для невалидного JSON или неизвестной группы, 401, 402, 403, 404 для неизвестной модели или эндпоинта и 429 из-за лимита ключа.
Форматы тел ошибок#
Одна и та же ошибка 401 выглядит так в каждом из трёх форматов:
{"type": "error", "error": {"type": "authentication_error", "message": "Invalid or revoked API key"}}Тип и статус для каждого кода#
| Статус | Anthropic error.type | OpenAI error.type и error.code | Gemini error.status |
|---|---|---|---|
400 | invalid_request_error | invalid_request_error | INVALID_ARGUMENT |
401 | authentication_error | authentication_error | UNAUTHENTICATED |
402 | billing_error | insufficient_quota | RESOURCE_EXHAUSTED |
403 | permission_error | permission_denied | PERMISSION_DENIED |
404 | not_found_error, или not_found для неизвестного эндпоинта | not_found_error, или not_found для неизвестного эндпоинта | NOT_FOUND |
413, 422 | invalid_request_error | invalid_request_error | INVALID_ARGUMENT |
429 | rate_limit_error | rate_limit_exceeded | RESOURCE_EXHAUSTED |
500 | api_error | api_error | INTERNAL |
502, 503 | api_error | api_error | UNAVAILABLE |
504 | api_error | api_error | DEADLINE_EXCEEDED |
В теле Gemini error.code всегда равен HTTP-статусу, а 402 и 429 оба соответствуют RESOURCE_EXHAUSTED, поэтому ветвитесь по числу, а не по строке. Даже ошибки, возникшие до чтения тела, например для неизвестного пути, используют формат, соответствующий вызванному пути.
Сообщения, которые вы можете увидеть#
| Статус | message |
|---|---|
400 | Body must be JSON |
400 | Unknown group 'xyz' |
400 | The model provider's content filter refused this request. Rewording it or using another model may help. |
400, 413, 422 | Собственное пояснение модели, например prompt is too long |
401 | Missing API key, Invalid or revoked API key, This API key has expired |
402 | Insufficient balance. Top up at the dashboard. |
402 | This API key has reached its spend limit. |
403 | This API key is not allowed to use 'gpt-6.1-sol'. |
404 | Model 'gpt-9' is not available. See GET /v1/models. |
404 | Unknown endpoint /v1/embeddings, No route for GET /v1/messages |
429 | This API key has reached its rate or concurrency limit. |
429 | All upstream capacity for this model is busy. Retry shortly. |
502 | The model provider returned an error. Please retry. |
Каждый ответ с ошибкой содержит заголовок x-halfoff-request-id, даже если запрос не дошёл до модели (неверный ключ, нулевой баланс, неизвестная модель). Укажите его, когда пишете в поддержку.
Ошибки внутри потока#
Когда поток уже открыт, статус 200 отправлен, поэтому более поздняя проблема приходит как событие внутри потока. Сбои до первого токена незаметно повторяются на другом канале, так что поток обычно либо успешен, либо до начала завершается обычной HTTP-ошибкой.
Если модель молчит 3 минуты посреди потока, Half Off Dev закрывает его событием ошибки в формате этого потока:
event: error
data: {"type": "error", "error": {"type": "api_error", "message": "Upstream stopped responding mid-stream"}}Ошибки, о которых канал сообщает посреди потока, заменяются обобщённым событием в том же формате. Занятая ёмкость превращается в overloaded_error у Anthropic, rate_limit_exceeded у OpenAI или RESOURCE_EXHAUSTED у Gemini с сообщением Upstream capacity for this model is busy. Retry shortly. Всё остальное становится api_error (у Gemini UNAVAILABLE) с сообщением The model provider returned an error. Please retry. В Responses API это событие error, например {"type": "error", "code": "server_error", "message": "…"}. Начните запрос заново; вывод, переданный до ошибки, тарифицируется.
Стратегия повторов#
Half Off Dev уже переносит сбойный запрос на другие каналы, прежде чем вернуть ошибку. Для оставшегося добавьте собственные повторы:
- Повторяйте при
429,500,502,503,504и сетевых ошибках. - Ждите
retry-after, если заголовок есть. Иначе увеличивайте задержку экспоненциально: 1, 2, 4, 8 секунд и так до 30, плюс до секунды случайного разброса, чтобы ваши воркеры не повторяли синхронно. - Сдавайтесь примерно после пяти попыток и сообщайте об ошибке.
- Не повторяйте
400,401,403,404,413или422без изменений. Повторять402имеет смысл только после пополнения баланса. - Помните о таймаутах. Запрос, у которого истёк таймаут на вашей стороне, мог завершиться и быть оплачен. См. Повторы и идемпотентность.
Официальные SDK Anthropic и OpenAI уже дважды повторяют 429 и 5xx с задержкой; чтобы это изменить, задайте max_retries (Python) или maxRetries (Node.js) у клиента. Для «сырых» HTTP-вызовов подойдёт такой помощник:
import os
import random
import time
import requests
RETRY_ON = {429, 500, 502, 503, 504}
def wait_time(attempt, retry_after=None):
if retry_after:
return float(retry_after)
return min(30, 2 ** (attempt - 1)) + random.random()
def call_with_retries(url, payload, attempts=5):
headers = {"Authorization": f"Bearer {os.environ['HALFOFF_API_KEY']}"}
for attempt in range(1, attempts + 1):
try:
resp = requests.post(url, json=payload, headers=headers, timeout=300)
except (requests.ConnectionError, requests.Timeout):
if attempt == attempts:
raise
time.sleep(wait_time(attempt))
continue
if resp.status_code in RETRY_ON and attempt < attempts:
time.sleep(wait_time(attempt, resp.headers.get("retry-after")))
continue
if not resp.ok: # a 4xx we shouldn't repeat, or the last attempt failed
raise RuntimeError(f"{resp.status_code}: {resp.text[:500]}")
return resp.json()
answer = call_with_retries(
"https://halfoff.dev/v1/chat/completions",
{
"model": "gpt-6.1-sol",
"messages": [{"role": "user", "content": "Name three uses for a paperclip."}],
},
)
print(answer["choices"][0]["message"]["content"])Независимо от формата, error.message написан для людей. Записывайте его в лог вместе с кодом статуса и заголовком x-halfoff-request-id.