halfoff.devДокументация
Получить ключ
Справочник API

Ошибки

HTTP-коды статуса Half Off Dev, тела ошибок в форматах Anthropic, OpenAI и Gemini, ошибки в потоке и код повторов, который можно скопировать.

Токены за полцены дёшевы, только если ваш код грамотно обрабатывает сбои. Half Off Dev делает это просто: ошибки используют обычные HTTP-коды статуса, а тело написано в формате того API, которое вы вызывали, поэтому SDK Anthropic, OpenAI и Google выбрасывают те же исключения, что и при обращении к вендору. Как правило, 4xx означает «измените запрос» (или пополните баланс), а 429 и 5xx временные, и их стоит повторять.

Коды статуса вкратце#

СтатусНазваниеТипичная причинаПовторять?
400Bad requestТело не является корректным JSON. Модель отклонила параметр, например max_tokens выше её предела. Неизвестный x-halfoff-group. Фильтр содержимого модели отклонил запрос.Нет, исправьте запрос
401UnauthenticatedКлюч отсутствует, содержит опечатку, отозван или просроченНет, проверьте ключ
402Payment requiredБаланс $0 или ключ достиг лимита расходовТолько после пополнения или повышения лимита
403ForbiddenМодели нет в списке разрешённых для ключаНет
404Not foundНеизвестный id модели, модель, не обслуживаемая в этом формате API, неизвестный эндпоинт или неверный HTTP-методНет
413Payload too largeПромпт слишком длинный для моделиНет, сократите его
422UnprocessableМодель отказалась обрабатывать содержимое запросаНет
429Too many requestsЛимит запросов или параллелизма ключа, либо ёмкость модели занятаДа, после retry-after
500Internal errorНепредвиденная ошибка в Half Off DevДа, с задержкой
502Bad 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.typeOpenAI error.type и error.codeGemini error.status
400invalid_request_errorinvalid_request_errorINVALID_ARGUMENT
401authentication_errorauthentication_errorUNAUTHENTICATED
402billing_errorinsufficient_quotaRESOURCE_EXHAUSTED
403permission_errorpermission_deniedPERMISSION_DENIED
404not_found_error, или not_found для неизвестного эндпоинтаnot_found_error, или not_found для неизвестного эндпоинтаNOT_FOUND
413, 422invalid_request_errorinvalid_request_errorINVALID_ARGUMENT
429rate_limit_errorrate_limit_exceededRESOURCE_EXHAUSTED
500api_errorapi_errorINTERNAL
502, 503api_errorapi_errorUNAVAILABLE
504api_errorapi_errorDEADLINE_EXCEEDED

В теле Gemini error.code всегда равен HTTP-статусу, а 402 и 429 оба соответствуют RESOURCE_EXHAUSTED, поэтому ветвитесь по числу, а не по строке. Даже ошибки, возникшие до чтения тела, например для неизвестного пути, используют формат, соответствующий вызванному пути.

Сообщения, которые вы можете увидеть#

Статусmessage
400Body must be JSON
400Unknown group 'xyz'
400The model provider's content filter refused this request. Rewording it or using another model may help.
400, 413, 422Собственное пояснение модели, например prompt is too long
401Missing API key, Invalid or revoked API key, This API key has expired
402Insufficient balance. Top up at the dashboard.
402This API key has reached its spend limit.
403This API key is not allowed to use 'gpt-6.1-sol'.
404Model 'gpt-9' is not available. See GET /v1/models.
404Unknown endpoint /v1/embeddings, No route for GET /v1/messages
429This API key has reached its rate or concurrency limit.
429All upstream capacity for this model is busy. Retry shortly.
502The 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.