Как отлаживать запросы к API нейросети: от curl до кодов ошибок

Чаще всего проблема не в модели, а в запросе: адрес, ключ, лимит, размер тела. Пошаговый порядок отладки и таблица кодов ошибок Zukko с решениями.

5 мин · · разработка · ошибки · API-ключи

Отладка запросов к API нейросети обычно сводится к одному вопросу: что именно ответил сервер. SDK часто прячет это за общим исключением, а логи приложения показывают только «что-то пошло не так». Ниже — порядок действий, который быстро находит причину: воспроизвести запрос в curl, прочитать JSON-ошибку, сверить код с таблицей и правильно настроить повторы и логирование.

Шаг 1. Воспроизведите запрос через curl

curl убирает из уравнения SDK, фреймворк и ваш код. Если в curl ошибка та же — дело в запросе или аккаунте; если в curl всё работает — ищите разницу в коде.

bash
curl -i https://api.zukko.pro/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.6-sol","messages":[{"role":"user","content":"привет"}]}'

Флаг -i выводит статус и заголовки ответа вместе с телом. Флаг -v показывает ещё и исходящие заголовки и детали соединения — полезно, когда подозреваете прокси, DNS или TLS. Помните, что -v печатает и заголовок Authorization с ключом: не вставляйте такой вывод в чаты и тикеты без маскировки.

На что смотреть:

  • Строка статуса: HTTP/2 200 или код ошибки.
  • Content-Type: для обычного ответа application/json, для стриминга — text/event-stream. HTML вместо JSON обычно означает, что запрос ушёл не на тот адрес.
  • Служебные заголовки Zukko в успешных ответах: X-Zukko-Cost-Micros и X-Fetch-Balance-Cents — стоимость запроса и остаток баланса. Для учёта расходов надёжнее раздел «Использование» в кабинете.

Проверьте и сам JSON: echo '<тело>' | python3 -m json.tool. Лишняя запятая или неэкранированная кавычка в shell-строке — частая причина ошибки 400.

Шаг 2. Прочитайте JSON-ошибку

Ошибки Zukko приходят в теле в такой форме:

bash
{
  "error": {
    "message": "Модель 'glm-5.3' временно недоступна. Актуальный список: GET /v1/models",
    "type": "service_unavailable",
    "code": "model_disabled"
  }
}
  • message — описание для человека. В конец сообщения может добавляться строка со ссылкой на поддержку, поэтому не сравнивайте message в коде целиком.
  • type — категория: authentication_error, permission_error, rate_limit_error, invalid_request_error, service_unavailable.
  • code — машинный код. Ветвите логику по нему и по HTTP-статусу. У некоторых ответов (например, 401) поля code может не быть — тогда ориентируйтесь на статус и type.

Если в ответе ошибка от провайдера модели, а не от шлюза, её форма может отличаться, но статус и текст помогут понять причину. В обращении в поддержку прикладывайте JSON целиком, а не пересказ.

Коды ошибок Zukko и что с ними делать

HTTPcodeПричинаРешение
401— (type: authentication_error)Ключ неверный, отозван или не переданПроверьте заголовок Authorization: Bearer fetch_... и переменные окружения
402insufficient_balanceНа балансе меньше $0.01Пополните баланс в кабинете
403email_unverifiedПочта аккаунта не подтвержденаПодтвердите email
403model_not_allowed_for_keyМодель не входит в список разрешённых для ключаДобавьте модель в настройках ключа или смените ключ
413payload_too_largeТело запроса больше 32 МБУменьшите тело: чаще всего виноваты картинки в base64 или огромные вложения
429key_daily_limit_reachedИсчерпан дневной лимит ключа в долларахЖдите сброса в 00:00 UTC или поднимите лимит в кабинете
429key_monthly_limit_reachedИсчерпан месячный лимит ключаПоднимите лимит в кабинете
400model_retiredМодель снята с обслуживанияВ сообщении указана замена; обновите имя модели
503model_disabledМодель временно выключенаПереключитесь на модель из GET /v1/models

Важно про 429: ограничения частоты запросов на стороне Zukko нет. Собственные 429 шлюза — это только исчерпанные лимиты ключа, и повторять такой запрос через секунду бесполезно. Прочие 5xx, пришедшие от провайдера, обычно временные — их имеет смысл повторить с паузой.

