# Artillect API — изображения, видео, аудио, 3D и чат

Kling 4 (`kling-4`): 3–30 секунд, 720p/1080p. `keyframes: [url, ...]` — до 10 последовательных кадров (и не больше duration + 1), task `keyframes`; либо `reference_images`, `reference_videos`, `reference_audios`, task `ref2v`. `elements` принимает до 7 именованных персонажей с 2–4 `image_urls` или одним `video_url`, опциональным `voice_url`; `voices: [{name, audio_url}]` — до 3 голосов. Кадры и референсы не смешиваются. Всего до 15 материалов, до 5 видео/30 секунд суммарно. `keep_original_sound` сохраняет звук входного видео; без него native audio создаётся автоматически. MCP принимает те же поля; CLI предоставляет `--keyframes`, `--elements`, `--voices`, `--keep-original-sound`. Сначала получите точную оценку через `/api/v1/estimate/`.

> **Обновление 2026-10-16:** [все изменения и действия для интеграторов](/docs/api-updates/2026-10-16.md). JSON без авторизации: [`GET /api/v1/updates/`](/api/v1/updates/). Каталоги Public API и Provider содержат поле `api_update`; в общих API-заголовках доступны `X-Artillect-API-Update` и `Link: <…>; rel="changelog"`. Сравнивайте ID обновления и перечитывайте уведомление при изменении.

REST API: **генерация изображений, видео, аудио и 3D** (async poll), **image/video upscale** (async poll), SwitchX (async poll) и **LLM chat** (sync).
Async: images/videos/upscale/switchx/audio/mesh submit → poll (или push через webhooks, где поддерживается). Chat: один запрос → ответ.

Typed media editing (`trim` / ordered `concat`) существует в контракте API, но сейчас является WIP: в production `POST /api/v1/media/operations/` и связанные poll/cancel endpoints возвращают `503 MEDIA_OPERATIONS_WIP`, пока не включён `MEDIA_OPERATIONS_PUBLIC_ENABLED`.

> **Обратная совместимость:** все изменения аддитивны. Старые запросы (без новых полей `project_id`, `billing_source`, без заголовков `Idempotency-Key`) работают как раньше. Новые поля в ответах (`request_id` и т.п.) можно игнорировать.

---

## FAL: новая партия 2026-10-02

Добавлены семь public slugs для десяти native endpoint'ов. Это контракт реализации; наличие в вашем окружении сверяйте через GET /api/v1/models/. Платный provider smoke этой партии локально не выполнен. 1 токен = 1 US cent; цена запроса ceil(USD × 100), без дополнительной наценки.

| Slug                 | Запрос                                                    | Цена в токенах                                                       |
| -------------------- | --------------------------------------------------------- | -------------------------------------------------------------------- |
| grok-imagine-15-lite | videos/generations: t2v либо start_image_url              | 480p 2/с, 720p 3/с, 1080p 14/с; i2v +1 за вход                       |
| pixelcut-loop        | videos/generations: одно start_image_url, prompt optional | 480p 8/с, 768p 16/с, 1080p 32/с                                      |
| flux-3-image         | images/generations: gen либо input_urls edit              | 1K 3 до 2026-10-08 UTC, затем 5                                      |
| ideogram-45          | images/generations: gen либо ordered input_urls edit      | low 3, medium 6, high 22 за изображение; edit very_low ceil(0.8 × n) |
| veed-clean-audio     | audio/generations: audio_url либо video_url               | ceil(1.25 × billableMinutes), минимум 1 минута                       |
| eleven-v4-turbo-tts  | audio/generations: text (или prompt)                      | ceil(4 × characters / 1000)                                          |
| eleven-v4-tts        | audio/generations: text (или prompt)                      | ceil(8 × characters / 1000)                                          |

Все submit paths начинаются с /api/v1/ и завершаются /. Native prefix fal-ai/ не добавляется. Slug flux-3 остаётся видео-моделью; FLUX Image использует отдельный flux-3-image.

- Grok Lite: duration 1–15 (default 6), resolution 480p/720p/1080p (720p); prompt <=4096. T2v AR 16:9/4:3/3:2/1:1/2:3/3:4/9:16; i2v наследует исходник. Нет end frame или refs.
- Pixelcut: duration 5–15 (5), resolution 480p/768p/1080p (1080p), motion subtle/spin (subtle), include_audio false/true (false); фото JPEG/PNG/WebP. Нет aspect_ratio или source video. Default 5 с/1080p = 160 токенов, камера фиксирована.
- FLUX Image: только подтверждённый 1K; другие provider sizes закрыты до полной ценовой сетки. Один результат, edit до 10 inputs (provider >=256 px/сторона и <=4 MP каждый), первый определяет auto AR. output_format jpeg/png (jpeg), safety_tolerance 0–4 (2), enable_prompt_expansion false. Дата конца акции опубликована без timezone, граница UTC — правило Artillect.
- Ideogram: n=1..8 (1); размер не меняет тариф, very_low доступно только edit. Первый input_urls — source, далее до 4 refs, с маской до 3. mask_url: чёрное меняется/белое сохраняется, обе области обязательны, размеры как source. edit_precision regular/high (regular); маска или high требуют image_size=auto. Unmasked regular custom sides >=256, multiple32, <=4194304 px, ratio<=6. Gen custom sizes ограничены точным enum схемы, не произвольные. Очень высокое very_high не публикуется: описание high precision расходится с enum и не содержит подтверждённой цены.
- VEED: до 30 минут/512 MB на стороне provider, strength 0–1 (0.874), target_lufs -40…-8 (-19), null отключает нормализацию, output_format flac/wav (flac). Выход — mono 48 kHz audio. Quote принимает duration секунды; submit измеряет источник сервером до billing. Целые секунды (Math.round), затем вверх до минут, затем вверх до токенов: 1/10/30 минут = 2/13/38 токенов. Лимиты upload могут быть ниже provider caps.
- V4/Turbo: 1–5000 символов фактически отправленного text, voice Rachel, stability 0–1 (0.5), similarity_boost 0–1 (0.75), optional seed uint32, ISO639-1 language_code, apply_text_normalization auto/on/off, timestamps false/true. Style/speed/previous_text/next_text не поддерживаются. MP3/Opus разрешены, default mp3_44100_128; raw PCM/μ-law/A-law закрыты до упаковки. Seed даёт best-effort воспроизводимость, не гарантию. Основной результат — audio, отдельный subtitle файл не обещается.

Источники с полями, defaults и ценами:

