# Документация API СОЮЗа Все страницы https://souz.ai/docs одним файлом. Справочник методов: https://souz.ai/docs/api-reference.md; контракт OpenAPI: https://api.souz.ai/openapi.json. ## Souz API Souz API даёт один ключ к моделям чата, картинок, видео, музыки, синтеза речи и транскрибации. Запросы совместимы с SDK OpenAI, а списание считается по факту выполнения и только за успешный запрос. | Что | Значение | |---|---| | Адрес | `https://api.souz.ai/v1` | | Ключ | заголовок `Authorization: Bearer $SOUZ_API_KEY`, ключ создаётся в кабинете [souz.ai](https://souz.ai) | | SDK | любой SDK OpenAI с `base_url = "https://api.souz.ai/v1"` | | Каталог | `GET /v1/models`, работает без ключа | | Спецификация | `https://api.souz.ai/openapi.json` и `https://api.souz.ai/openapi.yaml` | Начните с [быстрого старта](https://souz.ai/docs/quickstart.md): ключ, первый ответ чата, картинка и ролик. ### Как работает API Чат, картинки, музыка, синтез речи и транскрибация отвечают результатом сразу, чат ещё и потоком. Видео возвращает `202` с задачей, а результат приходит по [опросу, потоку или вебхуку](https://souz.ai/docs/jobs.md). У модели один или несколько каналов с разной ценой и скоростью. Без указанного канала его выбирает Авто-роутинг по пресету роутинга. Если канал не ответил, запрос переходит к следующему, а ответ показывает, какой канал его выполнил. Деньги записаны целыми [микроединицами](https://souz.ai/docs/pricing.md): `1000000` при `currency: "RUB"` равно 1 рублю. Ошибки приходят в одном конверте `{"error": {...}}`, подробнее на странице [Ошибки](https://souz.ai/docs/errors.md). ### Карта разделов | Задача | Страница | |---|---| | Получить ключ и отправить первый запрос | [Быстрый старт](https://souz.ai/docs/quickstart.md) | | Виды ключей и их хранение | [Ключи](https://souz.ai/docs/authentication.md) | | Формат, списки, совместимость с SDK OpenAI | [Запросы и ответы](https://souz.ai/docs/conventions.md) | | Выбрать модель и узнать её поля | [Модели](https://souz.ai/docs/models.md) | | Ответ чата или поток | [Чат](https://souz.ai/docs/chat.md) | | Картинка | [Картинки](https://souz.ai/docs/images.md) | | Ролик | [Видео](https://souz.ai/docs/video.md) | | Трек | [Музыка](https://souz.ai/docs/music.md) | | Озвучить текст | [Синтез речи](https://souz.ai/docs/speech.md) | | Расшифровать звук | [Транскрибация](https://souz.ai/docs/transcription.md) | | Забрать результат долгой задачи | [Результат задачи](https://souz.ai/docs/jobs.md) | | Повторить запрос без второго списания | [Повторы](https://souz.ai/docs/idempotency.md) | | Загрузить и скачать файл | [Файлы](https://souz.ai/docs/files.md) | | Уведомление о готовности | [Вебхуки](https://souz.ai/docs/webhooks.md) | | Управлять ценой и скоростью | [Авто-роутинг](https://souz.ai/docs/routing.md) | | Выбрать или закрепить канал | [Каналы](https://souz.ai/docs/channels.md) | | Сколько стоит запрос | [Цены и списание](https://souz.ai/docs/pricing.md) | | Остаток и пополнение | [Баланс](https://souz.ai/docs/balance.md) | | Обработать ошибку | [Ошибки](https://souz.ai/docs/errors.md) | | Пределы частоты, размеров и трат | [Лимиты](https://souz.ai/docs/limits.md) | | Вести аккаунт из кода | [Ключ управления](https://souz.ai/docs/management-key.md) | | Работать командой | [Команда](https://souz.ai/docs/account.md) | | Что и сколько хранится | [Хранение данных](https://souz.ai/docs/data-retention.md) | | Подключить ИИ-агента | [Для ИИ-агентов](https://souz.ai/docs/agents.md), [MCP-сервер](https://souz.ai/docs/mcp.md) | | Что изменилось | [Изменения API](https://souz.ai/docs/changelog.md) | Любая страница руководства доступна в Markdown: добавьте `.md` к её адресу. Всё руководство одним файлом лежит по адресу `https://souz.ai/llms-full.txt`. ## Быстрый старт За четыре шага вы получите ключ, ответ чата, картинку и ролик. В примерах SDK OpenAI меняются только адрес и ключ. ### 1. Получите ключ Войдите на [souz.ai](https://souz.ai) через Яндекс, GitHub, Google или Telegram. Первый вход создаёт аккаунт и начисляет приветственный бонус. Создайте ключ в разделе «API-ключи» и сохраните его в переменной окружения: ```bash export SOUZ_API_KEY="sk_..." ``` Храните ключ только на сервере ([Ключи](https://souz.ai/docs/authentication.md)). ### 2. Отправьте запрос в чат ```bash tab="cURL" curl https://api.souz.ai/v1/chat/completions \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "gpt-5-nano", "messages": [{"role": "user", "content": "Привет!"}]}' ``` ```python tab="Python" import os from openai import OpenAI client = OpenAI(base_url="https://api.souz.ai/v1", api_key=os.environ["SOUZ_API_KEY"]) chat = client.chat.completions.create( model="gpt-5-nano", messages=[{"role": "user", "content": "Привет!"}], ) print(chat.choices[0].message.content) ``` ```ts tab="TypeScript" import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.souz.ai/v1", apiKey: process.env.SOUZ_API_KEY }); const chat = await client.chat.completions.create({ model: "gpt-5-nano", messages: [{ role: "user", content: "Привет!" }], }); console.log(chat.choices[0].message.content); ``` Ответ повторяет формат OpenAI и добавляет `channel`, канал ответа, и `usage.cost`, списанную сумму в микроединицах (`1000000` равно 1 рублю). ```json { "id": "chatcmpl-1a2b3c4d5e6f", "object": "chat.completion", "model": "gpt-5-nano", "channel": "blue", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Привет! Чем помочь?" }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 9, "completion_tokens": 7, "total_tokens": 16, "cost": 233, "currency": "RUB" } } ``` ### 3. Сгенерируйте картинку Картинка приходит в том же ответе: объект задачи со ссылкой на файл в `data[0].url`. ```bash tab="cURL" curl https://api.souz.ai/v1/images/generations \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "nano-banana-pro", "prompt": "кот-космонавт на Луне", "size": "1024x1024"}' ``` ```python tab="Python" image = client.images.generate(model="nano-banana-pro", prompt="кот-космонавт на Луне", size="1024x1024") print(image.data[0].url) ``` ```ts tab="TypeScript" const image = await client.images.generate({ model: "nano-banana-pro", prompt: "кот-космонавт на Луне", size: "1024x1024" }); console.log(image.data?.[0]?.url); ``` ```json { "id": "job_1a2b3c4d5e6f", "object": "job", "status": "completed", "model": "nano-banana-pro", "channel": "blue", "price": 3676400, "currency": "RUB", "data": [{ "id": "file_9f8e7d6c5b4a", "type": "image", "url": "https://media.souz.ai/v1/files/file_9f8e7d6c5b4a/…", "expires_at": "…" }], "…": "…" } ``` > [!IMPORTANT] > Скачайте файл в течение 1 дня, потом ссылка перестаёт работать ([Файлы](https://souz.ai/docs/files.md)). ### 4. Сгенерируйте видео Ролик готовится несколько минут, поэтому `POST /v1/videos` сразу отвечает `202` с задачей в статусе `queued`. Опрашивайте её раз в `Retry-After` секунд до `completed` или `failed`. ```bash tab="cURL" JOB=$(curl -s https://api.souz.ai/v1/videos \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "seedance-2-fast", "prompt": "волны у маяка на закате", "duration_seconds": 4}' | jq -r .id) while true; do STATUS=$(curl -s https://api.souz.ai/v1/jobs/$JOB -H "Authorization: Bearer $SOUZ_API_KEY" | jq -r .status) echo "$STATUS" [ "$STATUS" = completed ] || [ "$STATUS" = failed ] && break sleep 15 done curl -s https://api.souz.ai/v1/jobs/$JOB -H "Authorization: Bearer $SOUZ_API_KEY" | jq -r '.data[0].url' ``` ```python tab="Python" import time video = client.videos.create(model="seedance-2-fast", prompt="волны у маяка на закате", seconds="4") while video.status not in ("completed", "failed"): time.sleep(15) video = client.videos.retrieve(video.id) job = video.to_dict() print(job["data"][0]["url"] if video.status == "completed" else job["error"]) ``` ```ts tab="TypeScript" let video: any = await client.videos.create({ model: "seedance-2-fast", prompt: "волны у маяка на закате", seconds: "4" }); while (video.status !== "completed" && video.status !== "failed") { await new Promise((r) => setTimeout(r, 15000)); video = await client.videos.retrieve(video.id); } console.log(video.status === "completed" ? video.data[0].url : video.error); ``` `client.videos.retrieve` читает тот же объект задачи, что `GET /v1/jobs/{id}`. Сервису вместо опроса удобнее [вебхук](https://souz.ai/docs/webhooks.md) с `callback_url`. **Дальше** - [Модели](https://souz.ai/docs/models.md): выбрать модель и узнать её поля. - [Результат задачи](https://souz.ai/docs/jobs.md): опрос, поток событий и долгие генерации. - [Авто-роутинг](https://souz.ai/docs/routing.md): управлять ценой и скоростью. - [Ошибки](https://souz.ai/docs/errors.md): что делать программе при отказе. ## Ключи Каждый запрос к API несёт ключ в заголовке `Authorization: Bearer <ключ>`. Без ключа или с неверным ключом API отвечает `401 invalid_api_key`. ```bash curl https://api.souz.ai/v1/key -H "Authorization: Bearer $SOUZ_API_KEY" ``` ### Виды ключей | Ключ | Приставка | Для чего | |---|---|---| | API-ключ | `sk_` | запускает модели, читает задачи, загружает файлы | | [Ключ управления](https://souz.ai/docs/management-key.md) | `mk_` | ведёт аккаунт: баланс, история, расходы, выдача API-ключей, настройки | Ключ одного вида не заменяет другой (коды отказа в таблице ниже). Исключение: API-ключ читает цену и итог своих запросов через `GET /v1/usage/{id}`, а на чужой запрос получает `404`. ### Создайте API-ключ Ключ создаётся в кабинете [souz.ai](https://souz.ai), раздел «API-ключи», или методом `POST /v1/keys` с ключом управления. Создать ключ может участник с ролью разработчика или выше. Траты ключа входят в лимит его автора, а списываются с общего баланса аккаунта. Кабинет показывает секрет сразу после создания. Позже его открывает только автор: в кабинете или через `GET /v1/keys/{id}/secret` своим ключом управления. В списке ключей вместо секрета стоит `secret_hint`, например `sk_9f3a…c1d2`. ### Храните ключ на сервере - Держите ключ в переменной окружения `SOUZ_API_KEY`, а не в коде клиента, логах или репозитории. В репозиторий кладите `.env.example` с пустым значением. - Вызывайте API со своего сервера. У API нет CORS, и браузер заблокирует запрос со страницы к `api.souz.ai`. - Если ключ утёк, отзовите его в разделе «API-ключи» и выпустите новый. ### Проверьте ключ `GET /v1/key` принимает ключи обоих видов и возвращает ключ запроса (схема `APIKey`). | Поле | Что показывает | |---|---| | `kind` | вид ключа; `management` у ключа управления | | `spend_limit` | потолок трат в микроединицах; `null` значит без потолка | | `spend_limit_reset` | как обновляется потолок: `none`, `daily` или `monthly` | | `spent` | завершённые списания в текущем окне потолка | | `routing`, `max_price_multiplier`, `routing_options` | [роутинг](https://souz.ai/docs/routing.md) по умолчанию | | `store`, `retention` | [хранение](https://souz.ai/docs/data-retention.md) текстов и файлов | Потолки ключа и участника, их окна и сброс описаны на странице [Лимиты](https://souz.ai/docs/limits.md). ### Методы без ключа - `GET /v1/models`, `GET /v1/models/{id}`, `GET /v1/models/{id}/stats`, `GET /v1/models/{id}/channels`, `GET /v1/channels`; - `POST /v1/models/{id}/routing/preview`; - `GET /v1/status`, состояние сервиса; - `GET /`, `/openapi.json`, `/openapi.yaml`, `/llms.txt`, `/llms-full.txt`; - `POST /mcp` для инструментов каталога ([MCP-сервер](https://souz.ai/docs/mcp.md)). С ключом каталог возвращает цены вашего аккаунта. Неверный ключ и здесь даёт `401`. ### Ошибки ключа | Код | Когда | Что делать | |---|---|---| | `401 invalid_api_key` | ключа нет, он неверный или отозван | проверьте заголовок `Authorization` и ключ в кабинете | | `403 forbidden` с `requires_api_key` | ключ управления на методе модели | запускайте модели API-ключом | | `403 forbidden` с `requires_management_key` | API-ключ на методе аккаунта | используйте ключ управления | | `403 forbidden` с `not_key_author` | секрет чужого ключа | секрет открывает только автор ключа | | `403 key_blocked` | ключ заблокирован службой поддержки | напишите в поддержку | | `403 account_blocked` | аккаунт заблокирован; чтение задач и результатов работает | напишите в поддержку | | `402 key_spend_limit_exceeded` | исчерпан потолок ключа | поднимите потолок или дождитесь нового окна | | `402 member_spend_limit_exceeded` | исчерпан лимит участника | попросите владельца поднять лимит | **Дальше** - [Запросы и ответы](https://souz.ai/docs/conventions.md): общие правила всех методов. - [Ключ управления](https://souz.ai/docs/management-key.md): выдача ключей и расходы из кода. - [Лимиты](https://souz.ai/docs/limits.md): частота запросов и потолки трат. ## Запросы и ответы Правила, общие для всех методов API. ### Формат | Что | Правило | |---|---| | Тело запроса | JSON с `Content-Type: application/json`; загрузка файла и транскрибация принимают `multipart/form-data` | | Ответ | JSON; синтез речи отдаёт звук, транскрибация в `text` и `srt` отдаёт текст, потоки идут в `text/event-stream` | | Время | RFC 3339 в UTC (`2026-09-25T12:00:00Z`); `created` у чата и задачи в unix-секундах | | Деньги | целые микроединицы рядом с полем `currency`: `1000000` означает 1 рубль ([Цены и списание](https://souz.ai/docs/pricing.md)) | | Сжатие | ответ JSON от 1 КиБ сжимается, если запрос передал `Accept-Encoding: gzip` | ### Идентификаторы Вид объекта называют поле `object` (`job`, `file`, `model`, `list`) и приставка id: | Приставка | Объект | |---|---| | `job_` | задача генерации | | `chatcmpl-` | ответ чата; его итог читает `GET /v1/jobs/chatcmpl-…` | | `file_` | файл | | `key_` | ключ | | `mem_` | участник аккаунта | | `user_` | аккаунт | | `whsec_` | секрет подписи вебхуков | ### Заголовок X-Request-Id Каждый ответ несёт `X-Request-Id`: id созданной задачи (`job_…` или `chatcmpl-…`) или `req_…` у остальных ответов и отказов. По нему поддержка найдёт запрос. Синтез речи и текстовая транскрибация дублируют id задачи в `X-Job-Id`. ### Списки | Метод | Ответ | Страницы (умолчание) | |---|---|---| | `GET /v1/models` | `{"object": "list", "data": [...]}` | список целиком | | `GET /v1/files` | `{"object": "list", "data": [...], "has_more": true}` | `?limit=` 1–100 (20) и `?after=` | | `GET /v1/usage`, `GET /v1/balance/history` | `{"items": [...], "next_before": "…"}` | `?limit=` до 1000 (100) и `?before=`, пока он не `null` | | `GET /v1/keys` | `{"items": [...]}` | список целиком | | `GET /v1/models/{id}/channels`, `GET /v1/channels` | `{"data": [...]}` | список целиком | Новые записи истории идут первыми. ### Лишние и пустые поля - `null` в необязательном поле равен его отсутствию: действует умолчание модели или ключа. - Поле, которого у метода нет, даёт `400 invalid_request` с `reason: "unsupported_field"`. - Значение, которое у модели ничего не меняет (умолчание, `auto`, то, что модель и так даёт), принимается молча. Простой запрос работает с любой моделью той же модальности. - Недоступное модели значение, которое изменило бы результат или цену, даёт `400 capability_mismatch` с подходящими значениями в `detail[].allowed`. Поля и значения модели перечислены в её [карточке](https://souz.ai/docs/models.md). ### Совместимость с SDK OpenAI Укажите в SDK `base_url = "https://api.souz.ai/v1"` без пути метода. На адрес вида `…/v1/chat/completions/chat/completions` API ответит `404 not_found` с подсказкой. Как есть работают методы SDK для чата, моделей, картинок, видео (`videos.create` и `videos.retrieve`), синтеза речи, транскрибации и файлов. Для музыки метода в SDK нет, отправьте `POST /v1/audio/generations`. Поля SDK `user` у чата и `purpose` у файла ни на что не влияют. Свои поля (`routing`, `routing_options`, `max_price_multiplier`, `callback_url`) передайте в `extra_body`, а заголовки `Idempotency-Key` и `Prefer` в `extra_headers`. В TypeScript поля кладут в тело, а заголовки во второй аргумент. ```python tab="Python" chat = client.chat.completions.create( model="gpt-5-nano", messages=[{"role": "user", "content": "Привет!"}], extra_body={"routing": "cheap"}, extra_headers={"Idempotency-Key": "greeting-42"}, ) print(chat.choices[0].message.content, chat.model_extra["channel"]) ``` ```ts tab="TypeScript" const chat = await client.chat.completions.create( { model: "gpt-5-nano", messages: [{ role: "user", content: "Привет!" }], // @ts-expect-error поле Souz API routing: "cheap", }, { headers: { "Idempotency-Key": "greeting-42" } }, ); console.log(chat.choices[0].message.content, (chat as any).channel); ``` ```bash tab="cURL" curl https://api.souz.ai/v1/chat/completions \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: greeting-42" \ -d '{"model": "gpt-5-nano", "messages": [{"role": "user", "content": "Привет!"}], "routing": "cheap"}' ``` ### Кеширование Ответы каталога несут `ETag`, и запрос с тем же `If-None-Match` получает `304` без тела. | Ответ | `Cache-Control` | |---|---| | каталог, каналы модели без ключа и `GET /v1/channels` | `public, max-age=30, stale-while-revalidate=300` | | `GET /v1/models/{id}/stats` | `public, max-age=0, must-revalidate` | | запрос с ключом (цены аккаунта) | `private, no-cache` | Ответ с ключом держите только в своём клиенте, не в общем кеше. **Дальше** - [Модели](https://souz.ai/docs/models.md): карточка как схема запроса. - [Ошибки](https://souz.ai/docs/errors.md): конверт ошибки и действия по коду. - [Повторы](https://souz.ai/docs/idempotency.md): повтор без второго списания. ## Модели Карточка модели описывает её запрос целиком: поля, значения, пределы, цены и статистику. Стройте запрос по карточке. ```bash tab="cURL" curl https://api.souz.ai/v1/models/nano-banana-pro ``` ```python tab="Python" model = client.models.retrieve("nano-banana-pro") print(model.model_extra["input_schema"]) ``` ```ts tab="TypeScript" const model: any = await client.models.retrieve("nano-banana-pro"); console.log(model.input_schema); ``` ### Каталог `GET /v1/models` возвращает все модели, `GET /v1/models/{id}` одну карточку (схема `Model`). Оба метода работают без ключа, остальные поля карточки есть в справочнике API. | Параметр или поле | Что делает | |---|---| | `?modality=` | оставляет одну модальность: `chat`, `image`, `video`, `music`, `speech`, `transcription` | | `id` | значение для поля `model` запроса | | `modality` | модальность и метод, которым модель вызывается | | `vendor`, `released_at` | автор модели и дата её выхода | Неизвестный `id` даёт `404 model_not_found`, неизвестная модальность `400 invalid_request`. ### Поля запроса Запрос описывают четыре поля карточки. В коде используйте `input_schema`, в интерфейсе `params`. | Поле | Что в нём | |---|---| | `input_schema` | JSON Schema 2020-12 тела запроса: поля, значения, умолчания и пределы | | `params[]` | те же поля для интерфейса: подписи, подсказки, умолчания и пределы | | `rules[]` | сочетания значений вне JSON Schema: условие `when` или `when_present`, список `allow` или `deny` | | `limits` | `context_tokens`, `max_output_tokens`, `max_references`, `max_output_seconds` | `input_schema` подходит как `parameters` инструмента LLM и `inputSchema` инструмента MCP. Условные пределы записаны через `allOf` с `if` и `then`. Пределы, для которых в JSON Schema нет стандартной границы, лежат в расширении `x-request-limits`. Нет поля в `limits`, значит число не опубликовано, а не равно нулю. Сочетание, запрещённое `rules[]`, даёт `400 capability_mismatch` с `reason: "incompatible_value"` и подходящими значениями в `detail[].allowed`. #### Возможности чата У чат-моделей есть `capabilities`: | Поле | Значение `true` | |---|---| | `tools` | модель вызывает инструменты | | `vision` | принимает картинку в `messages[].content` (`image_url`) | | `structured_output` | отвечает по JSON Schema из `response_format` | | `streaming` | отвечает потоком | | `file_input`, `audio_input`, `video_input` | принимает файл, например PDF (`file`), звук (`input_audio`), видео (`video_url`) | | `supported_parameters` | параметры, которые учитывает хотя бы один канал модели | Нет `supported_parameters`, значит список опубликован не у всех каналов модели. ### Длина текста Предел текстового поля указан как `maxLength` в `input_schema` и `max_length` в `params[]`. Длина считается в символах Unicode: буква кириллицы и эмодзи равны одному символу. Текст длиннее предела получает `400 capability_mismatch` с полем и допустимой длиной, мы его не обрезаем. Нет предела в карточке, значит карточка не задаёт границы, но любую длину не обещает. У чатов пределы в токенах: `limits.context_tokens` ограничивает вход вместе с запрошенным выходом, `limits.max_output_tokens` один ответ. Вход проверяется по предварительной оценке токенов. У канала предел бывает уже, чем у модели, и Авто-роутинг такой канал пропустит. Если запрос не принимает ни один допустимый канал, API отвечает `400 capability_mismatch`. ### Цены в карточке `pricing` показывает цену «от», наименьшую по каналам модели. Другие сочетания параметров лежат в `pricing.options`, пресеты роутинга в `pricing.presets`, каналы в `pricing.channels`. | Поле | Что в нём | |---|---| | `unit` | `per_1m_tokens` у чата; генерация, секунда звука или 1M символов у остальных | | `input_micro`, `output_micro`, `cache_read_micro` | ставки чата за 1M токенов входа, выхода и чтения кэша | | `amount_micro` | цена за `unit` у остальных модальностей | | `max_*_micro` | верх ставки; `null` значит, что общего верха нет | | `available` | `false`, если цены сейчас нет | | `dynamic` | `true`, если у части каналов итог считается по фактическому расходу | `null` в ставке значит «не опубликована», `0` означает нулевую ставку. Карточка показывает тариф, а списанную сумму показывают `price` задачи и `usage.cost` чата ([Цены и списание](https://souz.ai/docs/pricing.md)). С ключом каталог возвращает цены вашего аккаунта, без ключа публичные. Для чтения глазами есть [прайс-лист](https://souz.ai/pricing) и страница `https://souz.ai/models/{id}`. ### Статистика `stats_7d` в карточке содержит итоги 7 дней по запросам клиентов (схема `ModelStatsSummary`) и обновляется раз в час. | Поле | Что в нём | |---|---| | `requests`, `succeeded`, `failed` | запросы за 7 дней и их исход | | `success_rate` | доля успешных среди завершённых, от 0 до 1 | | `latency_p50_ms`, `latency_p95_ms` | медиана и p95 времени успешного запроса от приёма до результата, мс | | `tokens_out` | выданные токены, только у чата | Отказы по вине запроса (входные данные, правила модели, баланс) в долю успешных не входят. Время округлено с точностью ±10 %, `null` значит «нет данных», а не ноль. `GET /v1/models/{id}/stats` (без ключа, схема `ModelStats`) отдаёт те же числа по часам за 168 часов, ряд `latency.points` по 3 часа (медиана и границы центральных 90 % замеров) и ряды `speed` по 6 часов: скорость опций пресетов в токенах в секунду у чата и в секундах выполнения у остальных. Поле `typical` ряда — типичное значение опции за 7 дней в той же единице. Метод пересчитывается раз в минуту (`updated_at`). Числа по каждому каналу отдаёт `GET /v1/models/{id}/channels` ([Каналы](https://souz.ai/docs/channels.md)). **Дальше** - [Каналы](https://souz.ai/docs/channels.md): сравнить каналы модели и выбрать свой. - [Авто-роутинг](https://souz.ai/docs/routing.md): пресеты и потолок цены. - [Цены и списание](https://souz.ai/docs/pricing.md): как считается сумма запроса. ## Чат `POST /v1/chat/completions` отвечает текстом чат-модели по контракту OpenAI Chat Completions: сразу `200`, целиком или потоком с `"stream": true`. SDK OpenAI работает без правок, смените только `base_url` и ключ. ```bash tab="cURL" curl https://api.souz.ai/v1/chat/completions \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1842-summary" \ -d '{"model": "gpt-5-nano", "messages": [{"role": "user", "content": "Привет!"}], "routing": "fast"}' ``` ```python tab="Python" chat = client.chat.completions.create( model="gpt-5-nano", messages=[{"role": "user", "content": "Привет!"}], extra_body={"routing": "fast"}, extra_headers={"Idempotency-Key": "order-1842-summary"}, ) print(chat.choices[0].message.content, chat.usage) ``` ```ts tab="TypeScript" const chat = await client.chat.completions.create( { model: "gpt-5-nano", messages: [{ role: "user", content: "Привет!" }], // @ts-expect-error поле Souz API сверх контракта OpenAI routing: "fast", }, { headers: { "Idempotency-Key": "order-1842-summary" } }, ); console.log(chat.choices[0].message.content, chat.usage); ``` SDK OpenAI повторяет запрос после своего таймаута в 10 минут, а обрыв соединения вызов не отменяет. Без `Idempotency-Key` каждый повтор становится новым вызовом и новым списанием ([Повторы](https://souz.ai/docs/idempotency.md)). ### Поля чата | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `model` | string | обязательно | Модель с `modality: chat` | | `messages` | array | обязательно | Роли `system`, `developer`, `user`, `assistant`, `tool` | | `max_tokens` | integer | предел модели | Граница ответа; синоним `max_completion_tokens` | | `temperature` | number | у модели | От 0 до 2 или диапазон из `params` карточки | | `top_p` | number | у модели | От 0 до 1 или диапазон из `params` карточки | | `reasoning_effort` | string | у модели | Глубина рассуждения: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max` | | `tools`, `tool_choice` | array, string | нет | Вызов функций, как у OpenAI | | `response_format` | object | текст | `json_object` или `json_schema` | | `n` | integer | 1 | Сколько вариантов ответа | | `stream` | boolean | `false` | Ответ потоком событий | Общие поля `routing`, `routing_options`, `max_price_multiplier` и `store` модели не передаются ([Авто-роутинг](https://souz.ai/docs/routing.md), [Хранение данных](https://souz.ai/docs/data-retention.md)). Поля вне схемы, например `cache_control` в текстовом блоке и `reasoning.effort`, уходят модели как есть. Что читает модель и её ступени `reasoning_effort` указаны в `params` и `capabilities` карточки ([Модели](https://souz.ai/docs/models.md)). Ступени, которой у модели нет, канал получает ближайшую не ниже запрошенной, а без такой высшую. Имена полей чувствительны к регистру: `Max_Tokens` или `N` API отклоняет с `reason: "unsupported_field"`. Значения полей проверяются до списания: ```json {"path": "temperature", "reason": "out_of_range", "allowed": ["at most 2"]} ``` Если баланса хватает не на весь ответ, модель получает `max_tokens` по оплачиваемому объёму, но не меньше 1000 токенов на вариант и `min` из карточки. Если не хватает и на это, API отвечает `402 insufficient_balance`. ### Как обрабатываются параметры Возможность, без которой ответ будет другим, уходит только к умеющим каналам модели. | Поведение | Поля | |---|---| | Принимается и не влияет | `service_tier`, `provider`, `models`, `route`, `transforms`, `plugins` кроме `web` | | Канал без поддержки игнорирует | `temperature`, `top_p`, `top_k`, `min_p`, `top_a`, `seed`, `frequency_penalty`, `presence_penalty`, `repetition_penalty`, `logprobs`, `top_logprobs`, `logit_bias`, `stop`, `parallel_tool_calls`, `reasoning`, `reasoning_effort`, `include_reasoning` | | Уходит только к умеющим каналам | `tools` и `tool_choice`, `response_format`, `verbosity`, `"stream": true`, картинка, файл, звук и видео в `messages` | | `400 capability_mismatch` | Возможности нет ни у одного канала, например веб-поиска: `web_search_options` (и `{}`) или плагин `{"id": "web"}` | Параметры, которые учитывает хотя бы один канал, перечислены в `capabilities.supported_parameters` карточки. Каналы выбирают Авто-роутинг и `routing_options`, поэтому `provider`, `models` и `route` не нужны. PDF модель читает сама, без плагина `file-parser`. `tool_choice: "required"` и именованную функцию исполняют не все каналы, с `"auto"` модель решает сама. `response_format: {"type": "json_schema"}` требует `capabilities.structured_output`. Часть моделей принимает `{"type": "json_object"}` только со словом «json» в сообщениях, иначе API отвечает `400 invalid_request` с `reason: "incompatible_value"`. ### Файлы, звук и видео в сообщениях | Часть | Форма | Флаг в карточке | |---|---|---| | картинка | `{"type": "image_url", "image_url": {"url": "https://… или data:image/png;base64,…"}}` | `capabilities.vision` | | файл (PDF) | `{"type": "file", "file": {"filename": "doc.pdf", "file_data": "data:application/pdf;base64,…"}}` | `capabilities.file_input` | | звук | `{"type": "input_audio", "input_audio": {"data": "", "format": "wav"}}` | `capabilities.audio_input` | | видео | `{"type": "video_url", "video_url": {"url": "https://…/clip.mp4"}}` или data URL | `capabilities.video_input` | ```python tab="Python" import base64 pdf = base64.b64encode(open("contract.pdf", "rb").read()).decode() chat = client.chat.completions.create( model="gemini-3.8-flash", messages=[{"role": "user", "content": [ {"type": "text", "text": "Перечисли сроки из договора."}, {"type": "file", "file": {"filename": "contract.pdf", "file_data": f"data:application/pdf;base64,{pdf}"}}, ]}], ) ``` Файл и звук передавайте в base64, видео и картинку открытой ссылкой, например `url` из [Файлы](https://souz.ai/docs/files.md), или data URL. Тип картинки в data URL определяется по байтам. Тело запроса принимается до 25 МиБ, крупное вложение загрузите через `POST /v1/files` и передайте его `url`. Вложения модель считает во входных токенах, у части моделей токены звука дороже текстовых. Какие каналы примут вложение, покажет превью роутинга с `vision`, `file_input`, `audio_input` и `video_input` ([Каналы](https://souz.ai/docs/channels.md)). ### Ответ чата ```json { "id": "chatcmpl-1a2b3c4d5e6f", "object": "chat.completion", "created": 1758801600, "model": "gpt-5-nano", "channel": "blue", "choices": [ {"index": 0, "message": {"role": "assistant", "content": "Привет! Чем помочь?"}, "finish_reason": "stop"} ], "usage": {"prompt_tokens": 10, "completion_tokens": 24, "total_tokens": 34, "cost": 3000, "currency": "RUB"} } ``` - `usage.cost` — списание за вызов в микроединицах `usage.currency`; `null`, пока расчёт не завершён (итог — в `price` той же задачи) ([Цены и списание](https://souz.ai/docs/pricing.md)). - `channel` — цвет канала, который дал ответ ([Каналы](https://souz.ai/docs/channels.md)). - `message` несёт `reasoning`, `tool_calls`, `annotations`, `images` и `audio`, если их вернула модель. Картинки в `images` приходят data URL и не сохраняются. По `id` ответа `GET /v1/jobs/{id}` вернёт статус и списание без текста, `GET /v1/usage/{id}` тем же ключом вернёт токены и время. Если ответ остановил фильтр содержания, `finish_reason` равен `content_filter`, а `refusal` содержит нашу фразу вместо текста фильтра. С постоянным `routing_options.session_id` успешный канал закрепляется за диалогом на 10 минут бездействия ([Каналы](https://souz.ai/docs/channels.md)). ### Поток чата С `"stream": true` приходит `text/event-stream` по контракту OpenAI: чанки `chat.completion.chunk` с `choices[0]`, в конце `data: [DONE]`. ```python tab="Python" stream = client.chat.completions.create( model="gpt-5-nano", messages=[{"role": "user", "content": "Привет!"}], stream=True, stream_options={"include_usage": True}, ) for chunk in stream: if chunk.choices: print(chunk.choices[0].delta.content or "", end="") ``` ```ts tab="TypeScript" const stream = await client.chat.completions.create({ model: "gpt-5-nano", messages: [{ role: "user", content: "Привет!" }], stream: true, stream_options: { include_usage: true }, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` - Заголовки приходят с первым содержательным чанком, ошибка до него приходит обычным JSON. - Пока модель молчит дольше 15 секунд, поток пишет комментарий `: ping`, SDK его пропускают. Ошибка после пинга приходит событием `data: {"error": {…}}` без `data: [DONE]`. - С `stream_options: {"include_usage": true}` последний чанк содержит `usage` с `cost`, без флага списание видно в `GET /v1/jobs/{id}`. - `refusal` фильтра приходит одним куском в чанке с `finish_reason`. - Если после первого токена модель молчит 60 секунд, поток обрывается. У медленных моделей срок равен их сроку до первого токена. - Поток, оборвавшийся без `usage`, не списывается. Число открытых потоков ограничено ([Лимиты](https://souz.ai/docs/limits.md)). ### Ошибки чата | Код | Когда | Что делать | |---|---|---| | `400 invalid_request` | Значение вне диапазона, поле в другом регистре (`Max_Tokens`), пустой `messages` | Исправьте поле из `detail[].path` | | `400` | Вход длиннее контекста: `messages` с `too_long` | Сократите `messages` | | `400 capability_mismatch` | `max_tokens` или `max_completion_tokens` больше предела модели (`out_of_range`) | Уменьшите предел ответа | | `400 capability_mismatch` | Возможность или вложение не умеет ни один канал | Выберите модель по `capabilities` | | `400 capability_mismatch` | Картинка больше предела всех каналов (`too_large`), картинок больше предела (`too_many_items`) | Уменьшите картинку, передайте ссылкой или сократите число | | `400 invalid_request` | Тело больше, чем примет любой канал: `messages` с `too_large` | Передайте вложения ссылкой | | `400 invalid_request` | Модель не скачала вложение по ссылке (`unreachable`) | Передайте вложение data URL | | `409 idempotency_key_conflict` | Тот же `Idempotency-Key` с другим телом | Найдите, почему изменился запрос | | `413` | Тело больше 25 МиБ | Загрузите вложение через `POST /v1/files` | | `502 generation_failed` | Модель не ответила за 14 минут или поток оборвался до итога | Повторите запрос | Остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Авто-роутинг](https://souz.ai/docs/routing.md) — пресет, потолок цены и `routing_options`. - [Повторы](https://souz.ai/docs/idempotency.md) — `Idempotency-Key` для чата. - [Модели](https://souz.ai/docs/models.md) — `params` и `capabilities` в карточке. ## Картинки `POST /v1/images/generations` создаёт и редактирует картинки, как `images.generate` SDK OpenAI. Ответ `200` содержит готовую задачу с файлами в `data[]`. Если модель не успела за 9 минут, приходит `202` ([Результат задачи](https://souz.ai/docs/jobs.md)). ```bash tab="cURL" curl https://api.souz.ai/v1/images/generations \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1842-cover" \ -d '{"model": "nano-banana-pro", "prompt": "кот-космонавт на Луне", "aspect_ratio": "16:9", "output_format": "webp"}' ``` ```python tab="Python" image = client.images.generate( model="nano-banana-pro", prompt="кот-космонавт на Луне", size="1536x1024", extra_body={"output_format": "webp"}, extra_headers={"Idempotency-Key": "order-1842-cover"}, ) print(image.data[0].url) ``` ```ts tab="TypeScript" const image = await client.images.generate( { model: "nano-banana-pro", prompt: "кот-космонавт на Луне", size: "1536x1024", // @ts-expect-error поле Souz API сверх контракта OpenAI output_format: "webp", }, { headers: { "Idempotency-Key": "order-1842-cover" } }, ); console.log(image.data?.[0]?.url); ``` ### Поля картинки | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `model` | string | обязательно | Модель с `modality: image` | | `prompt` | string | обязательно | Что нарисовать или как изменить референс | | `aspect_ratio` | string | у модели | `16:9`, `1:1` или `auto` | | `resolution` | string | у модели | `1K`, `2K`, `4K` | | `references` | array | нет | Картинки, с которыми работает модель | | `output_format` | string | формат модели | `png`, `jpeg` или `webp` у любой модели | | `background` | string | у модели | `auto`, `transparent` или `opaque` | | `web_search` | boolean | нет | Сверка с интернетом | Общие поля `routing`, `routing_options`, `max_price_multiplier`, `store` и `callback_url` описаны в [Авто-роутинг](https://souz.ai/docs/routing.md), поля модели в её `input_schema` ([Модели](https://souz.ai/docs/models.md)). Синонимы из SDK OpenAI: - `size`: `ШxВ` от 16 до 16384 или `auto`. Даёт ближайшее `aspect_ratio` модели и разрешение по пикселям: до 2 Мп `1K`, до 8 Мп `2K`, больше `4K`. Без `aspect_ratio` и `resolution`. - `n`: число картинок, по умолчанию 1. Больше одной умеют модели с `n` в карточке, цена за каждую. - `response_format`: `url` или `b64_json`, второй добавляет base64 в `data[].b64_json`. - `quality`: `auto`, `low`, `medium`, `high`, `xhigh` или `max` по `params` модели. Без поля работает `medium`, `auto` включается только явно. Явный уровень не понижается: запрос уходит к каналам, которые его гарантируют (`parameters.quality.enum` в `GET /v1/models/{id}/channels`). - `stream`: картинки событиями. - `moderation`, `output_compression`, `style`, `partial_images`, `user` не влияют на результат. ### Референсы Референс передайте как `data:;base64,…`, ссылкой `https://…` или `file_…` из [Файлы](https://souz.ai/docs/files.md). Подойдут PNG, JPEG и WebP, фото с телефона передавайте как есть. Пределы проверяются по заголовку файла до запуска модели: | Предел | Значение | |---|---| | Размер файла | до 50 МиБ | | Пиксели JPEG | до 300 Мп | | Пиксели прогрессивного JPEG, PNG и WebP | 30–160 Мп по формату и сжатию: PNG 8 бит около 45 Мп, WebP с потерями около 160 Мп; точный предел в `detail[].allowed` | | Меньшая сторона | от 32 пикселей | | Тело запроса | до 70 МиБ, это один референс до 50 МиБ в base64 | Крупные референсы загрузите через `POST /v1/files`, запрос уйдёт быстрее. Картинка уходит модели как есть. Каналу, которому нужен другой формат или меньший размер, API один раз пережимает её с высоким качеством. Фото крупнее 40 Мп уменьшается, кроме каналов, которые получают ссылку. Маленький референс API увеличивает с сохранением пропорций, если модели нужна сторона больше. Очень вытянутую картинку, которую так не увеличить, API отклоняет с `too_small` до запуска модели. EXIF и GPS удаляются, поворот из EXIF применяется. ### Разложение на слои Модели `seedream-5.0-flash-layerize` и `seedream-5.0-pro-layerize` раскладывают картинку на фон и объекты. Передайте один `references` от 262 144 пикселей с пропорциями от 1:16 до 16:1, описание объектов в `prompt` по желанию. Разрешения `1K` (по умолчанию), `1.5K` и `2K`. Результат приходит в PNG с прозрачностью. `output_format: "webp"` прозрачность сохраняет, `"jpeg"` заливает её белым. Поля `aspect_ratio`, `background` и `quality` не принимаются. Задача возвращает фон с `role: "result"` и до 16 PNG объектов с `role: "layer"`, обрезанных по границам. Поле `layer` файла содержит `z_index` (порядок снизу вверх, фон равен 0), `name` и `description` объекта, размер `width` и `height`, края `bounding_box.absolute` в пикселях исходника и `bounding_box.normalized` от 0 до 1000. Складывайте слои по `z_index` в координатах `bounding_box`. Цена зависит от числа слоёв. ### Ответ с картинками ```json { "id": "job_1a2b3c4d5e6f", "object": "job", "created": 1758801600, "status": "completed", "model": "nano-banana-pro", "attempt_count": 1, "price": 5000000, "currency": "RUB", "data": [ {"id": "file_9f8e7d6c5b4a", "type": "image", "role": "result", "url": "https://media.souz.ai/v1/files/file_9f8e7d6c5b4a/souz_nano-banana-pro_9f8e7d6c.webp?exp=…&sig=…", "width": 1536, "height": 864, …} ], … } ``` Картинки идут в `data[]` по порядку, срок подписанной `url` описан в [Файлы](https://souz.ai/docs/files.md). SDK OpenAI читает из объекта `created`, `data[].url` и `data[].b64_json`. ### Поток картинок С `"stream": true` на каждую картинку приходит событие `image_generation.completed` с base64, как у `images.generate(stream=True)`, без промежуточных кадров. Id задачи приходит в заголовке `X-Job-Id`. Ошибка и задача, не успевшая за 9 минут, приходят обычным JSON. ### Ошибки картинок Задача, проваленная во время ожидания, приходит со статусом по причине и полем `error`. | Код | Когда | Что делать | |---|---|---| | `400 capability_mismatch` | `size` с `aspect_ratio` или `resolution` (`mutually_exclusive`) | Оставьте одно поле | | `400 capability_mismatch` | Модель не умеет значение, размер или `n` больше предела | Возьмите значение из `detail[].allowed` | | `400 invalid_input` | Файл референса не подходит: `unsupported_format` (HEIC, GIF), `animated_not_supported`, `too_large`, `too_many_pixels`, `too_small`, `undecodable` | Сохраните в PNG, JPEG или WebP в пределах таблицы | | `400 invalid_input` | Референс не получить: `unreachable`, `invalid_source`, `unknown_file`, `file_expired` | Проверьте ссылку или загрузите файл заново | | `400 content_policy` | Фильтр модели отклонил промпт или референс | Перепишите промпт или замените референс | | `413 invalid_request` | Тело больше 70 МиБ | Загрузите референсы через `POST /v1/files` | Остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Видео](https://souz.ai/docs/video.md) — ролики по тексту и кадрам. - [Результат задачи](https://souz.ai/docs/jobs.md) — что делать с `202`. - [Файлы](https://souz.ai/docs/files.md) — загрузка референсов и срок ссылок. ## Видео `POST /v1/videos` создаёт ролик по тексту, кадрам или референсам. Ролик готовится минутами, поэтому ответ всегда `202` с задачей в работе. Для видео передавайте `callback_url`: результат придёт на ваш сервер ([Вебхуки](https://souz.ai/docs/webhooks.md), [Результат задачи](https://souz.ai/docs/jobs.md)). ```bash tab="cURL" curl https://api.souz.ai/v1/videos \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: teaser-7" \ -d '{"model": "seedance-2", "prompt": "рассвет над морем, камера медленно поднимается", "duration_seconds": 5, "callback_url": "https://example.com/hooks/souz"}' ``` ```python tab="Python" video = client.videos.create( model="seedance-2", prompt="рассвет над морем, камера медленно поднимается", seconds="8", size="1280x720", extra_body={"callback_url": "https://example.com/hooks/souz"}, extra_headers={"Idempotency-Key": "teaser-7"}, ) video = client.videos.retrieve(video.id) ``` ```ts tab="TypeScript" let video = await client.videos.create( { model: "seedance-2", prompt: "рассвет над морем, камера медленно поднимается", seconds: "8", size: "1280x720", // @ts-expect-error поле Souz API сверх контракта OpenAI callback_url: "https://example.com/hooks/souz", }, { headers: { "Idempotency-Key": "teaser-7" } }, ); video = await client.videos.retrieve(video.id); ``` `GET /v1/videos/{id}` для `videos.retrieve` возвращает ту же задачу, что `GET /v1/jobs/{id}`. ### Опорные кадры Задайте начало и конец ролика через `first_frame` и `last_frame`. Для промежуточных моментов передайте `keyframes`. Модель определяет способ управления: последовательность изображений либо изображение с временем в секундах. Пример временных опор: ```json { "model": "grok-imagine-video-1.5", "duration_seconds": 5, "resolution": "720p", "first_frame": "file_start", "keyframes": [{"image": "file_middle", "timestamp_seconds": 2.5}], "last_frame": "file_end" } ``` Для цикла передайте один файл в первый и последний кадр. Обязательность описания, число кадров и допустимые сочетания с референсами проверьте в `input_schema` модели. У временных опор время должно быть строго внутри ролика, а интервал между опорами составляет не меньше трети секунды. Для последовательности передайте только `image`, без `timestamp_seconds`. ### Поля видео | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `model` | string | обязательно | Модель с `modality: video` | | `prompt` | string | обязательно | Что происходит в кадре | | `duration_seconds` | integer | у модели | Длительность в секундах | | `aspect_ratio` | string | у модели | `16:9`, `9:16` или `auto` | | `resolution` | string | у модели | `720p`, `1080p` | | `audio` | boolean | у модели | Управление звуковой дорожкой, если поле есть в схеме модели | | `output_format` | string | у модели | `mp4` или `mov` | | `return_last_frame` | boolean | `false` | Последний кадр вторым файлом, `role: "last_frame"` | | `web_search` | boolean | нет | Сверка с интернетом, только без входных файлов | Общие поля `routing`, `routing_options`, `max_price_multiplier`, `store` и `callback_url` описаны в [Авто-роутинг](https://souz.ai/docs/routing.md). Поля отдельных моделей, например `character_orientation`, есть в `input_schema` карточки ([Модели](https://souz.ai/docs/models.md)). Синонимы из SDK OpenAI: - `seconds`: строка `"8"`, как `duration_seconds`. - `size`: `1280x720` даёт ближайшее `aspect_ratio` модели и разрешение по короткой стороне (до 480 `480p`, до 720 `720p`, до 1080 `1080p`, больше `4k`). Без `aspect_ratio` и `resolution`. - `input_reference`: первый кадр файлом, `input_reference[image_url]` или `[file_id]`. - `user` не хранится. SDK шлёт форму `multipart/form-data`, поля Souz API передайте в `extra_body`. В форме объекты (`routing_options`, `shots`, `elements`, `keyframes`, `voices`) пишутся JSON-строкой, списки повтором поля. ### Кадры и референсы видео - `first_frame`, `last_frame`: первый и последний кадр. - `references[]`: картинки для персонажей и стиля. - `reference_videos[]`: клипы `mp4` или `mov` до 200 МиБ. - `reference_audio[]`: звук `wav` или `mp3` до 15 МиБ. - `elements[]`: именованные персонажи и объекты, в промпте `@name`. Передайте ракурсы `images` либо `video`; `tag`, `description` и привязанный `voice` задаёт схема модели. - `voices[]`: именованные голоса `{name, audio}`, в промпте `@name`. - `keep_original_sound`: сохранить звук видео-референса; для результата со звуком также передайте `audio: true`. - `shots[]`: сцены `{prompt, duration_seconds}` вместо одного промпта. Картинку передайте как в [Картинки](https://souz.ai/docs/images.md), клипы и звук только ссылкой `https://…` или `file_…` ([Файлы](https://souz.ai/docs/files.md)). Пропорции и размеры кадра указаны в карточке модели. Пределы суммарной длины видео и аудио применяются отдельно; смотрите `input_schema`. Если переключателя `audio` в схеме нет, ролик сохраняет встроенный звук. Совместимость кадров, референсов и последовательностей проверьте в `input_schema` модели. В одном запросе имена персонажей и голосов должны различаться. Поля файлов принимают те же источники, что и отдельные референсы; права на `file_…` проверяются на вашем аккаунте. ### Изменить, продолжить и завершить черновик Если `input_schema` модели содержит `mode`, у неё есть отдельные действия с видео. Для `seedance-2.5`: - `mode: "generate"` — создание по описанию, кадрам или референсам. - `mode: "edit"` — изменение одного клипа из `reference_videos`. Укажите, что заменить, в `prompt`, нужного персонажа передайте в `references`. Длительность исходника — от 4 до 30 секунд. `duration_seconds: -1` сохраняет её; если поле пропущено, оно получает `-1`. Пропорции — `auto`. Первый/последний входной кадр, сцены и поиск недоступны. - `mode: "extend"` — следующий фрагмент одного клипа из `reference_videos`. `duration_seconds` задаёт длину нового фрагмента, а результат содержит этот фрагмент. Пропорции — `auto`; ограничения входов такие же, как у изменения. `duration_seconds: -1` при создании позволяет модели выбрать длину; оценка цены до выполнения рассчитывается по верхней границе модели. При изменении сервер измеряет исходный клип и рассчитывает цену по его длине. Отрицательных секунд в расчёте нет. `draft: true` создаёт **платный черновик в 480p**. Сервер выбирает это разрешение независимо от сохранённых настроек формы. Готовая задача содержит `video.draft: true` и `video.complete_until` — срок завершения черновика. В течение семи дней от создания задачи можно отдельно заказать финал в 1080p: ```json {"model":"seedance-2.5","mode":"complete","draft_job":"job_…","resolution":"1080p","output_format":"mp4"} ``` `draft_job` — id готовой задачи вашего аккаунта и той же модели. Сцена, длительность, референсы, пропорции и звук наследуются из черновика; передавать их заново нельзя. Завершение — отдельная платная генерация из черновика, а не увеличение готового файла. Можно выбрать контейнер и запросить `return_last_frame`. Повторять завершение можно до истечения срока, каждое новое задание оплачивается отдельно; для безопасного повтора одного запроса используйте тот же `Idempotency-Key`. Завершение привязано к каналу исходного черновика. Недоступный канал не заменяется другим: другой канал не сможет продолжить этот черновик. Несовместимый явный выбор канала отклоняется до запуска. Истёкший, чужой или неподходящий `draft_job` даёт `capability_mismatch` с полем `draft_job`. Файлы по-прежнему доступны сутки. Срок завершения черновика хранится отдельно и не продлевает срок превью: даже после удаления файла исходную задачу можно завершить до `video.complete_until`. В финале `video.draft_job` ссылается на исходную задачу. ### Ответ с роликом Цена считается за оплачиваемые секунды ролика, они видны в `billed_seconds` из `pricing.options` карточки. С клипом на входе цена зависит от его измеренной длины. ```json { "id": "job_8d4e…", "object": "job", "status": "completed", "model": "seedance-2", "data": [ {"id": "file_…", "type": "video", "role": "result", "url": "https://media.souz.ai/…", "content_type": "video/mp4", …}, {"id": "file_…", "type": "image", "role": "last_frame", …} ], "price": 31500000, "currency": "RUB", … } ``` ### Ошибки видео | Код | Когда | Что делать | |---|---|---| | `400 invalid_request` | `seconds` не равно `duration_seconds` (`mutually_exclusive`) | Оставьте одно поле | | `400 capability_mismatch` | `size` с `aspect_ratio` или `resolution`, кадр с референсами (`mutually_exclusive`) | То же | | `400 capability_mismatch` | Модель не умеет поле или значение, нарушено сочетание: `requires_first_frame`, `requires_visual_reference`, `exceeds_duration`, `duplicate`, `text_to_video_only` | Исправьте поле из `detail[].path` по `input_schema` | | `400 invalid_input` | Кадр или файл не подходит модели | Возьмите пределы из `detail[].allowed` | Провал после `202` приходит в поле `error` задачи, остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Вебхуки](https://souz.ai/docs/webhooks.md) — результат на ваш сервер. - [Результат задачи](https://souz.ai/docs/jobs.md) — опрос и поток событий. - [Цены и списание](https://souz.ai/docs/pricing.md) — как считаются секунды. ## Музыка `POST /v1/audio/generations` создаёт трек по описанию, по вашему тексту или без вокала. Ответ `200` содержит готовую задачу со звуком в `data[]`, а если модель не успела, приходит `202` ([Результат задачи](https://souz.ai/docs/jobs.md)). В SDK OpenAI метода нет, отправьте `POST` с JSON. ```bash tab="cURL" curl https://api.souz.ai/v1/audio/generations \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: jingle-42" \ -d '{"model": "suno-v6", "prompt": "бодрый инди-поп, женский вокал", "lyrics": "[Verse]\nУтро светит в окно\n[Chorus]\nСолнце, мы снова живём", "title": "Утро"}' ``` ```python tab="Python" job = client.post( "/audio/generations", body={"model": "suno-v6", "prompt": "бодрый инди-поп, женский вокал", "instrumental": True}, cast_to=object, options={"headers": {"Idempotency-Key": "jingle-42"}}, ) print(job["data"][0]["url"]) ``` ```ts tab="TypeScript" const job: any = await client.post("/audio/generations", { body: { model: "suno-v6", prompt: "бодрый инди-поп, женский вокал", instrumental: true }, headers: { "Idempotency-Key": "jingle-42" }, }); console.log(job.data[0].url); ``` ### Поля музыки | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `model` | string | обязательно | Модель с `modality: music` | | `prompt` | string | нет | Описание трека: жанр, настроение, инструменты, темп, голос | | `lyrics` | string | пишет модель | Свой текст песни с разделами `[Verse]`, `[Chorus]`, `[Bridge]` | | `title` | string | первая строка текста | Название трека, если карточка объявляет поле | | `instrumental` | boolean | `false` | Трек без вокала; вместе с `lyrics` не передаётся | | `duration_seconds` | integer | у модели | Желаемая длительность, от 10 до 360 | Общие поля `routing`, `routing_options`, `max_price_multiplier`, `store` и `callback_url` описаны в [Авто-роутинг](https://souz.ai/docs/routing.md). Без `lyrics` модель пишет текст сама на языке описания. С `lyrics` она поёт ваш текст как написан, а `prompt` задаёт стиль. Остальные поля и пределы текста указаны в `input_schema` карточки ([Модели](https://souz.ai/docs/models.md)). Тело запроса принимается до 128 КиБ. ### Действия Suno У `suno-v6` и `suno-v6-mini` поле `action` выбирает действие, по умолчанию `generate`. Поле `version` выбирает `v6` (по умолчанию), `v6-mini` или `v6-wild`. Доступные действия и версии указаны в `input_schema` модели. Создание и переработка звука: | Действие | Исходное аудио | Основные поля | |---|---|---| | `generate`, музыка | нет | описание; свой стиль и текст с `custom_mode: true` | | `sounds`, звуковой эффект | нет | описание; `sound_loop`, `sound_tempo`, `sound_key`, `grab_lyrics` | | `cover`, кавер | один трек | описание или свой стиль и текст | | `extend`, продолжение | один трек | описание; `continue_at`, секунда начала | | `add_vocals`, вокал | один трек | стиль; обязательны `lyrics` и `negative_tags` | | `add_instrumental`, аккомпанемент | один трек | стиль; обязательно `negative_tags` | | `mashup`, смешивание | два трека | описание или свой стиль и текст | Действия над готовым треком: | Действие | Что передать | Результат | |---|---|---| | `replace_section` | трек или одна запись `reference_audio`; стиль в `prompt`, фрагмент в `lyrics`, `full_lyrics`, `start_seconds`, `end_seconds` | новые варианты | | `separate_vocals` | трек | вокал и инструментал | | `split_stems` | трек | все дорожки | | `isolate_stem` | трек, `stem_name` | одна дорожка | | `wav` | трек | WAV-файл | | `midi` | `source_job` разделения дорожек, `source_file` дорожки по желанию | ноты в `result.midi` | | `video` | трек, `author` и `domain_name` по желанию | MP4-клип | | `cover_image` | `source_job` задачи `generate` с текстом | обложки | | `lyrics` | тема в `prompt` | тексты в `result.lyrics` | | `boost_style` | стиль в `prompt` | улучшенный стиль в `result.result` | | `timestamps` | трек | слова с таймкодами и волна | | `persona` | трек, `name`, `description`; фрагмент и стиль по желанию | персона для запросов | ```json {"model": "suno-v6", "action": "separate_vocals", "source_job": "job_…", "source_file": "file_…"} ``` ### Исходный трек Свой трек передайте парой `source_job` и `source_file`: id задачи и id аудиофайла из её `data`, так сохраняется связь с песней. Чужую запись передайте в `reference_audio`: `file_…` ([Файлы](https://souz.ai/docs/files.md)) или открытую ссылку `https://…`, до двух файлов по 15 МиБ. Предел длины записи указан в `input_schema`. `source_job` и `reference_audio` вместе не передаются. - `extend` без `continue_at` продолжает трек в исходном стиле. Новый стиль или текст требуют `continue_at` меньше длительности трека, чужую запись продлевает только запрос с `continue_at`. `version` совпадает с исходной. - `duration_seconds`, `vocal_gender`, `style_weight`, `weirdness` и `audio_weight` в `generate`, `cover` и `mashup` работают только с `custom_mode` или `lyrics`. `vocal_gender` не сочетается с `instrumental`. - `mashup` не работает с треком без вокала. - Персону подключают полями `persona_id` и `persona_model: "style_persona"` в `generate` и `cover` с `custom_mode` или в `extend` с `continue_at`. - Фрагмент персоны длится от 10 до 30 секунд, по умолчанию первые 30. Заменяемый фрагмент длится от 10 до 60 секунд и не длиннее половины трека. ### Ответ с треком Модели Suno дают за запрос два варианта трека в `data[0]` и `data[1]` с `role: "result"`, цена включает оба. ```json { "id": "job_3f9a…", "object": "job", "status": "completed", "model": "suno-v6", "data": [ {"id": "file_a1…", "type": "audio", "role": "result", "title": "Утро", "duration_seconds": 142.6, "tags": "инди-поп, женский вокал", "cover_id": "file_c3…", "actions": ["extend", "cover", "separate_vocals"], …}, {"id": "file_b2…", "type": "audio", "role": "result", "title": "Утро", …}, {"id": "file_c3…", "type": "image", "role": "cover", …} ], "price": 9400000, "currency": "RUB", … } ``` Кроме полей примера, у аудиофайла бывают `lyrics` и `label` с названием дорожки. `actions` перечисляет действия, которые примут файл как `source_file`. Обложки приходят файлами с `role: "cover"`. Структурированный результат (`lyrics`, `midi`, таймкоды) лежит в `result` задачи и файлом с `type: "json"`. Цена зависит от действия и версии, она указана в `pricing.options` карточки. ### Ошибки музыки | Код | Когда | Что делать | |---|---|---| | `400 capability_mismatch` | Поле не подходит действию (`incompatible_value`), не хватает поля или трека (`required`), `lyrics` с `instrumental` (`mutually_exclusive`) | Исправьте поле из `detail[].path` | | `400 capability_mismatch` | Действие, версию или поле не умеет ни один канал модели | Проверьте `input_schema` | | `400 invalid_request` | `source_job` не ваша завершённая задача или её файл истёк (`unsupported_value`) | Передайте свою готовую задачу или запись в `reference_audio` | | `413 invalid_request` | Тело больше 128 КиБ | Передайте записи ссылкой или `file_…` | Остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Результат задачи](https://souz.ai/docs/jobs.md) — `202`, опрос и поток событий. - [Вебхуки](https://souz.ai/docs/webhooks.md) — уведомление о готовом треке. - [Файлы](https://souz.ai/docs/files.md) — загрузка своих записей. ## Синтез речи `POST /v1/audio/speech` озвучивает текст, как `audio.speech.create` SDK OpenAI. Ответ `200` содержит байты звука и id задачи в заголовке `X-Job-Id`. Если модель не успела, приходит `202` ([Результат задачи](https://souz.ai/docs/jobs.md)). ```bash tab="cURL" curl https://api.souz.ai/v1/audio/speech \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "qwen-audio-3.0-tts-plus", "input": "Добро пожаловать!", "voice": "longanlingxin"}' \ --output welcome.mp3 ``` ```python tab="Python" speech = client.audio.speech.create( model="qwen-audio-3.0-tts-plus", input="Добро пожаловать!", voice="longanlingxin", ) speech.write_to_file("welcome.mp3") ``` ```ts tab="TypeScript" import fs from "node:fs"; const speech = await client.audio.speech.create({ model: "qwen-audio-3.0-tts-plus", input: "Добро пожаловать!", voice: "longanlingxin", }); fs.writeFileSync("welcome.mp3", Buffer.from(await speech.arrayBuffer())); ``` ### Поля синтеза | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `model` | string | обязательно | Модель с `modality: speech` | | `input` | string | обязательно без `dialogue` | Текст или описание звуковой сцены | | `voice` | string | голос модели | Голос из карточки; без выбора в карточке не передавайте | | `response_format` | string | формат модели | `mp3`, `opus`, `aac`, `flac`, `wav` или `pcm` | | `instructions` | string | нет | Как говорить: тон, темп, эмоция | | `dialogue` | array | нет | Реплики разными голосами вместо `input` | | `stream_format` | string | `audio` | `sse` отдаёт звук событиями | Общие поля `routing`, `routing_options`, `max_price_multiplier`, `store` и `callback_url` описаны в [Авто-роутинг](https://souz.ai/docs/routing.md). Голоса, форматы и предел текста указаны в карточке модели ([Модели](https://souz.ai/docs/models.md)). Тело запроса принимается до 128 КиБ. Поле SDK OpenAI `speed` принимается от 0,25 до 4 и темп не меняет: о темпе напишите в `instructions`. Поле `user` не хранится. ### Манера речи и диалог Если карточка объявляет `instructions`, опишите в нём манеру: «спокойно и тепло». Если объявляет `dialogue`, передайте реплики по порядку вместо `input`, у каждой свой голос: ```bash tab="cURL" curl https://api.souz.ai/v1/audio/speech \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "gemini-3.1-flash-tts-preview", "instructions": "Спокойный вечерний разговор, говорят негромко.", "dialogue": [{"voice": "Kore", "text": "Ты готов?"}, {"voice": "Charon", "text": "Почти. Дай мне минуту."}]}' \ --output dialogue.wav ``` С `dialogue` поле `voice` не передаётся. Пределы голосов, реплик и текста указаны в карточке. Запрос с `instructions` или `dialogue` уходит только к умеющим каналам, их покажет превью роутинга с `speech_instructions` и `speech_dialogue` ([Каналы](https://souz.ai/docs/channels.md)). Текст `instructions` не оплачивается, а переносы строк между репликами считаются символами. ### Ответ синтеза Тело ответа содержит байты звука, `Content-Type` равен `audio/mpeg` или `audio/wav`. С `Accept: application/json` приходит объект задачи, но если `Accept` называет ещё `audio/*` или `application/octet-stream`, приходят байты. ```json { "id": "job_5e2f…", "object": "job", "status": "completed", "model": "qwen-audio-3.0-tts-plus", "usage": {"characters": 17, "seconds": 1.6, "billed_characters": 17}, "data": [{"id": "file_…", "type": "audio", "role": "result", "url": "https://media.souz.ai/…", …}], "price": 2000, "currency": "RUB", … } ``` `usage.characters` считает символы входа, `seconds` даёт длительность звука. При посимвольной оплате `billed_characters` показывает символы по счёту модели: у части моделей это байты UTF-8 (латиница — один, кириллица — два), цена «от» тогда — за латинский символ. При посекундной списание идёт за длительность готового звука по ставке `pricing.amount_micro`, предел указан в `limits.max_output_seconds` карточки. ### Поток синтеза С `"stream_format": "sse"` ответ приходит как `text/event-stream`: события `speech.audio.delta` с base64 звука, затем `speech.audio.done`. ### Ошибки синтеза | Код | Когда | Что делать | |---|---|---| | `400 invalid_request` | Нет ни `input`, ни `dialogue` (`required`), переданы оба или `voice` с `dialogue` (`mutually_exclusive`) | Оставьте одно поле | | `400 invalid_request` | `speed` вне 0,25–4, у реплики нет `text` или `voice` | Исправьте поле из `detail[].path` | | `400 capability_mismatch` | Голоса, формата или поля нет у модели (`unsupported_field`) | Возьмите значение из `detail[].allowed` | | `400 capability_mismatch` | Текст или `instructions` длиннее предела (`too_long`), реплик или голосов больше (`too_many_items`) | Разделите текст на запросы | | `413 invalid_request` | Тело больше 128 КиБ | Разделите текст на запросы | Остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Транскрибация](https://souz.ai/docs/transcription.md) — звук обратно в текст. - [Модели](https://souz.ai/docs/models.md) — голоса и пределы в карточке. - [Цены и списание](https://souz.ai/docs/pricing.md) — оплата за символы и секунды. ## Транскрибация `POST /v1/audio/transcriptions` превращает запись в текст, как `audio.transcriptions.create` SDK OpenAI. Отправьте файл формой `multipart/form-data`, ответ `200` вернёт расшифровку. Если модель не успела, приходит `202` ([Результат задачи](https://souz.ai/docs/jobs.md)). ```bash tab="cURL" curl https://api.souz.ai/v1/audio/transcriptions \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -F model=whisper-large-v3-turbo \ -F file=@meeting.m4a \ -F language=ru ``` ```python tab="Python" with open("meeting.m4a", "rb") as audio: t = client.audio.transcriptions.create( model="whisper-large-v3-turbo", file=audio, language="ru", ) print(t.text) ``` ```ts tab="TypeScript" import fs from "node:fs"; const t = await client.audio.transcriptions.create({ model: "whisper-large-v3-turbo", file: fs.createReadStream("meeting.m4a"), language: "ru", }); console.log(t.text); ``` ### Поля транскрибации | Поле | Тип | По умолчанию | Что делает | |---|---|---|---| | `file` | файл | обязательно | Запись для расшифровки | | `model` | string | обязательно | Модель с `modality: transcription` | | `language` | string | определит модель | Подсказка языка, код ISO 639-1 в нижнем регистре: `ru`, `en` | | `response_format` | string | `json` | `json`, `verbose_json`, `text` или `srt` | | `timestamp_granularities[]` | array | нет | `segment`, `word` или оба; только с `verbose_json` | | `stream` | boolean | `false` | Расшифровка событиями `text/event-stream` | Общие поля `routing`, `routing_options`, `max_price_multiplier`, `store` и `callback_url` описаны в [Авто-роутинг](https://souz.ai/docs/routing.md). Массив в форме передают повтором поля `timestamp_granularities[]=word` или JSON-массивом `["word"]`. Поля SDK OpenAI принимаются и не меняют результат: `prompt` (подсказка не передаётся), `temperature` от 0 до 1, `chunking_strategy` `auto` или `server_vad` (запись делит модель), `include[]` с `logprobs` (вероятностей в ответе нет) и `user` (не хранится). ### Запись и язык Подойдут `wav`, `mp3`, `flac`, `m4a`, `ogg`, `webm` и `aac` до 25 МиБ и 4 часов. Формат определяется по байтам, а не по имени. Длительность измеряется до вызова модели. В ответе `language` содержит язык, который назвала модель, а без него вашу подсказку. Запись видна в `GET /v1/files` как референс задачи и не занимает квоту загрузок, расшифровка становится файлом результата ([Файлы](https://souz.ai/docs/files.md)). ### Ответ транскрибации `json` и `verbose_json` возвращают объект задачи с `text`, `duration` и `language`. `usage.characters` считает символы расшифровки. ```json { "id": "job_7c1d…", "object": "job", "status": "completed", "model": "whisper-large-v3-turbo", "text": "Коллеги, начнём с итогов недели…", "language": "ru", "duration": 1834.2, "usage": {"characters": 21480}, "price": 1220000, "currency": "RUB", … } ``` У моделей с таймкодами `verbose_json` добавляет `segments` (фразы с `start`, `end`, `text`) и, если запрошено `word`, `words` (`word`, `start`, `end`). Фразы API собирает из слов, до 30 секунд каждая, а без слов отдаёт сегменты модели. Без меток приходит только текст. `text` возвращает расшифровку, `srt` возвращает субтитры по фразам у моделей, чья карточка его объявляет. Id задачи приходит в заголовке `X-Job-Id` у любого ответа `200`. ### Поток транскрибации С `stream=true` приходят события `transcript.text.delta` и `transcript.text.done`. Вся расшифровка приходит одним `delta`, когда задача готова. Не успевшая задача отвечает JSON с `202`. ```python tab="Python" with open("meeting.m4a", "rb") as audio: stream = client.audio.transcriptions.create( model="whisper-large-v3-turbo", file=audio, stream=True, ) for event in stream: if event.type == "transcript.text.delta": print(event.delta, end="") ``` ### Ошибки транскрибации | Код | Когда | Что делать | |---|---|---| | `400 invalid_input` | Формат не распознан (`unsupported_format`) или длительность не прочитать (`undecodable`) | Перекодируйте в `mp3` или `wav` | | `400 invalid_input` | Запись длиннее 4 часов (`too_large`) | Разделите запись | | `400 invalid_request` | `language` не кодом ISO 639-1 (`RU`, `ru-RU`, `russian`) | Возьмите код из `detail[].allowed` | | `400 invalid_request` | `timestamp_granularities[]` без `verbose_json` | Передайте `response_format=verbose_json` | | `400 capability_mismatch` | Модель не даёт формат или таймкоды | Выберите модель по `input_schema` | | `413 invalid_input` | Файл больше 25 МиБ (`too_large`) | Сожмите или разделите запись | Остальные коды описаны в [Ошибки](https://souz.ai/docs/errors.md). **Дальше** - [Синтез речи](https://souz.ai/docs/speech.md) — обратное превращение текста в звук. - [Результат задачи](https://souz.ai/docs/jobs.md) — что делать с `202`. - [Файлы](https://souz.ai/docs/files.md) — сколько хранится запись и расшифровка. ## Результат задачи Для видео и долгих моделей отправляйте запрос с `Prefer: respond-async` и `Idempotency-Key`, а результат получайте вебхуком. Так код один для любой модели. Каждый запрос, кроме чата, создаёт задачу, объект `Job`. Его отдают ответ на запрос, `GET /v1/jobs/{id}`, поток событий и вебхук. Итог чата тоже читается как задача по `id` ответа (`chatcmpl-…`): статус и цена, без текста. | `status` | Что происходит | |---|---| | `queued` | Задача принята и ждёт отправки модели | | `in_progress` | Модель работает | | `completed` | Готово: файлы в `data[]`, у транскрибации текст в `text` | | `failed` | Не получилось: причина в `error`, `price` равен `0` | `completed` и `failed` окончательные. У готовой задачи `price` бывает `null`, пока уточняется списание: результат уже доступен, цена появится в той же задаче. ### Способы получить результат | Способ | Как включить | Когда выбирать | |---|---|---| | Сразу `200` | Ничего не передавать | Картинки, музыка, речь, транскрибация, если код ждёт до 9 минут | | `202` и опрос | `Prefer: respond-async`, затем `GET /v1/jobs/{id}` | Скрипты и очереди без входящего адреса | | `202` и поток событий | `GET /v1/jobs/{id}/events` | Интерфейс, показывающий ход задачи | | Вебхук | Поле `callback_url` или адрес по умолчанию | Видео, долгие модели, серверы с публичным адресом | 9 минут считаются от прихода запроса, включая загрузку тела. Не успели — ответ `202` с задачей в работе. Видео всегда отвечает `202`. Синтез речи в ответе `200` отдаёт байты звука, а id задачи кладёт в `X-Job-Id` ([Синтез речи](https://souz.ai/docs/speech.md)). ### Отправьте задачу `Prefer: respond-async` даёт `202` сразу у любой модели, `Idempotency-Key` защищает от второй задачи при повторе ([Повторы](https://souz.ai/docs/idempotency.md)). ```bash tab="cURL" curl -i https://api.souz.ai/v1/images/generations \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Prefer: respond-async" \ -H "Idempotency-Key: order-1842-cover" \ -d '{"model": "nano-banana-pro", "prompt": "кот-космонавт на Луне"}' ``` ```python tab="Python" import httpx res = client.post( "/images/generations", cast_to=httpx.Response, body={"model": "nano-banana-pro", "prompt": "кот-космонавт на Луне"}, options={"headers": {"Prefer": "respond-async", "Idempotency-Key": "order-1842-cover"}}, ) job = res.json() print(job["id"], job["status"]) ``` ```ts tab="TypeScript" const job: any = await client.post("/images/generations", { body: { model: "nano-banana-pro", prompt: "кот-космонавт на Луне" }, headers: { Prefer: "respond-async", "Idempotency-Key": "order-1842-cover" }, }); console.log(job.id, job.status); ``` В ответе `202` заголовок `Location` указывает на задачу, а `Retry-After` задаёт паузу до опроса: 5 секунд, у видео 15. ```http HTTP/1.1 202 Accepted Location: /v1/jobs/job_1a2b3c4d5e6f Retry-After: 5 ``` ```json { "id": "job_1a2b3c4d5e6f", "object": "job", "created": 1758801600, "created_at": "2026-09-25T12:00:00Z", "completed_at": null, "model": "nano-banana-pro", "modality": "image", "status": "queued", "routing": "balanced", "max_price_multiplier": null, "routing_options": null, "attempt_count": 0, "price": null, "currency": "RUB", "usage": null, "data": [], "error": null } ``` Задачу нельзя отменить: `DELETE /v1/jobs/{id}` отвечает `405 invalid_request`. Обрыв соединения и конец опроса её не останавливают, она доделается и при успехе спишется. ### Опрашивайте задачу Запрашивайте `GET /v1/jobs/{id}` с паузой из `Retry-After`, пока `status` не станет `completed` или `failed`. ```bash tab="cURL" curl https://api.souz.ai/v1/jobs/job_1a2b3c4d5e6f \ -H "Authorization: Bearer $SOUZ_API_KEY" ``` ```python tab="Python" import time, httpx def wait_job(job_id: str, pause: int = 5) -> dict: while True: job = client.get(f"/jobs/{job_id}", cast_to=httpx.Response).json() if job["status"] in ("completed", "failed"): return job time.sleep(pause) ``` ```ts tab="TypeScript" async function waitJob(jobId: string, pause = 5): Promise { for (;;) { const job: any = await client.get(`/jobs/${jobId}`); if (job.status === "completed" || job.status === "failed") return job; await new Promise((r) => setTimeout(r, pause * 1000)); } } ``` Задача в работе: ```json {"id": "job_1a2b3c4d5e6f", "status": "in_progress", "attempt_count": 1, "channel": "blue", "price": null, "data": [], …} ``` Готовая задача: ```json { "id": "job_1a2b3c4d5e6f", "status": "completed", "completed_at": "2026-09-25T12:00:09Z", "price": 5000000, "currency": "RUB", "data": [{"id": "file_9f8e7d6c5b4a", "type": "image", "role": "result", "url": "https://media.souz.ai/v1/files/…", …}], … } ``` Ссылки на файлы живут 1 день ([Файлы](https://souz.ai/docs/files.md)). Проваленная задача несёт причину в `error` ([Ошибки](https://souz.ai/docs/errors.md)): ```json { "id": "job_1a2b3c4d5e6f", "object": "job", "created": 1758801600, "created_at": "2026-09-25T12:00:00Z", "completed_at": "2026-09-25T12:00:41Z", "model": "nano-banana-pro", "modality": "image", "status": "failed", "routing": "balanced", "max_price_multiplier": null, "routing_options": null, "attempt_count": 2, "price": 0, "currency": "RUB", "usage": null, "data": [], "error": { "type": "invalid_request_error", "code": "content_policy", "message": "the request was rejected by a content policy; rephrase the prompt or replace the attached files" } } ``` Если задача провалилась, пока `POST` её ждал, ответ придёт с тем же объектом и статусом `400`, `402`, `502` или `503`. ### Читайте поток событий `GET /v1/jobs/{id}/events` отдаёт `text/event-stream`: текущее состояние задачи, новый снимок при каждом изменении и `data: [DONE]` в конце. У завершённой задачи поток сразу отдаёт снимок и `[DONE]`. - Раз в 15 секунд приходит комментарий `: ping`, чтобы прокси не рвали соединение. - Соединение живёт до 35 минут. - Поток закрывается без `[DONE]` по сроку, при обновлении сервиса или обрыве сети. Переподключитесь: первым событием придёт текущее состояние. - Потоков на аккаунт не больше 1000 ([Лимиты](https://souz.ai/docs/limits.md)). `EventSource` не передаёт `Authorization`, поэтому читайте поток через `fetch` или HTTP-клиент. Функции ниже сами переподключаются: ```python tab="Python" import json, os, time, httpx def job_events(job_id: str): url = f"https://api.souz.ai/v1/jobs/{job_id}/events" headers = {"Authorization": f"Bearer {os.environ['SOUZ_API_KEY']}"} while True: try: with httpx.stream("GET", url, headers=headers, timeout=httpx.Timeout(60, connect=10)) as res: res.raise_for_status() for line in res.iter_lines(): if not line.startswith("data: "): continue # пустая строка или комментарий : ping payload = line[6:] if payload == "[DONE]": return yield json.loads(payload) except httpx.TransportError: pass # обрыв: переподключиться time.sleep(1) for job in job_events("job_1a2b3c4d5e6f"): print(job["status"]) ``` ```ts tab="TypeScript" async function* jobEvents(jobId: string) { const url = `https://api.souz.ai/v1/jobs/${jobId}/events`; const headers = { Authorization: `Bearer ${process.env.SOUZ_API_KEY}` }; for (;;) { let res: Response; try { res = await fetch(url, { headers }); } catch { await new Promise((r) => setTimeout(r, 1000)); // сеть недоступна: пауза и снова continue; } if (!res.ok) throw new Error(`events: HTTP ${res.status}`); const reader = res.body!.getReader(); const decoder = new TextDecoder(); let buffer = ""; try { for (;;) { const { done, value } = await reader.read(); if (done) break; // закрылся без [DONE]: переподключиться buffer += decoder.decode(value, { stream: true }); let cut: number; while ((cut = buffer.indexOf("\n\n")) !== -1) { const frame = buffer.slice(0, cut); buffer = buffer.slice(cut + 2); for (const line of frame.split("\n")) { if (!line.startsWith("data: ")) continue; // комментарий : ping const payload = line.slice(6); if (payload === "[DONE]") return; yield JSON.parse(payload); } } } } catch { // обрыв посреди потока: переподключиться } await new Promise((r) => setTimeout(r, 1000)); } } for await (const job of jobEvents("job_1a2b3c4d5e6f")) console.log(job.status); ``` ### Получите вебхук Передайте `callback_url` или задайте адрес по умолчанию в настройках аккаунта. Когда задача закончится, на адрес придёт `POST` с объектом задачи. ```json {"model": "seedance-2", "prompt": "рассвет над морем", "callback_url": "https://example.com/hooks/souz"} ``` Доставка, повторы и подпись описаны в [Вебхуки](https://souz.ai/docs/webhooks.md). ### Ошибки результата | Код | Когда | Что делать | |---|---|---| | `404 job_not_found` | Задачи нет у вашего аккаунта, чужая неотличима от несуществующей | Проверьте id | | `405 invalid_request` | Любой метод, кроме `GET` | Задачу отменить нельзя | | `429 rate_limited` | Открыто 1000 потоков разом | Закройте лишние или опрашивайте задачу | **Дальше** - [Вебхуки](https://souz.ai/docs/webhooks.md) — результат на ваш сервер и проверка подписи. - [Повторы](https://souz.ai/docs/idempotency.md) — как повторить запрос без второй задачи. - [Файлы](https://souz.ai/docs/files.md) — сколько живут ссылки на результат. - [Ошибки](https://souz.ai/docs/errors.md) — коды в поле `error`. ## Повторы Передавайте `Idempotency-Key` в каждом запросе, который создаёт задачу или вызывает чат. Повтор после обрыва или таймаута вернёт ту же задачу без второго вызова модели и второго списания. Обрыв соединения задачу не отменяет, и без ключа повтор создаст вторую. SDK OpenAI сам повторяет запрос после своего таймаута в 10 минут, поэтому ключ нужен и с SDK. ```bash tab="cURL" curl https://api.souz.ai/v1/images/generations \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-1842-cover" \ -d '{"model": "nano-banana-pro", "prompt": "кот-космонавт на Луне"}' ``` ```python tab="Python" image = client.images.generate( model="nano-banana-pro", prompt="кот-космонавт на Луне", extra_headers={"Idempotency-Key": "order-1842-cover"}, ) ``` ```ts tab="TypeScript" const image = await client.images.generate( { model: "nano-banana-pro", prompt: "кот-космонавт на Луне" }, { headers: { "Idempotency-Key": "order-1842-cover" } }, ); ``` ### Выберите ключ Ключ — строка до 255 символов, уникальная в аккаунте. Возьмите id своей операции: номер заказа, id сообщения или UUID, сохранённый до отправки. Ключ навсегда закреплён за одной задачей и не продлевает срок её файлов. Ключ принимают картинки, видео, музыка, транскрибация, синтез речи и чат. ### Повторите запрос Ответ на повтор зависит от того, что стало с первым запросом: | Первый запрос | Что вернёт повтор | |---|---| | Ещё выполняется | Ту же задачу с текущим статусом, чат ждёт ответа первого вызова | | Завершился успешно | Ту же задачу с результатом, чат получит тот же ответ или поток | | Завершился с `failed` | Ту же проваленную задачу. Для новой попытки нужен новый ключ | | Получил отказ до приёма | Ответ на новый запрос: задачи не было, ключ свободен | Отказ до приёма — ответ с одним `error` без задачи: `400`, `401`, `402`, `403`, `429`, `503`. Исправьте причину и повторите с тем же ключом. Повтор возвращает задачу такой, какой её приняли, даже если настройки аккаунта изменились: смотрите её `routing`, `max_price_multiplier` и `routing_options`. ### Что сравнивается Повтор должен совпадать с первым запросом в полях, от которых зависят результат и цена. Поля доставки можно поменять. | Метод | Сравнивается | Не сравнивается | |---|---|---| | Картинки, видео, музыка | Модель, все поля генерации, референсы как вы их прислали, `size` | `store`, `callback_url`, `response_format` | | Синтез речи | Модель, `input`, голос, формат звука, `instructions`, диалог | `store`, `callback_url`, `stream_format` | | Транскрибация | Модель, звук, язык, таймкоды, таймкоды слов (`timestamp_granularities`) | `store`, `callback_url`, формат ответа в пределах «с таймкодами или без» | | Чат | Тело запроса целиком, байт в байт | Ничего | Поля роутинга сравниваются так, как указаны в запросе: отсутствующее поле и поле со значением из настроек аккаунта дают разные запросы. ### Повтор чата Повтор чата ждёт первый вызов и получает его ответ, JSON или поток. Ответ хранится 1 сутки и не сохраняется, если он больше 16 МиБ или вызов оборвался на нашей стороне. Тогда повтор получит `502 generation_failed` со ссылкой на задачу, а итог и цену покажет `GET /v1/jobs/{id}` с `id` ответа. ### Ошибки повторов | Код | Когда | Что делать | |---|---|---| | `400 invalid_request` | Ключ длиннее 255 символов, `detail[].reason: too_long` | Сократите ключ | | `409 idempotency_key_conflict` | Тот же ключ пришёл с другим запросом | Не берите новый ключ автоматически: найдите, какое поле изменилось | | `502 generation_failed` | Повтор чата, а сохранённого ответа нет | Прочитайте итог в `GET /v1/jobs/{id}` | **Дальше** - [Результат задачи](https://souz.ai/docs/jobs.md) — как получить результат после `202`. - [Ошибки](https://souz.ai/docs/errors.md) — какие ошибки стоит повторять. - [Лимиты](https://souz.ai/docs/limits.md) — сроки ответа, после которых нужен повтор. ## Файлы Загрузите тяжёлый референс заранее через `POST /v1/files` и передайте в запрос его `file_…`. Результаты моделей тоже приходят файлами со ссылкой, которую можно отдать пользователю. Любой файл живёт 1 день: результат задачи, референс из запроса, ваша загрузка. Срок показывают `expires_at` файла и `retention.files_days` в `GET /v1/key`. Нужен дольше — скачайте. ```bash tab="cURL" curl https://api.souz.ai/v1/files \ -H "Authorization: Bearer $SOUZ_API_KEY" \ -F file=@reference.png ``` ```python tab="Python" with open("reference.png", "rb") as f: uploaded = client.files.create(file=f, purpose="user_data") print(uploaded.id) ``` ```ts tab="TypeScript" import fs from "node:fs"; const uploaded = await client.files.create({ file: fs.createReadStream("reference.png"), purpose: "user_data" }); console.log(uploaded.id); ``` Ответ `201`: ```json { "id": "file_9f8e7d6c5b4a", "object": "file", "kind": "upload", "url": "https://media.souz.ai/v1/files/file_9f8e7d6c5b4a/reference.png?exp=1758888000&sig=abc", "content_type": "image/png", "bytes": 2481152, "width": 2048, "height": 2048, "filename": "reference.png", "created_at": "2026-09-25T12:00:00Z", "expires_at": "2026-09-26T12:00:00Z", "expired": false } ``` `kind` показывает источник: `upload`, `reference` (файл из запроса) или `result`. У картинок-результатов бывает `preview_url`: сжатая копия для показа, JPEG с длинной стороной до 1600 точек. Показывайте её в списках и галереях, а скачивайте по `url`: там оригинал. Ссылка подписана до того же срока. У маленьких картинок и картинок с прозрачностью копии нет, показывайте `url`. ### Загрузите файл Отправьте `multipart/form-data` с частью `file`. Поле `purpose` из SDK OpenAI ни на что не влияет. | Тип | Форматы | Предел | |---|---|---| | Картинка | `png`, `jpeg`, `webp` | 50 МиБ и 300 Мп | | Видео | `mp4`, `mov` | 200 МиБ | | Звук | `wav`, `mp3` | 15 МиБ | Формат определяется по содержимому, а не по имени. У картинок мы удаляем метаданные и применяем поворот из EXIF, видео и звук храним как есть. Пределы модели указаны в её карточке ([Модели](https://souz.ai/docs/models.md)). ### Передайте файл в запрос Подставьте `file_…` в любое поле для файла: `references[]`, `first_frame` и другие. Подойдёт любой ваш файл с невышедшим сроком, в том числе результат прошлой задачи. ```json {"model": "nano-banana-pro", "prompt": "этот кот в скафандре", "references": ["file_9f8e7d6c5b4a"]} ``` Те же поля принимают `https://…` и data URL. Файл незавершённой задачи не удаляется до её конца, даже если срок вышел. ### Скачайте файл `url` подписан до `expires_at` и не меняется между чтениями. Доступ даёт подпись, ключ не нужен: ссылку можно вставить в `` и `