Шаг 3. Настройте повторы в коде

Официальный OpenAI SDK по умолчанию сам повторяет запрос дважды при ошибках соединения, 408, 409, 429 и 5xx. Для 429 от лимита ключа и 503 model_disabled это лишние запросы и задержка. Отключите встроенные повторы и решайте сами:

bash
import os
import time

import openai
from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["OPENAI_API_KEY"],
    max_retries=0,
)


def error_code(exc: Exception) -> str | None:

Функция error_code работает и с исключениями Anthropic SDK: у них тоже есть атрибуты status_code и body. В Anthropic SDK base URL указывается без /v1 — https://api.zukko.pro. Подробнее о клиентах — в руководстве по Python SDK.

Шаг 4. Логируйте безопасно

Правила простые:

  • ключ не пишется в лог никогда — ни целиком, ни частично; храните его в переменной окружения;
  • тело запроса логируйте выборочно: модель, число сообщений, длину текста, а не содержимое с личными данными;
  • заголовки ответа и usage логируйте полностью — в них нет секретов.

Чтобы получить заголовки ответа в OpenAI SDK, используйте with_raw_response:

bash
raw = client.chat.completions.with_raw_response.create(
    model="gpt-5.6-sol",
    messages=[{"role": "user", "content": "test"}],
)
completion = raw.parse()
print("status:", raw.status_code)
print("cost header:", raw.headers.get("X-Zukko-Cost-Micros"))
print("usage:", completion.usage)

Шаг 5. Проверьте base URL и имя модели

404 на /chat/completions почти всегда означает неверный адрес:

  • OpenAI SDK и OpenAI-совместимые клиенты: https://api.zukko.pro/v1; SDK добавит /chat/completions сам.
  • Anthropic SDK и Claude Code: https://api.zukko.pro без /v1, путь /v1/messages клиент добавляет сам.

Список включённых моделей отдаёт публичный эндпоинт, ключ для него не нужен:

bash
curl https://api.zukko.pro/v1/models
bash
{
  "object": "list",
  "data": [
    {"id": "claude-sonnet-5", "object": "model", "created": 1700000000, "owned_by": "zukko"},
    {"id": "gpt-5.6-sol", "object": "model", "created": 1700000000, "owned_by": "zukko"}
  ]
}

В списке только модели, которые включены прямо сейчас. Если нужной модели там нет, запрос к ней получит model_disabled или model_retired. Актуальный список также есть на странице «Модели» в кабинете; описание эндпоинта — в справке по моделям, про ключи и авторизацию — в разделе об аутентификации.

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

Как отлаживать стриминг?

Посмотрите сырые события через curl с флагом -N (без буферизации) и "stream": true в теле. Каждое событие — строка data: {...}; в Zukko последний чанк приходит с usage и пустым choices. Поэтому в коде перед chunk.choices[0] нужна проверка:

bash
stream = client.chat.completions.create(
    model="gpt-5.6-sol",
    messages=[{"role": "user", "content": "test"}],
    stream=True,
)
for chunk in stream:
    if chunk.choices:
        print(chunk.choices[0].delta.content or "", end="", flush=True)

Ошибка до первого события придёт обычным JSON с HTTP-статусом; обрыв посреди потока — это сетевая ошибка, её стоит повторить целиком.

Как узнать число токенов до отправки?

Для моделей Claude есть эндпоинт POST https://api.zukko.pro/v1/messages/count_tokens в Anthropic-формате (с заголовком anthropic-version: 2023-06-01). Он возвращает input_tokens — число входных токенов; выходные зависят от ответа и заранее не считаются. Подробнее — в документации count_tokens.

Получаю 429, хотя запросов мало. Почему?

Потому что 429 в Zukko — это не частота, а деньги: у ключа исчерпан дневной (key_daily_limit_reached) или месячный (key_monthly_limit_reached) лимит. Проверьте настройки ключа на странице ключей.

Нужно ли логировать текст сообщений?

Только если без этого не обойтись, и с учётом личных данных. Обычно хватает модели, размера запроса, usage, HTTP-статуса и error.code. Если текст нужен, храните его отдельно с ограниченным доступом и сроком хранения.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.