- Grok Lite [t2v](https://fal.ai/models/xai/grok-imagine-video/v1.5/lite/text-to-video/llms.txt), [i2v](https://fal.ai/models/xai/grok-imagine-video/v1.5/lite/image-to-video/llms.txt).
- FLUX Image [gen](https://fal.ai/models/blackforestlabs/flux-3/text-to-image/llms.txt), [edit](https://fal.ai/models/blackforestlabs/flux-3/edit-image/llms.txt).
- Ideogram [gen](https://fal.ai/models/ideogram/v4.5/llms.txt), [edit](https://fal.ai/models/ideogram/v4.5/edit/llms.txt), [edit schema](https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=ideogram/v4.5/edit).
- [Pixelcut](https://fal.ai/models/pixelcut/looping-video/llms.txt), [VEED](https://fal.ai/models/veed/clean-audio/llms.txt), Eleven [V4](https://fal.ai/models/elevenlabs/tts/eleven-v4/llms.txt), [Turbo](https://fal.ai/models/elevenlabs/tts/eleven-v4-turbo/llms.txt).

SDK передаёт JSON body в generateImage/generateVideo/generateAudio; MCP использует соответствующий generate_* и extra для advanced полей, CLI — --extra. Jobs возвращаются асинхронно: poll/get_task; estimate не отправляет генерацию и не списывает баланс.

## Base URL

| Окружение      | URL                         | Примечание    |
| -------------- | --------------------------- | ------------- |
| **Production** | `https://app.artillect.pro` | Public API v1 |

Все запросы — к тому же origin, где у пользователя аккаунт.  
`verification_url` и URL картинок формируются из домена запроса.

Документация: `https://app.artillect.pro/docs/artillect-api.md`.

**OpenAPI 3.1:** [`/docs/artillect-openapi.yaml`](./artillect-openapi.yaml) — `https://app.artillect.pro/docs/artillect-openapi.yaml`. **Interactive reference (Scalar):** [`/docs/api/`](/docs/api/) — `https://app.artillect.pro/docs/api/` (Try-it: paste `art_…` Bearer; auth persists in browser localStorage). **MCP install (Claude/Cursor):** [`/docs/mcp`](/docs/mcp). **CLI (терминал):** [`/docs/cli`](/docs/cli) · markdown [`artillect-cli.md`](./artillect-cli.md). **Curated use cases (UGC / overnight / ecommerce):** [`/docs/use-cases`](/docs/use-cases) · markdown [`artillect-use-cases.md`](./artillect-use-cases.md). **Features (mechanics):** [`/docs/features`](/docs/features) · [`artillect-features.md`](./artillect-features.md). Core v1 paths + schemas; model-specific params → `GET /api/v1/models/`.

### Changelog (integrator-facing)

| Version | Notes                                                                                                                                                                                                                                                                                                                                    |
| ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1.3.4   | Personal LoRA list/get (`models:read`), immutable version selection for Z-Image Turbo image generation; training/import available in Studio.                                                                                                                                                                                             |
| 1.3.3   | Generation cancel/refund: independent four-second pre-submit window for Studio, REST API, MCP and CLI; after provider submission cancellation is unavailable. Deferred jobs survive navigation/process restart via reconcile.                                                                                                            |
| 1.3.2   | OpenAPI: assets (`ast_…` + content/tile), universal `GET /generations/{id}/`, chat threads, agent `POST /feedback/`, media operations (WIP). Drift gate covers all v1 `route.ts`.                                                                                                                                                        |
| 1.3.0   | Media operations: asynchronous typed `trim` / ordered `concat` for owned video `asset_id` values; `POST /api/v1/media/operations/` → poll / cancel. No arbitrary FFmpeg arguments. MCP: `edit_media`, `get_media_operation`, `cancel_media_operation`.                                                                                   |
| 1.1.10  | Poll: `completed` only with durable MinIO `images`/`videos`; premature empty → `running` + finalizing hint (fixes agent pipelines). Webhook `generation.completed` deferred until stable URLs. MCP 0.4.8                                                                                                                                 |
| 1.2.0   | **LLM на OpenRouter**: новые слаги (`gpt-5.6-luna` default, `gpt-5.6-terra`, `claude-sonnet-5`, `claude-opus-5`, `deepseek-v4-flash`); старые (`gpt-5-5`, `claude-sonnet-4-6`, `claude-opus-4-8`) — deprecated-алиасы; **лимит 32 сообщения снят**; тред-режим `POST /chat/conversations/{id}/messages/`; `stream:true` для всех моделей |
| 1.1.9   | Poll/MCP: `media_urls` / `images` **only** Artillect media (no `tempfile.aiquickdraw.com` fallback); empty + finalizing hint until mirror ready; document submit spacing ~3s (429)                                                                                                                                                       |
| 1.1.8   | Files: `POST /files/from-url/`, quota-aware `POST /files/presign/` + `/files/presign/finalize/` (image/audio); input media **max 1 redirect hop**; MCP Agent DX (`get_started`, `upload_file({url})`, `create_upload_url` + `finalize_upload`, element≠turbo)                                                                            |
| 1.1.7   | 3D: `POST /api/v1/mesh/generations/` (Meshy v6/v7 t2m/i2m/multi-i2m, GLB) + MCP `generate_mesh`; `estimate` `type: "mesh"`                                                                                                                                                                                                               |
| 1.1.6   | Аудио: `POST /api/v1/audio/generations/` (исходно Suno V5.5, сейчас V6) + MCP `generate_audio`; `estimate` `type: "audio"`                                                                                                                                                                                                               |
| 1.1.5   | SSRF harden (input + webhook delivery); daily per-key spend cap; chat per-key submit RL; MCP HTTP Bearer-only                                                                                                                                                                                                                            |
| 1.1.4   | DeepSeek chat: `thinking` disabled by default; clearer empty/`max_tokens` errors (no silent sanitize)                                                                                                                                                                                                                                    |
| 1.1.3   | Chat default → `gpt-5-5` (KIE Claude capacity); `PROVIDER_BUSY` for tight resources                                                                                                                                                                                                                                                      |
| 1.1.2   | Per-key usage: `request_count`, `tokens_spent`, `last_route` on key list / `/api` UI                                                                                                                                                                                                                                                     |
| 1.1.1   | API key `name` / `last_used_at` / `scopes`; `PATCH /api/v1/keys/{id}`; `403 INSUFFICIENT_SCOPE`                                                                                                                                                                                                                                          |
| 1.1.0   | Scalar Try-it auth persist; OpenAPI↔routes CI drift gate; project manage + webhooks deliveries already in surface                                                                                                                                                                                                                        |
| 1.0     | Initial OpenAPI 3.1 + Scalar docs                                                                                                                                                                                                                                                                                                        |

### Trailing slash

Канон URL — **со слешем** в конце (`/api/v1/device/code/`).  
В приложении включён `skipTrailingSlashRedirect`: Next **не** отвечает 308 на вариант без `/`, оба пути обслуживаются.

Клиент приложения нормализует URL через `authFetch` / `appFetch`. Внешним клиентам всё равно лучше слать канон со `/` (curl без `-L` и без слеша раньше терял POST body на 308).

---

## Лимиты ввода (все модели)

Источник в коде: `src/lib/generation/kieInputLimits.js`, registry `imageModels.ts` / `llmModels.ts`. Каталог `GET /api/v1/models/` — машинно-читаемый источник тех же лимитов.
Для **images** лимит — поле `prompt` (символы Unicode, `String.length`). Для **chat** — сумма всех `messages[].content` (только строки).

### Images (`POST /api/v1/images/generations/`)

| `model`                 | max `prompt` (символов) | Источник                                                                    | Другое на вход                                                                                                                             |
| ----------------------- | ----------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `gpt-image-2` (default) | **7 000**               | KIE/FAL route cap                                                           | i2i: до **16** ref (`input_urls` / `images`)                                                                                               |
| `gpt-image-25-flare`    | **20 000**              | FAL GPT Image 2.5 Flare                                                     | i2i: до **16** refs; `quality` auto\|low\|medium\|high\|xhigh\|max; extra: `background`, `mask_url`, `output_format`, `output_compression` |
| `gpt-image-25-sunburst` | **20 000**              | FAL GPT Image 2.5 Sunburst                                                  | то же, прецизионный вариант                                                                                                                |
| `gpt-image-15`          | **2 000**               | app/provider probe (2k accepted, 5k rejected)                               | i2i: до **16** refs; `quality` medium\|high                                                                                                |
| `nano-banana`           | **8 000**               | app guardrail                                                               | i2i: до **10** refs                                                                                                                        |
| `nano-banana-2`         | **8 000**               | app guardrail                                                               | i2i: до **14** refs                                                                                                                        |
| `nano-banana-2-lite`    | **8 000**               | app guardrail                                                               | i2i: до **10** refs; 1K only                                                                                                               |
| `nano-banana-pro`       | **10 000**              | [KIE Nano Banana Pro](https://docs.kie.ai/market/google/pro-image-to-image) | i2i: до **8** refs                                                                                                                         |
| `seedream-4`            | **5 000**               | KIE/FAL ByteDance Seedream 4.0                                              | i2i: до **10** refs                                                                                                                        |
| `seedream-4-5`          | **3 000**               | app/provider probe (3k accepted, 4k rejected)                               | i2i: до **10** refs                                                                                                                        |
| `seedream-5-lite`       | **2 000**               | app/provider probe (2k accepted, 3k rejected)                               | i2i: до **14** refs                                                                                                                        |
| `seedream-5-flash`      | **20 000**              | app guardrail; FAL не публикует лимит                                       | t2i/i2i: до **10** refs; 3 токена за изображение                                                                                           |
| `recraft-v41`           | **20 000**              | app guardrail; FAL не публикует лимит                                       | только t2i, без refs; 4 токена за изображение                                                                                              |
| `recraft-v41-vector`    | **20 000**              | app guardrail; FAL не публикует лимит                                       | только t2i, SVG-результат; 8 токенов за изображение                                                                                        |
| `seedream-5-pro`        | **5 000**               | KIE Seedream 5 Pro                                                          | i2i: до **14** refs; `quality` **1K\|2K** (→ basic\|high)                                                                                  |
| `seedream-5-pro-layers` | **5 000**               | KIE Seedream layer-decomposition                                            | ровно **1** input; `size` auto\|1K\|1.5K\|2K; prompt optional                                                                              |
| `qwen3`                 | **800**                 | [KIE Qwen3](https://docs.kie.ai/market/qwen3/text-to-image.md)              | i2i: до **3** refs; resolution 1K\|2K                                                                                                      |
| `qwen3-pro`             | **800**                 | [KIE Qwen3 Pro](https://docs.kie.ai/market/qwen3-pro/text-to-image.md)      | i2i: до **3** refs; 1K\|2K                                                                                                                 |
| `flux2-pro`             | **8 000**               | app guardrail                                                               | i2i: до **8** refs; resolution 1K\|2K                                                                                                      |
| `flux2-max`             | **8 000**               | app guardrail                                                               | i2i: до **8** refs; resolution 1K\|2K                                                                                                      |
| `flux2-flex`            | **8 000**               | app guardrail                                                               | i2i: до **8** refs; resolution 1K\|2K                                                                                                      |
| `grok-imagine`          | **5 000**               | app/provider probe (5k accepted, 6k rejected)                               | i2i: до **1** ref                                                                                                                          |
| `wan-27-image`          | **500**                 | FAL route cap                                                               | i2i: до **9** refs; default resolution 2K                                                                                                  |
| `wan-27-image-pro`      | **500**                 | FAL route cap                                                               | i2i: до **9** refs; default resolution 2K                                                                                                  |
| `cosmos-3-super`        | **8 000**               | app/provider probe                                                          | t2i only (refs → `400`)                                                                                                                    |
| `mai-image-25`          | **8 000**               | app/provider probe                                                          | i2i: до **1** ref                                                                                                                          |
| `mai-image-25-pro`      | **8 000**               | app/provider probe                                                          | i2i: до **1** ref                                                                                                                          |
| `reve-21`               | **8 000**               | app guardrail                                                               | i2i: 1→edit / 2+→remix, max **8**                                                                                                          |
| `krea-v2-large`         | **8 000**               | app guardrail                                                               | style refs на t2i, max **10**                                                                                                              |
| `krea-v2-medium`        | **8 000**               | app guardrail                                                               | style refs на t2i, max **10**                                                                                                              |
| `krea-v2-medium-turbo`  | **8 000**               | app guardrail                                                               | style refs на t2i, max **10**                                                                                                              |
| `qwen-multiple-angles`  | **8 000**               | app guardrail                                                               | **обязателен** 1 ref; `prompt` опционален                                                                                                  |

Превышение → `400 PROMPT_TOO_LONG`. Одинаковый лимит для text-to-image и image-to-image (поле `prompt`). Значения с пометкой `app guardrail` — безопасный предел Artillect, пока провайдер не публикует число.

### Chat (`POST /api/v1/chat/completions/`)

| `model`                | Контекст OpenRouter | Выход OpenRouter |   Вход Artillect | Vision |
| ---------------------- | ------------------: | ---------------: | ---------------: | :----: |
| `gemini-4-argon`       |   1 048 576 токенов |   65 536 токенов | контекст модели¹ |   ✓    |
| `claude-sonnet-5.5`    |   1 000 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `claude-haiku-5.5`     |   1 000 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `gpt-6.1-sol`          |   1 050 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `gpt-6.1-sol-pro`      |   1 050 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `muse-wonder`          |   1 048 576 токенов |  943 718 токенов | контекст модели¹ |   ✓    |
| `mistral-large-4-0`    |   1 048 576 токенов |  262 144 токенов | контекст модели¹ |   ✓    |
| `step-5-preview`       |   1 048 576 токенов |   64 000 токенов | контекст модели¹ |   ✓    |
| `pareto-26.9`          |     262 144 токенов |  131 072 токенов | контекст модели¹ |   ✓    |
| `lfm-2.5-24b-a2b`      |     128 000 токенов |    8 192 токенов | контекст модели¹ |   ✗    |
| `ling-3.1-flash`       |     262 144 токенов |  235 929 токенов | контекст модели¹ |   ✗    |
| `perceptron-mk15`      |      36 864 токенов |    8 192 токенов | контекст модели¹ |   ✓    |
| `gpt-6-luna` (default) |   1 050 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `gpt-6-astra`          |   1 050 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `claude-fable-5.1`     |   1 000 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `claude-opus-5.5`      |   1 000 000 токенов |  128 000 токенов | контекст модели¹ |   ✓    |
| `gemini-3.8-flash`     |   1 048 576 токенов |   65 536 токенов | контекст модели¹ |   ✓    |
| `deepseek-v4.1-flash`  |   1 048 576 токенов |  943 718 токенов | контекст модели¹ |   ✓    |
| `deepseek-v4-pro`      |   1 048 576 токенов |  393 216 токенов | контекст модели¹ |   ✗    |
| `qwen3.8-flash`        |   1 000 000 токенов |  131 072 токенов | контекст модели¹ |   ✓    |
| `qwen3.8-max-prime`    |   1 000 000 токенов |  131 072 токенов | контекст модели¹ |   ✓    |
| `grok-4.7`             |     500 000 токенов |  450 000 токенов | контекст модели¹ |   ✓    |
| `muse-spark-1.3`       |   1 048 576 токенов |  943 718 токенов | контекст модели¹ |   ✓    |
| `command-a-plus`       |     192 000 токенов |   64 000 токенов | контекст модели¹ |   ✓    |
| `kimi-k3`              |   1 048 576 токенов |  943 718 токенов | контекст модели¹ |   ✓    |

**Удалённые модели:** GPT-5.6 Luna/Terra/Sol, GPT-6 Sol, Claude Sonnet 5 и Opus 5, Gemini 3.6 Flash, Grok 4.5, DeepSeek V4 Flash и Muse Spark 1.2 больше не доступны для новых запросов. Их старые алиасы также отклоняются; выберите актуальный ID из `GET /api/v1/models/`. Исторические записи и их стоимость сохраняются. Дефолт без `model` — `gpt-6-luna`.

¹ Общего лимита символов текста у LLM API нет: вход ограничен токеновым контекстом модели с учётом ответа. `max_input_chars` и `chat.max_total_chars` в discovery равны `null`. Размер HTTP JSON ограничен 8 MiB (413 `BODY_TOO_LARGE`); это защита памяти сервера, а не оценка токенов. В discovery также есть `provider_context_length_tokens` — окно конкретного top-provider-маршрута. Значения контекста и вывода взяты из официального [OpenRouter Models API](https://openrouter.ai/docs/api/api-reference/models/get-models) на 2026-10-15; фактический доступный input зависит от токенизации и места, оставленного под ответ.

**Vision-лимиты (модели с vision):** зависят от модели; используйте `max_images_per_message`, `max_images_total` и `image_limit_doc_url` в discovery. Ограничения сайта 4/8 не применяются к API. Изображения зеркалируются в Artillect media; большие и экзотические форматы нормализуются перед провайдером.
**Видео во входе chat — не поддерживается.** Для text-only моделей картинки → `400 CHAT_VISION_UNSUPPORTED`; проверяйте `supports_vision` выбранной модели в каталоге.

**Выход chat:** API по умолчанию использует максимум выбранной модели. `max_tokens` (алиасы `max_completion_tokens`, `max_output_tokens`, `maxTokens`; только одно поле) позволяет запросить до `provider_max_output_tokens` из `GET /api/v1/models/`. Завышенное значение отклоняется с 400, без молчаливого уменьшения. Мышление входит в общий бюджет. Ассистент сайта отдельно ограничен 4096 токенами.

**Оплата API chat:** до каждого вызова резервируется верхняя оценка стоимости входа и запрошенного выхода, включая мышление и тарифные ступени. Нужен достаточный баланс и, при оплате владельцем проекта, лимит редактора. После ответа списывается фактический `usage.cost` с прежней наценкой 20%, с округлением вверх; остаток возвращается. Обрыв клиента не отменяет оплату потреблённых токенов. Если стоимость неизвестна, резерв остаётся до расчёта воркером; `billing_pending: true` означает, что `cost_tokens` пока показывает резерв. Итог отражается в балансе и истории. `finish_reason` возвращается и в JSON, и в SSE; `length` означает неполный ответ. При таймауте не повторяйте платный запрос автоматически.

Превышение 200k символов → `400 MESSAGES_TOO_LONG`. Лимита на число сообщений нет; история ограничивается объёмом контекста. Слишком много картинок → `400 MESSAGES_TOO_MANY_IMAGES`.

---

## Обзор

```
1. POST /api/v1/device/code/        → device_code + verification_url
2. ⛔ ПОКАЗАТЬ verification_url пользователю → ждать «выдал разрешение»
3. POST /api/v1/device/token/       → poll до api_key (art_...)
4. POST /api/v1/images/generations/ → job id (images)
5. GET  /api/v1/images/generations/{id}/ → poll image
6. POST /api/v1/videos/generations/ → job id (video)
7. GET  /api/v1/videos/generations/{id}/ → poll video
8. POST /api/v1/upscale/generations/ → job id (image/video upscale)
9. GET  /api/v1/upscale/generations/{id}/ → poll upscale
10. GET /api/v1/projects/            → список доступных проектов
11. GET /api/v1/projects/{id}/elements/ → element library проекта
12. GET /api/v1/projects/{id}/tags/      → теги проекта (+ POST/PATCH/DELETE)
13. POST /api/v1/switchx/generations/ → job id (SwitchX / Beeble)
14. GET  /api/v1/switchx/generations/{id}/ → poll SwitchX
15. POST /api/v1/audio/generations/  → job id (Suno V6, музыка; другие аудиозадачи по `model`)
16. GET  /api/v1/audio/generations/{id}/ → poll аудио
17. POST /api/v1/mesh/generations/   → job id (Meshy v6/v7, 3D mesh)
18. GET  /api/v1/mesh/generations/{id}/ → poll mesh (GLB)
19. POST /api/v1/chat/completions/   → LLM ответ, stateless (sync; опц. SSE)
19b. POST /api/v1/chat/conversations/{id}/messages/ → тред-режим (историю держит сервер)
20. GET  /api/v1/models/             → список моделей и лимитов (без auth)
```

### Вспомогательные эндпоинты

| Эндпоинт                                                | Назначение                                                                                      |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `GET /api/v1/me/`                                       | Кто я: `user_id`, `email`, `token_balance` (проверить ключ)                                     |
| `GET /api/v1/balance/`                                  | Личный баланс токенов                                                                           |
| `POST /api/v1/estimate/`                                | Узнать цену **до** генерации (без списания), `{type, ...body}`                                  |
| `POST /api/v1/media/operations/`                        | WIP: typed `trim` / ordered `concat` для owned video Assets (сейчас 503 в production)           |
| `GET /api/v1/media/operations/{id}/`                    | WIP: poll media operation                                                                       |
| `POST /api/v1/media/operations/{id}/cancel/`            | WIP: cancel media operation                                                                     |
| `POST /api/v1/files/`                                   | Загрузить файл (multipart `file`) → media key + URL для повторного использования на входе       |
| `POST /api/v1/files/from-url/`                          | Зеркалировать публичный HTTPS URL в media (SSRF-гард) → тот же `{ key, url }`                   |
| `POST /api/v1/files/presign/`                           | Quota-aware presigned PUT для image/audio (не video) → `{ asset_id, upload_url, finalize_url }` |
| `POST /api/v1/files/presign/finalize/`                  | Завершить direct PUT, активировать Asset Library и списать резерв квоты                         |
| `GET /api/v1/images/generations/`                       | История image-генераций (пагинация)                                                             |
| `GET /api/v1/videos/generations/`                       | История video-генераций (пагинация)                                                             |
| `GET /api/v1/upscale/generations/`                      | История upscale-генераций (пагинация)                                                           |
| `GET /api/v1/switchx/generations/`                      | История SwitchX-генераций (пагинация)                                                           |
| `GET /api/v1/audio/generations/`                        | История аудио-генераций (пагинация)                                                             |
| `GET /api/v1/mesh/generations/`                         | История 3D-генераций (пагинация)                                                                |
| `GET /api/v1/projects/`                                 | Проекты, доступные API-пользователю (owned + member)                                            |
| `POST /api/v1/projects/`                                | Создать проект `{ name, description?, status? }`                                                |
| `GET /api/v1/projects/{id}/`                            | Один проект + роль вызывающего                                                                  |
| `PATCH /api/v1/projects/{id}/`                          | Обновить проект (owner): `{ name?, description?, status? }`                                     |
| `DELETE /api/v1/projects/{id}/`                         | Удалить проект (owner; не личный хаб «Мои генерации»)                                           |
| `GET /api/v1/projects/roles/`                           | Каталог ролей участников (`slug` / `title` / `level`)                                           |
| `GET /api/v1/projects/{id}/members/`                    | Участники проекта (`?state=active_or_invited\|active\|…`)                                       |
| `POST /api/v1/projects/{id}/members/`                   | Пригласить `{ user_id\|email, role?, token_limit? }` (owner)                                    |
| `PATCH /api/v1/projects/{id}/members/{userId}/`         | Роль / лимит / `action: remove\|activate` (owner)                                               |
| `DELETE /api/v1/projects/{id}/members/{userId}/`        | Удалить участника (owner)                                                                       |
| `DELETE /api/v1/projects/{id}/members/me/`              | Выйти из проекта (не owner)                                                                     |
| `PATCH /api/v1/projects/{id}/members/me/`               | Сайдбар: `{ is_favorite }`                                                                      |
| `GET /api/v1/projects/{id}/generations/`                | Лента: `source`, `kind`, `q`, `tag_ids`, `date`/`date_from`/`date_to`, `mine_only`, …           |
| `PATCH /api/v1/projects/{id}/generations/{genId}/`      | Move (`project_id`) / favorite mask (`favorited`); genId = DB id или external_job_id            |
| `DELETE /api/v1/projects/{id}/generations/{genId}/`     | Удалить генерацию                                                                               |
| `POST /api/v1/projects/{id}/generations/{genId}/copy/`  | Копия в другой проект `{ project_id }`                                                          |
| `GET /api/v1/projects/{id}/favorites/`                  | Избранное проекта: counts + items                                                               |
| `GET /api/v1/projects/{id}/analytics/`                  | Сводка: members/gens/tokens + recent ledger/audit                                               |
| `GET /api/v1/projects/{id}/billing/`                    | Роль вызывающего + `token_limit` / `tokens_spent`                                               |
| `GET /api/v1/projects/{id}/elements/`                   | Element library проекта (`@имя` для видео)                                                      |
| `POST /api/v1/projects/{id}/elements/`                  | Создать элемент `{ name, kind, media_json, preview_url }`                                       |
| `GET /api/v1/projects/{id}/elements/{elementId}/`       | Один элемент                                                                                    |
| `PATCH /api/v1/projects/{id}/elements/{elementId}/`     | Обновить элемент (`name?`, `kind?`, `media_json?`, `preview_url?`)                              |
| `DELETE /api/v1/projects/{id}/elements/{elementId}/`    | Soft-delete элемента                                                                            |
| `POST /api/v1/projects/{id}/elements/reorder/`          | `{ ordered_ids: string[] }`                                                                     |
| `GET /api/v1/projects/{id}/tags/`                       | Теги проекта (custom shared; без лайков/системных)                                              |
| `POST /api/v1/projects/{id}/tags/`                      | Создать тег `{ name, color? }`                                                                  |
| `PATCH /api/v1/projects/{id}/tags/{tagId}/`             | Переименовать / перекрасить                                                                     |
| `DELETE /api/v1/projects/{id}/tags/{tagId}/`            | Удалить тег и связи                                                                             |
| `GET /api/v1/projects/{id}/generations/{genId}/likes/`  | Кто лайкнул + `liked_by_me`                                                                     |
| `POST /api/v1/projects/{id}/generations/{genId}/likes/` | Свой лайк add/remove/toggle                                                                     |
| `POST /api/v1/projects/{id}/generations/{genId}/tags/`  | Assign custom tags на существующую генерацию                                                    |
| `POST /api/v1/webhooks/`                                | Зарегистрировать HTTPS webhook для текущего API-ключа                                           |
| `GET /api/v1/webhooks/`                                 | Текущая подписка (`secret_last4`, без полного secret)                                           |
| `DELETE /api/v1/webhooks/`                              | Удалить подписку                                                                                |
| `POST /api/v1/webhooks/rotate-secret/`                  | Новый signing secret (показывается один раз)                                                    |
| `GET /api/v1/webhooks/deliveries/`                      | История доставок (outbox): `limit`, `cursor`, `status`, `job_id`                                |
| `GET /api/v1/webhooks/deliveries/{event_id}/`           | Деталь доставки + `payload`                                                                     |

**Webhooks (MVP):** одна HTTPS-подписка на API-ключ (URL должен резолвиться в публичные IP; redirects при delivery не следуем). По умолчанию события `generation.completed` / `generation.failed` для jobs с `client_source=public_api`, `mcp` или `cli`. Опция `include_studio: true` — также Studio jobs того же пользователя. Poll остаётся source of truth.

### Webhook signing cookbook

Заголовок: `X-Artillect-Signature: sha256=<hex>` = HMAC-SHA256(`secret`, **raw body bytes as UTF-8 string**).

Правила:

1. Сверяй подпись по **сырому** телу запроса — не `JSON.stringify(JSON.parse(body))` (пробелы/порядок ключей сломают HMAC).
2. Сравнивай через timing-safe equal (`crypto.timingSafeEqual` / `hmac.compare_digest`).
3. Дедуп по `payload.id` (`event_id`, вид `evt_…`) — доставки могут ретраиться.
4. Статусы outbox: `pending` → `delivered` | `dead` (после исчерпания попыток). Смотри `GET /api/v1/webhooks/deliveries/`.

Node:

```js
import crypto from "node:crypto";

function verify(secret, rawBody, header) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(String(header || "").trim()));
  } catch {
    return false;
  }
}
```

Python:

```python
import hashlib, hmac

def verify(secret: str, raw_body: bytes, header: str) -> bool:
    digest = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    expected = f"sha256={digest}"
    return hmac.compare_digest(expected, (header or "").strip())
```

**История генераций** (`GET .../generations/` для images, videos, upscale, switchx, audio, mesh): каждый item — `{ id, status, model, created_at, cost_tokens, tokens_charged, project_id, preview_text, source, preview_url }`, плюс `next_cursor`.

**Фильтры листингов** (не листайте курсором вглубь истории — сужайте выборку на сервере): `date=YYYY-MM-DD`, `date_from` / `date_to`, `before_created_at` (ISO), `q` (подстрока промпта, минимум 2 символа), `kind`, `source`. Фид проекта дополнительно принимает `mine_only=1`, `actor_user_id`, `tag_ids=1,2` (совпадение по ВСЕМ тегам) и `service`. Невалидные границы дат дают пустую выборку, а не игнорируются. Query `?source=all` (default) \| `public_api` \| `mcp` \| `cli` \| `studio` фильтрует по `client_source` в БД. Новые Public image jobs: id `{uuid}-api-image` (legacy poll: `{uuid}-api`). Project feed: `GET /api/v1/projects/{id}/generations/`. Poll по id — `{ id, status, model, cost_tokens, … }` (video/switchx также `task`).

**Универсальный poll:** `GET /api/v1/generations/{id}/` — сам определяет модальность по записи генерации и отвечает телом соответствующего роута плюс поле `modality`. Нужен, когда модальность из id не выводится: суффикс несёт её только у Public API сабмитов, а студийные job'ы получают голый `-api` независимо от вида. Per-modality роуты остаются как были.

**История и poll:** живая job-запись живёт 24 часа. Дальше poll по id отдаёт результат **из истории генераций** — терминальный статус, media URL и `hint`, что запись архивная (повторный поллинг не нужен). Доступ — создатель генерации **или** любой участник её проекта, так что чужую студийную генерацию из своего проекта тоже можно опросить по id из `GET /api/v1/projects/{id}/generations/`. Если медиа удалено ретеншеном (`media_purged_at`), придёт пустой список и явная ошибка.

**Отмена генерации:** REST/MCP/CLI и Studio могут отменить задачу с возвратом токенов только в первые 4 секунды, до отправки провайдеру. После provider submit отмена недоступна: провайдерская генерация уже оплачена и может продолжиться; сервер отвечает `409 GENERATION_NOT_CANCELLABLE`.

**Оценка цены:** `POST /api/v1/estimate/` с тем же телом, что и submit, плюс `type: "image" | "video" | "chat" | "switchx" | "upscale" | "audio" | "mesh"` → `{ cost_tokens }`. Ничего не списывает. Для Seedance 2.5 с видео передайте `source_video_url` или `reference_videos`: сервер измерит каждый ролик. Если измерить не удалось, API вернёт `VIDEO_DURATION_UNAVAILABLE` вместо оценки по максимальной длительности. При запуске сервер повторно измеряет видео. В режиме edit `duration: -1` следует длительности исходного ролика.

**Несколько картинок и seed:** для image-генерации можно передать `n` (кол-во картинок, где модель поддерживает — см. `GET /models`) и `seed` (воспроизводимость). Цена масштабируется на `n`. В poll вернётся массив `images`.

**Загрузка файлов:** `POST /api/v1/files/` (multipart), `POST /api/v1/files/from-url/` (`{ "url": "https://…" }`), или `POST /api/v1/files/presign/` (image/audio only → PUT на `upload_url` с `required_headers`, затем `POST /api/v1/files/presign/finalize/` с `{ "asset_id": "ast_…" }`). Все пути создают Asset Library-запись и учитываются в квоте. Лимиты: изображение 150MB, видео 200MB, аудио 50MB. HEIC/HEIF, AVIF, WebP, TIFF, GIF и SVG принимаются и нормализуются перед провайдером. Video **не** поддерживает direct presign (нужен server transform).

**Стабильные ссылки на результат:** для завершённых генераций API **предпочитает** постоянное хранилище Artillect (`…/api/backend/media/download/?key=…`). Если зеркалирование ещё не успело — в ответе может быть временный CDN провайдера; скачайте сразу (см. poll §).

**Images:** omit `model` → `gpt-image-2` (обратная совместимость). Неизвестный `model` → `400 MODEL_UNKNOWN` (как у video).  
**Video:** omit `model` → `kling-3-turbo`.  
**Chat:** omit `model` → `gpt-6-luna`. Без встроенных system prompts — только ваши `messages`.
(При нехватке ёмкости у провайдера → `503 PROVIDER_BUSY`: retry или модель подешевле.)  
По умолчанию списание — с **личного баланса**, результаты — в проект **«Мои генерации {userId}»** (отдельная запись в БД).  
Это **не** `users.default_project_id` (стартовая страница в UI — может быть любой проект, например «зверолес»).

Альтернатива device flow: `POST /api/v1/keys` с session JWT в браузере (см. таблицу эндпоинтов), либо страница **`/api`** в браузере — создание/копирование/удаление ключей.

Опционально при создании: `{ "name": "ci", "scopes": ["images:write", "models:read"] }`. Список ключей отдаёт `name`, `last_used_at`, `scopes` (секрет никогда). `PATCH /api/v1/keys/{id}` — переименовать / сменить scopes. Пустые/`null` scopes = полный доступ `["*"]` (обратная совместимость). Недостающий scope на Public API → `403 INSUFFICIENT_SCOPE`.

### Выбор проекта и источника списания (`project_id`, `billing_source`)

Опциональные поля тела для **всех** submit-эндпоинтов (images / videos / chat):

| Поле             | Значения          | Поведение                                                                                                    |
| ---------------- | ----------------- | ------------------------------------------------------------------------------------------------------------ |
| `project_id`     | int               | Проект, куда писать генерацию. **Omit** → «Мои генерации» + личный баланс (как раньше).                      |
| `billing_source` | `owner` \| `self` | С чьего баланса списывать. Обязателен **только** если `project_id` — чужой проект, где вы редактор (editor). |
| `tag_ids`        | int[]             | **Опционально.** Сразу повесить custom-теги проекта на новую генерацию. Неверный id молча пропускается.      |
| `tag_id`         | int               | То же, один тег (удобный алиас).                                                                             |

Логика:

- `project_id` не задан (или = ваш личный hub) → личный баланс. `billing_source` игнорируется. **Полная обратная совместимость.**
- `project_id` — проект, которым вы **владеете** → списание с вашего баланса.
- `project_id` — **чужой** проект, где вы **editor**:
  - `billing_source: "owner"` → списание с **владельца** проекта (проверяется роль editor и `token_limit`).
  - `billing_source: "self"` → списание с **вашего** личного баланса.
  - поле не передано → `400 BILLING_SOURCE_REQUIRED`.
- Нет доступа к проекту → `403 PROJECT_ACCESS_DENIED`. Несуществующий → `400 PROJECT_NOT_FOUND`.

### Теги проекта (`tag_ids` на submit + CRUD)

Теги в UI — короткие цветные метки на генерациях (до **10** custom на проект). Личные «лайки» и системные метки (архив и т.п.) **не** отдаются в Public API и не принимаются в `tag_ids`.

| Метод    | Путь                                  | Назначение                                            |
| -------- | ------------------------------------- | ----------------------------------------------------- |
| `GET`    | `/api/v1/projects/{id}/tags/`         | Список `{ items: [{ id, name, color, sort_order }] }` |
| `POST`   | `/api/v1/projects/{id}/tags/`         | Создать: `{ name, color? }` → `201 { tag }`           |
| `PATCH`  | `/api/v1/projects/{id}/tags/{tagId}/` | `{ name?`, `color? }` → `{ tag }`                     |
| `DELETE` | `/api/v1/projects/{id}/tags/{tagId}/` | Удалить тег и все связи                               |

Права: смотреть — любой участник проекта; create/update/delete — owner / editor / admin / client (**не** viewer).

На submit (images / videos / upscale / switchx) можно передать опциональные `tag_ids: [1, 2]` или `tag_id: 1` вместе с `project_id`. Теги должны принадлежать этому проекту. Поле необязательное — omit = генерация без тегов.

Пример:

```http
POST https://app.artillect.pro/api/v1/images/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{
  "prompt": "hero shot",
  "project_id": 42,
  "tag_ids": [7]
}
```

### Идемпотентность и `request_id`

- Заголовок **`Idempotency-Key`** на **всех** async submit и chat:
  - `POST /api/v1/images/generations/`
  - `POST /api/v1/videos/generations/`
  - `POST /api/v1/upscale/generations/`
  - `POST /api/v1/switchx/generations/`
  - `POST /api/v1/chat/completions/`
- Повтор с тем же ключом (тот же пользователь) вернёт **тот же** ответ без повторного списания/сабмита (TTL 24 ч). В replay-ответе — заголовок `Idempotent-Replay: true`.
- Если тот же ключ ещё **в обработке** (параллельный запрос) → `409 IDEMPOTENCY_CONFLICT`.
- Каждый ответ содержит заголовок **`X-Request-Id`** (и поле `request_id` в теле ошибок) — указывайте его при обращении в поддержку.

### Rate limits

Дорогие async submit (`POST /api/v1/images|videos|upscale|switchx/generations/`) и sync chat ограничены **дважды**: per-IP окно (30–60 запросов/мин в зависимости от модальности) и **per-API-key** минимальный интервал **0.5 с** между submit одним ключом (Redis; при недоступности Redis throttle деградирует до per-process и **не** блокирует submit). При превышении — `429` с кодом `RATE_LIMITED`, заголовками `Retry-After` и `RateLimit-*`; тело ошибки называет само правило. Идемпотентный replay (`Idempotency-Key`) не обходит per-key throttle.

> Интервал был 3 с и срабатывал на нормальном батче агента — из-за этого весь публичный rate limiter ушёл под kill switch `PUBLIC_API_RATE_LIMIT_ENABLED`. 0.5 с ловит runaway-цикл и двойной submit, не мешая человеческому темпу.

**Daily spend cap (per key / OIDC identity):** когда `PUBLIC_API_KEY_DAILY_TOKEN_CAP` > 0 (в production по умолчанию **500000**, если env не задан; `0` = выкл), submit отклоняется с `429 SPEND_CAP_EXCEEDED`, если UTC-дневной счётчик токенов по ключу уже ≥ cap. Счётчик растёт при успешном charge.

### Хранилище и квота

Хранилище доступно через Public API, MCP и CLI. Помимо медиа, можно хранить инструкции агентов `AGENTS.md`, определения skills `SKILL.md` и сопроводительные Markdown/TXT-документы (UTF-8, до 10 МиБ). Сохранение не запускает агента и не устанавливает skill; документ скачивает и подключает клиент.

Загрузка: `POST /api/v1/files/` (multipart `file`, необязательный `project_id`; ключ с `files:write`) или `POST /api/v1/files/from-url/` (`url`, необязательные `filename` и `project_id`). Ответ содержит `asset_id`, `url`, `content_type`, `bytes`, `kind: "file"` для текста. Идентификатор `ast_…` сохраняется для последующих обращений.

Поиск: `GET /api/v1/assets/?kind=file&q=SKILL`; получить свежий URL и метаданные: `GET /api/v1/assets/{asset_id}/`. Для скачивания выполните GET по возвращённому `url` без передачи API-ключа. Копия: `POST /api/v1/assets/{asset_id}/` с `{"target":"project","project_id":12}` или `{"target":"personal"}`. Удаление неиспользуемого файла: `DELETE /api/v1/assets/{asset_id}/`. Права доступа и квота личного хранилища/проекта сохраняются; копия расходует место отдельно. Presigned upload поддерживает только изображения и аудио.

MCP: `upload_file`, `list_media_assets`, `get_media_asset`, `copy_media_asset`, `delete_media_asset`. CLI: `artillect upload ./SKILL.md`, `artillect assets list --kind file`, `artillect assets download ast_… --out ./skills/example/SKILL.md`, `artillect assets copy`, `artillect assets delete`. Полный пример и восстановление многофайлового skill: [хранилище и инструкции агентов](/docs/knowledge/guides/storage-and-privacy.md).

Загруженные файлы и результаты генераций становятся **Asset'ами** и занимают место в хранилище
владельца: **100 ГиБ бесплатно** на пользователя, плюс подключённые add-ons. Ассет, созданный в
проекте, списывается на **владельца проекта** — независимо от того, кто его загрузил.

Когда место кончилось, запись отклоняется с `413 STORAGE_FULL`: файл не сохраняется, генерация не
запускается и токены не списываются. Ничего не удаляется автоматически — освободить место можно в
`/storage`. Текущее потребление и остаток — `GET /api/storage/usage/`.

---

## 1. Получить API-ключ (device flow)

> **⛔ СТОП — обязательно для CLI, скриптов и AI-агентов**
>
> После `POST /api/v1/device/code/` **не вызывайте** `POST /api/v1/device/token/`, пока пользователь **явно не подтвердил** выдачу ключа в браузере.
>
> **Порядок (строго):**
>
> 1. `POST /api/v1/device/code/`
> 2. **Сразу показать пользователю** (не только в лог): полный `verification_url` и запасной `user_code`
> 3. **Остановиться** и дождаться «выдал разрешение» / «готово» — не poll «на всякий случай»
> 4. Только после этого — `POST /api/v1/device/token/` с `device_code`
>
> Poll до подтверждения всегда вернёт `{ "status": "pending" }` — это нормально, не повод молча крутить цикл.

### 1.1 Запросить код

```http
POST https://app.artillect.pro/api/v1/device/code/
```

Ответ:

```json
{
  "device_code": "secret-token-for-polling",
  "user_code": "X7KM-9P2Q",
  "verification_url": "https://app.artillect.pro/connect?code=X7KM-9P2Q",
  "expires_in": 900,
  "interval": 5,
  "instructions": "Open verification_url in a browser..."
}
```

| Поле               | Описание                                       |
| ------------------ | ---------------------------------------------- |
| `device_code`      | Секрет для poll (`POST /api/v1/device/token/`) |
| `user_code`        | Код `XXXX-XXXX` для пользователя               |
| `verification_url` | **Готовая ссылка** — открыть в браузере        |
| `instructions`     | Краткая подсказка от API                       |

### 1.2 Подтверждение в браузере (до poll)

**Кто:** человек, залогиненный в Artillect.  
**Где:** полный `verification_url` из ответа — **не** `/connect/` без `?code=`.

| URL                       | Что видит пользователь                           |
| ------------------------- | ------------------------------------------------ |
| `/connect?code=X7KM-9P2Q` | Код уже подставлен — нажать «Выдать API-ключ»    |
| `/connect/` без `?code=`  | Заглушка `XXXX-XXXX`, ввести `user_code` вручную |

| Что показать пользователю | Зачем                                        |
| ------------------------- | -------------------------------------------- |
| `verification_url`        | Открыть в браузере, нажать «Выдать API-ключ» |
| `user_code`               | Запасной вариант на `/connect/`              |

Ключ **не показывается в браузере** — приложение забирает его через poll (§1.3).

### 1.3 Poll ключа (только после §1.2)

```http
POST https://app.artillect.pro/api/v1/device/token/
Content-Type: application/json

{"device_code": "secret-token-for-polling"}
```

Pending: `{ "status": "pending", "interval": 5 }`

Готово:

```json
{
  "status": "completed",
  "api_key": "art_xxxxxxxx",
  "usage": "Use Authorization: Bearer <api_key> for /api/v1/images/generations"
}
```

| Поле    | Описание                                                     |
| ------- | ------------------------------------------------------------ |
| `usage` | Подсказка, как использовать ключ в заголовке `Authorization` |

Истёк или неверный `device_code`: HTTP **410**, `code: DEVICE_CODE_EXPIRED`.

Poll каждые **5 с**, таймаут кода **15 мин**.

**API-ключ (`art_...`) долгоживущий** — сохраните в `ARTILLECT_API_KEY`. Полное значение больше не вернётся; при потере — новый device flow.

### Node.js — получение ключа

```javascript
const BASE = "https://app.artillect.pro";

/** В CLI/агенте: дождаться явного подтверждения пользователя. */
async function waitForUserApproval() {
  // Реализация на вашей стороне: readline, UI-кнопка, сообщение в чате и т.д.
  throw new Error("Implement: block until user confirms browser approval");
}

export async function obtainArtillectApiKey(onOpenUrl) {
  const start = await fetch(`${BASE}/api/v1/device/code/`, { method: "POST" });
  if (!start.ok) throw new Error(await start.text());
  const { device_code, user_code, verification_url, interval } = await start.json();

  // ⛔ Gate: показать ссылку и код, затем ждать подтверждения
  onOpenUrl?.(verification_url, user_code);
  await waitForUserApproval();

  for (let i = 0; i < 180; i++) {
    await new Promise((r) => setTimeout(r, (interval || 5) * 1000));
    const poll = await fetch(`${BASE}/api/v1/device/token/`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ device_code }),
    });
    const body = await poll.json();
    if (body.status === "completed") return body.api_key;
    if (body.status === "expired") throw new Error("Device code expired or invalid");
  }
  throw new Error("Timeout waiting for approval");
}
```

---

## 2. Создать генерацию

```http
POST https://app.artillect.pro/api/v1/images/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
```

### Модели (`model`)

| `model`                      | Описание                           | max `prompt`                                      | t2i | i2i (refs)                           |
| ---------------------------- | ---------------------------------- | ------------------------------------------------- | --- | ------------------------------------ |
| `gpt-image-2`                | **Default** — если `model` опущен  | **7 000** симв.                                   | да  | да (`input_urls` / `images`)         |
| `gpt-image-25-flare`         | GPT Image 2.5 Flare (FAL, fast)    | **20 000** симв.                                  | да  | да, max **16** refs (`image_urls`)   |
| `gpt-image-25-sunburst`      | GPT Image 2.5 Sunburst (FAL)       | **20 000** симв.                                  | да  | да, max **16** refs (`image_urls`)   |
| `gpt-image-15`               | GPT Image 1.5                      | **2 000** симв.                                   | да  | да, max **16** refs                  |
| `nano-banana`                | Nano Banana (legacy)               | **8 000** симв.                                   | да  | да, max **10** refs                  |
| `nano-banana-2`              | Nano Banana 2                      | **8 000** симв.                                   | да  | да, max **14** refs                  |
| `nano-banana-2-lite`         | Nano Banana 2 Lite                 | **8 000** симв.                                   | да  | да, max **10** refs; 1K only         |
| `nano-banana-pro`            | Nano Banana Pro                    | **10 000** симв.                                  | да  | да, max **8** refs                   |
| `seedream-4`                 | Seedream 4.0 (ByteDance)           | **5 000** симв.                                   | да  | да, max **10** refs                  |
| `seedream-4-5`               | Seedream 4.5 (ByteDance)           | **3 000** симв.                                   | да  | да, max **10** refs                  |
| `seedream-5-lite`            | Seedream 5 Lite (ByteDance)        | **2 000** симв.                                   | да  | да, max **14** refs                  |
| `seedream-5-flash`           | Seedream 5 Flash                   | **20 000** симв.                                  | да  | да, max **10** refs                  |
| `recraft-v41`                | Recraft V4.1                       | **20 000** симв.                                  | да  | нет (refs → `400`)                   |
| `recraft-v41-vector`         | Recraft V4.1 Vector                | **20 000** симв.                                  | да  | нет (refs → `400`); результат SVG    |
| `recraft-v4-style`           | Recraft V4 Styles                  | **20 000** симв.                                  | да  | 1–10 refs или сохранённый `style_id` |
| `image2svg`                  | Image2SVG                          | prompt не используется                            | —   | обязателен один input; результат SVG |
| `ideogram-remove-background` | Ideogram Remove Background         | prompt не используется                            | —   | один input; прозрачный PNG           |
| `bria-rmbg-20`               | Bria RMBG 2.0                      | prompt не используется                            | —   | один input; прозрачный PNG           |
| `bria-product-holding`       | Bria Product Holding               | необязательная инструкция                         | —   | человек + 1–3 фото товара            |
| `bria-fibo-try-on`           | Bria Virtual Try-On                | необязательная инструкция                         | —   | человек + 1–3 фото одежды            |
| `bria-extract-object`        | Bria Extract Object                | описание нужного предмета                         | —   | одно фото; изображение и маска       |
| `bria-product-dimensions`    | Bria Product Dimensions            | prompt не используется                            | —   | одно фото + массив `dimensions`      |
| `ideogram-v4-instant`        | Ideogram V4 Instant                | **4 096** симв.                                   | да  | нет                                  |
| `riverflow-25-fast`          | Riverflow 2.5 Fast (OpenRouter)    | **8 000** симв.                                   | да  | до **4** refs                        |
| `riverflow-25-pro`           | Riverflow 2.5 Pro (OpenRouter)     | **8 000** симв.                                   | да  | до **10** refs                       |
| `seedream-5-pro`             | Seedream 5 Pro (ByteDance)         | **5 000** симв.                                   | да  | да, max **14** refs                  |
| `seedream-5-pro-layers`      | Seedream 5 Pro Layers              | **5 000** симв.                                   | —   | да, ровно **1** input                |
| `qwen3`                      | Qwen3 (Alibaba, KIE)               | **800** симв.                                     | да  | да, max **3** refs                   |
| `qwen3-pro`                  | Qwen3 Pro (Alibaba, KIE)           | **800** симв.                                     | да  | да, max **3** refs                   |
| `flux2-pro`                  | Flux 2 Pro                         | **8 000** симв.                                   | да  | да, max **8** refs                   |
| `flux2-max`                  | Flux 2 Max                         | **8 000** симв.                                   | да  | да, max **8** refs                   |
| `flux2-flex`                 | Flux 2 Flex                        | **8 000** симв.                                   | да  | да, max **8** refs                   |
| `grok-imagine`               | Grok Imagine                       | **5 000** симв.                                   | да  | да, max **1** ref                    |
| `wan-27-image`               | Wan 2.7 Image                      | **500** симв.                                     | да  | да, max **9** refs                   |
| `wan-27-image-pro`           | Wan 2.7 Image Pro                  | **500** симв.                                     | да  | да, max **9** refs                   |
| `cosmos-3-super`             | Nvidia Cosmos 3S (FAL)             | **8 000** симв.                                   | да  | нет (refs → `400`)                   |
| `mai-image-25`               | MAI Image 2.5 (Microsoft, FAL)     | **8 000** симв.                                   | да  | да, max **1** ref                    |
| `mai-image-25-pro`           | MAI Image 2.5 Pro (Microsoft, FAL) | **8 000** симв.                                   | да  | да, max **1** ref                    |
| `reve-21`                    | Reve 2.1 (FAL)                     | **8 000** симв.                                   | да  | 1→edit / 2+→remix, max **8**         |
| `krea-v2-large`              | Krea 2 Large (FAL)                 | **8 000** симв.                                   | да  | style refs на t2i, max **10**        |
| `krea-v2-medium`             | Krea 2 Medium (FAL)                | **8 000** симв.                                   | да  | style refs на t2i, max **10**        |
| `krea-v2-medium-turbo`       | Krea 2 Medium Turbo (FAL)          | **8 000** симв.                                   | да  | style refs на t2i, max **10**        |
| `qwen-multiple-angles`       | Qwen Multiple Angles (FAL angles)  | **8 000** симв.                                   | нет | **обязателен** 1 ref image           |
| `muse-image`                 | Meta Muse Image (FAL)              | **8 000** (app guardrail; FAL max не опубликован) | да  | edit: **1–10** refs                  |
| `google-virtual-try-on`      | Google Virtual Try On (FAL)        | **0** (prompt не используется)                    | нет | ровно **2** input: человек → вещь    |

Роутинг как в UI: есть `input_urls` или `images` → **image-to-image** (edit); иначе text-to-image. Исключения: Krea, Recraft V4 Styles и Riverflow принимают refs на основном endpoint; `qwen-multiple-angles` — всегда `angles` (1 ref, camera params); Recraft V4.1 принимает только текст. Для `recraft-v4-style` передайте 1–10 `input_urls` для создания стиля или `style_id` для повторного использования; полученный ID возвращается полем `style_id` в GET генерации и сохраняется в `display_meta.recraft_style_id`. Riverflow допускает 1K/2K (Pro также 4K); цена зависит от разрешения и окончательно учитывается по `usage.cost` провайдера.

Алиасы: `nano-banana-2-gen`, `nano-banana-pro-edit`, `gpt-image-2-gen`, `gpt-image-2.5-flare`, `gpt-image-2-5-sunburst`, `gpt-image-1.5`, `flux-2-pro`, `wan-2.7-image`, `seedream-4.5`, `seedream-45`, `seedream-5-lite-edit`, … → та же семья.

`output_format` доступен у поддерживающих его моделей (см. `GET /api/v1/models`): Seedream 5 Flash принимает `jpeg`\|`png`, Nano Banana и Meta Muse — `jpeg`\|`png`\|`webp`.

`gpt-image-15`: поле `quality` (`medium`\|`high`); `resolution` `2K`/`4K`/`high` → `high`, иначе `medium`.

**Text-to-image** (default model, без поля `model`):

```json
{
  "prompt": "Photorealistic red apple on white background",
  "aspect_ratio": "1:1",
  "resolution": "1K"
}
```

**Text-to-image** (Nano Banana 2):

```json
{
  "model": "nano-banana-2",
  "prompt": "Product photo of a red apple",
  "aspect_ratio": "1:1",
  "resolution": "2K",
  "output_format": "png"
}
```

**Meta Muse Image** (`muse-image`):

```json
{
  "model": "muse-image",
  "prompt": "A product hero shot with clean studio lighting",
  "aspect_ratio": "16:9",
  "n": 3,
  "output_format": "webp"
}
```

**Google Virtual Try On** (`google-virtual-try-on`):

```json
{
  "model": "google-virtual-try-on",
  "input_urls": ["https://example.com/person.jpg", "https://example.com/jacket.jpg"],
  "n": 2
}
```

Ровно два входных изображения обязательны: `input_urls[0]` — человек, `input_urls[1]` —
фото вещи. `n`/`num_images` принимает целое **1–4**. `prompt`, `aspect_ratio`,
`resolution`, `quality`, `output_format` и `sync_mode` для этой модели не
используются. Цена — **8 токенов за каждое output image**; при `n=2` — 16 токенов.

Для редактирования передайте `input_urls` или `images` (от 1 до 10 URL). Muse принимает
`aspect_ratio` из `21:9`, `16:9`, `4:3`, `3:2`, `1:1`, `2:3`, `3:4`, `9:16`, `9:21`; без
него размеры выбираются автоматически. `n`/`num_images` — целое от 1 до 10, цена — **1
токен за каждый output image**. `output_format` — `jpeg`\|`png`\|`webp`; `sync_mode=true`
просит data URI вместо сохранённого URL результата.

**Image-to-image** — внешний HTTPS URL (проверен на dev):

```json
{
  "model": "nano-banana-2",
  "prompt": "Make it look like a watercolor painting",
  "input_urls": ["https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?w=512"],
  "aspect_ratio": "auto",
  "resolution": "1K"
}
```

**Image-to-image** — уже загруженный Artillect media path (без повторного fetch):

```json
{
  "prompt": "Make it look like a watercolor painting",
  "images": ["/api/backend/media/download/?key=2026%2F06%2F21%2Fabc-api%2Finput%2F0.jpg"],
  "aspect_ratio": "auto",
  "resolution": "1K"
}
```

Алиас поля UI: `images` (массив HTTPS URL или `/api/backend/media/download/?key=...`).

Ответ **202**:

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api",
  "status": "queued",
  "model": "gpt-image-2",
  "cost_tokens": 4,
  "project_id": 123,
  "poll_url": "/api/v1/images/generations/550e8400-e29b-41d4-a716-446655440000-api/"
}
```

Poll URL (относительный): `` `${BASE}${poll_url}` `` → `https://app.artillect.pro/api/v1/images/generations/550e8400-...-api/`.  
`project_id` — id проекта «Мои генерации» в БД.

### Параметры

| Поле                    | Обяз. | Значения                                                                                                                                                                  | Default       |
| ----------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `prompt`                | да*   | string; max символов **зависит от модели** (см. ниже); для `google-virtual-try-on` не используется                                                                        | —             |
| `model`                 | нет   | см. таблицу моделей выше (`gpt-image-2`, `gpt-image-15`, `nano-banana*`, `seedream-*`, `flux2-*`, `grok-imagine`, `wan-27-image*`, `muse-image`, `google-virtual-try-on`) | `gpt-image-2` |
| `aspect_ratio`          | нет   | см. каталог модели (`auto`, `1:1`, `21:9`, …)                                                                                                                             | из каталога   |
| `resolution`            | нет   | `1K`, `2K`, `4K` (где есть в каталоге; Wan default **2K**)                                                                                                                | из каталога   |
| `quality`               | нет   | `gpt-image-15`: `medium`\|`high`. `gpt-image-25-flare` / `sunburst`: `auto`\|`low`\|`medium`\|`high`\|`xhigh`\|`max` (default **high**)                                   | из каталога   |
| `output_format`         | нет   | `png`, `jpg`/`jpeg`, `webp` — Nano Banana, Muse, GPT Image 2.5                                                                                                            | из каталога   |
| `background`            | нет   | `auto`\|`opaque`\|`transparent` — только `gpt-image-25-flare` / `sunburst`                                                                                                | `auto`        |
| `mask_url`              | нет   | URL маски (edit); только GPT Image 2.5; применяется к первому рефу                                                                                                        | —             |
| `output_compression`    | нет   | 0–100; jpeg/webp, только GPT Image 2.5                                                                                                                                    | —             |
| `input_urls` / `images` | нет   | string[], max зависит от модели (см. таблицу)                                                                                                                             | —             |
| `n` / `num_images`      | нет   | integer **1–10** для `muse-image`, **1–4** для `google-virtual-try-on` (у остальных — по каталогу)                                                                        | из каталога   |
| `sync_mode`             | нет   | boolean; data URI вместо persisted URL для моделей, которые это поддерживают                                                                                              | `false`       |

**Лимит `prompt` (символы, как в [KIE Market](https://docs.kie.ai)):**

| `model`                        | max `prompt` |
| ------------------------------ | ------------ |
| `gpt-image-2`                  | **7 000**    |
| `gpt-image-25-flare`           | **20 000**   |
| `gpt-image-25-sunburst`        | **20 000**   |
| `gpt-image-15`                 | **2 000**    |
| `nano-banana`                  | **8 000**    |
| `nano-banana-2`                | **8 000**    |
| `nano-banana-2-lite`           | **8 000**    |
| `nano-banana-pro`              | **10 000**   |
| `seedream-4`                   | **5 000**    |
| `seedream-4-5` / `seedream-45` | **3 000**    |
| `seedream-5-lite`              | **2 000**    |
| `seedream-5-flash`             | **20 000**   |
| `recraft-v41`                  | **20 000**   |
| `recraft-v41-vector`           | **20 000**   |
| `seedream-5-pro`               | **5 000**    |
| `flux2-pro` / `flux2-flex`     | **8 000**    |
| `mai-image-25` / `-pro`        | **8 000**    |
| `reve-21`                      | **8 000**    |
| `grok-imagine`                 | **5 000**    |
| `wan-27-image` / `-pro`        | **500**      |

Превышение → `400 PROMPT_TOO_LONG`.

Есть непустой `input_urls` или `images` (валидный HTTPS URL или Artillect media path) → **image-to-image** (edit).  
**Без них** — **text-to-image** (это не ошибка; `INPUT_URLS_REQUIRED` не возвращается).

**Input (image-to-image):** внешние **HTTPS** URL на **публичных** хостах сервер **скачивает и кладёт в Artillect media (MinIO)** перед KIE (SSRF: private/metadata IP блокируются; **не более одного** redirect hop с повторной проверкой). Провайдер получает `https://{origin}/api/backend/media/download/?key=...`. Уже загруженные пути `/api/backend/media/download/?key=...` принимаются как есть. Max **150MB** на файл. `http://` и внутренние адреса → `4xx`.

**Какие URL сработают:** прямой GET без auth, `Content-Type: image/*`. На dev проверен `images.unsplash.com`. `picsum.photos`, Wikimedia, hotlink-защита и часть CDN **могут вернуть 403/404** при server-side fetch → `INPUT_URL_FETCH_FAILED` (до submit, без списания).

**Загрузка файла без внешнего URL:** `POST /api/v1/files/` (multipart `file`) → `{ key, url }` — полученный `url`/`key` можно передать в `input_urls` / слоты видео.

Опционально: `project_id`, `billing_source`, `tag_ids` / `tag_id` (см. «Выбор проекта…» и «Теги проекта» выше).

---

### Свои LoRA: ID и конкретная версия

Поддерживаемые точные базы: `model=z-image-turbo-lora`, `qwen-image-2512-lora`,
`flux-2-dev-lora` для text-to-image; `model=ltx-23-lora` для text-to-video,
image-to-video и первого/последнего кадров. Версия весов совместима только с её базой.
Обычные Qwen Image 3, FLUX.2 Pro/Max/Flex и LTX 2.3 не загружают эти adapters.
LTX 2.3 LoRa: 720p, 16:9 / 9:16 / 1:1, 5 или 10 секунд, 24 fps,
121 / 241 кадров. Цена считается по фактическим размерам и числу кадров.
Для LTX 2.3 LoRa поле `lora_selection` обязательно в `POST /api/v1/videos/generations/`; отсутствие весов отклоняется до списания.
В Studio новые LTX LoRa обучаются на фотографиях: внешность и стиль, без обучения движению или звуку.
Загрузить можно `.safetensors` до 512 МиБ; неподдерживаемые форматы остаются для проверки,
но не отправляются на генерацию.
Готовые веса создаются или загружаются в Studio на `/my-models/`; публичных
API-key endpoints обучения/импорта пока нет. Загрузка медиа через `/files/`
сама по себе не создаёт LoRA и не заменяет проверку весов.

- `GET /api/v1/loras/` — `{items:[...]}`, до 100 личных ресурсов;
- `GET /api/v1/loras/{id}/` — один принадлежащий владельцу ключа ресурс и его версии;
- оба GET требуют scope `models:read`; image submit — `images:write`.

`lor_…` — постоянный ID ресурса; `lov_…` — неизменяемые веса конкретной версии.
Сначала выберите `versions[].id` со `status=ready` и
`validation=structure_checked|provider_verified`. `current_version_id` может
быть `null`, пока первая версия не готова. Имя и настройки можно менять в
Studio, а новую версию создают новый импорт или обучение.

Пример тела для `POST /api/v1/images/generations/` (ID заменить своими):

```json
{
  "model": "z-image-turbo-lora",
  "prompt": "mycharacter, portrait in warm evening light",
  "aspect_ratio": "1:1",
  "num_images": 1,
  "output_format": "png",
  "lora_selection": {
    "id": "lor_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "version_id": "lov_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "scale": 0.8
  }
}
```

`scale` обязателен, от 0 до 2. Обученную `trigger_phrase` при необходимости
включите в `prompt`; переименование ресурса её не меняет. Чужой ID,
непроверенная версия или неподходящая модель отклоняются **до списания**.
Поля FAL `loras[].path` и произвольные ссылки на веса не принимаются.
Версия сохраняется в запросе генерации; последующий новый импорт не меняет
параметры исторического результата.

Z-Image Turbo: prompt до 10000 символов; `num_images` 1–4; аспект
`1:1`, `4:3`, `3:4`, `16:9`, `9:16`; PNG/JPEG/WebP. В первом пути длинная
сторона 1024 px, входных референсов нет. Цена вычисляется на сервере из
реальных output pixels × count, с единым округлением вверх; `1024×1024`,
один результат — **1 токен**. Для актуальных лимитов используйте `/models/`,
для оценки — `/estimate/`.

SDK `generateImage(body)` принимает это же тело. В MCP `generate_image`
передайте `lora_selection` внутри `extra`; в CLI `artillect generate image`
используйте `--model z-image-turbo-lora --extra '{"lora_selection":{...}}'`.
Эти универсальные параметры уже передаются в API; отдельные команды управления
библиотекой/обучения ещё не добавлены. Один ID не даёт совместимость со всеми
image/video моделями: поддерживаемая база проверяется на сервере.

## 3. Poll результата

```http
GET https://app.artillect.pro/api/v1/images/generations/{id}/
Authorization: Bearer art_xxxxxxxx
```

Poll каждые **2–5 с** (или по `recommended_poll_interval_sec` / заголовку `Retry-After` в ответе). Статусы: `queued` → `running` → `completed` | `failed` | `cancelled`.

Во время `queued`/`running` поле `progress` равно `null`: Artillect не подставляет фиктивный процент, если провайдер не дал достоверный промежуточный прогресс. На `completed` оно равно `100`, на явном `failed`/`cancelled` — `0`.

**Пример ответа (ещё в работе):**

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api",
  "status": "running",
  "model": "gpt-image-2",
  "cost_tokens": 4,
  "images": [],
  "error": null,
  "progress": null,
  "provider_sync": "fresh",
  "recommended_poll_interval_sec": 5
}
```

**Если KIE временно не отвечает на проверку статуса** — poll **не падает с ошибкой**. Ответ **200**, последнее известное состояние, `provider_sync: "deferred"` и поле `hint`:

```json
{
  "status": "running",
  "progress": null,
  "provider_sync": "deferred",
  "hint": "Could not refresh provider status in time. The generation may still be running — keep polling this URL every few seconds.",
  "recommended_poll_interval_sec": 5
}
```

Это **не отказ генерации**: задача уже принята на submit (**202**), KIE может доделать картинку в фоне. **Продолжайте poll** того же `id` каждые несколько секунд (рекомендуем до **15 минут**).

### 3.1 Таймауты и надёжность (images)

| Фаза                                        | Что происходит                                                | Ошибка?                                                                  | Что делать клиенту                                                                                       |
| ------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| **POST submit**                             | Artillect отправляет задачу в KIE и сразу отдаёт `id`         | **202** + `poll_url` — успех                                             | Сохранить `id`, начать poll                                                                              |
| **POST submit**, KIE не подтвердил за ~50 с | Задача **не создана**, токены **не списаны** (или возвращены) | **504** `PROVIDER_TIMEOUT`, `phase: "submit"`, `retryable: true`, `hint` | Повторить POST (безопасно)                                                                               |
| **GET poll**, KIE отвечает                  | Актуальный статус                                             | **200**, `provider_sync: "fresh"`                                        | Если `queued`/`running` — poll снова через 2–5 с                                                         |
| **GET poll**, KIE не ответил вовремя        | Отдаём **кэш** последнего статуса                             | **200**, `provider_sync: "deferred"`, `hint`                             | **Не считать ошибкой** — poll снова; генерация может завершиться позже                                   |
| **GET poll**, долгая генерация (минуты)     | Норма для тяжёлых моделей                                     | **200**, статус `running`                                                | Терпеливо poll до `completed` / `failed`                                                                 |
| Edge **502** HTML / обрыв                   | Сбой до приложения или таймаут edge (Caddy → контейнер)       | Не JSON от API                                                           | Повторить; при живом API ответ JSON (**504** submit/chat, **200** poll). Edge — см. `docs/DEV_DEPLOY.md` |

**Главное правило:** после успешного **202** единственный источник правды — **poll по `id`**. Отдельный неудачный poll (deferred) **не отменяет** задачу.

Заголовок **`Retry-After`** на poll (секунды) дублирует `recommended_poll_interval_sec`, пока статус `queued` или `running`.

Health-check доступности API (без генерации): `GET /api/v1/health/` — не путать с `device/code`.

**failed:**

```json
{
  "status": "failed",
  "images": [],
  "error": "Could not download input_urls[0] (HTTP 403)...",
  "progress": 0
}
```

Текст в `error` — человекочитаемый; машинный код смотрите в ответе submit (`INPUT_URL_FETCH_FAILED` и т.д.) или в UI проекта.

**completed** (Artillect media — единственный контракт для агентов):

```json
{
  "status": "completed",
  "images": [
    { "url": "https://app.artillect.pro/api/backend/media/download/?key=2026%2F06%2F21%2F..." }
  ],
  "progress": 100
}
```

Если провайдер уже закончил, а зеркало в MinIO ещё нет — **`status` остаётся `running`** (не `completed`), `images: []`, `hint` про mirroring, `recommended_poll_interval_sec: 3`, `progress` ~95. Пайплайны/агенты не должны останавливаться на пустых URL. Provider CDN (`tempfile.aiquickdraw.com`) **не отдаём** в poll. Через ~15 мин без durable URL → `failed`.

Webhook `generation.completed` тоже **не шлётся**, пока нет stable media (следующий poll/finalize докинет outbox).

### Формат URL (input vs output)

| Фаза                       | Формат                     | Примечание                                         |
| -------------------------- | -------------------------- | -------------------------------------------------- |
| Input после зеркалирования | Stable Artillect media URL | Передаётся обратно в submit/poll как transport URL |
| Output                     | Stable Artillect media URL | Только после mirror; иначе poll ещё раз            |

Не разбирайте URL и не извлекайте из него внутренние object keys. Для долгоживущей идентификации
пользовательского файла используйте **Asset ID** `ast_…`: `GET /api/v1/assets/{asset_id}/` возвращает
свежий разрешённый URL и метаданные, а `GET /api/v1/assets/?q=…&kind=image|video|audio` позволяет
найти ранее сохранённый файл. Asset можно повторно использовать в другом запуске или чате, пока у
ключа есть доступ к personal/project scope. Копирование между personal и project создаёт независимый
Asset и отдельно учитывается в квоте.

**Скачивание результата:** `GET` по `images[0].url` **без** Authorization для Artillect media. Не используйте CDN провайдера.

---

## 3b. Видео-генерация

Async, как images: `POST /api/v1/videos/generations/` → `id` → poll `GET /api/v1/videos/generations/{id}/`.

```http
POST https://app.artillect.pro/api/v1/videos/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
```

### Модели и медиа-слоты

Медиа задаётся **явными именованными слотами**. Задача (`t2v` / `i2v` / `flf2v` / `ref2v` / `v2v` / `edit` / `motion` / `avatar` / `lipsync`) определяется **детерминированно** по заполненным слотам; опционально можно передать `"task"` для явного выбора.

| Слот               | Тип      | Описание                                                                            |
| ------------------ | -------- | ----------------------------------------------------------------------------------- |
| `start_image_url`  | string   | Первый кадр (i2v)                                                                   |
| `end_image_url`    | string   | Последний кадр (только с `start_image_url` → first+last)                            |
| `source_video_url` | string   | Исходное видео для **edit** / **v2v** (Kling O3)                                    |
| `reference_images` | string[] | Референс-изображения (стиль/объект). На **`flux-3`** — alias для `keyframes`        |
| `keyframes`        | string[] | **Только `flux-3`:** упорядоченная раскадровка (storyboard), ≤10 URL. Не style-refs |
| `reference_videos` | string[] | Референс-видео (Seedance ref2v; для Kling O3 `[0]` = alias `source_video_url`)      |
| `reference_audios` | string[] | Референс-аудио                                                                      |
| `audio_url`        | string   | Аудио для **avatar** / **lipsync** (alias `reference_audios[0]`)                    |
| `motion_video_url` | string   | Видео движения для **motion** (alias `reference_videos[0]` / `source_video_url`)    |

**Промпт и `@`-токены:** API нормализует регистр: `@video1` → `@Video1`, `@image2` → `@Image2`, `@audio3` → `@Audio3`.  
Индексация: `reference_videos[0]` = `@Video1`, `reference_images[0]` = `@Image1`, `reference_audios[0]` = `@Audio1` (порядок массива сохраняется).

**Element library:** если в промпте есть `@имя` из библиотеки проекта — передайте `project_id` (проект, где создан элемент). Элементы резолвятся из `project_element_library` (`kind=image|video`). Список элементов: `GET /api/v1/projects/{id}/elements/`. Альтернатива `@` в промпте — массив `element_names: ["hero", "bg"]` (имена без `@`).

Поддерживают element library: `kling-3`, `kling-o3`, `seedance-2`, `seedance-2-mini`, `seedance-2.5`, `happy-horse`, `happy-horse-1-1`, `minimax-h3`, `wan-27`, `veo-31`, `grok-imagine-video`, `flux-3`.

На **`flux-3`** `@имя` подтягивает frontal из библиотеки проекта и кладёт его в `keyframes` (раскадровка, не native character-id как у Kling). Нужен `project_id`.

Задача выбирается автоматически: как только в промпте есть `@элемент` (или задан `element_names`), генерация уходит на reference-эндпоинт модели — задавать `task` вручную не нужно.

**`kling-3` с элементами требует `start_image_url`** (стартовая сцена). Без него — `400 VIDEO_START_FRAME_REQUIRED`. Стартовый кадр — это фон/сцена, элементы (`@имя`) накладываются поверх.

URL — HTTPS (сервер скачает и зеркалирует в Artillect media) или готовый `/api/backend/media/download/?key=...`. Лимиты: изображение **150MB**, видео **200MB**, аудио **50MB**.

**Матрица слотов по моделям** (любое нарушение → `400` до списания):

| `model`                   | t2v |    i2v / flf2v    | ref2v                                                    | edit / v2v | Extra params                                                                                                                                    | Разрешения  |
| ------------------------- | :-: | :---------------: | -------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `kling-3-turbo` (default) |  ✓  |     start / —     | ✗                                                        | ✗          | —                                                                                                                                               | 720p, 1080p |
| `happy-horse-1-1`         |  ✓  |       start       | images 1–9                                               | ✗          | —                                                                                                                                               | 720p, 1080p |
| `minimax-h3`              |  ✓  | start / start+end | img≤9, vid≤3, aud≤3, Σ≤15                                | ✗          | resolution **768P**\|**2K**; duration 4–15                                                                                                      | 768P, 2K    |
| `minimax-h3-max`          |  ✓  | start / start+end | img/video/audio, **12 combined**                         | ✗          | resolution **480P**\|**768P**; duration **5–15**; aspect ratio/**adaptive**; seed; prompt expansion; safety checker; sync mode; frames XOR refs | 480P, 768P  |
| `flux-3`                  |  ✓  | start / start+end | **keyframes** ≤10 (storyboard; alias `reference_images`) | ✗          | `generate_audio` (default on); duration **5–20**; aspect **auto\|21:9\|2:1\|16:9\|4:3\|1:1\|3:4\|9:16**; timed `[sec,url]` pins not in API yet  | 720p, 1080p |

| `happy-horse` | ✓ | start | images 1–9 | **edit** (`source_video_url`) | — | 720p, 1080p |
| `seedance-2-mini` | ✓ | start / start+end | img≤9, vid≤3, aud≤3, Σ≤15 | ✗ | `generate_audio` | 480p, 720p |
| `seedance-2.5` | ✓ | start / start+end | img≤30, vid≤10, aud≤10, Σ≤50 | ✗ | `generate_audio`; `output_format` **mp4** (default) \| **mov**; MOV — выше битрейт для монтажа и больше файл, MP4 — компактнее и совместимее; duration 4–30\|`-1` (auto); video-ref clips > 4s and ≤ 30s; video-edit 422 → adaptive / `-1` | 480p, 720p, **1080p** |
| `seedance-2` | ✓ | start / start+end | img≤9, vid≤3, aud≤3, Σ≤15 | ✗ | `seedance_mode` quality\|fast (default **quality**), `generate_audio` | quality: 480p–**4k**; fast: 480p, 720p |
| `kling-3` | ✓ | start / start+end | images + @elements | ✗ | `kling_mode` std\|pro\|4k (default pro), `generate_audio`, `multi_shots` + `multi_prompt` | по tier |
| `kling-o3` | ✓ | start / start+end | refs + @elements + frames | **edit**, **v2v** (`source_video_url`) | `kling_mode` pro\|4k; **v2v** `duration` 3–15 (default 10); **edit** без `duration`; `shot_type`; `multi_prompt`; `keep_audio` (edit/v2v); `cfg_scale` 0–1 (default 0.5, t2v–v2v, не edit); `generate_audio` | pro/4k |
| `kling-26` | ✓ | start / start+end | ✗ | ✗ | `generate_audio` | — |
| `wan-27` | ✓ | start / start+end | img≤5, vid≤5, aud≤1 | **edit** (`source_video_url`) | — | 720p, 1080p |
| `wan-30` | ✓ | start / start+end | img≤10, vid≤5, aud≤5, **one file XOR one public link** | ✗ | `audio` (default true), `seed` 0–2147483647, `nsfw_checker` (default false); prompt ≤20000; duration 2–30 or -1 | 480P, 720P, 1080P |
| `wan-30-prime` | ✓ | start / start+end | img≤10, vid≤5, aud≤5, **one file XOR one public link** | ✗ | Same contract as Wan 3; high-speed KIE market model | 480P, 720P, 1080P |
| `veo-31` | ✓ | start / start+end | images 1–3 | ✗ | — | 720p, 1080p, 4k |
| `grok-imagine-video` | ✓ | start | ✗ | **edit** (`source_video_url`) | ≠ image `grok-imagine` | 480p, 720p |
| `grok-imagine-15` | ✗ | start | ✗ | ✗ | **i2v only** | 480p, 720p |
| `ltx-23` | ✓ | start / start+end | ✗ | ✗ | `generate_audio` (avatar/audio-to-video — не в Public API) | 1080p, 1440p, 2160p |
| `ltx-23-retake` | ✗ | ✗ | ✗ | **edit** (`source_video_url`) | `start_time` (секунды), `duration` (1–20 секунд), `retake_mode`; фрагмент должен помещаться в исходное видео | — |
| `ltx-23-reframe` | ✗ | ✗ | ✗ | **edit** (`source_video_url`) | prompt не нужен; `aspect_ratio`; исходное видео не дольше 60 секунд | 720p, 1080p |
| `auto-subtitle` | ✗ | ✗ | ✗ | **edit** (`source_video_url`) | Субтитры; prompt не нужен; `language`: `ru` (по умолчанию) или `en`; до 5 минут | исходное |
| `veed-subtitles` | ✗ | ✗ | ✗ | **edit** (`source_video_url`) | 30 стилей субтитров; `preset`: `simple` по умолчанию, динамические стили (`glass`, `whisper` и др.) ×2; `language`: `ru-RU` или `en-US`; до 5 минут | исходное |
| `bria-video-background` | ✗ | ✗ | ✗ | **edit** (`source_video_url`) | Удаление фона; prompt не нужен; прозрачный WebM со звуком; до 60 секунд | исходное |
| `pixverse-v6` | ✓ | start | ✗ | ✗ | `generate_audio`; 1–15 секунд | 360p–1080p |
| `vidu-q3` | ✓ | start / start+end | images 1–4 | ✗ | `generate_audio`; 1–16 секунд | 360p–1080p |
| `vidu-q4` | ✗ | start | images 1–15 + MP3 0–3 | ✗ | `generate_audio` default true; 3–16 секунд | 540p, 720p, 1080p, 2k, 4k |
| `luma-ray-32` | ✓ | start / start+end | ✗ | **v2v** (`source_video_url`) | — | 540p, 720p, 1080p |
| `runway-gen-45` | ✓ | start | ✗ | ✗ | `seed` | 720p; 2–10 sec |
| `runway-aleph-2` | ✓ | ✗ | ✗ | **edit** (`source_video_url`) | `seed` | exactly one source video + prompt; no image refs |

Luma Ray 3.2 **v2v** accepts only 5 or 10 seconds. Optional `edit_strength` values:
`adhere_1..3`, `flex_1..3`, `reimagine_1..3`. `auto_controls=true` excludes explicit
`edit_strength` and `controls`. Manual `controls` accepts `pose` (`enabled`, `strength=precise|coarse`),
`depth` (`enabled`, `blur`), `normals` (`enabled`, `augmentation`), `trajectory`
(`enabled`, `sparsity`), and `face` (`enabled`). Up to 64 `keyframes` URL strings must
have paired, unique, non-negative integer `keyframe_indexes` in **source-frame positions**;
this mode excludes `start_image_url`. `hdr=true` requires 720p/1080p and provider HDR access;
`exr_export=true` also requires HDR. HDR costs twice SDR, HDR+EXR costs three times SDR.
EXR may be delivered as a ZIP sidecar; Studio offers a separate EXR download.

Для **`seedance-2.5`** формат результата выбирается полем `output_format`: `mp4` (default,
компактнее и совместимее) или `mov` (выше битрейт для монтажа, файл больше). Это поле
поддерживается только этой моделью и одинаково принимается submit/estimate, MCP и CLI.

```json
{
  "model": "seedance-2.5",
  "prompt": "cinematic product shot",
  "output_format": "mov"
}
```

### Vidu Q4

`model=vidu-q4`: `start_image_url` для первого кадра (промпт необязателен, формат наследуется) или `reference_images` (1–15) + `reference_audios` (0–3 MP3) с обязательным промптом. Кадр и референсы взаимоисключаются. JPG/PNG/WEBP; MP3 по 3–12 секунд, до 50 MB каждый. Промпт до 20 000 символов. `duration` 3–16 целых секунд, default 5; `resolution` default 720p; `generate_audio` default true. Для refs `aspect_ratio=16:9|9:16|1:1|4:3|3:4`, default 16:9.

Цена в токенах: `ceil(duration × rate)`, где rate: 540p=3.6, 720p=7.6, 1080p=9.6, 2k=15.2, 4k=31.2. Например, 5s/720p — 38 токенов; звук не меняет цену.

```json
{
  "model": "vidu-q4",
  "prompt": "Animate the person in reference image 1 using reference audio 1",
  "reference_images": ["https://example.com/person.png"],
  "reference_audios": ["https://example.com/voice.mp3"],
  "duration": 5,
  "resolution": "720p",
  "generate_audio": true
}
```

### Лимиты `prompt` для видео

`max_prompt_chars` возвращается для каждой модели в `GET /api/v1/models/` и используется
одинаково для t2v/i2v/ref2v, если модель поддерживает эти режимы. Для `seedance-2.5`
лимит провайдера — 10 000 токенов; API/UI используют приблизительный эквивалент
40 000 символов (по оценке 4 символа на токен).

| Модели                                                                                        |        max `prompt` (символов) |
| --------------------------------------------------------------------------------------------- | -----------------------------: |
| `seedance-2`, `seedance-2-mini`                                                               |                     **20 000** |
| `seedance-2.5`                                                                                |                     **40 000** |
| `kling-3`, `kling-3-turbo`, `kling-o3`                                                        |                      **2 500** |
| `kling-26`                                                                                    |                      **1 000** |
| `happy-horse`, `happy-horse-1-1`                                                              |                      **2 500** |
| `minimax-h3`, `minimax-h3-max`                                                                |                      **7 000** |
| `wan-27`                                                                                      |                      **5 000** |
| `wan-30`, `wan-30-prime`, `veo-31`, `flux-3`, `gemini-omni*`                                  |                      **8 000** |
| `grok-imagine-video`, `grok-imagine-15`                                                       |                      **4 096** |
| `ltx-23`                                                                                      |                      **5 000** |
| `luma-ray-32`                                                                                 |                      **6 000** |
| `runway-gen-45`                                                                               |                      **1 000** |
| `wan-22`, `kling-3-motion`, `kling-26-motion`, `volcengine`, `sync-v3`, `heygen`, `omnihuman` | **0** (prompt не используется) |

Для моделей, не перечисленных выше, действует bounded fallback **8 000** до появления
числа в каталоге провайдера. Абсолютный потолок Artillect — 20 000 символов.

### MiniMax H3 Max — full FAL contract

`minimax-h3-max` uses `minimax/h3-max/text-to-video` for text generation,
`minimax/h3-max/image-to-video` for first/last-frame generation, and
`minimax/h3-max/reference-to-video` for multimodal references. The public API accepts
these fields:

| Field                           | Values / limits                                         | Default                      |
| ------------------------------- | ------------------------------------------------------- | ---------------------------- |
| `prompt`                        | required string                                         | —                            |
| `start_image_url` / `image_url` | optional first-frame URL                                | —                            |
| `end_image_url`                 | optional last-frame URL                                 | —                            |
| `reference_images`              | image reference URLs                                    | —                            |
| `reference_videos`              | video reference URLs                                    | —                            |
| `reference_audios`              | audio reference URLs                                    | —                            |
| `duration`                      | integer 5–15 seconds                                    | `5`                          |
| `resolution`                    | `480P` or `768P`                                        | `768P`                       |
| `aspect_ratio`                  | `adaptive`, `21:9`, `16:9`, `4:3`, `1:1`, `3:4`, `9:16` | `16:9` / `adaptive` in ref2v |
| `seed`                          | integer                                                 | —                            |
| `prompt_expansion_mode`         | `disabled`, `balanced`, `quality`                       | `balanced`                   |
| `enable_safety_checker`         | boolean                                                 | `true`                       |
| `sync_mode`                     | boolean; returns base64 instead of a CDN URL            | `false`                      |

Task selection is automatic: no media → `t2v`; a first frame → `i2v`; both first and last
frames → `flf2v`; any references → `ref2v`. `end_image_url` is only valid together with the
first frame. Frames and references are mutually exclusive. In `ref2v`, at least one image or
video is required; audio cannot be the only reference. Each video/audio reference is 2–15
seconds, with at most 15 seconds total per media type and 12 reference files combined.
Documents, web inputs, source-video input, and multishot controls are unsupported. FAL returns
`video` and may include `expanded_prompt` and `timings`.

Pricing is time-sensitive: through **2026-09-07**, 480P costs $0.025/s (2.5 Artillect
tokens/s) and 768P costs $0.04/s (4 tokens/s). After the promotion, rates are
$0.05/s (5 tokens/s) and $0.08/s (8 tokens/s). The estimate is
`ceil(rate × ceil(duration))`, with reference-to-video billed at the regular $0.08/s plus 2
tokens per billable 1K image tokens after FAL's first 4096 image tokens; fractional tokens round up.

### Wan 3 / Wan 3 Prime — full KIE contract

Both slugs expose the same KIE input contract; only the provider model id differs:
`wan/3-0-video` and `wan/3-0-video-prime` (Prime is the high-speed tier). The exact
provider payload fields are `prompt`, `first_frame_url`, `last_frame_url`,
`reference_image_urls`, `reference_video_urls`, `reference_audio_urls`,
`reference_file_urls`, `reference_link_urls`, `resolution`, `aspect_ratio`, `duration`,
`audio`, `seed`, and `nsfw_checker`.

Rules from KIE:

- `prompt`: max **20,000** characters; required for t2v, recommended with media, and
  optional for i2v/flf2v/ref2v. In ref2v, address inputs as `Image1`, `Video1`, and
  `Audio1` in array order.
- `first_frame_url` and `last_frame_url`: one image each; JPEG/JPG/PNG (no
  transparency), BMP, or WEBP; each side 240–8000 px; aspect ratio ≤8:1; ≤20 MB.
  They are strict first/last frames and cannot be combined with any `reference_*_urls`.
- `reference_image_urls`: max **10**, same image constraints as frames.
- `reference_video_urls`: max **5**; MP4/MOV; each 1–15 s, total ≤15 s; sides
  240–4096 px; aspect ratio ≤8:1; ≤100 MB each; input video duration + output
  duration ≤30 s.
- `reference_audio_urls`: max **5**; WAV/MP3; each 1–15 s, total ≤15 s; ≤15 MB
  each. Audio-only input is not recommended; pair it with image/video media.
- `reference_file_urls`: max **1**; document formats `docx`, `doc`, `xlsx`, `xls`,
  `pptx`, `ppt`, `pdf`, `txt`, `key`, `pages`, `numbers`, `md`; ≤100 MB and
  document/PDF-style inputs ≤50 pages.
- `reference_link_urls`: max **1** public webpage, with no login required. A file
  and a webpage are mutually exclusive, and both are mutually exclusive with frames.
- `resolution`: `480P` | `720P` | `1080P`, default `1080P`.
- `aspect_ratio`: `adaptive` | `16:9` | `4:3` | `1:1` | `3:4` | `9:16`, default
  `adaptive`.
- `duration`: integer 2–30 seconds, default **5**; `-1` delegates duration to the
  model. With video references, the combined input + output cap still applies.
- `audio`: output audio-track switch, default **true**. `seed`: integer
  0–2147483647. `nsfw_checker`: KIE default **false**; KIE documents false as
  disabling its content filtering, so use it deliberately.

Example (document and webpage cannot be sent together):

```json
{
  "model": "wan-30-prime",
  "prompt": "Create a concise product video from this source",
  "reference_file_urls": ["https://example.com/product.pdf"],
  "resolution": "720P",
  "aspect_ratio": "adaptive",
  "duration": 8,
  "audio": true,
  "seed": 12345,
  "nsfw_checker": false
}
```

Pricing is converted from KIE credits using the Artillect rule **2 KIE credits = 1
Artillect token**, with the final fractional token rounded up. Wan 3 is 4 / 8 / 16
tokens per second at 480P / 720P / 1080P; Prime is 6.1 / 12.6 / 25.2 tokens per
second. The estimate is `ceil(rate × ceil(duration))`; `duration=-1` uses 5 seconds
for a deterministic pre-submit estimate.

**`flux-3` — что есть что:**

| Режим                    | Слоты                                                | Комментарий                                                                                                                                                          |
| ------------------------ | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| t2v                      | только `prompt`                                      | текст → видео                                                                                                                                                        |
| i2v                      | `start_image_url`                                    | один стартовый кадр                                                                                                                                                  |
| flf2v                    | `start_image_url` + `end_image_url`                  | first + last                                                                                                                                                         |
| keyframes (task `ref2v`) | `keyframes` (предпочтительно) или `reference_images` | упорядоченный storyboard ≤10 URL; **не** style/character refs как у Seedance/Kling. FAL сам раскладывает кадры по `duration`. Timed-пины `[sec, url]` в API пока нет |

```json
{
  "model": "flux-3",
  "prompt": "Cinematic pass through the storyboard",
  "keyframes": ["https://.../frame1.jpg", "https://.../frame2.jpg", "https://.../frame3.jpg"],
  "duration": 12,
  "aspect_ratio": "16:9",
  "resolution": "1080p",
  "generate_audio": true
}
```

### Avatar / Omni / Motion / Lip-sync (wave 2)

| `model`                | task                     | Required media                                                                | Prompt          | Extra                                                |
| ---------------------- | ------------------------ | ----------------------------------------------------------------------------- | --------------- | ---------------------------------------------------- |
| `gemini-omni`          | t2v/i2v/ref2v/edit       | ref2v: `reference_images` and/or `motion_video_url`; edit: `source_video_url` | required (edit) | KIE quota 7 units on ref2v; expensive at 4k          |
| `gemini-omni-flash`    | t2v/i2v/ref2v/edit       | i2v: `start_image_url`; ref2v: `reference_images`; edit: `source_video_url`   | required        | FAL; frames XOR refs                                 |
| `gemini-omni-flash-11` | t2v/i2v/flf2v/ref2v/edit | ref2v: `reference_images` / `reference_videos`; edit: `source_video_url`      | required (edit) | 7 weighted units; 360p/720p/1080p/4k                 |
| `kling-3-motion`       | motion                   | `start_image_url` + `motion_video_url`                                        | optional        | `character_orientation`, `background_source`         |
| `kling-26-motion`      | motion                   | same                                                                          | optional        | Kling 2.6 motion                                     |
| `wan-22`               | motion, swap             | `start_image_url` + `motion_video_url` / `source_video_url`                   | optional        | `task`: `motion` (default) \| `swap`; 480p/580p/720p |
| `kling-ai-avatar-pro`  | avatar                   | `start_image_url` + `audio_url`                                               | **required**    | talking head                                         |
| `omnihuman`            | avatar                   | same                                                                          | optional        | `pe_fast_mode`, `seed`, `resolution` (720/1080)      |
| `volcengine`           | lipsync                  | `source_video_url` + `audio_url`                                              | optional        | `lipsync_mode` lite\|basic                           |
| `sync-v3`              | lipsync                  | same                                                                          | optional        | `sync_mode` (cut_off default)                        |
| `heygen`               | lipsync                  | same                                                                          | optional        | precision only (`heygen_mode: speed` → 400)          |

**Motion transfer example:**

```json
{
  "model": "kling-3-motion",
  "prompt": "Dance like the reference",
  "start_image_url": "https://.../character.jpg",
  "motion_video_url": "https://.../dance.mp4",
  "resolution": "1080p"
}
```

**Avatar example:**

```json
{
  "model": "kling-ai-avatar-pro",
  "prompt": "Natural talking head",
  "start_image_url": "https://.../face.jpg",
  "audio_url": "https://.../voice.mp3"
}
```

**Lip-sync example (prompt optional):**

```json
{
  "model": "volcengine",
  "source_video_url": "https://.../talk.mp4",
  "audio_url": "https://.../dub.mp3",
  "lipsync_mode": "lite"
}
```

Коды ошибок wave 2: `MOTION_IMAGE_REQUIRED`, `MOTION_VIDEO_REQUIRED`, `AVATAR_IMAGE_REQUIRED`, `AUDIO_REQUIRED`, `GEMINI_OMNI_QUOTA_EXCEEDED`, `REFERENCE_MEDIA_REQUIRED`, `HEYGEN_MODE_UNSUPPORTED`.

`aspect_ratio` — по каталогу модели. Default `16:9`. Для **`kling-3` + start frame** можно опустить / `"auto"` — KIE auto-adapt по кадру (см. секцию ниже).  
`seedance-2` + `seedance_mode: fast` + `resolution: 4k` → `400 VIDEO_RESOLUTION_UNSUPPORTED`.

Коды ошибок видео: `VIDEO_SLOT_UNSUPPORTED`, `VIDEO_FRAMES_REFS_EXCLUSIVE`, `VIDEO_END_REQUIRES_START`, `VIDEO_TOO_MANY_REFS`, `VIDEO_TASK_UNSUPPORTED`, `VIDEO_TIER_TASK_UNSUPPORTED`, `SOURCE_VIDEO_REQUIRED`, `DURATION_NOT_SUPPORTED`, `SHOT_TYPE_INVALID`, `MULTI_PROMPT_INVALID`, `CFG_SCALE_INVALID`, `CFG_SCALE_OUT_OF_RANGE`, `CFG_SCALE_UNSUPPORTED`, `PROJECT_ID_REQUIRED_FOR_ELEMENTS`, `ELEMENT_NOT_FOUND`. Также общие `PROMPT_REQUIRED`, `PROMPT_TOO_LONG` (max 5000 симв.), `INPUT_VIDEO_*`, `INPUT_AUDIO_*`.

**Text-to-video:**

```json
{
  "model": "kling-3-turbo",
  "prompt": "A cat surfing a wave",
  "resolution": "1080p",
  "duration": "5",
  "aspect_ratio": "16:9"
}
```

**Image-to-video (первый кадр):**

```json
{
  "model": "seedance-2-mini",
  "prompt": "Camera slowly zooms in",
  "start_image_url": "https://images.unsplash.com/photo-...",
  "resolution": "720p",
  "duration": 5
}
```

**Keyframe (start+end, только Seedance):**

```json
{
  "model": "seedance-2-mini",
  "prompt": "Morph between the two frames",
  "start_image_url": "https://.../a.jpg",
  "end_image_url": "https://.../b.jpg"
}
```

**Reference-to-video (Seedance, 3 видео → @Video1..3):**

```json
{
  "model": "seedance-2",
  "seedance_mode": "quality",
  "prompt": "Transfer motion from @Video1 onto the character in @Image1",
  "reference_videos": ["https://.../a.mp4", "https://.../b.mp4", "https://.../c.mp4"],
  "reference_images": ["https://.../char.jpg"],
  "resolution": "1080p",
  "duration": 5
}
```

**Kling O3 — edit видео:**

```json
{
  "model": "kling-o3",
  "kling_mode": "pro",
  "task": "edit",
  "prompt": "Change the jacket to red, keep everything else the same",
  "source_video_url": "https://.../clip.mp4"
}
```

**Kling O3 — edit с референсами и keep_audio:**

```json
{
  "model": "kling-o3",
  "task": "edit",
  "prompt": "Change the jacket to red",
  "source_video_url": "https://.../clip.mp4",
  "reference_images": ["https://.../style.jpg"],
  "keep_audio": true,
  "shot_type": "customize"
}
```

`reference_images` в edit/v2v маппятся в provider `image_urls`. CamelCase-алиасы: `shotType`, `multiPrompt`, `keepAudio`, `cfgScale`, `startImageUrl`, `sourceVideoUrl`, …

**`cfg_scale` (только `kling-o3`, FAL):** насколько строго модель следует промпту. Float **0–1**, default **0.5** (0 — больше свободы, 1 — строго по тексту). Задачи: t2v / i2v / flf2v / ref2v / v2v; **не** edit. У **`kling-3`** (KIE `kling-3.0/video`) этого параметра в provider API **нет** — передача `cfg_scale` → `400 CFG_SCALE_UNSUPPORTED`.

### Multi-shot (`multi_prompt`) — Kling 3 и Kling O3

Обе модели поддерживают сценарий из нескольких шотов. Вместо одного `prompt` передаётся массив `multi_prompt` с отдельным промптом и длительностью на каждую сцену.

**Общие правила:**

| Параметр               | Kling 3 (`kling-3`, KIE)                                                           | Kling O3 (`kling-o3`, FAL)                                         |
| ---------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Флаг режима            | `multi_shots: true` (обязателен)                                                   | `shot_type: "customize"`                                           |
| Сцен                   | 2–5                                                                                | 2–5                                                                |
| Длительность сцены     | 1–12 с (целое)                                                                     | 1–12 с, **строка** `"1"`…`"12"`                                    |
| Суммарная длительность | 3–15 с (`duration` = сумма)                                                        | 3–15 с (`duration` = сумма, **строка** `"3"`…`"15"`)               |
| Промпт сцены           | ≤500 символов                                                                      | ≤500 символов                                                      |
| Верхний `prompt`       | не нужен при `multi_shots` + `multi_prompt`                                        | не нужен при `shot_type: customize` + `multi_prompt`               |
| Задачи                 | t2v, i2v (**не** flf2v: end frame запрещён)                                        | t2v, i2v, flf2v, ref2v, v2v (не edit)                              |
| Валидация              | те же лимиты, что UI (`MULTI_PROMPT_INVALID` / `MULTI_SHOT_END_FRAME_UNSUPPORTED`) | то же; `intelligent` + `multi_prompt` → `MULTI_PROMPT_UNSUPPORTED` |

> **Важно для Kling O3:** FAL принимает `duration` только как **строковый литерал** (`"3"`, `"5"`, `"10"`), в том числе внутри каждого элемента `multi_prompt`. Число `3` без кавычек даст ошибку валидации provider (`literal error`).

**Kling 3 — multi-shot (ручной сценарий):**

```json
{
  "model": "kling-3",
  "kling_mode": "pro",
  "multi_shots": true,
  "multi_prompt": [
    { "prompt": "A dog runs across a sunny field", "duration": 3 },
    { "prompt": "The dog stops and looks at the camera", "duration": 3 }
  ],
  "duration": "6",
  "generate_audio": true,
  "start_image_url": "https://.../scene.jpg"
}
```

При multi-shot Kling 3 использует только `start_image_url` (первый кадр); end frame недоступен. `@элементы` и `@ImageN` можно указывать в промптах отдельных сцен.

### Kling 3 — aspect auto-adapt (в т.ч. ultrawide / 21:9)

По [KIE Kling 3.0](https://docs.kie.ai/market/kling/kling-3-0.md): enum `aspect_ratio` только **`16:9` | `9:16` | `1:1`**. Значения вроде `21:9` в enum **нет** — `aspect_ratio: "21:9"` не сработает.

Отдельно в доке описан **Aspect Ratio Auto-Adaptation**: если переданы `image_urls` (start / start+end), `aspect_ratio` **можно не слать** — провайдер подстраивает соотношение под кадр(ы). Явный `16:9` / `9:16` / `1:1` по-прежнему форсит выбранный ratio.

Как вызвать через этот API:

1. Модель `kling-3` (single-shot или multi-shot).
2. Обязательно `start_image_url` — картинка нужного соотношения, напр. **21:9** (task станет `i2v`). Multi-shot: только первый кадр (`image_urls[0]` у провайдера).
3. **Не передавайте** `aspect_ratio` — или явно `"auto"`. Тогда Artillect не подставит default `16:9`.
4. Чистый **t2v** (без `start_image_url`) auto-adapt **недоступен** — только `16:9` / `9:16` / `1:1`.
5. Явный `aspect_ratio: "16:9"` (и т.п.) форсит enum, даже если кадр шире.

```json
{
  "model": "kling-3",
  "kling_mode": "pro",
  "prompt": "Camera slowly pans across the desert highway",
  "start_image_url": "https://.../scene-21x9.jpg",
  "duration": "5",
  "generate_audio": true
}
```

Multi-shot то же самое — `multi_shots` + `multi_prompt` + `start_image_url` нужного AR, без `aspect_ratio`:

```json
{
  "model": "kling-3",
  "kling_mode": "pro",
  "multi_shots": true,
  "multi_prompt": [
    { "prompt": "Wide establishing shot of the canyon", "duration": 4 },
    { "prompt": "Hero walks into frame from the left", "duration": 4 }
  ],
  "duration": "8",
  "start_image_url": "https://.../scene-21x9.jpg",
  "generate_audio": true
}
```

> Это поведение **KIE Kling 3.0** (раздел Aspect Ratio Auto-Adaptation). Имеет смысл только при наличии start-кадра. Для t2v / без кадра — не работает.

**Kling O3 — multi-shot, ручной режим (`customize`):**

```json
{
  "model": "kling-o3",
  "kling_mode": "pro",
  "shot_type": "customize",
  "multi_prompt": [
    { "prompt": "Wide shot: city skyline at dawn", "duration": "4" },
    { "prompt": "Close-up: hero wakes up and smiles", "duration": "3" }
  ],
  "duration": "7",
  "aspect_ratio": "16:9",
  "generate_audio": false
}
```

`prompt` на верхнем уровне **не передавайте** — provider использует только `multi_prompt`.

**Kling O3 — multi-shot, авто-режим (`intelligent`):**

Модель сама режет один промпт на сцены. Передаётся обычный `prompt`, **без** `multi_prompt`:

```json
{
  "model": "kling-o3",
  "kling_mode": "pro",
  "shot_type": "intelligent",
  "prompt": "A short story: a cat explores an abandoned library, finds a glowing book, and opens it",
  "duration": "10",
  "aspect_ratio": "16:9"
}
```

**Kling O3 — multi-shot ref2v с элементами:**

```json
{
  "model": "kling-o3",
  "shot_type": "customize",
  "multi_prompt": [
    { "prompt": "@hero walks into frame from the left", "duration": "5" },
    { "prompt": "@hero picks up @Image1 and examines it", "duration": "5" }
  ],
  "duration": "10",
  "project_id": 42,
  "element_names": ["hero"],
  "reference_images": ["https://.../prop.jpg"],
  "start_image_url": "https://.../scene.jpg"
}
```

CamelCase-алиасы: `multiShots`, `multiPrompt`, `shotType`, `startImageUrl`, `generateAudio`.

**Kling O3 — v2v (замена персонажа, @Image1 + исходное видео):**

```json
{
  "model": "kling-o3",
  "task": "v2v",
  "prompt": "Replace the character in the video with the person from @Image1",
  "source_video_url": "https://.../clip.mp4",
  "reference_images": ["https://.../face.jpg"],
  "duration": 10
}
```

`duration` (секунды, **3–15**, default **10**) задаёт длину результата для **v2v**. Для **edit** поле не принимается — длина выхода следует `source_video_url`.

**Element library (@имя из проекта):**

```json
{
  "model": "seedance-2-mini",
  "project_id": 42,
  "prompt": "Animate @hero walking through the scene, style like @Image1",
  "element_names": ["hero"],
  "reference_images": ["https://.../style.jpg"]
}
```

`element_names` — опционально, если не хотите писать `@имя` в промпте (имена из `GET .../elements/`).

---

## Проекты и element library

Discovery + управление проектами (без платежей / покупки токенов). Платежи намеренно не в Public API.

### Список / создание проектов

```http
GET https://app.artillect.pro/api/v1/projects/?limit=50
Authorization: Bearer art_xxxxxxxx
```

```http
POST https://app.artillect.pro/api/v1/projects/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{ "name": "Campaign A", "description": "optional", "status": "active" }
```

Query GET: `limit` (1–100, default **50**), `cursor` (id проекта для следующей страницы), `status` (`active` default | `all`).

Ответ list:

```json
{
  "items": [
    {
      "id": 42,
      "name": "зверолес",
      "description": null,
      "status": "active",
      "owner_user_id": 7,
      "role": "owner",
      "is_personal_generations_hub": false,
      "created_at": "2025-06-01T12:00:00.000Z",
      "updated_at": "2025-06-01T12:00:00.000Z"
    },
    {
      "id": 123,
      "name": "Мои генерации",
      "status": "active",
      "owner_user_id": 7,
      "role": "owner",
      "is_personal_generations_hub": true,
      "created_at": "2025-01-01T00:00:00.000Z",
      "updated_at": "2025-01-01T00:00:00.000Z"
    }
  ],
  "next_cursor": null
}
```

`is_personal_generations_hub: true` — проект по умолчанию, куда попадают генерации без `project_id`.  
`role: "owner"` — ваш проект; иначе slug роли участника (`editor`, …). Для чужого проекта при submit нужен `billing_source` (см. выше).

### Один проект / update / delete

```http
GET https://app.artillect.pro/api/v1/projects/42/
PATCH https://app.artillect.pro/api/v1/projects/42/
DELETE https://app.artillect.pro/api/v1/projects/42/
Authorization: Bearer art_xxxxxxxx
```

PATCH body: `{ "name"?, "description"?, "status"?: "active"|"closed"|"archived" }` — только owner.  
DELETE — только owner; личный хаб «Мои генерации» удалить нельзя.

### Роли и участники

```http
GET /api/v1/projects/roles/
GET /api/v1/projects/42/members/?state=active_or_invited
POST /api/v1/projects/42/members/
{ "email": "editor@example.com", "role": "editor", "token_limit": 5000 }
PATCH /api/v1/projects/42/members/99/
{ "role": "viewer", "token_limit": null }
DELETE /api/v1/projects/42/members/99/
DELETE /api/v1/projects/42/members/me/
```

`token_limit` можно задавать только роли `editor`. Приглашение в личный хаб запрещено.

### Лента / move / copy / delete / favorites

Лента: `GET /api/v1/projects/42/generations/?q=hero&tag_ids=1,2&mine_only=1&date_from=2026-07-01&date_to=2026-07-28`  
Move: `PATCH .../generations/{genId}/` `{ "project_id": 99 }`  
Favorite mask: `{ "favorited": 1 }`  
Copy: `POST .../generations/{genId}/copy/` `{ "project_id": 99 }`  
Delete: `DELETE .../generations/{genId}/`  
Favorites list: `GET .../favorites/`  
Analytics: `GET .../analytics/` · Billing context: `GET .../billing/`

`genId` — numeric DB id **или** `external_job_id` из ленты.

### Элементы проекта

```http
GET https://app.artillect.pro/api/v1/projects/42/elements/?kinds=image,video
Authorization: Bearer art_xxxxxxxx
```

Query: `limit` (1–200, default **100**), `cursor` (element id), `kinds` — фильтр `image`, `video` (через запятую).

POST create: `{ "name", "kind": "image"|"video", "media_json", "preview_url" }` or `{ "name", "kind", "asset_id" }` for an Asset already copied into the project. Use `media_json: { "images": ["frontal", "ref2"] }` for an image and `{ "video_url": "clip" }` for a video. GET/PATCH/DELETE на `.../elements/{elementId}/` · reorder `POST .../elements/reorder/` `{ "ordered_ids": ["1","2"] }`.

System archive/favorite: `POST .../generations/{genId}/tags/` `{ "system_slugs": ["__archive__"], "mode": "add" }` (`__favorite__` тоже). `genId` = DB id или `external_job_id` из ленты (то же для likes).

**Не в Public API (пока):** Gemini/audio element create (KIE+billing), share-public links, paginated ledger/audit сверх analytics, feed/meta facets, payments. MCP project/element tools — **есть** (см. `/docs/mcp`).

Ответ:

```json
{
  "project_id": 42,
  "items": [
    {
      "id": "9001",
      "project_id": 42,
      "name": "hero",
      "kind": "image",
      "mention": "@hero",
      "preview_url": "https://app.artillect.pro/api/backend/media/download/?key=...",
      "image_urls": ["https://app.artillect.pro/api/backend/media/download/?key=..."],
      "sort_order": 0,
      "created_at": "2026-03-01T10:00:00.000Z"
    }
  ],
  "next_cursor": null
}
```

**Использование в video:** передайте `project_id` того же проекта и `@hero` в `prompt` **или** `element_names: ["hero"]` в `POST /api/v1/videos/generations/`. Native elements работают в Kling 3/O3; image frontal как pseudo-element — в поддержанных Seedance/Wan/Veo/Happy Horse/Grok/Flux моделях. Video-element на image-only/pseudo image adapters отклоняется.

**Использование в image:** image-element можно передать тем же способом в `POST /api/v1/images/generations/` — `project_id` + `@hero` в `prompt` или `element_names: ["hero"]`. Frontal добавляется в reference inputs, а prompt для provider переписывается в `image 1`, `image 2`, …; video-element для image generation запрещён.

### Теги проекта

```http
GET https://app.artillect.pro/api/v1/projects/42/tags/
Authorization: Bearer art_xxxxxxxx
```

```json
{
  "items": [{ "id": 7, "name": "Hero", "color": "#7C5CFF", "sort_order": 0 }],
  "request_id": "…"
}
```

```http
POST https://app.artillect.pro/api/v1/projects/42/tags/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{ "name": "Hero", "color": "#7C5CFF" }
```

```http
PATCH https://app.artillect.pro/api/v1/projects/42/tags/7/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{ "name": "Hero2", "color": "#22C55E" }
```

```http
DELETE https://app.artillect.pro/api/v1/projects/42/tags/7/
Authorization: Bearer art_xxxxxxxx
```

Коды: `PROJECT_ACCESS_DENIED`, `PROJECT_NOT_FOUND`, `PROJECT_ID_INVALID`, `ELEMENT_NOT_FOUND`, `PROJECT_ID_REQUIRED_FOR_ELEMENTS`, `TAG_ID_INVALID`, `TAG_NOT_FOUND`, `TAG_NAME_TAKEN`, `TAG_LIMIT_REACHED`, `VIEWER_READONLY`, `SYSTEM_TAG_PROTECTED`.

### Лайки и теги на существующей генерации

Лайк в UI — персональная метка участника (не путать с custom-тегами). Любой участник проекта может лайкнуть; список лайкнувших виден всем с доступом к проекту.

```http
GET https://app.artillect.pro/api/v1/projects/42/generations/9001/likes/
Authorization: Bearer art_xxxxxxxx
```

```json
{
  "generation_id": 9001,
  "project_id": 42,
  "result_index": -1,
  "count": 2,
  "liked_by_me": true,
  "liked_by": [
    { "user_id": 7, "name": "Rustam" },
    { "user_id": 12, "name": "Anna" }
  ],
  "request_id": "…"
}
```

Query: опционально `result_index` (слайс multi-image; без параметра — все лайки на генерации).

```http
POST https://app.artillect.pro/api/v1/projects/42/generations/9001/likes/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{ "mode": "toggle" }
```

`mode`: `add` | `remove` | `toggle` (default). Ответ — тот же shape, что у GET.

Повесить custom-тег на уже созданную генерацию (нужна роль не-viewer):

```http
POST https://app.artillect.pro/api/v1/projects/42/generations/9001/tags/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json

{ "tag_ids": [7], "mode": "add" }
```

Коды: `GENERATION_NOT_FOUND`, `GENERATION_ID_INVALID`, `TAG_IDS_REQUIRED`, `TAG_NOT_CUSTOM`, `VIEWER_READONLY`.

---

Ответ **202** (video submit):

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api-video",
  "status": "queued",
  "model": "seedance-2-mini",
  "task": "i2v",
  "cost_tokens": 55,
  "project_id": 123,
  "poll_url": "/api/v1/videos/generations/550e8400-...-api-video/"
}
```

### Poll видео

```http
GET https://app.artillect.pro/api/v1/videos/generations/{id}/
Authorization: Bearer art_xxxxxxxx
```

Статусы и `provider_sync`/`hint`/`recommended_poll_interval_sec` — как у images (видео рендерится дольше, рекомендуемый интервал **8 с**). Готово:

```json
{
  "status": "completed",
  "model": "seedance-2-mini",
  "task": "i2v",
  "cost_tokens": 55,
  "videos": [{ "url": "https://app.artillect.pro/api/backend/media/download/?key=..." }],
  "poster_url": "https://app.artillect.pro/api/backend/media/download/?key=...",
  "progress": 100
}
```

Только Artillect media URLs; если `videos` пустые при `completed` — поллите ещё (mirror).

`poster_url` — первый кадр готового видео (появляется только вместе с непустым `videos`). Нужен там, где само видео показать нельзя: превью в списке, `<video poster>`, и агенты, которым видео не заинлайнить.

---

## Upscale (Topaz + Crystal + FLUX Video Upscale)

Topaz, Crystal и FLUX Video Upscale через FAL. Topaz — **только Public API** (не в UI `/enhance`). Crystal и FLUX — тот же backend, что Studio `/enhance`. Async poll, как images/video.

```http
POST https://app.artillect.pro/api/v1/upscale/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
```

Poll:

```http
GET https://app.artillect.pro/api/v1/upscale/generations/{id}/
```

Job id: `{uuid}-api-upscale`. Discovery: `GET /api/v1/models/` → секция `upscale`.

### Модели

| `model` (slug)                | Вход        | FAL endpoint                         |
| ----------------------------- | ----------- | ------------------------------------ |
| `topaz-image` (default image) | `image_url` | `fal-ai/topaz/upscale/image`         |
| `topaz-video`                 | `video_url` | `fal-ai/topaz/upscale/video`         |
| `crystal-image`               | `image_url` | `clarityai/crystal-upscaler`         |
| `crystal-video`               | `video_url` | `clarityai/crystal-video-upscaler`   |
| `flux-video-upscale`          | `video_url` | `blackforestlabs/flux-video-upscale` |

**Поле `model` vs `enhancement_model` (Topaz only):**

- `model` — slug Artillect API (`topaz-image`, `topaz-video`, `crystal-image`, `crystal-video`, `flux-video-upscale`).
- `enhancement_model` — Topaz-модель (FAL field `model`: `Standard V2`, `Proteus`, `Gaia 2`, …). Alias: `topaz_model`.
- **Shorthand:** если `model` = имя Topaz-модели (например `"Gaia 2"`) и передан `video_url` / `image_url`, slug выводится автоматически.

URL — HTTPS или `/api/backend/media/download/?key=...` (как у images/video).

### Цена (1 USD cent = 1 Artillect token, округление **ceil**)

**Video** (`topaz-video`) — за секунду выходного видео (передайте `duration`, `width`, `height`, `upscale_factor` для точной оценки):

| Выходное разрешение (height после upscale) | tokens / сек |
| ------------------------------------------ | ------------ |
| ≤ 720p                                     | 1            |
| 720p–1080p                                 | 2            |
| > 1080p                                    | 8            |

- `target_fps: 60` → **×2** к цене.
- `enhancement_model: "Gaia 2"` → **×0.5** (половина цены).
- Итог: `ceil(duration × tokens_per_sec × fps_multiplier × model_multiplier)`.

**Image** (`topaz-image`) — по выходным мегапикселям (`width × height × upscale_factor²`):

| Output MP | tokens |
| --------- | ------ |
| ≤ 24      | 8      |
| ≤ 48      | 16     |
| ≤ 96      | 32     |
| ≤ 512     | 136    |

**Crystal image** (`crystal-image`) — `ceil(output_MP × 1.6)` tokens; используйте `scale_factor` (не `upscale_factor`), опционально `creativity` 0–1.

**Crystal video** (`crystal-video`) — base **10** + input MP (`per_megapixel_crystal`) + `duration` (per second) + FPS tier (`fps` / 30, ceil). Передайте `duration`, `width`, `height`, `upscale_factor`, `fps` для точной оценки.

**FLUX Video Upscale** (`flux-video-upscale`) — видео до **20 секунд и 50 MB**. Обязательное поле `video_url`; исходное соотношение сторон сохраняется. Параметры FAL: `upscale_factor` **1.5–3** (default 2), `creativity` **0/1** (0 = precise, 1 = creative, default 1), необязательный `prompt` для creative detail enhancement и `safety_tolerance` **0–4** (default 2). Выходной tier определяется размерами входа × коэффициентом: 1080p — **14/20 токенов/с**, 2K — **25/35**, 4K — **55/79** (precise/creative). Для оценки передайте `duration`, `width`, `height`; эти три поля нужны только биллингу и не отправляются провайдеру.

```json
{
  "model": "flux-video-upscale",
  "video_url": "https://cdn.example.com/input.mp4",
  "upscale_factor": 2,
  "creativity": 0,
  "safety_tolerance": 2,
  "duration": 10,
  "width": 1280,
  "height": 720
}
```

FAL reference: [blackforestlabs/flux-video-upscale](https://fal.ai/models/blackforestlabs/flux-video-upscale/llms.txt)

Оценка до submit: `POST /api/v1/estimate/` с `type: "upscale"` и тем же телом.

### Пример — image

```json
{
  "model": "topaz-image",
  "image_url": "https://example.com/photo.jpg",
  "enhancement_model": "Standard V2",
  "upscale_factor": 2,
  "width": 1920,
  "height": 1080,
  "output_format": "jpeg",
  "face_enhancement": true,
  "sharpen": 0.3,
  "denoise": 0.2
}
```

### Пример — video

```json
{
  "model": "topaz-video",
  "video_url": "https://example.com/clip.mp4",
  "enhancement_model": "Proteus",
  "upscale_factor": 2,
  "width": 1280,
  "height": 720,
  "duration": 12,
  "target_fps": 30,
  "compression": 0.5,
  "noise": 0.3
}
```

Shorthand (slug не нужен):

```json
{
  "model": "Gaia 2",
  "video_url": "https://example.com/clip.mp4",
  "duration": 10
}
```

### Параметры `topaz-image`

Все параметры опциональны (есть defaults); можно задать любое допустимое число в диапазоне.

Доступны также отдельные модели `seedvr2-image`, `seedvr2-seamless-image`, `clarity-image`, `bria-creative-image`, `realesrgan-video`, `seedvr2-video` и `flashvsr-video`. У этих моделей свои параметры; актуальный список входов и ограничений всегда возвращает `GET /api/v1/models/` → `upscale.models[]`.

| Параметр                      | Тип / default                                          | Описание                                                                                                                                                                                                                       |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `enhancement_model`           | enum, default `Standard V2`                            | Topaz model → FAL `model`. Полный актуальный список режимов (включая новые High Fidelity, Wonder, Recover, Bloom и Transparent) возвращает `GET /api/v1/models/` → `upscale.models[]` → `parameters.enhancement_model.values`. |
| `upscale_factor`              | number 1–4, default **2**                              | Масштаб (выход ≈ вход × factor)                                                                                                                                                                                                |
| `width`, `height`             | integer, default **1024**                              | Размер входа (px) — для billing MP tiers                                                                                                                                                                                       |
| `crop_to_fill`                | boolean, default **false**                             | Crop to fill после upscale                                                                                                                                                                                                     |
| `output_format`               | `jpeg` \| `png`, default **jpeg**                      | Формат файла                                                                                                                                                                                                                   |
| `subject_detection`           | `All` \| `Foreground` \| `Background`, default **All** | Standard / Recovery V2                                                                                                                                                                                                         |
| `face_enhancement`            | boolean, default **true**                              | Standard / Recovery V2                                                                                                                                                                                                         |
| `face_enhancement_creativity` | 0–1, default **0**                                     | При `face_enhancement=true`                                                                                                                                                                                                    |
| `face_enhancement_strength`   | 0–1, default **0.8**                                   | При `face_enhancement=true`                                                                                                                                                                                                    |
| `sharpen`                     | 0–1                                                    | Standard V2, Low Res V2, CGI, High Fidelity V2, Text Refine, Redefine                                                                                                                                                          |
| `denoise`                     | 0–1                                                    | Те же модели                                                                                                                                                                                                                   |
| `fix_compression`             | 0–1                                                    | Standard V2, Low Res V2, High Fidelity V2, Text Refine                                                                                                                                                                         |
| `strength`                    | 0.01–1                                                 | **Text Refine** only                                                                                                                                                                                                           |
| `creativity`                  | 1–6                                                    | **Redefine** only                                                                                                                                                                                                              |
| `texture`                     | 1–5                                                    | **Redefine** only                                                                                                                                                                                                              |
| `prompt`                      | string, max 1024                                       | **Redefine** — generative prompt                                                                                                                                                                                               |
| `autoprompt`                  | boolean                                                | **Redefine** — auto prompt                                                                                                                                                                                                     |
| `detail`                      | 0–1                                                    | **Recovery V2** only                                                                                                                                                                                                           |
| `enhancement_strength`        | `low` \| `medium` \| `high`                            | **Wonder 3** only (omit = Topaz auto)                                                                                                                                                                                          |

FAL reference: [topaz/upscale/image](https://fal.ai/models/fal-ai/topaz/upscale/image/llms.txt)

### Параметры `topaz-video`

| Параметр            | Тип / default              | Описание                                                     |
| ------------------- | -------------------------- | ------------------------------------------------------------ |
| `enhancement_model` | enum, default **Proteus**  | FAL `model`. Gaia 2 = half price. См. список в `GET /models` |
| `upscale_factor`    | number 1–4, default **2**  | Масштаб                                                      |
| `width`, `height`   | integer, default **1024**  | Вход (px) — tier pricing по выходной height                  |
| `duration`          | number ≥1, default **5**   | Длина видео (сек) — **обязательно для точного billing**      |
| `target_fps`        | integer 16–60              | Frame interpolation; **60 → ×2 price**                       |
| `compression`       | 0–1                        | Удаление артефактов сжатия                                   |
| `noise`             | 0–1                        | Шумоподавление                                               |
| `halo`              | 0–1                        | Halo reduction                                               |
| `grain`             | 0–0.1 (step 0.01)          | Film grain                                                   |
| `recover_detail`    | 0–1                        | Recover original detail                                      |
| `H264_output`       | boolean, default **false** | H.264 вместо H.265/HEVC                                      |

Video enhancement modes включают отдельные модели Proteus, Iris, Dione, Artemis, Gaia, Rhea, Theia, Astra и Starlight. Передавайте точное значение из `GET /api/v1/models/` → `upscale.models[]` → `parameters.enhancement_model.values`: варианты зависят от выбранного Topaz-режима.

FAL reference: [topaz/upscale/video](https://fal.ai/models/fal-ai/topaz/upscale/video/llms.txt)

### Параметры `crystal-image`

| Поле           | Тип                       | Описание                        |
| -------------- | ------------------------- | ------------------------------- |
| `scale_factor` | number 1–4, default **2** | Масштаб (выход ≈ вход × factor) |
| `creativity`   | number 0–1, default **0** | Генеративная креативность       |
| `width`        | integer, default 1024     | Ширина входа (оценка цены)      |
| `height`       | integer, default 1024     | Высота входа (оценка цены)      |

Alias `model`: `crystal-image-upscaler`.

### Параметры `crystal-video`

| Поле               | Тип                       | Описание                          |
| ------------------ | ------------------------- | --------------------------------- |
| `upscale_factor`   | number 1–4, default **2** | Масштаб                           |
| `duration`         | number, default **5**     | Длительность в секундах (billing) |
| `width` / `height` | integer, default 1024     | Размер входа (оценка)             |
| `fps`              | integer, default **24**   | FPS (tier pricing)                |

Alias `model`: `crystal-video-upscaler`.

### Poll upscale

Ответ **202** (submit):

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api-upscale",
  "status": "queued",
  "model": "topaz-image",
  "cost_tokens": 8,
  "poll_url": "/api/v1/upscale/generations/550e8400-...-api-upscale/"
}
```

Готово (image):

```json
{
  "status": "completed",
  "model": "topaz-image",
  "images": [{ "url": "https://.../upscaled.jpg" }],
  "progress": 100
}
```

Готово (video):

```json
{
  "status": "completed",
  "model": "topaz-video",
  "videos": [{ "url": "https://.../upscaled.mp4" }],
  "progress": 100
}
```

Коды ошибок: `MODEL_UNKNOWN`, `IMAGE_URL_REQUIRED`, `VIDEO_URL_REQUIRED`, `INVALID_MEDIA_URL`, `PARAM_OUT_OF_RANGE`, `ENUM_INVALID`.

---

## Аудио (музыка, речь, клонирование и диалоги)

Async submit → poll, как images/video. Список моделей и точные параметры доступны в `GET /api/v1/models/` → `audio.models[]`; для всех режимов используется один `POST /api/v1/audio/generations/` и общий poll route.

```http
POST https://app.artillect.pro/api/v1/audio/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
Idempotency-Key: optional-unique-key

{
  "model": "suno-v6",
  "prompt": "dreamy lo-fi beat for late night coding",
  "style": "lo-fi hip-hop, warm tape saturation",
  "instrumental": true
}
```

### Модели и параметры

| slug                                                                                                                                         | Режим                | Провайдер / модель                                           |
| -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------ |
| `suno-v6` (default), `suno-v6-mini`, `suno-v6-wild`                                                                                          | песня                | KIE Suno V6 / Mini / Wild                                    |
| `suno-generate-lyrics`                                                                                                                       | текст песни          | KIE Suno · 2–3 варианта текста одним запросом                |
| `suno-boost-style`                                                                                                                           | улучшение стиля      | KIE Suno · расширенное описание музыкального стиля           |
| `suno-timed-lyrics`                                                                                                                          | слова по таймкодам   | KIE Suno · слова выбранного трека с точным временем          |
| `suno-music-video`                                                                                                                           | действие с треком    | KIE Suno · MP4 из выбранного варианта                        |
| `suno-upload-cover`, `suno-upload-extend`, `suno-add-vocals`, `suno-add-instrumental`, `suno-mashup`                                         | работа с аудио       | KIE Suno V6 / Mini / Wild                                    |
| `suno-extend`, `suno-replace-section`, `suno-wav`, `suno-separate-vocals`, `suno-split-stems`, `suno-extract-stem`                           | действие с треком    | KIE Suno Extend / замена фрагмента / WAV / разделение стемов |
| `minimax-music-v3`                                                                                                                           | песня                | FAL MiniMax Music 3                                          |
| `elevenlabs-music-v25`                                                                                                                       | песня                | FAL ElevenLabs Music v2.5                                    |
| `google-lyria-35`                                                                                                                            | песня                | FAL Google Lyria 3.5                                         |
| `mmaudio-v2-text-to-audio`                                                                                                                   | звуки                | FAL MMAudio V2                                               |
| `sonilo-v11-music`                                                                                                                           | музыка               | FAL Sonilo v1.1, до 4 вариантов в одном job                  |
| `sonilo-v11-sfx`                                                                                                                             | звуки                | FAL Sonilo v1.1 Sound Effects                                |
| `mirelo-sfx16-text-to-audio`                                                                                                                 | звуки и атмосфера    | FAL Mirelo SFX 1.6, 1–4 варианта в одном job                 |
| `sonilo-v11-video-to-music`                                                                                                                  | музыка к видео       | FAL Sonilo v1.1 · до 4 музыкальных дорожек                   |
| `sonilo-v11-video-to-sfx`, `sonilo-v11-video-to-video-sfx`                                                                                   | озвучивание видео    | FAL Sonilo v1.1 · отдельный звук либо видео + звук           |
| `stable-audio-25-text-to-audio`                                                                                                              | музыка и звуки       | FAL Stable Audio 2.5                                         |
| `stable-audio-3-small-music`, `stable-audio-3-small-music-base`, `stable-audio-3-small-sfx`, `stable-audio-3-small-sfx-base`                 | музыка и звуки       | FAL Stable Audio 3 Small, до 120 секунд                      |
| `stable-audio-3-medium`, `stable-audio-3-medium-base`                                                                                        | музыка и звуки       | FAL Stable Audio 3 Medium, до 380 секунд                     |
| `stable-audio-3-a2a-small-music`, `stable-audio-3-a2a-small-music-base`, `stable-audio-3-a2a-small-sfx`, `stable-audio-3-a2a-small-sfx-base` | преобразование аудио | FAL Stable Audio 3 Small, нужен `audio_url`                  |
| `stable-audio-3-a2a-medium`, `stable-audio-3-a2a-medium-base`                                                                                | преобразование аудио | FAL Stable Audio 3 Medium, нужен `audio_url`                 |
| `stable-audio-3-inpaint-<variant>`; варианты: `small-music`, `small-music-base`, `small-sfx`, `small-sfx-base`, `medium`, `medium-base`      | замена фрагмента     | FAL Stable Audio 3, нужны `audio_url` и границы фрагмента    |
| `stable-audio-3-outpaint-<variant>`; те же шесть вариантов                                                                                   | продолжение аудио    | FAL Stable Audio 3, нужен `audio_url`                        |
| `minimax-speech-02-hd`, `minimax-speech-02-turbo`                                                                                            | речь                 | FAL MiniMax Speech-02                                        |
| `minimax-speech-28-hd`, `minimax-speech-28-turbo`                                                                                            | речь                 | FAL MiniMax Speech 2.8                                       |
| `seed-speech-tts-v2`, `gemini-31-tts`, `xai-tts-v1`                                                                                          | речь                 | FAL Seed Speech / Gemini / xAI                               |
| `gemini-38-flash-tts`, `gemini-38-flash-lite-tts`                                                                                            | речь                 | FAL Gemini 3.8 Flash / Flash Lite                            |
| `eleven-v3-tts`, `eleven-turbo-25-tts`, `eleven-multilingual-v2-tts`                                                                         | речь                 | FAL ElevenLabs: выразительность / скорость / ровная начитка  |
| `inworld-tts-15-max`                                                                                                                         | речь                 | FAL Inworld TTS-1.5 Max                                      |
| `qwen3-tts-17b`, `qwen3-tts-06b`                                                                                                             | речь                 | FAL Qwen3 TTS 1.7B / 0.6B                                    |
| `qwen3-voice-design-17b`                                                                                                                     | дизайн голоса        | FAL Qwen3 Voice Design 1.7B · пробная речь новым голосом     |
| `dia-tts`                                                                                                                                    | речь/диалог          | FAL Dia TTS · текст с тегами говорящих                       |
| `chatterbox-multilingual-tts`                                                                                                                | речь                 | FAL Chatterbox Multilingual · 23 языка                       |
| `chatterbox-tts`, `chatterbox-turbo-tts`                                                                                                     | речь                 | FAL Chatterbox Base / Turbo                                  |
| `mai-voice-2`, `mai-voice-2-flash`                                                                                                           | речь                 | OpenRouter Microsoft MAI Voice 2 / Flash                     |
| `qwen-audio-3-tts-flash`, `qwen-audio-3-tts-plus`                                                                                            | речь                 | OpenRouter Qwen Audio 3.0 TTS Flash / Plus                   |
| `deepgram-aura-2`, `orpheus-3b-tts`, `sesame-csm-tts`, `kokoro-82m-tts`, `voxtral-mini-tts`                                                  | речь                 | OpenRouter Deepgram / Canopy / Sesame / Kokoro / Mistral     |
| `minimax-voice-clone`                                                                                                                        | речь по образцу      | FAL MiniMax Voice Clone; нужен `audio_url`                   |
| `fish-s21-voice-clone`                                                                                                                       | речь по образцу      | OpenRouter Fish Audio S2.1 Pro; нужен WAV `audio_url`        |
| `gemini-31-dialogue`                                                                                                                         | диалог               | FAL Gemini 3.1 Flash                                         |
| `gemini-38-flash-dialogue`, `gemini-38-flash-lite-dialogue`                                                                                  | диалог               | FAL Gemini 3.8 Flash / Flash Lite                            |
| `scribe-v2-transcribe`, `speech-to-text-turbo`                                                                                               | транскрибация        | FAL Scribe V2 / Speech to Text Turbo                         |
| `gpt-transcribe`                                                                                                                             | транскрибация        | OpenRouter GPT Transcribe                                    |
| `mai-transcribe-2`, `whisper-large-v3-turbo`                                                                                                 | транскрибация        | OpenRouter MAI Transcribe 2 / Whisper Large V3 Turbo         |
| `whisper-large-v3`, `whisper-1`, `deepgram-nova-3`                                                                                           | транскрибация        | OpenRouter Whisper / Deepgram                                |
| `voxtral-mini-transcribe`, `qwen3-asr-06b`, `mai-transcribe-15`                                                                              | транскрибация        | OpenRouter Mistral / Qwen / Microsoft                        |
| `fish-transcribe-1-pro`, `fish-transcribe-1`, `muse-voice-transcribe-1`, `grok-stt-1`                                                        | транскрибация        | OpenRouter Fish Audio / Meta / xAI                           |
| `nemotron-35-asr-06b`, `parakeet-tdt-06b-v3`, `voxtral-small-24b-stt`, `voxtral-mini-3b`                                                     | транскрибация        | OpenRouter NVIDIA / Mistral                                  |
| `qwen3-asr-17b`, `qwen3-asr-flash`, `chirp-3-transcribe`                                                                                     | транскрибация        | OpenRouter Qwen / Google                                     |
| `assemblyai-universal-35-pro`                                                                                                                | транскрибация        | OpenRouter AssemblyAI Universal 3.5 Pro                      |

Поля отличаются по модели. Например, MiniMax Music 3 принимает `style`, `lyrics`, `duration`; ElevenLabs Music — `prompt`, `duration` или `music_length_ms`, `instrumental`; TTS-модели — `text` и собственные поля `voice`/языка. Для Gemini dialogue передавайте `inputs: [{"speaker":"Speaker A","voice":"Kore","text":"Hello"}]`. Не переносите поля Suno на остальные модели. Цены рассчитываются по каталогу выбранной модели; используйте `POST /api/v1/estimate/` перед submit.

MMAudio V2 создаёт короткий звук по `prompt` (до 5000 символов). Дополнительные поля: `negative_prompt`, `duration` (1–30 секунд, default 8), `num_steps` (4–50, default 25), `cfg_strength` (0–20, default 4.5), `seed` (необязательное целое) и `mask_away_clip` (boolean, параметр FAL). Цена FAL $0.001 за выбранную секунду, в Artillect `ceil(duration × 0.1)` токенов за запрос. Результат — один аудиофайл в общем `artifacts[]`/`audio[]` poll.

Sonilo v1.1 Music принимает `prompt` (до 5000 символов), `duration` (целое 1–600 секунд, default 90) и `num_samples` (1–4, default 1). FAL тарифицирует минимум 10 секунд на вариант по $0.0025 за секунду: цена в Artillect — `ceil(max(10, duration) × num_samples × 0.25)` токенов. Например, 90 секунд и 3 варианта стоят 68 токенов. Все варианты возвращаются как отдельные элементы `audio[]`/`artifacts[]` одного job, без повторного списания. `audio` в ответе FAL повторяет первый элемент массива `audios`; Artillect устраняет этот дубль.

Sonilo v1.1 Sound Effects принимает `prompt` (до 5000 символов), `duration` (0.5–180 секунд, default 8) и `audio_format` (`aac|mp3|wav|flac`, default `aac`). Цена FAL $0.0018 за секунду; цена Artillect — `ceil(duration × 0.18)` токенов. Здесь `num_samples` недоступен. Оба варианта Sonilo сохраняются асинхронно через FAL queue; для проверки стоимости перед submit используйте `POST /api/v1/estimate/`.

Mirelo SFX 1.6 принимает `text_prompt` (или `prompt`, до 5000 символов), `duration` (0.1–60 секунд, default 10), `ambience` (бесшовная петля, минимум 1 секунда), `double_output` (удвоенный экспорт петли, только с `ambience=true`), `num_samples` (1–4, default 2), `seed` (`-1` либо неотрицательное целое число) и `upload_audio_format` (`wav|mp3|aac|flac`, default `wav`). Цена FAL $0.01 за секунду созданного звука; Artillect резервирует `ceil(duration × num_samples)` токенов за job. Все варианты возвращаются в `audio[]` и `artifacts[]` одного задания. Live smoke должен подтвердить тарификацию нескольких вариантов в счёте FAL.

Sonilo Video Sound Effects принимает `video_url` (HTTPS или медиа Artillect), необязательные `prompt` (до 5000 символов), `segments: [{start, end, prompt?}]` и `audio_format` (`aac|mp3|wav|flac`, default `aac`). Пустые `segments` включают автоматическую разбивку на сцены; пустой prompt позволяет модели самой описать звук. Первый заданный фрагмент начинается с 0, остальные идут без промежутков и наложений; последний заканчивается в пределах ролика. Artillect принимает видео до 300 секунд и измеряет его на сервере до списания. Для предварительного `POST /api/v1/estimate/` передайте также `duration` в секундах; фактический submit не доверяет этой подсказке. Цена обеих моделей — $0.009 за секунду, `ceil(duration × 0.9)` токена за job. `sonilo-v11-video-to-sfx` возвращает аудиодорожку (`output_kind: "audio"`); `sonilo-v11-video-to-video-sfx` возвращает готовое видео и отдельный звук (`output_kind: "video"`, оба файла в `artifacts[]`, звук также в `audio[]`/`tracks[]`).

Sonilo Video-to-Music (`sonilo-v11-video-to-music`) создаёт синхронизированную с видео музыку. Обязателен `video_url`; необязательны `prompt` (до 5000 символов), `num_samples` (1–4, default 1), `prompt_influence` (0–1, default 0.5), `start_offset` (секунды, default 0) и `duration` (длительность выбранного фрагмента; без неё — от `start_offset` до конца). Исходное видео — до 300 секунд; `start_offset + duration` должен помещаться в него. Сервер измеряет видео и считает стоимость выбранного фрагмента **до списания**: $0.009 за секунду за каждый вариант, `ceil(segment_seconds × num_samples × 0.9)` токена. Для предварительного `POST /api/v1/estimate/` передайте `source_duration` в секундах; submit повторно измерит исходник. Ответ имеет `output_kind: "audio"`; все варианты доступны в `audio[]`/`tracks[]` и `artifacts[]`. Первый `audio` из ответа FAL дублирует первый элемент `audios[]` и сохраняется один раз.

Inworld TTS-1.5 Max принимает `text` до 2000 символов, `voice` из перечисления FAL (default `Craig (en)`) и `sample_rate_hertz` (`8000|16000|24000|32000|40000|48000`, default `48000`). Полное перечисление голосов возвращает discovery; среди них есть русские `Svetlana (ru)`, `Elena (ru)`, `Dmitry (ru)`, `Nikolai (ru)`. Цена FAL $0.01/1000 символов, Artillect — `ceil(length(text) / 1000)` токенов, минимум один токен. Результат — один аудиофайл.

ElevenLabs V3, Turbo v2.5 и Multilingual v2 принимают `text` до 10000 символов, `voice` (default `Rachel`), `stability` (0–1), `timestamps`, `language_code` (ISO 639-1) и `apply_text_normalization` (`auto|on|off`). V3 делает эмоциональную речь с тегами вроде `[whispers]`; **только** Turbo и Multilingual дополнительно принимают `similarity_boost` (0–1), `style` (0–1), `speed` (0.7–1.2), `previous_text` и `next_text`. Старые дополнительные поля V3 теперь отклоняются: его endpoint их не поддерживает. При `timestamps=true` массив таймкодов слов доступен в первом аудиоартефакте как `artifacts[0].timestamps`. Цены FAL: V3 $0.10, Turbo $0.05, Multilingual $0.10 за 1000 символов; Artillect считает соответственно `ceil(length(text) × 10/5/10 ÷ 1000)` токенов. [V3 API](https://fal.ai/models/fal-ai/elevenlabs/tts/eleven-v3/api), [Turbo API](https://fal.ai/models/fal-ai/elevenlabs/tts/turbo-v2.5/api), [Multilingual API](https://fal.ai/models/fal-ai/elevenlabs/tts/multilingual-v2/api).

Аудиомодели в `GET /api/v1/models/` содержат `selection: {tier, badge, why}`. `featured` означает основной выбор Studio, `specialized` — отдельный сценарий, `hidden` — сохранённый для совместимости slug без нового выбора в Studio. Поле помогает выбрать модель; стоимость всегда проверяйте через `POST /api/v1/estimate/`.

Qwen3 TTS 1.7B и 0.6B принимают `text` до 10000 символов; `voice` — один из `Vivian|Serena|Uncle_Fu|Dylan|Eric|Ryan|Aiden|Ono_Anna|Sohee` (default `Vivian`), `language` — `Auto|English|Chinese|Spanish|French|German|Italian|Japanese|Korean|Portuguese|Russian` (default `Auto`). Необязательны `prompt` (подача речи), `speaker_voice_embedding_file_url` (HTTPS URL файла `.safetensors` из Qwen3 Clone Voice) и `reference_text` (текст образца). При embedding обычный `voice` и `prompt` игнорируются провайдером. Точные настройки: `top_k` (default 50), `top_p` (1), `temperature` (0.9), `repetition_penalty` (1.05), `subtalker_dosample` (`true`), `subtalker_top_k` (50), `subtalker_top_p` (1), `subtalker_temperature` (0.9), `max_new_tokens` (200). Цена FAL $0.09/$0.07 за 1000 символов соответственно; Artillect списывает `ceil(length(text) × 9 / 1000)` или `ceil(length(text) × 7 / 1000)` токенов, минимум один. Источники: [1.7B](https://fal.ai/models/fal-ai/qwen-3-tts/text-to-speech/1.7b/api), [0.6B](https://fal.ai/models/fal-ai/qwen-3-tts/text-to-speech/0.6b/api).

Qwen3 Voice Design 1.7B принимает `text` (до 10000 символов) и обязательный `prompt` (описание желаемого голоса и подачи, до 2000 символов). Необязательны `language` и те же sampling поля Qwen3 TTS с теми же границами и defaults. Предустановленный `voice` и `speaker_voice_embedding_file_url` не принимаются: модель создаёт **пробное аудио** по описанию, но [ответ FAL](https://fal.ai/models/fal-ai/qwen-3-tts/voice-design/1.7b/api) содержит только `audio`, не файл голосового embedding. Для последующего многократного использования этого голоса нужен отдельный Clone Voice flow. Цена $0.09 за 1000 символов `text`, `ceil(length(text) × 9 / 1000)` токенов, минимум один.

Dia TTS принимает только `text` до 10000 символов. Для диалога можно писать `[S1]`/`[S2]` перед репликами и действия в круглых скобках, например `(laughs)`; отдельного поля `voice` у [этого endpoint FAL](https://fal.ai/models/fal-ai/dia-tts/api) нет. Результат — один аудиофайл. Цена $0.04 за 1000 символов, `ceil(length(text) × 4 / 1000)` токенов, минимум один.

Chatterbox Multilingual принимает `text` до 300 символов и `voice` как язык (23 значения в discovery; default `russian`). Необязательный `voice_url` — HTTPS-образец тембра; при нём адаптер отправляет его в FAL как `voice`, а выбранный язык как `custom_audio_language`. Настройки: `exaggeration` (0.25–2, default 0.5), `temperature` (0.05–2, default 0.8), `cfg_scale` (0–1, default 0.5), `seed` (неотрицательное целое, 0 — случайный). [Цена FAL](https://fal.ai/models/fal-ai/chatterbox/text-to-speech/multilingual) $0.025 за 1000 символов; Artillect берёт 1 токен за запрос в пределах лимита 300 символов. Результат — один аудиофайл.

Chatterbox Base (`chatterbox-tts`) принимает `text` до 5000 символов, необязательный HTTPS `audio_url` голосового образца, `exaggeration` (0–1, default 0.25), `cfg` (0–1, default 0.5), `temperature` (0.05–2; default 0.7 в API) и `seed` (неотрицательное целое, 0 — случайный). Без своего образца FAL использует демонстрационный голос. Цена FAL $0.025 за 1000 символов; Artillect — `ceil(число символов × 3 / 1000)` токенов, минимум один.

Chatterbox Turbo (`chatterbox-turbo-tts`) принимает `text` до 5000 символов, `voice` (default `lucy`) или HTTPS `audio_url` короткого голосового образца; образец заменяет выбранный голос. Рекомендуемая длина образца — 5–10 секунд. Остальные поля: `temperature` (0.05–2; default 0.8), `seed` (неотрицательное целое, 0 — случайный). Текст поддерживает теги эмоций модели. Цена FAL $0.02 за 1000 символов; Artillect — `ceil(число символов × 2 / 1000)` токенов, минимум один. Для обоих вариантов Public API зеркалирует `audio_url` до отправки FAL, результат возвращается как один аудиофайл в `audio[]`/`artifacts[]`. Источник: [Base API](https://fal.ai/models/fal-ai/chatterbox/text-to-speech/api), [Turbo API](https://fal.ai/models/fal-ai/chatterbox/text-to-speech/turbo/api).

Девять моделей OpenRouter TTS принимают `text` до 4096 символов, необязательный `voice` из `input_schema` discovery и возвращают один MP3-файл. MAI дополнительно принимает `speed` (0.5–2), `style` и `styledegree` (0–2); остальные семь этих полей не принимают. Адаптер сохраняет результат как аудиоассет одного job. Тарифы за 1000 символов: MAI Voice 2/Flash — $0.022/$0.015, Qwen Flash/Plus — $0.015/$0.020, Aura 2 — $0.030, Orpheus и Sesame — $0.007, Kokoro — $0.004, Voxtral Mini — $0.016; итог `ceil(символы × ставка в центах / 1000)`, минимум один токен. Голоса и тарифы: [OpenRouter TTS](https://openrouter.ai/docs/guides/overview/multimodal/tts), [каталог](https://openrouter.ai/api/v1/models?output_modalities=speech).

`fish-s21-voice-clone` принимает `audio_url` WAV-образца до 15 МиБ, `text` до 4096 символов и необязательный `reference_text` (расшифровка образца, до 1000 символов). Сервис отправляет образец в `input_references` OpenRouter, получает один MP3-файл и сохраняет его в той же генерации. Fish Audio тарифицирует **UTF-8 байты** нового текста: $0.015/1000 байт, итог `ceil(byteLengthUtf8(text) × 1.5 / 1000)` токенов. Для кириллицы это отличается от расчёта по символам. [Описание модели и тариф](https://openrouter.ai/fish-audio/s2.1-pro), [контракт клонирования](https://openrouter.ai/docs/guides/overview/multimodal/tts#voice-cloning).

Stable Audio 2.5 создаёт музыку или звуковую сцену по `prompt` (до 10000 символов). Параметры: `seconds_total` (1–190 секунд, default 190), `num_inference_steps` (1–200, default 8), `guidance_scale` (0–20, default 1), необязательный `seed` (0–4294967295). `sync_mode` допускает только `false`: файл сохраняется для истории и галереи. Цена FAL $0.20 за аудиофайл, в Artillect 20 токенов за запрос независимо от длительности.

Stable Audio 3 принимает `prompt` (до 10000 символов), необязательные `negative_prompt`, `duration` (1–120 секунд у Small; 1–380 у Medium; default 30), `num_inference_steps` (1–200; default 8 у обычных и 50 у Base), `guidance_scale` (0–20; default 1/7), `seed`, `enable_prompt_expansion`, `enable_safety_checker` (default true), `output_format` (`mp3|wav|flac|ogg|opus|m4a|aac`) и `bitrate` (например, `192k`; для WAV/FLAC не используется). `sync_mode` допускает только `false`. Цена за аудиофайл: Small Music $0.0217, Small Music Base $0.0281, Small SFX $0.0206, Small SFX Base $0.0283 — по 3 токена; Medium $0.0376 — 4 токена; Medium Base $0.0479 — 5 токенов. Результат — один аудиофайл в `artifacts[]`/`audio[]`.

Stable Audio 3 audio-to-audio использует те же настройки и дополнительно требует `audio_url`: HTTPS-ссылку либо URL медиа Artillect. `init_noise_level` (0–1, default 0.9) задаёт силу преобразования; меньшее значение сохраняет больше исходного звука. Исходник должен иметь измеримую длительность до 120 секунд у Small и до 380 у Medium; сервер проверяет это до списания. Цена за аудиофайл: Small Music $0.0248 → 3 токена, Small Music Base $0.032 → 4, Small SFX $0.024 → 3, Small SFX Base $0.0315 → 4, Medium $0.0417 → 5, Medium Base $0.0523 → 6. Результат — один аудиофайл в `artifacts[]`/`audio[]`.

Stable Audio 3 inpainting заменяет участок исходной записи: обязательны `audio_url`, `prompt`, `mask_start_seconds`, `mask_end_seconds` (`0 ≤ start < end ≤` фактической длительности). Outpainting продолжает запись: обязательны `audio_url`, `prompt`, необязательны `extend_seconds_before` (default 0) и `extend_seconds_after` (default 30); сумма добавленного времени должна быть положительной. Исходник, а для outpainting и итоговая длительность, ограничены 120 секундами у Small и 380 у Medium. Сервер измеряет исходник и проверяет границы до списания. Общие настройки Stable Audio 3 действуют без `duration` и `init_noise_level`. Цена inpainting по вариантам Small Music/Base, Small SFX/Base, Medium/Base: $0.026/$0.0333/$0.0259/$0.0331/$0.0442/$0.0537 → 3/4/3/4/5/6 токенов. Цена outpainting: $0.0265/$0.0339/$0.0271/$0.0337/$0.0446/$0.0554 → те же 3/4/3/4/5/6 токенов. Результат — один аудиофайл в `artifacts[]`/`audio[]`.

`suno-generate-lyrics` принимает `prompt` длиной 1–200 символов. Один запрос стоит 1 токен и возвращает несколько вариантов текста. Poll возвращает `output_kind: "text"`, пустые `audio[]`/`tracks[]` и по одному элементу `artifacts[]` для каждого готового варианта: `kind: "text"`, `role: "lyrics"`, `index`, `title`, `text`. Индекс сохраняет позицию варианта у KIE, даже если соседний вариант не удался.

`suno-boost-style` принимает описание стиля в `prompt` длиной 1–1000 символов. Один запрос стоит 1 токен. Сервер отправляет его в KIE как `input.content`; результат доступен как один текстовый артефакт с `role: "style"`, `title: "Улучшенный стиль"` и полным `text`. Poll возвращает `output_kind: "text"` и пустые `audio[]`/`tracks[]`. Ответ KIE может содержать готовый `result` сразу при создании задачи либо после polling; оба варианта обрабатываются одинаково.

`suno-timed-lyrics` принимает `source_job_id` и `result_index` выбранного трека Suno. Доступ к исходному заданию проверяется сервером; KIE `task_id` и `audio_id` клиент не передаёт. Цена — 1 токен. Инструментальный исходный трек отклоняется до списания, если этот признак сохранён у источника. Poll возвращает `output_kind: "text"`, пустые `audio[]`/`tracks[]` и один артефакт `kind: "text"`, `role: "timed_lyrics"`, с полным `text` и `segments[]` по словам (`start`, `end` в секундах, `text`, `type: "word"`, при наличии `success` и `palign`). Дополнительные поля KIE `waveformData`, `hootCer`, `isStreamed` доступны в `metadata`. Studio показывает исходное аудио, позволяет перейти по таймкоду и экспортирует TXT, JSON, SRT и VTT. KIE может вернуть готовые `alignedWords` прямо при создании задачи без `taskId`; такой ответ обрабатывается как завершённый.

Gemini 3.8 TTS принимает `text`, `voice`, `style_instructions` и возвращает WAV. Gemini 3.8 dialogue принимает `turns` (или `inputs`) с `speaker_id`, `text`, необязательным `style_instructions` и ровно двух разных спикеров с голосами; `language_code` в схеме 3.8 отсутствует. Цена Flash/Lite: от 5/3 токенов за 1000 произнесённых символов, итог округляется вверх до целого токена.

Транскрибация принимает `audio_url`. Scribe V2 поддерживает `keyterms: string[]` (до 100 слов по 80 символов), возвращает слова с таймкодами и спикерами; цена $0.008 за минуту и +30% при `keyterms`. Speech to Text Turbo поддерживает `use_pnc: boolean` (пунктуация и регистр, default `true`), возвращает текст без таймкодов; цена $0.0008 за секунду. Сервер измеряет длительность аудио до списания, клиентское поле `duration` служит только для предварительной оценки. Цена Artillect округляется вверх до целого токена.

GPT Transcribe через OpenRouter поддерживает необязательное `language` (двухбуквенный код) и стоит $0.0045 за минуту. В текущем фоновом обработчике принимаются записи до 300 секунд и 24 МБ; превышение длительности отклоняется до списания, превышение размера завершает job с возвратом токенов. Длинные файлы не обрезаются.

MAI Transcribe 2 поддерживает `language`, `keyterms`, `diarization` (default `true`), `transcribe_style: "verbatim"|"clean"`; запрашиваются слова с таймкодами. Цена $0.10/час. Whisper Large V3 Turbo поддерживает `language` и запрашивает слова с таймкодами; цена $0.00000333/секунду. На обе модели OpenRouter действует тот же предел записи.

Whisper Large V3, Whisper 1 и Deepgram Nova-3 поддерживают `language` и запрос таймкодов. Стоимость по OpenRouter на 2026-09-26: $0.0000075, $0.0001 и $0.0000716667 за секунду соответственно. Для них действуют те же ограничения входного файла и серверный расчёт длительности.

Ещё 11 моделей OpenRouter с оплатой за секунду доступны через тот же `audio_url` и необязательный двухбуквенный `language`. По порядку строк выше: Fish Audio Pro/1 — $0.0001/с каждый; Meta Muse — $0.00005/с; Grok STT — $0.0000277777777778/с; Nemotron — $0.00000333/с; Parakeet — $0.000025/с; Voxtral Small/Mini 3B — $0.00005/$0.0000166667 за секунду; Qwen3 ASR 1.7B/Flash — $0.0000075/$0.000035 за секунду; Chirp 3 — $0.000266666666667/с. Сервис измеряет файл до списания, применяет `ceil(USD × 100)` и возвращает полный текст в `artifacts[]`; для этих моделей слово/спикер/таймкод не гарантируются. Вход ограничен 300 секундами и 24 МБ. OpenRouter объявляет снятие Qwen3 ASR Flash 2026-10-09; после этой даты проверяйте актуальный каталог моделей.

AssemblyAI Universal 3.5 Pro принимает запись через `audio_url` до **120 секунд и 24 МБ**. Сервис преобразует её в 16-битный WAV 16 кГц mono до отправки. Необязательные поля: `language` (двухбуквенный код), `prompt` (контекст записи, до 4096 символов), `keyterms` (до 100 строк по 80 символов), `conversation_context` (массив до 20 предыдущих реплик по 500 символов). Запрашиваются слова с таймкодами. Базовый тариф OpenRouter — $0.0000625/секунду; при подсказке — опубликованные $0.000069/секунду. При максимальной длительности оба тарифа округляются до **1 токена**. В ответе используется общий текстовый `artifacts[]`.

Voxtral Mini Transcribe, Qwen3 ASR 0.6B и MAI Transcribe 1.5 используют стандартный вход OpenRouter с `audio_url` и необязательным `language`. Результат сохраняется как текст; таймкоды для этих трёх моделей не запрашиваются. Тарифы: $0.00005, $0.00000333 и $0.0001 за секунду соответственно. Предел входа тот же: 300 секунд и 24 МБ.

MiniMax Speech 2.8 HD/Turbo принимает публичное `text`, `voice`, `voice_setting`, `audio_setting`, `language_boost`, `output_format`, а также объекты `pronunciation_dict`, `normalization_setting`, `voice_modify`. Адаптер отправляет текст в нативное поле `prompt`. Цены: $0.10/$0.06 за 1000 символов.

```json
{
  "model": "scribe-v2-transcribe",
  "audio_url": "https://example.com/interview.mp3",
  "keyterms": ["Artillect", "Suno"]
}
```

| Поле                   | Тип           | Заметка                                                                                 |
| ---------------------- | ------------- | --------------------------------------------------------------------------------------- |
| `prompt`               | string        | Идея в обычном режиме (max **3000**), запасной текст песни в custom mode                |
| `lyrics`               | string        | Точные слова песни (max **5000**); приоритетнее `prompt` в custom mode                  |
| `style`                | string        | Стиль/жанр (max **1000**); в обычном режиме может заполняться из `prompt`               |
| `title`                | string        | Название трека (max 80); в custom mode обязательно, иначе опускается                    |
| `custom_mode`          | boolean       | default `false`; в custom mode нужно хотя бы одно из `style`, `lyrics`, `negative_tags` |
| `instrumental`         | boolean       | default `false` — с вокалом; `true` — без вокала                                        |
| `negative_tags`        | string        | Что исключить (max 1000)                                                                |
| `vocal_gender`         | `m` \| `f`    | Только в custom mode                                                                    |
| `style_weight`         | number 0–1    | Насколько строго следовать стилю                                                        |
| `weirdness_constraint` | number 0–1    | Креативность                                                                            |
| `audio_weight`         | number 0–1    | Влияние аудио                                                                           |
| `variety`              | integer 0–4   | Разнообразие вариантов, только custom mode                                              |
| `duration`             | number 10–360 | Целевая длительность в секундах, только custom mode                                     |
| `persona_id`           | string        | ID персоны или голоса, только custom mode                                               |
| `persona_model`        | enum          | `style_persona` или `voice_persona`, только custom mode                                 |
| `image_urls`           | string[]      | До 5 изображений, только обычный режим                                                  |
| `video_urls`           | string[]      | До 1 видео, только обычный режим                                                        |
| `audio_urls`           | string[]      | Аудиореференсы, только обычный режим                                                    |

Старые значения `suno-v55` и `kie-suno-v55` принимаются как миграционные алиасы и направляются на V6: V5.5 снята у KIE. Ответ всегда сообщает фактически выбранную V6-модель. На один submit может прийти несколько вариантов трека; цена относится к запросу целиком.

`suno-upload-cover` принимает `upload_url` исходного аудио (публичный HTTPS URL или медиа Artillect), `model_version: "V6"|"V6_MINI"|"V6_WILD"` (default V6) и `instrumental` (default false). Исходник зеркалируется, длительность проверяется сервером: максимум 8 минут. В Studio доступны локальные MP3/WAV до 50 МБ. Для кавера можно указать `lyrics` или fallback `prompt` (до 5000 символов), `style` (до 1000), `title` (до 80), `negative_tags` (до 1000), `vocal_gender`, `style_weight`, `weirdness_constraint`, `audio_weight` (0–1, до двух знаков), `variety` (0–4), `duration` (10–360 секунд), `persona_id` и `persona_model`. При `instrumental: true` слова и пол вокала не передаются. Цена — 6 токенов за запрос; все выходные треки находятся в одном job и перечислены в `tracks[]`/`artifacts[]`.

Тот же `upload_url` и варианты V6 используются в трёх других операциях. `suno-upload-extend` продолжает внешний трек: `continue_at` (необязательно, больше нуля и строго до конца исходника), `instrumental`, слова, стиль, название до 100 символов, исключения, веса, разнообразие и Persona. `suno-add-vocals` требует `title`, `style`, `negative_tags`; слова, пол вокала и веса необязательны. `suno-add-instrumental` требует `title`, `tags` (стиль сопровождения) и `negative_tags`; слова, пол вокала и веса необязательны. Общий лимит источника в Artillect — 8 минут. Каждая операция стоит 6 токенов за весь job; все варианты сохраняются отдельно в нём.

`suno-mashup` принимает `upload_url_list` — ровно два аудио URL (публичные HTTPS или медиа Artillect), обязательные `title` до 100 символов и `style` до 1000. Можно добавить `lyrics`/`prompt`, пол вокала, веса, `variety`, `duration` (10–360 секунд) и Persona. Оба входа зеркалируются и проверяются на длительность до 8 минут перед списанием. Цена — 6 токенов за job, независимо от числа выходных вариантов.

`suno-extend` и `suno-wav` используют тот же submit/poll. Передайте `source_job_id` завершённой генерации Suno и `result_index` точного варианта из `tracks[]`/`artifacts[]`. Сервер проверяет доступ к источнику и сам берёт KIE `task_id`/`audio_id`; клиент не передаёт эти идентификаторы. Продолжение наследует версию V6/Mini/Wild исходного трека и стоит 6 токенов, WAV стоит 1 токен. Для продолжения обязателен `continue_at` в секундах внутри исходного трека; необязательны `lyrics` (до 5000), `style` (до 1000), `title` (до 100), `negative_tags`, `vocal_gender`, веса 0–1, `variety` 0–4 и `persona_id`/`persona_model`.

Для разделения готового трека доступны `suno-separate-vocals` (вокал и инструментал, 5 токенов), `suno-split-stems` (до 12 дорожек, 25 токенов) и `suno-extract-stem` (выбранный инструмент и остаток, 10 токенов). Для последнего обязательно `stem_name`, например `Lead Vocal` или `Drums`. Все результаты одного запуска приходят в `artifacts[]` с `role: "stem"`, отдельными `index` и `title`; стоимость относится ко всему запуску.

`suno-music-video` создаёт MP4 из одного завершённого Suno-трека. Передайте `source_job_id` и `result_index`; сервер проверит доступ и подставит KIE `task_id`/`audio_id`. Необязательные `author` и `domain_name` — строки до 50 символов каждая. Цена — **2 токена** за запуск (4 кредита KIE). В Studio действие находится у исходного трека, результат показывается в видеогалерее. `GET /api/v1/audio/generations/{id}/` возвращает `output_kind: "video"`, пустые `audio[]`/`tracks[]` и `artifacts[]` с `kind: "video"`, `role: "music_video"`, постоянным URL Artillect и `parent_artifact_id`. Исходная ссылка KIE действует 14 дней, поэтому MP4 сохраняется в хранилище при завершении job. Если KIE не вернул task ID или доступный MP4, job не объявляется успешным.

`suno-replace-section` стоит 3 токена и возвращает новую аудиоверсию. Обязательны `prompt`, `tags`, `title`, `full_lyrics`, `infill_start_s`, `infill_end_s`; сервер добавляет `task_id` и `audio_id` выбранного варианта. Интервал в секундах задаётся с точностью до сотых, находится внутри исходного трека, имеет длину минимум 10 секунд и максимум половину длительности. Необязательны `lyrics` заменяемого участка, `negative_tags`, `vocal_gender`, `style_weight`, `weirdness_constraint`, `audio_weight`, `variety`, `persona_id` и `persona_model`. Если длительность исходного трека не сохранена, запрос отвергается до списания.

```json
{
  "model": "suno-extend",
  "source_job_id": "550e8400-e29b-41d4-a716-446655440000-api-audio",
  "result_index": 2,
  "continue_at": 60,
  "lyrics": "[Bridge] A new melody begins"
}
```

Оценка цены: `POST /api/v1/estimate/` с `type: "audio"`.

### Poll аудио

Ответ **202** (submit):

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api-audio",
  "status": "queued",
  "model": "suno-v6",
  "cost_tokens": 6,
  "poll_url": "/api/v1/audio/generations/550e8400-...-api-audio/"
}
```

Готово (Suno обычно отдаёт два трека):

```json
{
  "status": "completed",
  "model": "suno-v6",
  "audio": [{ "url": "https://.../track-1.mp3" }, { "url": "https://.../track-2.mp3" }],
  "tracks": [
    {
      "url": "https://.../track-1.mp3",
      "result_index": 0,
      "provider_audio_id": "suno-track-1",
      "title": "Night Drive",
      "duration": 183.4,
      "image_url": "https://.../cover-1.webp"
    },
    {
      "url": "https://.../track-2.mp3",
      "result_index": 2,
      "provider_audio_id": "suno-track-2",
      "title": "Night Drive II",
      "duration": 179.1,
      "image_url": "https://.../cover-2.webp"
    }
  ],
  "output_kind": "audio",
  "artifacts": [
    {
      "id": "550e8400-...-api-audio:audio:0",
      "kind": "audio",
      "role": "track",
      "index": 0,
      "url": "https://.../track-1.mp3"
    },
    {
      "id": "550e8400-...-api-audio:audio:2",
      "kind": "audio",
      "role": "track",
      "index": 2,
      "url": "https://.../track-2.mp3"
    }
  ],
  "progress": 100
}
```

Готовая транскрипция возвращает `output_kind: "text"`, `audio: []`, `tracks: []` и `artifacts: [{"kind":"text","role":"transcript","text":"...","segments":[...],"language_code":"..."}]`. Полный текст хранится отдельно от краткого `preview_text`; при наличии сегментов Studio предлагает экспорт TXT, JSON, SRT и VTT. В `GET /api/v1/models/` аудиомодели содержат `pricing`: у моделей с оплатой по длительности или символам `cost_tokens: null`, а `base_cost_tokens` и `factors` описывают правило расчёта. Точную стоимость конкретного запроса возвращает `POST /api/v1/estimate/` с `type: "audio"`.

Suno отдаёт результат через callback — трек может появиться через несколько минут; при `provider_sync: "deferred"` продолжайте poll (`recommended_poll_interval_sec` = 10). Webhooks для аудио пока не рассылаются — используйте poll.

Цена и `cost_tokens` относятся к одному запросу; число объектов `tracks[]` на списание не влияет. Метаданные отдельных треков сохраняются вместе с результатом и доступны в архивном poll, когда исходный job уже удалён из Redis. Для старых генераций без метаданных `tracks[]` содержит URL и индекс результата.

Коды ошибок: `MODEL_UNKNOWN`, `PROMPT_REQUIRED`, `PROMPT_TOO_LONG`, `STYLE_REQUIRED`, `STYLE_TOO_LONG`, `TITLE_TOO_LONG`, `PARAMETER_INVALID`.

---

## SwitchX (Beeble video compositing)

Отдельный endpoint — **не** смешивается с Kling/Seedance `videos/generations`.

```http
POST https://app.artillect.pro/api/v1/switchx/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
Idempotency-Key: optional-unique-key

{
  "model": "switchx",
  "generation_type": "video",
  "source_url": "https://example.com/source.mp4",
  "reference_image_url": "https://example.com/ref.jpg",
  "alpha_mode": "auto",
  "max_resolution": 1080,
  "frame_count": 90,
  "prompt": "Place the character into the scene"
}
```

Обязательно: `source_url`. Нужен `prompt` **или** `reference_image_url`. Для `alpha_mode: "custom"|"select"` — `alpha_url`.

Ответ `202`:

```json
{
  "id": "550e8400-...-api-switchx",
  "status": "queued",
  "model": "switchx",
  "cost_tokens": 90,
  "poll_url": "/api/v1/switchx/generations/550e8400-...-api-switchx/"
}
```

Poll:

```http
GET https://app.artillect.pro/api/v1/switchx/generations/{id}/
```

Формат ответа как у video poll (`status`, `videos`, `progress`, `cost_tokens`, …). Цена: блоки по 30 кадров × множитель 1080p (×3). Оценка: `POST /api/v1/estimate/` с `type: "switchx"`.

---

## 3D-модели и обработка (Meshy, Hunyuan, Tripo, TRELLIS, SAM 3D)

`texture_image_url` принимает HTTPS-ссылку на изображение или image Data URI. Описание материала передавайте в `texture_prompt`; текст вместо ссылки отклоняется до списания. Успешный mesh-результат содержит файл геометрии; одно превью не считается готовой моделью.

Асинхронная генерация 3D-моделей поддерживает Meshy 6/7, Hunyuan 3D 3.1 Pro, Tripo H3.1 и Tripo P2. P2 доступна отдельными моделями `tripo-p2-t2m` и `tripo-p2-i2m` (по одному изображению максимум, без multiview). Провайдер — FAL, сервис `generate-mesh`. Параметры доступны в `GET /api/v1/models/` → `mesh.models[]`. Для Hunyuan multi-view передавайте `input_image_url` и при наличии `back_image_url`, `left_image_url`, `right_image_url`; для Tripo H3.1 multi-view — `image_urls` (до четырёх изображений). Запекание PBR-текстур (`texture-pbr`) в Public API **не** выведено.

```http
POST https://app.artillect.pro/api/v1/mesh/generations/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
Idempotency-Key: optional-unique-key

{
  "model": "meshy-v6-t2m",
  "prompt": "a small brass lantern, game-ready asset",
  "topology": "triangle",
  "target_polycount": 30000,
  "art_style": "realistic"
}
```

Image-to-3D — тот же роут с картинкой:

```json
{
  "model": "meshy-v6-i2m",
  "image_url": "https://example.com/toy.png",
  "should_texture": true
}
```

**Выбор модели.** `model` можно не слать: при наличии `image_url` (алиасы `input_url`, `image`, `input_urls[0]`) выбирается `meshy-v6-i2m`, иначе `meshy-v6-t2m` (default). Альтернатива — `task: "t2m" | "i2m"`. `prompt` (≤ 2000 символов) обязателен для t2m; для i2m опционален (идёт в подпись генерации). Слать картинку в t2m нельзя — `IMAGE_NOT_SUPPORTED`.

**Входное изображение** проходит тот же SSRF-гард, что и images: только публичные HTTPS-URL (max 1 redirect hop / без приватных IP), base64 data URI или media-URL из `POST /api/v1/files/`. Файл зеркалируется в Artillect media, провайдер читает уже нашу ссылку.

| Параметр                  | Модели | Значения                             |
| ------------------------- | ------ | ------------------------------------ |
| `topology`                | обе    | `triangle` (default) \| `quad`       |
| `target_polycount`        | обе    | 100–300000, default 30000            |
| `symmetry_mode`           | обе    | `off` \| `auto` (default) \| `on`    |
| `should_remesh`           | обе    | bool, default `true`                 |
| `enable_pbr`              | обе    | bool, default `false`                |
| `is_a_t_pose`             | обе    | bool, default `false`                |
| `texture_prompt`          | обе    | строка                               |
| `mode`                    | t2m    | `full` (default) \| `preview`        |
| `art_style`               | t2m    | `realistic` (default) \| `sculpture` |
| `enable_prompt_expansion` | t2m    | bool, default `false`                |
| `seed`                    | t2m    | 0–4294967295                         |
| `should_texture`          | i2m    | bool, default `true`                 |

### Tripo P2

```json
{
  "model": "tripo-p2-t2m",
  "prompt": "a small brass lantern",
  "texture": true,
  "pbr": true,
  "texture_quality": "detailed",
  "face_limit": 30000,
  "quad": false
}
```

Для image-to-3D укажите `model: "tripo-p2-i2m"` и один `image_url`; P2 не принимает prompt на image endpoint и не поддерживает несколько ракурсов. `texture_quality`: `fast`, `standard`, `detailed` или `extreme`. Fast автоматически использует совместимую версию текстур. `face_limit`: 48–50000; при `quad: true` максимум 25000. Также доступны `texture_version`, `delight`, `auto_size`, `export_uv`, `export_orientation`, `model_seed` и `texture_seed`; `image_seed` доступен только в text-to-3D. Полный перечень и значения по умолчанию возвращает `GET /api/v1/models/`.

Цена указана за одну модель и оценивается тем же контрактом `POST /api/v1/estimate/`: **100 токенов ($1.00)** без текстур (`texture: false` и `pbr: false`), **110 ($1.10)** для fast/standard, **120 ($1.20)** для detailed и **130 ($1.30)** для extreme. PBR включает текстуры и тарифицируется по выбранному качеству; quad-топология доплаты не добавляет.

Ответ `202`:

```json
{
  "id": "550e8400-...-api-mesh",
  "status": "queued",
  "model": "meshy-v6-t2m",
  "task": "t2m",
  "cost_tokens": 80,
  "poll_url": "/api/v1/mesh/generations/550e8400-...-api-mesh/"
}
```

Poll — `GET /api/v1/mesh/generations/{id}/`; готовый ответ отдаёт `mesh[]` (GLB первым):

```json
{
  "status": "completed",
  "model": "meshy-v6-t2m",
  "task": "t2m",
  "mesh": [
    {
      "url": "https://app.artillect.pro/api/backend/media/download/model.glb?key=...",
      "format": "glb"
    }
  ],
  "progress": 100,
  "cost_tokens": 80
}
```

Дополнительные модели используют тот же submit, estimate и poll:

| Model                              | Task               | Вход                                                                                                | Цена, токены        |
| ---------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------- | ------------------- |
| `trellis-2-i2m`                    | `i2m`              | image_url, resolution 512/1024/1536                                                                 | 25/30/35            |
| `trellis-i2m`, `trellis-multi-i2m` | `i2m`, `multi-i2m` | image_url или 2–4 image_urls                                                                        | 2                   |
| `sam-3d-objects`                   | `objects`          | image_url; optional prompt, point_prompts, box_prompts, mask_urls, pointmap_url                     | 2                   |
| `sam-3d-body`                      | `body`             | image_url; optional mask_url                                                                        | 2                   |
| `meshy-retexture`                  | `retexture`        | model_url + text_style_prompt (≤600) или image_style_url; enable_original_uv=true, enable_pbr=false | 30                  |
| `trellis-2-retexture`              | `retexture`        | mesh_url + image_url, resolution 512/1024                                                           | 20/24               |
| `meshy-rigging`                    | `rig`              | model_url GLB, height_meters=1.7; optional enable_animation, animation_action_id 0–696              | 20 + 12 с движением |
| `meshy-multi-animation`            | `animate`          | model_url GLB, animation_action_ids 0–696, 1–10 разных; height_meters=1.7                           | 20 + 12 за движение |

Для рига нужна гуманоидная GLB-модель с отдельными конечностями. Retexture принимает GLB/GLTF/OBJ/FBX/STL; у Meshy фото стиля имеет приоритет над текстом. SAM 3D Objects по умолчанию экспортирует текстурированный GLB и сохраняет PLY Gaussian Splat; тело — GLB, optional PLY, 3D-суставы и параметры MHR. JSON, архивы, текстуры и дополнительные движения сохраняются вместе с моделью. Координаты SAM — целые пиксели исходника. Параметры каждой модели — в discovery.

SAM изображений: `POST /api/v1/images/generations/`, model `sam-3-image` (PNG/WEBP/JPEG маски) или `sam-3-image-rle` (JSON с RLE). Одно изображение в `input_urls` и выделение через prompt, point_prompts или box_prompts обязательны. Можно запросить return_multiple_masks, max_masks 1–32, include_scores, include_boxes; цена — 1 токен.

SAM видео: модели `sam-3`, `sam-3-rle`, `sam-3-1`, `sam-3-1-rle`, task `edit`, source_video_url ≤60 секунд. apply_mask, detection_threshold; у 3.1 max_num_objects 1–128; у RLE frame_index, boundingbox_zip, у 3.1 RLE return_object_rles. Дополнительные параметры допускаются в MCP и CLI через `--extra`; для mesh CLI есть `--model-file`, `--animation-actions`, `--height`.

До отправки провайдеру mesh-задачу можно отменить через общий или modality-specific cancel endpoint в первые 4 секунды с возвратом токенов; после submit отмена отсутствует. История — `GET /api/v1/mesh/generations/`. Оценка цены: `POST /api/v1/estimate/` с `type: "mesh"` (для i2m передайте любой валидный `image_url` — он не скачивается).

Коды ошибок: `MODEL_UNKNOWN`, `TASK_INVALID`, `MODEL_TASK_MISMATCH`, `PROMPT_REQUIRED`, `PROMPT_TOO_LONG`, `IMAGE_URL_REQUIRED`, `IMAGE_NOT_SUPPORTED`, `TEXTURE_PARAMS_UNSUPPORTED`, `SERVICE_UNSUPPORTED`, `ENUM_INVALID`, `PARAM_OUT_OF_RANGE`, `INPUT_URL_INVALID`.

### Meshy 7

Meshy 7.1 доступен как `meshy-v71-t2m`, `meshy-v71-i2m`, `meshy-v71-multi-i2m`. Входы, лимит промпта и цена соответствуют v7, но вместо `ultra_mode` принимается `geometry_resolution`: `standard|2k|4k` для текста/одного изображения и `standard|2k` для нескольких ракурсов. Старые slugs v7 продолжают работать.

Доступны `meshy-v7-t2m`, `meshy-v7-i2m` и `meshy-v7-multi-i2m`. Для v7 text-to-3D лимит `prompt` — 600 символов. Image-to-3D принимает один `image_url`; multi-image принимает `image_urls` от 1 до 4 изображений одного объекта с разных углов. Входы JPG/JPEG/PNG, а AVIF/HEIF автоматически конвертируются.

Общие параметры v7: `model_type` (`standard|lowpoly|smart-topology`), `topology` (`quad|triangle`), `target_polycount` (100–300000; smart-topology максимум 15000), `symmetry_mode` (`off|auto|on`), `should_remesh`, `enable_pbr`, `pose_mode` (`a-pose|t-pose|""`), `texture_prompt`, `texture_image_url`, `enable_rigging`, `rigging_height_meters` (default 1.7), `enable_animation`, `animation_action_id` (0–696), `enable_safety_checker`.

У `meshy-v7-t2m` дополнительно есть `mode` (`preview|full`), `seed` и `enable_prompt_expansion`; `ultra_mode` доступен только для стандартной полнотекстурированной генерации. У `meshy-v7-i2m` есть `should_texture` и `ultra_mode` с тем же ограничением. У multi-image есть `should_texture`, но текущая схема endpoint не принимает `ultra_mode`.

Цена v7: 80 токенов без текстур, 120 с текстурами, 140 с текстурами и ultra; `enable_rigging` добавляет 20, `enable_animation` — 12. Multi-image: 120 с текстурами, +20/+12 за rigging/animation. Оценка учитывает все переданные параметры через `POST /api/v1/estimate/` с `type: "mesh"`.

> Webhooks для `-api-mesh` пока не рассылаются — используйте poll.

---

## 4. Chat completions (LLM)

Синхронный запрос: один `POST` → текст ответа. Poll не нужен.  
История сохраняется в проект **«Мои генерации»** (`kind: text`, `service: generate-chat`).

**Диалог / память:** сервер **не** хранит conversation id для Public API. Клиент (или MCP) передаёт полный `messages[]` с предыдущими ходами. Для DeepSeek стабильный system-префикс даёт disk KV-cache (~дешевле на длинных диалогах).

**`assistant` (additive):**

| Значение      | Поведение                                                                                                                                            |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| omit / `none` | Как раньше — **без** серверного system prompt (BC)                                                                                                   |
| `artillect`   | Подставляет studio system prompt «Artillect Assistant» (карта продукта), если в `messages` ещё нет `role:system`. Опционально `locale`: `ru` / `en`. |

```http
POST https://app.artillect.pro/api/v1/chat/completions/
Authorization: Bearer art_xxxxxxxx
Content-Type: application/json
```

### Модели (`model`)

| `model`                | Название            | Вход, $/M | Выход, $/M |
| ---------------------- | ------------------- | --------: | ---------: |
| `gemini-4-argon`       | Gemini 4 Argon      |         2 |         10 |
| `claude-sonnet-5.5`    | Claude Sonnet 5.5   |         2 |         10 |
| `claude-haiku-5.5`     | Claude Haiku 5.5    |       0.1 |        0.5 |
| `gpt-6.1-sol`          | GPT-6.1 Sol         |         2 |         10 |
| `gpt-6.1-sol-pro`      | GPT-6.1 Sol Pro     |         2 |         10 |
| `muse-wonder`          | Muse Wonder         |         4 |         20 |
| `mistral-large-4-0`    | Mistral Large 4     |      0.68 |       2.09 |
| `step-5-preview`       | Step 5 Preview      |         1 |        2.7 |
| `pareto-26.9`          | Pareto 26.9         |       2.5 |        7.5 |
| `lfm-2.5-24b-a2b`      | LFM2.5-24B-A2B      |      0.05 |        0.7 |
| `ling-3.1-flash`       | Ling 3.1 Flash      |     0.075 |      0.225 |
| `perceptron-mk15`      | Perceptron Mk1.5    |      0.15 |        1.5 |
| `gpt-6-luna` (default) | GPT-6 Luna          |       0.1 |        0.5 |
| `gpt-6-astra`          | GPT-6 Astra         |        10 |         50 |
| `claude-fable-5.1`     | Claude Fable 5.1    |        10 |         50 |
| `claude-opus-5.5`      | Claude Opus 5.5     |         4 |         20 |
| `gemini-3.8-flash`     | Gemini 3.8 Flash    |      0.75 |       3.75 |
| `deepseek-v4.1-flash`  | DeepSeek V4.1 Flash |   0.02376 |       0.72 |
| `deepseek-v4-pro`      | DeepSeek V4 Pro     |      0.66 |       1.98 |
| `qwen3.8-flash`        | Qwen3.8 Flash       |      0.15 |       0.47 |
| `qwen3.8-max-prime`    | Qwen3.8 Max Prime   |         4 |         12 |
| `grok-4.7`             | Grok 4.7            |         2 |          6 |
| `muse-spark-1.3`       | Muse Spark 1.3      |      1.25 |       4.25 |
| `command-a-plus`       | Command A+          |       0.3 |        1.5 |
| `kimi-k3`              | Kimi K3             |      0.45 |         15 |

Все модели API оплачиваются по фактической стоимости провайдера с наценкой 20% и округлением вверх. Таблица показывает базовый тариф провайдера за миллион токенов; тарифные ступени, кэш и мышление учитываются в фактическом `usage.cost`. До вызова резервируется полная верхняя оценка стоимости входа и допустимого выхода; после завершения остаток возвращается. `estimate` показывает минимум, а не точную цену или размер резерва; окончательная сумма приходит в `cost_tokens`, либо после расчёта воркером при `billing_pending: true`.

Вход ограничен контекстом выбранной модели в токенах с учётом ответа; общего ограничения символов нет. HTTP JSON — до 8 MiB. См. [обновление API от 2026-10-16](/docs/api-updates/2026-10-16.md).

¹ Сумма `messages[].content` (только текст). **Лимита на количество сообщений больше нет** —
ограничивается объём, а не длина диалога.

### Два режима чата

|                       | `POST /chat/completions/`                    | `POST /chat/conversations/{id}/messages/` |
| --------------------- | -------------------------------------------- | ----------------------------------------- |
| История               | **вы присылаете** весь `messages` каждый раз | **держит сервер**                         |
| Картинки между ходами | вы переприсылаете сами                       | живут в треде, переподаются автоматически |
| Формат                | OpenAI-совместимый                           | `{ "message": "...", "images": ["..."] }` |
| `conversation_id`     | только архивирует ход                        | сам тред                                  |

Тред-режим — то, что нужно для многоходового разговора про картинку: изображение
зеркалится к нам один раз и переподаётся модели на следующих ходах, поэтому
«обсудить картинку» работает и на втором, и на десятом сообщении.

```bash
# 1) создать тред
curl -X POST https://app.artillect.pro/api/v1/chat/conversations/ \
  -H "Authorization: Bearer $ARTILLECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"title":"Разбор фото"}'

# 2) первый ход с картинкой
curl -X POST https://app.artillect.pro/api/v1/chat/conversations/123/messages/ \
  -H "Authorization: Bearer $ARTILLECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"message":"Что на картинке?","images":["https://example.com/photo.jpg"]}'

# 3) второй ход — картинку присылать НЕ нужно
curl -X POST https://app.artillect.pro/api/v1/chat/conversations/123/messages/ \
  -H "Authorization: Bearer $ARTILLECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"message":"А какого цвета фон?"}'
```

Ответ: `{ id, conversation_id, model, content, usage, cost_tokens, project_id }`.
Ошибки: `409 CHAT_IN_FLIGHT` (предыдущий ход ещё отвечает), `429 RATE_LIMITED`,
`404 CONVERSATION_NOT_FOUND`.

**Vision (картинки):** доступно у моделей с `supports_vision: true` в каталоге. Форматы тела (backward-compatible):

```json
{
  "messages": [
    {
      "role": "user",
      "content": "What is in this image?",
      "images": ["https://example.com/photo.jpg"]
    }
  ]
}
```

или OpenAI-style:

```json
{
  "role": "user",
  "content": [
    { "type": "text", "text": "What is in this image?" },
    { "type": "image_url", "url": "https://example.com/photo.jpg" }
  ]
}
```

Количество изображений ограничивается выбранной моделью, суммарно по всем сообщениям запроса. Единый потолок 4/8 убран; API-тред также сохраняет новые вложения в контексте в пределах лимита модели. `GET /api/v1/models/` возвращает `max_images_per_message`, `max_images_total`, `image_limit_basis` и `image_limit_doc_url` у каждой chat-модели.

Документированные потолки семейств (сверка 2026-09-30): [OpenAI](https://developers.openai.com/api/docs/guides/images-vision) — 1500; [Claude](https://platform.claude.com/docs/en/build-with-claude/vision) — 600 (100 при provider context ≤200k); [Gemini](https://ai.google.dev/gemini-api/docs/image-understanding) — 3600; [Cohere](https://docs.cohere.com/docs/image-inputs) — 20. Для text-only моделей — 0. [Grok](https://docs.x.ai/developers/model-capabilities/images/understanding) не задаёт числового потолка; для остальных vision-семейств точный count не подтверждён, discovery возвращает `null`, а не выдуманный лимит.

Это верхние границы из документации семейств, не доказательство приёма такого пакета конкретным маршрутом OpenRouter: [его ограничения зависят от модели и провайдера](https://openrouter.ai/docs/guides/overview/multimodal/image-understanding). Размер payload, разрешения изображений и контекст могут ограничить запрос раньше. Artillect сохраняет ограничение **150MB**/файл; внешние URL зеркалируются в Artillect media. API-запросы с изображениями рассчитываются по фактическому usage, включая модели с обычной flat-ценой; Studio сохраняет свой компактный лимит вложений.
**Видео во входе chat не поддерживается.** Проверяйте `supports_vision` выбранной модели в каталоге; text-only модели отклоняют картинки с `400 CHAT_VISION_UNSUPPORTED`.
**Видео / PDF:** у GPT 5.5 в KIE есть `input_file` для PDF/доков, но Artillect API v1 это пока не экспонирует.

**Дефолты:** `stream: false` (sync JSON), output — собственный максимум выбранной модели. Для Opus 5.5 Artillect по умолчанию добавляет `reasoning.effort: low`, если клиент не передал собственные настройки reasoning. Явные `reasoning` / `reasoning_effort` сохраняются; у остальных моделей сохраняются дефолты провайдера. Reasoning у Opus обязателен; слишком маленький `max_tokens` всё ещё может исчерпаться до видимого текста. Stateless API не добавляет tools и web search.

**Управление reasoning:** JSON, SSE и API-диалоги принимают `reasoning: {effort, max_tokens, enabled, exclude}`; MCP `chat_completion` и `chat_send` тоже передают объект `reasoning`. Допустимые общие уровни `effort`: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`; конкретная модель может поддерживать только часть. Алиас `reasoning_effort` поддержан REST API; не задавайте его вместе с `reasoning.effort`. Бюджет `reasoning.max_tokens`, где он поддержан моделью, должен быть меньше общего output. `exclude: true` скрывает reasoning, но не выключает его и не отменяет оплату. Неверная структура/неподдерживаемый уровень → 400 до резерва и inference. У GPT-6 Luna / GPT-6.1 Sol при server tool calling требуется `effort: none`; конфликтующий явный уровень также отклоняется.

Для длинного творческого текста через Opus 5.5 используйте, например:

```json
{
  "model": "claude-opus-5.5",
  "max_tokens": 12000,
  "reasoning": { "effort": "low" },
  "messages": [{ "role": "user", "content": "Write a detailed long video prompt…" }]
}
```

Это общий бюджет reasoning и видимого текста, а не обещание ровно 12000 текстовых токенов. Нельзя отправлять `none` / `enabled: false` для модели с обязательным reasoning. Ставки и весь фактический usage, включая reasoning, участвуют в общем metered-биллинге. Неиспользованный резерв возвращается; `billing_pending` означает ожидание authoritative стоимости.

**Streaming (опционально):** `stream: true` — **для всех моделей** (на OpenRouter стрим единый). Ответ `Content-Type: text/event-stream` с OpenAI-ish чанками (`object: chat.completion.chunk`, `choices[0].delta.content`), затем `data: [DONE]`. В финальном чанке — `finish_reason: stop`, `usage`, `cost_tokens`. Заголовок `Idempotency-Key` со stream несовместим (`400 STREAM_IDEMPOTENCY_UNSUPPORTED`). MCP `chat_completion` всегда sync.

**Запрос (sync):**

```json
{
  "model": "gpt-6-luna",
  "messages": [{ "role": "user", "content": "Explain recursion in one paragraph." }]
}
```

**Запрос (SSE):**

```json
{
  "model": "gpt-6-luna",
  "stream": true,
  "messages": [{ "role": "user", "content": "Say hello in one sentence." }]
}
```

Роли: `system`, `user`, `assistant`. Текст передаётся без обрезки; допустимый вход определяется токеновым контекстом модели и местом под ответ. Общего char-cap и лимита 32 сообщений нет. HTTP JSON ограничен 8 MiB; превышение → `413 BODY_TOO_LARGE`. Превышение контекста отклоняется провайдером; ввод не укорачивается автоматически.

Aliases для DeepSeek: `deepseek-v4`, `deepseek-4-flash`, `deepseek`.

**Ответ 200:**

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000-api-chat",
  "model": "claude-sonnet-5.5",
  "content": "Recursion is when a function calls itself...",
  "usage": { "input_tokens": 12, "output_tokens": 84 },
  "cost_tokens": 1,
  "project_id": 123
}
```

`usage` — из ответа KIE, может содержать `null` если провайдер не вернул счётчики.

### 4.1 Таймауты (chat)

Chat **синхронный**: ответ приходит в том же HTTP-запросе (до ~120 с ожидания KIE / DeepSeek). Poll нет.

| Ситуация                          | HTTP                       | Поля                                                                                         |
| --------------------------------- | -------------------------- | -------------------------------------------------------------------------------------------- |
| Успех                             | **200**                    | `content`, `usage`, …                                                                        |
| KIE / DeepSeek не ответил вовремя | **504** `PROVIDER_TIMEOUT` | `phase: "chat"`, `retryable: true`, `hint` — токены **возвращены**, можно **повторить POST** |
| Ошибка провайдера                 | **502** `CHAT_FAILED`      | см. `error`                                                                                  |

Долгая генерация картинки **не относится** к chat — у images свой async poll (§3.1).

`project_id` / `billing_source` поддерживаются как у images (см. «Выбор проекта и источника списания»).

---

## 5. Пример (Python)

```python
import os, time, requests

BASE = os.environ.get("ARTILLECT_BASE_URL", "https://app.artillect.pro").rstrip("/")
API_KEY = os.environ["ARTILLECT_API_KEY"]
HDR = {"Authorization": f"Bearer {API_KEY}"}
UA = {"User-Agent": "Mozilla/5.0"}

def generate_image(prompt: str) -> str:
    r = requests.post(
        f"{BASE}/api/v1/images/generations/",
        headers=HDR,
        json={"prompt": prompt, "aspect_ratio": "1:1", "resolution": "1K"},
        timeout=60,
    )
    r.raise_for_status()
    body = r.json()
    job_id = body["id"]
    poll_url = f"{BASE}{body['poll_url']}"
    poll: dict = {}

    for _ in range(120):
        time.sleep(poll.get("recommended_poll_interval_sec", 3))
        p = requests.get(poll_url, headers=HDR, timeout=60)
        p.raise_for_status()
        poll = p.json()
        # provider_sync "deferred" — KIE не ответил на этот poll; генерация может идти дальше
        if poll.get("provider_sync") == "deferred":
            continue
        if poll["status"] == "completed":
            img_url = poll["images"][0]["url"]
            img = requests.get(img_url, headers=UA, timeout=120)
            img.raise_for_status()
            return img_url
        if poll["status"] in ("failed", "cancelled"):
            raise RuntimeError(poll.get("error") or poll["status"])
    raise TimeoutError("generation timeout")
```

---

## 6. Пример (bash, end-to-end)

Нужны: `bash`, `curl`, `jq`. Если `ARTILLECT_API_KEY` уже в env — device flow пропускается.

```bash
#!/usr/bin/env bash
set -euo pipefail

BASE="${ARTILLECT_BASE_URL:-https://app.artillect.pro}"
BASE="${BASE%/}"
CURL=(curl -sS -L)
HDR=(-H "Content-Type: application/json")

# --- 1–3. Device flow (пропуск, если ключ уже есть) ---
if [[ -z "${ARTILLECT_API_KEY:-}" ]]; then
  read -r DEVICE_CODE USER_CODE VERIFICATION_URL INTERVAL < <(
    "${CURL[@]}" -X POST "$BASE/api/v1/device/code/" \
      | jq -r '[.device_code, .user_code, .verification_url, (.interval // 5)] | @tsv'
  )

  echo "⛔ Откройте в браузере (залогиньтесь в Artillect):"
  echo "   $VERIFICATION_URL"
  echo "   Запасной код: $USER_CODE"
  read -r -p "Нажмите Enter после «Выдать API-ключ»…"

  while true; do
    RESP=$("${CURL[@]}" -X POST "$BASE/api/v1/device/token/" "${HDR[@]}" \
      -d "$(jq -n --arg dc "$DEVICE_CODE" '{device_code:$dc}')")
    STATUS=$(jq -r .status <<<"$RESP")
    [[ "$STATUS" == "completed" ]] && ARTILLECT_API_KEY=$(jq -r .api_key <<<"$RESP") && break
    [[ "$STATUS" == "expired" ]] && echo "Device code expired" >&2 && exit 1
    sleep "$INTERVAL"
  done
  echo "export ARTILLECT_API_KEY=$ARTILLECT_API_KEY"
fi

AUTH=(-H "Authorization: Bearer $ARTILLECT_API_KEY")

# --- 4. Submit text-to-image ---
SUBMIT=$("${CURL[@]}" -X POST "$BASE/api/v1/images/generations/" "${AUTH[@]}" "${HDR[@]}" \
  -d '{"prompt":"Photorealistic red apple on white background","aspect_ratio":"1:1","resolution":"1K"}')
POLL_URL="$BASE$(jq -r .poll_url <<<"$SUBMIT")"

# --- 5. Poll + скачать PNG ---
while true; do
  POLL=$("${CURL[@]}" "$POLL_URL" "${AUTH[@]}")
  STATUS=$(jq -r .status <<<"$POLL")
  if [[ "$STATUS" == "completed" ]]; then
    IMG_URL=$(jq -r '.images[0].url' <<<"$POLL")
    "${CURL[@]}" -A "Mozilla/5.0" -o result.png "$IMG_URL"
    echo "OK: result.png ← $IMG_URL"
    break
  fi
  if [[ "$STATUS" == "failed" || "$STATUS" == "cancelled" ]]; then
    jq . <<<"$POLL" >&2
    exit 1
  fi
  sleep 3
done
```

**One-liner** (ключ уже в env, только генерация → poll → файл):

```bash
BASE="${ARTILLECT_BASE_URL:-https://app.artillect.pro}"; BASE="${BASE%/}"; \
POLL="$BASE$(curl -sS -L -X POST "$BASE/api/v1/images/generations/" \
  -H "Authorization: Bearer $ARTILLECT_API_KEY" -H "Content-Type: application/json" \
  -d '{"prompt":"Photorealistic red apple","aspect_ratio":"1:1","resolution":"1K"}' | jq -r .poll_url)"; \
while true; do R=$(curl -sS -L "$POLL" -H "Authorization: Bearer $ARTILLECT_API_KEY"); \
  S=$(jq -r .status <<<"$R"); [[ "$S" == "completed" ]] && curl -sS -L -A "Mozilla/5.0" -o result.png "$(jq -r '.images[0].url' <<<"$R")" && echo OK && break; \
  [[ "$S" == "failed" || "$S" == "cancelled" ]] && echo "$R" >&2 && exit 1; sleep 3; done
```

Сохраните скрипт в `artillect-generate.sh`, `chmod +x`, запустите. One-liner удобен для быстрой проверки, когда `ARTILLECT_API_KEY` уже экспортирован.  
В **zsh** (macOS по умолчанию) путь в jq обязательно в кавычках: `'.images[0].url'` (иначе `no matches found`).

---

## Ошибки

| HTTP | code                                                                         | Описание                                                                                                                                               |
| ---- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 401  | `API_KEY_REQUIRED`                                                           | Нет заголовка Authorization                                                                                                                            |
| 401  | `INVALID_API_KEY`                                                            | Ключ неверный, не `art_...` или отозван                                                                                                                |
| 401  | `SESSION_REQUIRED`                                                           | Нужен session JWT (браузер): `POST/GET /keys`, `DELETE /keys/{id}`, `POST /device/approve`                                                             |
| 402  | `INSUFFICIENT_BALANCE`                                                       | Недостаточно токенов на личном балансе                                                                                                                 |
| 402  | `PROJECT_TOKEN_LIMIT_EXCEEDED`                                               | Квота редактора в проекте (не личный баланс)                                                                                                           |
| 400  | `MODEL_UNKNOWN`                                                              | Неизвестный `model`                                                                                                                                    |
| 400  | `PROMPT_REQUIRED`                                                            | Пустой `prompt`                                                                                                                                        |
| 400  | `PROMPT_TOO_LONG`                                                            | `prompt` длиннее лимита модели (см. таблицу)                                                                                                           |
| 400  | `MESSAGES_TOO_LONG`                                                          | Слишком много сообщений или символов в chat                                                                                                            |
| 400  | `MESSAGES_INVALID`                                                           | Невалидная структура messages                                                                                                                          |
| 400  | `MESSAGES_TOO_MANY_IMAGES`                                                   | Слишком много картинок в chat                                                                                                                          |
| 400  | `CHAT_VISION_UNSUPPORTED`                                                    | Модель не поддерживает картинки                                                                                                                        |
| 400  | `INPUT_URL_FETCH_FAILED`                                                     | Artillect не смог скачать внешний URL (403, 404, timeout, hotlink)                                                                                     |
| 400  | `INPUT_URL_INVALID`                                                          | URL не картинка или неверный формат                                                                                                                    |
| 400  | `INPUT_URL_TOO_LARGE`                                                        | Файл > 150MB (изображение)                                                                                                                             |
| 400  | `INPUT_VIDEO_FETCH_FAILED` / `INPUT_VIDEO_INVALID` / `INPUT_VIDEO_TOO_LARGE` | Референс-видео (max 200MB)                                                                                                                             |
| 400  | `INPUT_AUDIO_FETCH_FAILED` / `INPUT_AUDIO_INVALID` / `INPUT_AUDIO_TOO_LARGE` | Референс-аудио (max 50MB)                                                                                                                              |
| 400  | `INPUT_MEDIA_UPLOAD_FAILED`                                                  | Не удалось сохранить input в media store                                                                                                               |
| 400  | `VIDEO_SLOT_UNSUPPORTED`                                                     | Слот не поддерживается выбранной видео-моделью                                                                                                         |
| 400  | `VIDEO_FRAMES_REFS_EXCLUSIVE`                                                | Кадры (start/end) и референсы вместе недопустимы                                                                                                       |
| 400  | `VIDEO_END_REQUIRES_START`                                                   | `end_image_url` без `start_image_url`                                                                                                                  |
| 400  | `VIDEO_TOO_MANY_REFS`                                                        | Превышен лимит референсов                                                                                                                              |
| 400  | `VIDEO_TASK_UNSUPPORTED`                                                     | Задача не поддерживается моделью/слотами                                                                                                               |
| 400  | `VIDEO_TIER_TASK_UNSUPPORTED`                                                | Tier (напр. kling 4k) не поддерживает задачу                                                                                                           |
| 400  | `SOURCE_VIDEO_REQUIRED`                                                      | Нужен source_video_url для edit/v2v                                                                                                                    |
| 400  | `PROJECT_ID_REQUIRED_FOR_ELEMENTS`                                           | @element в промпте без project_id                                                                                                                      |
| 400  | `ELEMENT_NOT_FOUND`                                                          | @element не найден в библиотеке проекта                                                                                                                |
| 400  | `VIDEO_RESOLUTION_UNSUPPORTED`                                               | Разрешение недоступно для mode/tier                                                                                                                    |
| 400  | `PROJECT_ID_INVALID`                                                         | `project_id` не положительное целое                                                                                                                    |
| 400  | `PROJECT_NOT_FOUND`                                                          | Проект не найден / не активен                                                                                                                          |
| 400  | `BILLING_SOURCE_REQUIRED`                                                    | Нужен `billing_source` для чужого проекта (editor)                                                                                                     |
| 403  | `PROJECT_ACCESS_DENIED`                                                      | Нет доступа к проекту                                                                                                                                  |
| 403  | `PROJECT_ROLE_FORBIDDEN`                                                     | Роль не editor — генерация в проекте запрещена                                                                                                         |
| 404  | `GENERATION_NOT_FOUND`                                                       | Записи с таким `external_job_id` нет                                                                                                                   |
| 403  | `GENERATION_ACCESS_DENIED`                                                   | Генерация существует, но вы не её создатель и не участник её проекта                                                                                   |
| 404  | `JOB_ID_FORMAT_REJECTED`                                                     | Формат id не подходит этому эндпоинту (студийные id — голый `-api`). Зовите `GET /api/v1/generations/{id}/`                                            |
| 404  | `GENERATION_JOB_EXPIRED`                                                     | Live job-запись истекла (24ч), а генерация так и не дошла до финального статуса. Поллить дальше бесполезно                                             |
| 409  | `IDEMPOTENCY_CONFLICT`                                                       | Тот же `Idempotency-Key` ещё обрабатывается (параллельный запрос)                                                                                      |
| 410  | `DEVICE_CODE_EXPIRED`                                                        | Device code истёк или не найден                                                                                                                        |
| 429  | `RATE_LIMITED`                                                               | Rate limit; см. `Retry-After` и заголовки `RateLimit-*`. Повторите позже                                                                               |
| 429  | `SPEND_CAP_EXCEEDED`                                                         | Дневной лимит spend по API-ключу (UTC); см. `spent`/`cap` в теле. Env `PUBLIC_API_KEY_DAILY_TOKEN_CAP`                                                 |
| 400  | `FILE_REQUIRED` / `FILE_TYPE_UNSUPPORTED` / `FILE_TOO_LARGE`                 | Ошибки загрузки файла (`POST /files`)                                                                                                                  |
| 413  | `STORAGE_FULL`                                                               | Хранилище владельца заполнено (100 ГиБ бесплатно + add-ons). Файл не записан и токены не списаны — освободите место в `/storage` или подключите add-on |
| 503  | `ASSET_STORAGE_UNAVAILABLE` / `ASSET_WRITE_FAILED`                           | Хранилище временно недоступно или запись не удалась. Резерв квоты снимается автоматически; повторите позже                                             |
| 400  | `MESSAGES_REQUIRED`                                                          | Пустой или невалидный `messages`                                                                                                                       |
| 400  | `INVALID_JSON`                                                               | Тело не JSON                                                                                                                                           |
| 502  | `SUBMIT_FAILED`                                                              | KIE submit images вернул ошибку (задача не принята)                                                                                                    |
| 502  | `BILLING_REFUND_FAILED`                                                      | Submit упал после списания; автоматический refund не завершился — токены могут остаться списанными, см. `hint`                                         |
| 502  | `CHAT_FAILED`                                                                | Ошибка LLM-провайдера                                                                                                                                  |
| 504  | `PROVIDER_TIMEOUT`                                                           | KIE не ответил в срок. Смотрите **`phase`**: `submit` — id не создан, retry POST; `chat` — ответа нет, retry POST. Поле **`hint`** — что делать дальше |
| 503  | `PROVIDER_NOT_CONFIGURED`                                                    | Нет `KIE_API_KEY` на сервере                                                                                                                           |

---

## Эндпоинты

| Method | Path                                               | Auth                                                                |
| ------ | -------------------------------------------------- | ------------------------------------------------------------------- |
| POST   | `/api/v1/device/code/`                             | —                                                                   |
| POST   | `/api/v1/device/token/`                            | —                                                                   |
| POST   | `/api/v1/device/approve/`                          | Session JWT (браузер)                                               |
| POST   | `/api/v1/keys`                                     | Session JWT (браузер, альтернатива device flow)                     |
| GET    | `/api/v1/keys`                                     | Session JWT — список активных ключей                                |
| PATCH  | `/api/v1/keys/{id}`                                | Session JWT — name / scopes                                         |
| DELETE | `/api/v1/keys/{id}`                                | Session JWT — отзыв ключа                                           |
| GET    | `/connect?code=XXXX-XXXX`                          | —                                                                   |
| GET    | `/api`                                             | Session (браузер) — страница управления ключами                     |
| GET    | `/api/v1/health/`                                  | — (проверка backend + KIE key, **не** вызывает KIE)                 |
| GET    | `/api/v1/models/`                                  | — (список моделей и лимитов)                                        |
| GET    | `/api/v1/me/`                                      | Bearer `art_...` — identity + balance                               |
| GET    | `/api/v1/balance/`                                 | Bearer `art_...` — баланс токенов                                   |
| POST   | `/api/v1/estimate/`                                | Bearer `art_...` — оценка цены без списания                         |
| POST   | `/api/v1/files/`                                   | Bearer `art_...` — загрузка файла (multipart)                       |
| POST   | `/api/v1/files/from-url/`                          | Bearer — зеркало публичного HTTPS → `{ key, url }`                  |
| POST   | `/api/v1/files/presign/`                           | Bearer — quota-aware presign PUT image/audio                        |
| POST   | `/api/v1/files/presign/finalize/`                  | Bearer — activate direct-upload Asset + commit quota                |
| GET    | `/api/v1/projects/`                                | Bearer — список проектов                                            |
| POST   | `/api/v1/projects/`                                | Bearer — создать проект                                             |
| GET    | `/api/v1/projects/{id}/`                           | Bearer — детали + роль                                              |
| PATCH  | `/api/v1/projects/{id}/`                           | Bearer — обновить (owner)                                           |
| DELETE | `/api/v1/projects/{id}/`                           | Bearer — удалить (owner)                                            |
| GET    | `/api/v1/projects/roles/`                          | Bearer — каталог ролей                                              |
| GET    | `/api/v1/projects/{id}/members/`                   | Bearer — участники                                                  |
| POST   | `/api/v1/projects/{id}/members/`                   | Bearer — пригласить                                                 |
| PATCH  | `/api/v1/projects/{id}/members/{userId}/`          | Bearer — роль / лимит / remove                                      |
| DELETE | `/api/v1/projects/{id}/members/{userId}/`          | Bearer — удалить участника                                          |
| DELETE | `/api/v1/projects/{id}/members/me/`                | Bearer — выйти                                                      |
| GET    | `/api/v1/projects/{id}/generations/`               | Bearer — лента (+ фильтры)                                          |
| PATCH  | `/api/v1/projects/{id}/generations/{genId}/`       | Bearer — move / favorited                                           |
| DELETE | `/api/v1/projects/{id}/generations/{genId}/`       | Bearer — удалить генерацию                                          |
| POST   | `/api/v1/projects/{id}/generations/{genId}/copy/`  | Bearer — копировать                                                 |
| GET    | `/api/v1/projects/{id}/favorites/`                 | Bearer — избранное проекта                                          |
| GET    | `/api/v1/projects/{id}/analytics/`                 | Bearer — сводка                                                     |
| GET    | `/api/v1/projects/{id}/billing/`                   | Bearer — лимит вызывающего                                          |
| GET    | `/api/v1/projects/{id}/elements/`                  | Bearer — element library                                            |
| POST   | `/api/v1/projects/{id}/elements/`                  | Bearer — создать элемент                                            |
| PATCH  | `/api/v1/projects/{id}/elements/{elementId}/`      | Bearer — обновить элемент                                           |
| DELETE | `/api/v1/projects/{id}/elements/{elementId}/`      | Bearer — soft-delete                                                |
| POST   | `/api/v1/projects/{id}/elements/reorder/`          | Bearer — порядок                                                    |
| GET    | `/api/v1/projects/{id}/tags/`                      | Bearer — custom теги проекта                                        |
| POST   | `/api/v1/projects/{id}/tags/`                      | Bearer — создать тег                                                |
| PATCH  | `/api/v1/projects/{id}/tags/{tagId}/`              | Bearer — rename/recolor                                             |
| DELETE | `/api/v1/projects/{id}/tags/{tagId}/`              | Bearer — удалить тег                                                |
| POST   | `/api/v1/projects/{id}/generations/{genId}/tags/`  | Bearer — assign/remove custom tags                                  |
| GET    | `/api/v1/projects/{id}/generations/{genId}/likes/` | Bearer — кто лайкнул                                                |
| POST   | `/api/v1/projects/{id}/generations/{genId}/likes/` | Bearer — поставить/снять свой лайк                                  |
| POST   | `/api/v1/images/generations/`                      | Bearer `art_...`                                                    |
| GET    | `/api/v1/images/generations/`                      | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/images/generations/{id}/`                 | Bearer `art_...`                                                    |
| POST   | `/api/v1/videos/generations/`                      | Bearer `art_...`                                                    |
| GET    | `/api/v1/videos/generations/`                      | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/videos/generations/{id}/`                 | Bearer `art_...`                                                    |
| POST   | `/api/v1/upscale/generations/`                     | Bearer `art_...` — image/video upscale                              |
| GET    | `/api/v1/upscale/generations/`                     | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/upscale/generations/{id}/`                | Bearer `art_...`                                                    |
| POST   | `/api/v1/switchx/generations/`                     | Bearer `art_...` — Beeble SwitchX                                   |
| GET    | `/api/v1/switchx/generations/`                     | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/switchx/generations/{id}/`                | Bearer `art_...`                                                    |
| POST   | `/api/v1/audio/generations/`                       | Bearer `art_...` — музыка, речь, звуки и текст                      |
| GET    | `/api/v1/audio/generations/`                       | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/audio/generations/{id}/`                  | Bearer `art_...`                                                    |
| POST   | `/api/v1/{modality}/generations/{id}/cancel/`      | Bearer `art_...` — cancel + refund only before provider submit (4s) |
| POST   | `/api/v1/generations/{id}/cancel/`                 | Bearer `art_...` — modality-agnostic MCP/CLI cancel (4s)            |
| POST   | `/api/v1/mesh/generations/`                        | Bearer `art_...` — Meshy v6/v7 (3D mesh)                            |
| GET    | `/api/v1/mesh/generations/`                        | Bearer `art_...` — история (пагинация)                              |
| GET    | `/api/v1/mesh/generations/{id}/`                   | Bearer `art_...`                                                    |
| POST   | `/api/v1/webhooks/`                                | Bearer `art_...` — register/update webhook                          |
| GET    | `/api/v1/webhooks/`                                | Bearer `art_...` — current subscription                             |
| DELETE | `/api/v1/webhooks/`                                | Bearer `art_...` — delete subscription                              |
| POST   | `/api/v1/webhooks/rotate-secret/`                  | Bearer `art_...` — rotate signing secret                            |
| GET    | `/api/v1/webhooks/deliveries/`                     | Bearer `art_...` — delivery outbox list                             |
| GET    | `/api/v1/webhooks/deliveries/{event_id}/`          | Bearer `art_...` — delivery detail + payload                        |
| POST   | `/api/v1/chat/completions/`                        | Bearer `art_...`                                                    |

---

## Переменные окружения

```bash
ARTILLECT_BASE_URL=https://app.artillect.pro
ARTILLECT_API_KEY=art_...
```

---

_Последнее обновление: 2026-09-01 — общий независимый 4-секундный pre-submit cancel + refund для Studio, REST API, MCP и CLI; после отправки провайдеру отмена недоступна. Предыдущая редакция: 2026-08-01 — аудио (153), 3D (154)._

## Midjourney 8 и Mureka V9

`midjourney-8` создаёт четыре изображения за запрос, принимает до четырёх `input_urls`. Версии `8.2` (по умолчанию) и `8.1`; `speed=relax|fast|turbo` — 6/7/12 токенов за запрос. Доступны `quality`, `style`, `seed`, `negative_prompt`, `stylize`, `chaos`, `weird`, `tile`, `raw`, `draft`, `hd`, `iw`, `cw`, `sw`, `cref`, `sref`, `dref`, `dw`. Список значений и лимитов — в `/api/v1/models/`. Модель поддерживает новую генерацию; действия над ранее созданными задачами не входят в этот endpoint.

`mureka-v9` требует `lyrics` (1–3000 символов), принимает описание стиля `prompt` (до 1024 символов), `number_of_songs=1|2|3` (6/10/15 токенов), `output_format=mp3|wav|flac`. `reference_id`, `vocal_id`, `melody_id` — ID из системы загрузки Mureka, не URL файлов. Обе модели доступны в Studio, REST, MCP и CLI; стоимость учитывает выбранные параметры.

CLI: `artillect generate image --model midjourney-8 --prompt "A quiet garden" --version 8.2 --speed relax --extra '{"stylize":200}'`; `artillect generate audio --model mureka-v9 --lyrics "Morning light, a new beginning" --prompt "Warm acoustic pop" --number-of-songs 1 --output-format mp3`. MCP `generate_image` / `generate_audio` принимают соответствующие поля напрямую.

Seedance: `bitrate_mode=default|standard|high`, `watermark`, `return_last_frame` (кроме Mini); 2.5 и 2.5 Eco также `seed`, `web_search`, `output_format=mp4|mov`. CLI предоставляет `--bitrate-mode`, `--watermark`, `--return-last-frame`. При нехватке баланса ReAPI переход на KIE использует стандартный файл; Eco без fallback.

### H3 Max operations and Mureka

Video models: `minimax-h3-max-turbo`, `minimax-h3-max-camera`, `minimax-h3-max-insert`, `minimax-h3-max-extend`, `minimax-h3-max-3d`, `minimax-h3-max-recast`, `minimax-h3-max-relight`, `minimax-h3-max-lipsync`, `mureka-lyrics-video`. Discover each model's parameters with `GET /api/v1/models/`; use `model get` in CLI or `list_models` in MCP.

`mureka-lyrics-video` accepts exactly one of `song_id` or `upload_audio_id`, optional `background_id`, `title`, `layout=layout_1…layout_7`, and `aspect_ratio=16:9|9:16|3:4|4:3`. `cover_url` requires layout_2…layout_7. Select a fragment with paired `lyrics_start_row`/`lyrics_end_row` (1-based) or paired `selection_start`/`selection_end` (milliseconds); these selection modes are exclusive. It costs 15 tokens and returns a video. `song_id` is the reusable ID in Mureka audio results (`tracks[].provider_audio_id`); uploaded IDs are distinct from file URLs.

Mureka audio models: `mureka-9.5-song`, `mureka-9-song`, `mureka-8-song`, `mureka-o2-song`, `mureka-7.6-song` and matching `-instrumental` variants except O2. Existing `mureka-v9` keeps its own contract. New songs accept `lyrics` up to 5000 characters or `prompt`; `styles` is a genre array for prompt-only mode. Optional controls: `gender=female|male`, `reference_id`, `vocal_id`, `melody_id`; melody excludes prompt/reference/vocal IDs. Instrumental generation accepts exactly one of `prompt` or `instrumental_id`.

Songs with lyrics and instrumentals cost 23 tokens for 9.5, 7 for 9/8/O2, or 5 for 7.6. Prompt-only songs cost 75 tokens for 9.5 or 45 for other versions. `mureka-lyrics` costs 2 tokens and produces title + lyrics as text artifacts. `mureka-podcast` accepts `conversations=[{text,voice}]`, up to 400 characters per turn; voices: Ethan, Victoria, Jake, Luna, Emma. Its estimate reserves tokens; the final charge is rounded upward from the measured generated duration at 17.25 tokens/minute, with unused reserve refunded.

```sh
artillect generate video mureka-lyrics-video --song-id SONG_ID --layout layout_2 --selection-start 0 --selection-end 15000 --wait
artillect generate video minimax-h3-max-camera --start-image ./scene.png --camera-trajectory '[{"time":0,"azimuth":0,"elevation":0,"distance":1},{"time":1,"azimuth":90,"elevation":15,"distance":1}]' --duration 5 --wait
artillect generate audio --model mureka-9.5-song --lyrics 'Morning light, a new beginning' --prompt 'Warm acoustic pop' --wait
artillect generate audio --model mureka-podcast --conversations '[{"text":"Welcome!","voice":"Ethan"},{"text":"Hello!","voice":"Victoria"}]' --wait
```

Additional native fields are accepted in `--extra` and MCP `extra`. H3 Max Director is a separate real-time interface and is not registered as a queued video model.
