# ApiraCloud API

Production Base URL: `https://apiracloud.ru/v1`. Локальный сервер по умолчанию: `http://localhost:8787/v1`.
Ключ клиента создаётся в кабинете → API-ключи. Доступы к внешним маршрутам остаются на сервере.

## Доступные методы

- `GET /v1/models` — публичный каталог: ID, название, описание, контекст, максимум ответа, возможности и цены в рублях за миллион токенов. Выбирайте `id` из ответа.
- `POST /v1/chat/completions` — текстовый Chat Completions, с обычным ответом или SSE.
- `POST /v1/images` — изображения, JSON с `model` и `prompt`.
- `POST /v1/audio/speech` — озвучка текста, JSON с `model`, `input`, `voice`; возвращает аудиопоток.
- `POST /v1/audio/transcriptions` — распознавание PCM WAV как base64 в `input_audio`.
- `POST /v1/audio/generations` — генерация звука аудио-моделями; полный результат в base64 после сверки стоимости.
- `POST /v1/media/understand` — анализ inline PNG/JPEG, короткого MP4 или WAV с текстовым вопросом.
- `POST /v1/videos` — запуск видео; `GET /v1/videos/<id>` — статус; `GET /v1/videos/<id>/content` — готовый файл.
- `GET /healthz` — работоспособность процесса; это не проверка всех внешних поставщиков.

`/api/client/*` и `/api/admin/*` — API кабинета с cookie-сессией; Bearer-ключ для них не подходит.
Responses, embeddings, универсальная загрузка файлов и мультимодальный чат пока не реализованы. Медиа работает только через API для моделей с `metadata.route_ready=true`; карточки «Скоро» остаются справочными. Для платного запроса нужен актуальный утверждённый тариф и доступный баланс. Видео обрабатывается асинхронно и требует отдельного разрешения режима хранения без ZDR.

## Первый запрос (curl)

```sh
export APIRACLOUD_BASE_URL='http://localhost:8787/v1'
export APIRACLOUD_API_KEY='mg_live_...'
curl "$APIRACLOUD_BASE_URL/models"
curl "$APIRACLOUD_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $APIRACLOUD_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: example-001' \
  -d '{"model":"apiracloud/free","messages":[{"role":"user","content":"Привет!"}],"max_tokens":256}'
```

`apiracloud/free` автоматически выбирает доступный бесплатный маршрут. Для него действует общий клиентский лимит, указанный в каталоге.

## Python

```python
import os
import uuid
from openai import OpenAI

client = OpenAI(base_url=os.environ['APIRACLOUD_BASE_URL'],
                api_key=os.environ['APIRACLOUD_API_KEY'], max_retries=0)
response = client.chat.completions.create(
    model='apiracloud/free',
    messages=[{'role': 'user', 'content': 'Привет!'}],
    max_tokens=256,
    extra_headers={'Idempotency-Key': str(uuid.uuid4())},
)
print(response.choices[0].message.content)
print(response.usage)
```

## JavaScript (fetch)

