NormaHub knowledge base

Ошибки и безопасные повторы

Определяйте причину по HTTP-статусу и полю error.code: постоянные ошибки чинятся, временные повторяются с backoff. Диагностика — по X-Request-Id.

1. Формат ошибки

Каждая ошибка NormaHub — это HTTP-статус плюс тело с машиночитаемым кодом. Статус говорит классу проблемы (авторизация, оплата, лимиты, доступность), а поле error.code — точной причине внутри класса. Стройте ветвление именно по паре статус+код, а не по тексту message: тексты могут меняться, коды стабильны.

{
  "error": {
    "message": "Human-readable message",
    "type": "invalid_request_error",
    "code": "invalid_request"
  }
}

Для поддержки сохраняйте заголовок X-Request-Id каждого запроса — он связывает ваш клиентский вызов с записью в журнале и находится без ключа и промпта.

2. Таблица кодов

Постоянные ошибки повтором не лечатся: исправляйте запрос, ключ, баланс, разрешения или размер. Быстрая проверка ключа — один curl до /v1/models: он отделяет мёртвый ключ от живых денег за секунды.

400
invalid_request
Проверьте JSON и обязательные поля маршрута.
401
invalid_api_key
Ключ отсутствует, неверен или отозван.
402
insufficient_balance
Пополните баланс владельца API-ключа.
403
model_not_allowed
Модель или endpoint недоступны этому ключу.
413
request_too_large
Уменьшите тело запроса или передаваемый контекст.
429
budget_exceeded
Достигнут лимит API-ключа.
503
provider_unavailable
Временно недоступна модель или upstream-провайдер.
# 401 или 402? Проверьте curl за 10 секунд:
curl -s -o /dev/null -w "%{http_code}
" \
  https://api.normahub.cc/v1/models \
  -H "Authorization: Bearer $NORMAHUB_API_KEY"
# 401 — ключ неверен или отозван, выпускайте новый в кабинете
# 200 — ключ жив, смотрите баланс и лимиты ключа

3. Retry policy

Код 429 бывает двух видов — читайте error.code. При budget_exceeded исчерпан spend limit ключа: поднимайте его в кабинете, повтор с тем же ключом не поможет. При rate_limit_exceeded запросы идут слишком часто: повторяйте с экспоненциальной задержкой, учитывайте Retry-After и ограничивайте общее число попыток и deadline операции. Интерактивному UI чаще лучше быстро показать понятный отказ с кнопкой «повторить», чем держать пользователя в невидимой очереди.

import os
import random
import time
from openai import OpenAI, APIError

client = OpenAI(
    api_key=os.environ["NORMAHUB_API_KEY"],
    base_url="https://api.normahub.cc/v1",
)

RETRIABLE = {429, 500, 502, 503}

def chat(messages, tries=4, deadline=60):
    started = time.monotonic()
    for attempt in range(tries):
        try:
            return client.chat.completions.create(
                model="MODEL_ID",
                messages=messages,
                timeout=20,
            )
        except APIError as e:
            retryable = (e.status_code in RETRIABLE
                         and time.monotonic() - started < deadline)
            if not retryable or attempt == tries - 1:
                raise
            delay = min(2 ** attempt + random.uniform(0, 1), 15)
            time.sleep(delay)
    raise RuntimeError("unreachable")

Не повторяйте автоматически 400, 401, 402, 403 и 413: очередь таких повторов создаёт нагрузку, не приближая успех. Отдельный риск — восстановление после инцидента: вернувшиеся без верхнего предела повторы устраивают вторую волну нагрузки. Примеры обработки в SDK — в гайде Python и гайде Node.js.

4. Диагностика и лимиты

Минимальный набор для расследования: время, endpoint, model ID, HTTP-статус, error.code, X-Request-Id. Полный ключ, промпты и ответы в обычные application logs не пишите — проверяйте также CI-артефакты, docker history и тексты исключений HTTP-клиента, куда секреты протекают чаще всего. Системно про наблюдаемость — в статье «что логировать».

Лимиты бывают двух уровней: баланс владельца и spend limit конкретного ключа. После восстановления проверяйте оба, иначе «деньги есть, а запросы отклоняются». Баланс, лимиты и историю операций смотрите в личном кабинете, а цену каждого повтора считайте по гайду по стоимости.

Частые вопросы

401 или 403 — в чём разница?

401 означает проблему ключа: отсутствует, неверен или отозван. 403 означает проблему доступа: ключ жив, но модель или endpoint ему недоступны. Лечатся по-разному: новый ключ против смены модели или маршрута.

Запрос упёрся в 402. Что пополнять?

Баланс владельца API-ключа и лимит самого ключа (spend limit). Это разные сущности: деньги могут быть, а лимит ключа — исчерпан. Оба проверяются в личном кабинете.

Повторять ли запрос после обрыва соединения?

Только если операция идемпотентна или вы можете проверить её состояние. Upstream мог уже обработать запрос до обрыва — слепой повтор выполнит действие дважды.

Что отправить в поддержку при ошибке?

Время запроса, endpoint, model ID, HTTP-статус, error.code и X-Request-Id. Этого достаточно для поиска операции. Не прикладывайте полный API-ключ и конфиденциальное содержимое.