AgentAPI

Ошибки и лимиты

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

Большинство ошибок приходят в стандартном формате:

{
  "statusCode": 401,
  "message": "Invalid or inactive API key",
  "error": "Unauthorized"
}

Ошибки валидации содержат массив в message:

{
  "statusCode": 400,
  "message": [
    "temperature must not be greater than 2",
    "messages should not be empty"
  ],
  "error": "Bad Request"
}

Формат отличается от OpenAI: ответ не обёрнут в объект error с полями type и code. Библиотеки, ожидающие OpenAI-формат ошибок, покажут сообщение, но не смогут разобрать его тип.

Коды ответов

400 — Bad Request

СообщениеПричина
Negative or zero balance. Available: 0.00 RUB. Please top up your account.Баланс исчерпан. Пополните счёт в профиле
массив сообщений валидацииПараметр вне допустимого диапазона или отсутствует обязательное поле
content must be a stringПередан массив блоков в content — мультимодальный ввод не поддерживается
property X should not existНеизвестное поле в запросе к /v1/ai/embeddings (там строгая валидация)

Баланс проверяется до отправки запроса провайдеру. Он может стать отрицательным после списания за уже выполненный запрос — блокируется только следующий.

401 — Unauthorized

СообщениеПричина
API key is required in x-api-key or Authorization headerЗаголовок не передан
Invalid API key formatКлюч не начинается с agentapi-
Invalid or inactive API keyКлюч не найден или отключён (active = false)
API key not foundКлюч не связан с пользователем

403 — Forbidden

Insufficient balance to create an API key — при попытке создать ключ с нулевым балансом.

404 — Not Found

Чаще всего означает неверный URL, а не отсутствующий ресурс:

Что запрошеноПроблема
https://agentapi.ru/v1/...Основной домен отдаёт сайт. Нужен api.agentapi.ru
https://api.agentapi.ru/v1/chat/completionsПропущен сегмент /ai
https://api.agentapi.ru/v1/messagesПравильный путь — /v1/ai/messages

429 — Too Many Requests

{
  "statusCode": 429,
  "message": "ThrottlerException: Too Many Requests"
}

502 — Bad Gateway

Ошибка на стороне провайдера. Формат отличается от остальных — без поля statusCode:

{
  "message": "Provider returned error: model not found"
}

Типичные причины: неверный слаг модели, модель временно недоступна, запрос отклонён провайдером. Для устойчивости используйте fallback-роутинг через models[].

Ошибки внутри стрима

Если генерация оборвалась после начала стриминга, HTTP-статус уже равен 200 — заголовки отправлены до первого токена. Ошибка приходит кадром:

data: {"error":{"message":"..."}}

Для формата Anthropic — именованным событием:

event: error
data: {"type":"error","error":{"type":"api_error","message":"..."}}

Поэтому проверяйте поле error в каждом кадре, а не только код ответа.

Сейчас в теле такого кадра сообщение может сериализоваться как пустой объект ({"error":{"message":{}}}) — см. Ограничения.

Rate limit

Лимит60 запросов за 60 секунд
Ключ подсчётаIP-адрес, не API-ключ
ИсключениеPOST /v1/ai/embeddings — не лимитируется

Важные следствия:

  • Лимит считается по IP, поэтому несколько приложений с одного сервера (или из-за одного NAT) расходуют общий лимит, даже с разными ключами.
  • Разные ключи одного аккаунта не дают независимых лимитов.
  • Стриминговый запрос — это один запрос независимо от длительности.

Ограничения на количество токенов в минуту нет: фактический потолок — баланс аккаунта и лимиты самого провайдера.

Рекомендации по обработке

КодСтратегия
400 (баланс)Не повторять. Уведомить о пополнении
400 (валидация)Не повторять. Исправить запрос
401Не повторять. Проверить ключ
429Повтор с экспоненциальной задержкой
502Повтор 1–2 раза; при повторе — сменить модель через models[]

Пример повторов только для восстановимых ошибок:

async function withRetry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (error) {
      const status = (error as { status?: number }).status;

      // Повторяем только то, что может пройти со второй попытки.
      if (status !== 429 && status !== 502) throw error;
      if (i === attempts - 1) throw error;

      await new Promise((r) => setTimeout(r, 2 ** i * 1000));
    }
  }

  throw new Error("unreachable");
}