```js
const response = await fetch(`${process.env.APIRACLOUD_BASE_URL}/chat/completions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.APIRACLOUD_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({model:'apiracloud/free', messages:[{role:'user',content:'Привет!'}], max_tokens:256}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error?.message || `HTTP ${response.status}`);
if (response.status === 202) console.log('Запрос уже существует:', result.id);
else console.log(result.choices[0].message.content);
```

## Параметры

Обязательны `model` и `messages` (1–200 сообщений). `content` — строка; роли: `system`, `developer`, `user`, `assistant`, `tool`.
Допускаются поля сообщения `name` и `tool_call_id`. Массивы контента с изображениями и `assistant.tool_calls` пока отклоняются. Полный цикл tool calling пока не поддерживается.

- `max_tokens` **или** `max_completion_tokens`: положительное целое; по умолчанию 1024, ограничивается максимумом модели.
- `stream`: boolean, по умолчанию false.
- `temperature`: 0–2; `top_p`: 0–1.
- `presence_penalty`, `frequency_penalty`: от −2 до 2.
- `seed`: целое в диапазоне ±2147483647.
- `stop`, `tools` (до 32), `tool_choice`, `response_format` передаются поставщику; поддержка зависит от модели.
- Другие поля, включая `n`, `plugins`, `provider`, `modalities`, `stream_options`, отклоняются. Usage шлюз запрашивает сам при необходимости.

Ограничения: тело запроса до 2 MiB по умолчанию, текст сообщений до 1 500 000 байт; контекст проверяется предварительной оценкой токенов.

## Потоковые ответы

Передайте `"stream":true`; ответ имеет тип `text/event-stream`. Читайте строки `data: <JSON>`, собирайте `choices[].delta.content`; завершение — `data: [DONE]`.
Сетевые чанки не совпадают с границами SSE-событий: используйте буфер и разделитель пустой строки. Ошибка после начала потока может прийти событием SSE; одного HTTP 200 недостаточно для признания запроса успешным.
Usage приходит в последнем событии. Шлюз продолжает получать его при разрыве клиентского соединения в пределах серверных таймаутов. Если расход не установлен, запрос остаётся `usage_pending`, а резерв не освобождается автоматически.

## Стоимость и идентификаторы

Сохраняйте заголовок `X-Request-Id`. В обычном ответе есть `gateway.request_id`, `gateway.charged_micros`, `gateway.currency`, `gateway.price_version`, а также заголовок `X-ApiraCloud-Cost-Micros`.
Один рубль = 1 000 000 micros. Для SSE итоговую сумму смотрите в кабинете; она ещё неизвестна при отправке HTTP-заголовков.

Для медиа проверяйте `X-ApiraCloud-Billing-Status`. Если он равен `usage_pending`, файл уже доставлен, но поставщик ещё не подтвердил финальную стоимость. Не отправляйте запрос повторно: резерв и итоговое списание автоматически сверяются, а финальная сумма появляется в кабинете.

Перед запросом блокируется предварительный резерв. После ответа стоимость рассчитывается по опубликованным входной и выходной ставкам модели, а остаток резерва освобождается. Итог доступен в кабинете.

## Повторы и ошибки

Повторяйте один логический запрос с прежним `Idempotency-Key` и тем же JSON. Повтор возвращает **202**, `id`, `status`, `idempotent_replay:true`; текст первоначального ответа не хранится и не воспроизводится. Изменение тела с тем же ключом даёт 409.
Новый ключ означает новый оплачиваемый запрос. Не включайте слепые автоматические повторы после таймаута.

Ошибки имеют форму `{"error":{"code":"...","message":"...","request_id":"req_..."}}`. До создания запроса `request_id` может отсутствовать.

- 400 — неверные параметры, неподдерживаемый контент или превышение контекста.
- 401 — отсутствующий/отозванный/истёкший ключ.
- 402 — недостаточно доступного баланса для резерва.
- 403 — запрет модели для ключа; в кабинете также CSRF/права/2FA.
- 404 — модель или объект не найдены.
- 409 — конфликт идемпотентности, резерва или версии цены.
- 413 — слишком большой запрос.
- 429 — RPM/TPM, дневной или месячный бюджет.
- 502/503/504 — ошибка поставщика, недоступный маршрут, таймаут или глобальный бюджет.

## API кабинета

Сессия получается через `POST /api/auth/login`. `GET /api/auth/me` возвращает пользователя и `csrf_token`; для изменения данных нужен `X-CSRF-Token`, сессионная и CSRF-cookie. Origin должен совпадать с `PUBLIC_ORIGIN`. `POST /api/auth/logout` удаляет сессию и cookies.

- `GET /api/client/usage`: последние 200 запросов текущего клиента.
- `GET /api/client/usage-summary?from=<ISO>&to=<ISO>`: агрегат по моделям за период `[from,to)`, по умолчанию текущий месяц UTC.
- `GET /api/client/requests/<id>`: состояние собственного запроса.
- `GET /api/client/ledger`: последние 200 операций.
Для входа на новом устройстве при включённой защите нужен код из письма. После подтверждения устройство запоминается на 30 дней. Административная документация доступна отдельно операторам сервиса.
