Batch запросы

Много запросов одним пакетом, результаты забираете позже. Удобно, когда ответ не нужен сразу: прогон датасета, ночная генерация, массовые эмбеддинги.

Окно выполнения — 24 часа. После завершения результаты приходят в том же GET, отдельный файл скачивать не нужно.

Какие модели

Обычное имя из каталога: gpt-6-astra, gemini-3.8-flash, claude-opus-4.8. Batch есть, если у модели в каталоге поле batch:

JSON
{
  "batch": { "discount": 0.5, "window": "24h" }
}

Прямые маршруты отдельных провайдеров в Batch не ходят — только модели с полем batch.

Эндпоинты

МетодПутьЧто делает
POST/v1/batchesСоздать пакет, 202 Accepted
GET/v1/batchesСписок ваших пакетов
GET/v1/batches/{id}Статус и результаты
DELETE/v1/batches/{id}Удалить входные данные и результаты завершённого пакета

Отмены нет: провайдер начинает работу сразу и выставляет нам счёт независимо от того, ждёте вы результат. «Отмена» означала бы вернуть вам деньги за уже оплаченную нами работу. DELETE — это не отмена: он работает только для завершённого пакета.

Base URL
Авторизация
Тот же Bearer, что для чата

Форма запроса

endpoint
Форма API для всего пакета
model
Id модели из каталога
requests
Непустой массив. У каждого элемента — custom_id и body
provider
Необязательно. { "only": ["anthropic"] } закрепляет пакет за конкретным провайдером. Кроме only, в Batch ничего не принимается: sort, order и прочее вернут 400. Если ни у одного из перечисленных провайдеров нет Batch для этой модели — 404
completion_window
Необязательно, единственное значение 24h

custom_id уникален внутри пакета. body — то же, что вы бы отправили на выбранный endpoint. Модель в body можно не ставить: берётся верхний model. Если поставить — должно совпасть. stream: true в body не поддерживается.

Весь пакет выполняет один провайдер. По умолчанию выбирается самый дешёвый из тех, у кого есть Batch для модели.

Один пакет — одна модель и один endpoint, не больше 10 000 запросов.

endpointAPI
/v1/chat/completionsChat Completions
/v1/responsesResponses
/v1/messagesMessages
/v1/embeddingsEmbeddings

Отправка

curl https://api.aitunnel.ru/v1/batches \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "endpoint": "/v1/chat/completions",
    "model": "claude-opus-4.8",
    "requests": [
      {
        "custom_id": "req-0001",
        "body": {
          "messages": [
            { "role": "user", "content": "Суммируй AITUNNEL одним предложением." }
          ]
        }
      }
    ]
  }'

Успешный POST возвращает 202 и объект со статусом validating — пакет принят и встал в очередь, это ещё не готовые ответы.

JSON
{
  "id": "batch_123",
  "object": "batch",
  "endpoint": "/v1/chat/completions",
  "model": "claude-opus-4.8",
  "completion_window": "24h",
  "status": "validating",
  "created_at": 1782097200,
  "finalized_at": null,
  "request_counts": {
    "total": 1,
    "completed": 0,
    "failed": 0
  },
  "usage": null,
  "results": null,
  "error": null
}

Единственное окно — 24h.

Опрос

cURL
curl https://api.aitunnel.ru/v1/batches/batch_123 \
  -H "Authorization: Bearer sk-aitunnel-xxx"

Цепочка статусов:

Статусы
validating → in_progress → finalizing → completed

Ещё бывают failed, expired, cancelling, cancelled. Терминальные: completed, failed, expired, cancelled. Опрашивайте, пока не дойдёте до одного из них.

Опрос нужен только чтобы узнать результат. Пакет мы досчитываем сами: раз в минуту планировщик проверяет незавершённые задачи, поэтому резерв освобождается без вашего участия — даже если процесс, отправивший пакет, давно завершился. Можно спокойно закрыть скрипт и вернуться за results позже.

Сразу после POST пакет может пару десятков секунд не находиться у провайдера — в это время GET вернёт статус из нашей записи (validating). Это нормально, повторите опрос.

request_counts — прогресс: total, completed, failed.

Пока пакет не completed, results равен null. После — массив в том же ответе. Каждый элемент склеивается со входом по custom_id. Заполнено ровно одно: response или error.

JSON
{
  "id": "batch_req_123",
  "custom_id": "req-0001",
  "response": {
    "status_code": 200,
    "request_id": "request_123",
    "body": {
      "id": "gen-…",
      "object": "chat.completion",
      "model": "claude-opus-4.8",
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "AITUNNEL — единый API к сотням моделей с оплатой в рублях."
          },
          "finish_reason": "stop"
        }
      ]
    }
  },
  "error": null
}

Удаление

