Генерация видео
Генерируйте видео из текстовых промптов через RouterAI API.
RouterAI поддерживает генерацию видео через модели, у которых "video" указан в output_modalities. Генерация асинхронная: вы создаёте задачу, а готовое видео получаете позже — опросом статуса (polling) или через webhook на ваш callback_url.
Полный жизненный цикл:
- Создание —
POST /api/v1/videosвозвращаетidзадачи иpolling_url. - Ожидание — опрашивайте статус (
GET /api/v1/videos/{id}) или получите webhook, если указалиcallback_url. - Скачивание — когда статус
completed, заберите mp4 через content-эндпоинт.
Поиск моделей
Модели для генерации видео — это модели с "video" в output_modalities. Найти их можно:
- на странице моделей, отфильтровав по выходным модальностям;
- запросом общего каталога моделей
GET /api/v1/models— видео-модели отличаются значениемvideoвarchitecture.output_modalities:
curl https://routerai.ru/api/v1/models \
-H "Authorization: Bearer $ROUTERAI_API_KEY"
Шаг 1. Создание генерации
Отправьте POST /api/v1/videos с обязательными model и prompt. Необязательное поле callback_url — HTTPS-адрес, на который придёт webhook о готовности (см. Шаг 3).
curl https://routerai.ru/api/v1/videos \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ROUTERAI_API_KEY" \
-d '{
"model": "x-ai/grok-imagine-video",
"prompt": "Кот в скафандре медленно плывёт в невесомости на космической станции, кинематографичный свет",
"aspect_ratio": "16:9",
"duration": 4,
"resolution": "480p",
"callback_url": "https://example.com/hooks/routerai-video"
}'
Ответ 202 Accepted — задача принята:
{
"id": "Cq4gNzomlZDrNyy72GHC",
"status": "pending",
"polling_url": "https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC"
}
id— идентификатор задачи (используется во всех последующих запросах).status— текущий статус (pendingсразу после создания).polling_url— URL для опроса статуса (указывает на домен RouterAI).
Шаг 2. Поллинг статуса
Опрашивайте статус задачи, пока он не станет терминальным:
curl https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC \
-H "Authorization: Bearer $ROUTERAI_API_KEY"
Статусы:
| Статус | Тип | Значение |
|---|---|---|
pending |
промежуточный | задача создана |
in_progress |
промежуточный | видео генерируется |
completed |
успех | готово — можно скачивать |
failed |
терминальная ошибка | ошибка генерации (см. поле error) |
cancelled |
терминальная ошибка | задача отменена |
expired |
терминальная ошибка | срок хранения результата истёк |
Ответ для готовой задачи (completed):
{
"id": "Cq4gNzomlZDrNyy72GHC",
"status": "completed",
"polling_url": "https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC",
"unsigned_urls": [
"https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC/content?index=0"
],
"usage": { "cost": 18.2 }
}
unsigned_urls — ссылки на content-эндпоинт RouterAI; по ним скачивается готовое видео (см. Шаг 4).
Шаг 3. Webhook о готовности
Если при создании указан callback_url, RouterAI отправит на него POST с JSON-телом, когда задача достигнет терминального статуса (completed, failed, expired, cancelled). Это избавляет от постоянного опроса статуса.
Что приходит
{
"type": "video.generation.completed",
"created_at": "2026-01-01T00:00:00.000Z",
"data": {
"id": "Cq4gNzomlZDrNyy72GHC",
"status": "completed",
"generation_id": "gen-abc123",
"model": "x-ai/grok-imagine-video",
"unsigned_urls": [
"https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC/content?index=0"
],
"usage": { "cost": 18.2 }
}
}
type— тип события, одно из:video.generation.completedvideo.generation.failedvideo.generation.expiredvideo.generation.cancelled
data.unsigned_urls— ссылки на content-эндпоинт RouterAI (только приcompleted).data.usage.cost— стоимость в рублях (только приcompleted).data.error— текст ошибки (дляfailed/expired).
Заголовки запроса:
| Заголовок | Значение |
|---|---|
Content-Type |
application/json |
X-RouterAI-Timestamp |
момент отправки, unix-секунды |
X-RouterAI-Signature |
HMAC-SHA256 в hex (см. ниже) |
Подпись
Подпись считается над строкой "<timestamp>.<body>", где body — сырое тело запроса (та же JSON-строка, что пришла):
signature = HMAC_SHA256(secret, "<X-RouterAI-Timestamp>.<raw_body>")
Особенность RouterAI: отдельный секрет не нужен — секретом служит SHA-256-дайджест вашего API-ключа в hex. То есть вы вычисляете секрет прямо из своего ключа:
secret = sha256_hex(ROUTERAI_API_KEY)
Так подпись можно проверить, имея только свой API-ключ. Запрос подписывается ключом, которым была создана задача.
Как проверить подпись
- Прочитайте
X-RouterAI-Timestampи сырое тело запроса (до парсинга JSON). - Вычислите
secret = sha256_hex(api_key). - Пересчитайте
HMAC_SHA256(secret, "<ts>.<raw_body>")и сравните сX-RouterAI-Signatureв постоянном времени (constant-time), не обычным==. - Проверьте свежесть
timestamp(например, ±5 минут) — защита от повторного воспроизведения (replay).
Node.js (Express)
const crypto = require("crypto");
// важно получить сырое тело: app.use(express.raw({ type: "application/json" }))
app.post("/hooks/routerai-video", (req, res) => {
const apiKey = process.env.ROUTERAI_API_KEY;
const ts = req.get("X-RouterAI-Timestamp");
const sig = req.get("X-RouterAI-Signature");
const rawBody = req.body; // Buffer
// свежесть метки времени (анти-replay)
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
return res.status(401).end();
}
// секрет = sha256(api_key) в hex
const secret = crypto.createHash("sha256").update(apiKey).digest("hex");
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
const ok =
sig &&
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end();
const event = JSON.parse(rawBody.toString("utf8"));
// ... ваша логика: event.type, event.data.unsigned_urls ...
res.status(200).end();
});
Python (Flask)
import os, hmac, hashlib, time
from flask import request, abort
@app.post("/hooks/routerai-video")
def routerai_video():
api_key = os.environ["ROUTERAI_API_KEY"]
ts = request.headers.get("X-RouterAI-Timestamp", "")
sig = request.headers.get("X-RouterAI-Signature", "")
raw = request.get_data() # bytes, сырое тело
if abs(time.time() - int(ts)) > 300:
abort(401)
# секрет = sha256(api_key) в hex
secret = hashlib.sha256(api_key.encode()).hexdigest()
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
# ... ваша логика: event["type"], event["data"]["unsigned_urls"] ...
return "", 200
Доставка и ретраи
- Доставка считается успешной при ответе HTTP 2xx. Любой другой код или таймаут — неуспех.
- При неуспехе RouterAI повторяет отправку: до 10 попыток с экспоненциальной задержкой (несколько часов суммарно).
- Бюджет одной попытки — около 8 секунд (на установку соединения и ответ).
- Endpoint должен быть публично доступен — RouterAI не подключается к адресам внутри приватных сетей (защита от SSRF).
Шаг 4. Скачивание видео
Когда статус completed, скачайте готовое видео через content-эндпоинт. index — позиция ссылки в массиве unsigned_urls (по умолчанию 0):
curl "https://routerai.ru/api/v1/videos/Cq4gNzomlZDrNyy72GHC/content?index=0" \
-H "Authorization: Bearer $ROUTERAI_API_KEY" \
-o video.mp4
Эндпоинт отдаёт бинарный mp4. Если модель вернула несколько видео, в unsigned_urls будет несколько ссылок — скачайте каждую, меняя index (?index=1, ?index=2, …).
Устранение неполадок
Не приходит webhook?
- Убедитесь, что
callback_url— валидный HTTPS-адрес и публично доступен (приватные сети блокируются). - Endpoint должен отвечать 2xx; при ошибках/таймаутах RouterAI ретраит, но после исчерпания попыток перестаёт.
- Webhook отправляется только при терминальном статусе (
completed/failed/expired/cancelled).
Подпись не сходится?
- Считайте HMAC по сырым байтам тела, без
JSON.parse+ пересериализации. - Секрет — это
sha256_hex(api_key)именно того ключа, которым создавалась задача, а не сам ключ и не отдельный секрет. - Подписываемая строка —
"<X-RouterAI-Timestamp>.<raw_body>"(timestamp, точка, тело).
Статус failed или expired?
- Смотрите
data.error(в webhook) или полеerrorв ответе поллинга. expiredозначает, что срок хранения результата истёк — создайте задачу заново.