Удалить входные данные и результаты раньше 30 дней:

cURL
curl -X DELETE https://api.aitunnel.ru/v1/batches/batch_123 \
  -H "Authorization: Bearer sk-aitunnel-xxx"
JSON
{ "id": "batch_123", "object": "batch", "deleted": true }

Работает только для пакета в терминальном статусе (completed, failed, expired, cancelled). Пока пакет выполняется, ответ — 409. Удаление не влияет на списание: итог уже посчитан, запись о пакете и расход в статистике остаются. Повторный DELETE тоже вернёт deleted: true. Если ответ — 5xx, повторите запрос.

Списание

При POST на балансе резервируется оценка (худший случай по составу пакета, уже со скидкой Batch). Резерв — это не списание: деньги удержаны, но ещё не потрачены, и в истории расходов их нет.

Когда пакет доходит до терминального статуса, списывается факт, а лишнее возвращается. При failed / expired / cancelled резерв обычно возвращается целиком. Но если провайдер успел выполнить часть запросов и выставил за них счёт, списывается эта часть — сумма в usage.cost_rub. Больше зарезервированного с вас не спишут: если провайдер выставил нам больше, разницу оплачиваем мы.

У пакета есть жёсткий срок (expires_at, чуть больше суток — окно выполнения плюс запас). Если к этому времени результата нет, пакет переводится в expired и резерв возвращается полностью. Зависнуть навсегда, удерживая деньги, пакет не может.

Итог в рублях — usage.cost_rub после completed.

Список пакетов и кабинет

cURL
# Ваши пакеты, новые сверху
curl "https://api.aitunnel.ru/v1/batches?limit=20" \
  -H "Authorization: Bearer sk-aitunnel-xxx"

# Все асинхронные задачи (пакеты и видео), которые ещё держат резерв
curl "https://api.aitunnel.ru/v1/jobs?active=true" \
  -H "Authorization: Bearer sk-aitunnel-xxx"

Параметры списка: status, active=true, limit (до 100), after. GET /v1/jobs дополнительно возвращает active_count и active_reserved_rub — сколько задач выполняется и сколько рублей они держат; у каждой записи есть kind, reserved_rub, а после расчёта cost_rub и refunded_rub. Чужой id вернёт 404.

То же самое без кода — Статистика → Задачи в кабинете: сумма резерва, статусы, фактическое списание и возврат, фильтры по типу и статусу. Результаты завершённого пакета оттуда можно выгрузить в JSON. Во вкладке «Расходы» рядом — только фактически потраченное, резервы туда не попадают.

Другие формы API

Все элементы одного пакета — один endpoint. Смешать формы — несколько пакетов.

Пример Messages:

JSON
{
  "endpoint": "/v1/messages",
  "model": "claude-opus-4.8",
  "requests": [
    {
      "custom_id": "req-1",
      "body": {
        "max_tokens": 32,
        "messages": [
          { "role": "user", "content": "Скажи привет." }
        ]
      }
    }
  ]
}

/v1/embeddings работает так же, если у модели в каталоге есть batch. В body обязательно input: строка, массив строк или массив токенов. Картинки и прочий мультимодальный вход, а также input_type в Batch не принимаются — для них синхронный /embeddings.

Ограничения

  • Картинки — только публичные http(s) URL. Base64 и data: URI не принимаются. URL работают, если модель принимает картинки и провайдер пакета умеет их скачивать: OpenAI, Anthropic, xAI, DeepInfra (только /v1/chat/completions).
  • Файлы (input_file в Responses, document в Messages, file в Chat Completions) — только по URL и только у OpenAI (только /v1/responses), Anthropic, Mistral и DeepInfra (только /v1/chat/completions). Байты файла и id загруженных файлов не принимаются.
  • Аудио и видео на входе не принимаются. В /v1/chat/completions отклоняются и запросы на нетекстовый вывод (modalities, audio, image_config).
  • Веб-поиск — только встроенный поиск провайдера: web_search у OpenAI в /v1/responses, web_search_20250305 у Anthropic в /v1/messages. Плагин web и web_search_options (кроме моделей OpenAI) не принимаются.
  • Прочее: пустой messages / input, stream: true, speed, лимит вывода меньше 1 и бета-функции Anthropic отклоняются. Неизвестные параметры отбрасываются, как в синхронном API.
  • На моделях Google все body в пакете должны иметь одинаковый response_format (или все без него), а для json_schema — одинаковую схему. Иначе пакет не пройдёт проверку: отправляйте отдельный пакет на каждый response_format.
  • Имена пресетов в model не подходят — только каталожные id.

Большинство этих проверок проходит уже после 202: пакет переходит в failed, в error — причина, резерв возвращается полностью. Такие запросы отправляйте в синхронный API или закрепите пакет за подходящим провайдером через provider.only.