hubris

Генерация видео (API)

Асинхронный /v1/videos — создание, опрос статуса, скачивание mp4. Оплата в рублях по факту, хранение результата 72 часа.

/v1/videos — асинхронный API генерации видео. Вы создаёте задачу, опрашиваете её статус и, когда видео готово, скачиваете mp4. Тот же Bearer-ключ sk-gw-..., что и для остального API. Цена — в рублях, списывается по факту.

Идентификатор задачи (id), polling_url и unsigned_urls указывают только на эндпоинты Hubris (https://api.hubris.pw).

Scope ключа

  • POST /v1/videos требует scope videos:write (денежно-мутирующий — создаёт задачу и резервирует средства).
  • GET /v1/videos/{id} и GET /v1/videos/{id}/content требуют videos:read.
  • GET /v1/videos/models прав не требует: это тот же открытый каталог, что GET /v1/models?output_modalities=video.

Обычный ключ sk-gw- покрывает всё. Ключ только для чтения смотрит статус и скачивает готовые ролики, но не создаёт новые. Приложению, подключённому через OAuth, оба права выдаёт пользователь на экране согласия; подключённым до 14 октября 2026 года нужно подключиться заново.

Жизненный цикл

POST /v1/videos  →  202 { id, status: "pending", polling_url }
        │
        ▼  (опрос раз в несколько секунд)
GET /v1/videos/{id}  →  200 { status: "pending" | "in_progress" | "completed" | "failed" }
        │
        ▼  (когда status = "completed")
GET /v1/videos/{id}/content?index=0  →  video/mp4 (сырые байты)

Статусы наружу: pending, in_progress, completed, failed. Промежуточные внутренние состояния схлопываются в in_progress.

Создание — POST /v1/videos

POSThttps://api.hubris.pw/v1/videos

Обязательны model (из каталога видео-моделей, см. GET /v1/videos/models) и prompt. Ответ — HTTP 202.

curl -s https://api.hubris.pw/v1/videos \-H "Authorization: Bearer $HUBRIS_API_KEY" \-H "Content-Type: application/json" \-d '{  "model": "kwaivgi/kling-v3.0-pro",  "prompt": "Кот в скафандре катается на скейтборде по Луне",  "duration": 8,  "resolution": "720p",  "aspect_ratio": "16:9",  "generate_audio": true}'
import os, requestsr = requests.post(  "https://api.hubris.pw/v1/videos",  headers={"Authorization": f"Bearer {os.environ['HUBRIS_API_KEY']}"},  json={      "model": "kwaivgi/kling-v3.0-pro",      "prompt": "Кот в скафандре катается на скейтборде по Луне",      "duration": 8,      "resolution": "720p",      "aspect_ratio": "16:9",      "generate_audio": True,  },)job = r.json()print(job["id"], job["status"])
const r = await fetch("https://api.hubris.pw/v1/videos", {method: "POST",headers: {  Authorization: `Bearer ${process.env.HUBRIS_API_KEY}`,  "Content-Type": "application/json",},body: JSON.stringify({  model: "kwaivgi/kling-v3.0-pro",  prompt: "Кот в скафандре катается на скейтборде по Луне",  duration: 8,  resolution: "720p",  aspect_ratio: "16:9",  generate_audio: true,}),});const job = await r.json();console.log(job.id, job.status);
{
  "id": "b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
  "status": "pending",
  "polling_url": "https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
  "created": 1752192000
}

Опциональные поля: size ("1280x720", шорткат к resolution+aspect_ratio; при явном resolution и/или aspect_ratio они перекрывают size), seed (int, только если модель поддерживает детерминизм), frame_images[] — до двух опорных кадров, чтобы оживить картинку первым и/или последним кадром:

{
  "frame_images": [
    {
      "type": "image_url",
      "image_url": { "url": "https://example.com/frame.png" },
      "frame_type": "first_frame"
    }
  ]
}

image_url.url принимает https://-ссылку (без логина/пароля в URL) или data:image/png;base64,... / data:image/jpeg;base64,... (до 10 МБ в base64). Поле callback_url в v1 не поддерживается — запрос с ним вернёт 400 invalid_request (не молчаливый дроп). Про input_references (исходное видео или фото для аватара) — отдельный раздел ниже.

Идемпотентность

Передайте заголовок Idempotency-Key: <ваш-ключ> — повторный POST с тем же ключом от того же аккаунта вернёт уже созданную задачу, не создавая вторую и не резервируя средства повторно. Используйте при сетевых ретраях создания.

Исходное видео и фото — input_references

Часть видео-моделей берёт на вход не только текстовый промпт, а готовое видео или фото: апскейл и монтаж существующего ролика, оживление фотографии в аватар. Передавайте исходник в input_references — массиве из 1–4 элементов:

{
  "input_references": [
    { "type": "video_url", "video_url": { "url": "https://example.com/source.mp4" } }
  ]
}

или для фото:

{
  "input_references": [
    { "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
  ]
}

Требования к video_url.url: ссылка только https:// с именем домена (IP-адрес не принимается), прямая ссылка на файл без перенаправлений, сервер отвечает заголовком Content-Type: video/*, размер файла — не больше 50 МБ, формат mp4, mov или webm. Одно видео на запрос.

Какой вход обязателен и какие пределы у исходника — у каждой модели свои:

МодельОбязателенДлина и файлКадр
black-forest-labs/flux-video-upscaleвидео20 с2560×1440
black-forest-labs/flux-video-editвидеоот 1 до 15 ссторона кадра от 160 px
runway/aleph-2видеоот 2 до 30 с, не больше 30 кадров/с, файл до 32 МБ—
heygen/avatar-ivфото180 с речи (длина берётся из текста)—

У моделей без оси длительности (например, heygen/avatar-iv) поле duration передавать не нужно — модель его не принимает, запрос с duration вернёт 400 invalid_request: длина ролика определяется исходником (black-forest-labs/flux-video-edit, runway/aleph-2) либо длиной озвучиваемого текста (heygen/avatar-iv). Длина ролика аватара оценивается по тексту с запасом: цифры, сокращения и знаки вроде % считаются длиннее букв, паузы <break time="…"/> и медленная речь (voice_settings.speed) прибавляются. Если оценка выходит дольше 180 секунд, запрос вернёт 400 invalid_request до списания.

Какой вход требует конкретная модель и её пределы — также в ответе GET /v1/videos/models (required_input).

Настройки upscale_factor и creativity

У моделей с осью апскейла есть upscale_factor — во сколько раз увеличить разрешение: от 1.5 до 3 с шагом 0.5, по умолчанию 2. Другое значение вернёт 400 invalid_request. У black-forest-labs/flux-video-upscale есть ещё creativity — от 0 (точно, без додумывания деталей) до 1 (творчески: модель дорисовывает больше деталей); диапазон модели — в поле creativity того же ответа.

Настройки конкретного поставщика — provider.options

Параметры своего поставщика сейчас принимают четыре модели: heygen/avatar-iv (голос voice_id, voice_settings, движения, фон, субтитры и др.), black-forest-labs/flux-video-upscale и black-forest-labs/flux-video-edit (safety_tolerance), runway/aleph-2 (keyframes). Точный список ключей — в поле allowed_passthrough_parameters ответа GET /v1/videos/models; у остальных моделей поле пустое, и provider.options в запросе вернёт 400. Передавайте ключи через provider.options.<поставщик>.parameters, где <поставщик> — часть id модели до / (например, black-forest-labs, runway, heygen). Разрешён только один поставщик за запрос — два ключа в options вернут 400.

Например, у heygen/avatar-iv обязателен голос — без него запрос отклонит сам поставщик:

{
  "model": "heygen/avatar-iv",
  "prompt": "Здравствуйте! Это видео записано с помощью Hubris.",
  "input_references": [{ "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }],
  "provider": {
    "options": {
      "heygen": {
        "parameters": { "voice_id": "80441555167a467e967ab9487d844a30" }
      }
    }
  }
}

Сколько удерживается

Перед запуском задачи Hubris резервирует на балансе не меньше наибольшей возможной стоимости ролика: по длительности и разрешению, которые вы запросили, а для исходного видео или фото — по его фактическому замеру (длина, разрешение) и, для аватара, по длине озвучиваемого текста. Когда генерация завершается, списывается уже фактическая стоимость (usage.cost), а разница между удержанием и фактом возвращается на баланс. При сбое генерации (status: "failed") удержание возвращается целиком.

У runway/aleph-2 оплачивается не меньше 5 секунд ролика: исходник короче 5 секунд стоит как 5-секундный. Минимум за ролик — в ответе GET /v1/videos/models (pricing.per_unit).

Опрос статуса — GET /v1/videos/{id}

GEThttps://api.hubris.pw/v1/videos/{id}
curl -s https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a \
  -H "Authorization: Bearer $HUBRIS_API_KEY"
{
  "id": "b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a",
  "status": "completed",
  "created": 1752192000,
  "unsigned_urls": [
    "https://api.hubris.pw/v1/videos/b0f2c1a4-3e9d-4b7a-9c2e-8f1a6d5c4b3a/content?index=0"
  ],
  "usage": { "cost": 4200 }
}
  • usage.cost — стоимость в копейках (integer), появляется только при status: "completed". 4200 = 42,00 ₽. Это итоговая сумма, списанная с баланса — не оценка.
  • usage.estimated_cost — пока ролик в работе: сумма в копейках, удержанная с баланса перед запуском. Это верхняя оценка; после готовности вместо неё приходит usage.cost, а разница возвращается на баланс.
  • unsigned_urls появляются только при status: "completed" и, несмотря на имя (унаследовано от формата апстрима), требуют тот же Bearer-ключ — это эндпоинты Hubris, не публичные подписанные ссылки.
  • При status: "failed" приходит поле error с нейтральным текстом.

Пример полного цикла (bash)

ID=$(curl -s https://api.hubris.pw/v1/videos \
  -H "Authorization: Bearer $HUBRIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"kwaivgi/kling-v3.0-pro","prompt":"Закат над океаном, дрон"}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

while true; do
  STATUS=$(curl -s "https://api.hubris.pw/v1/videos/$ID" \
    -H "Authorization: Bearer $HUBRIS_API_KEY" \
    | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')
  echo "status=$STATUS"
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && { echo "generation failed"; exit 1; }
  sleep 5
done

curl -s "https://api.hubris.pw/v1/videos/$ID/content?index=0" \
  -H "Authorization: Bearer $HUBRIS_API_KEY" -o result.mp4

Опрашивайте не чаще раза в несколько секунд — частые повторы одного и того же GET не ускорят генерацию.

Пример с исходным видео (black-forest-labs/flux-video-edit)

ID=$(curl -s https://api.hubris.pw/v1/videos \
  -H "Authorization: Bearer $HUBRIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "black-forest-labs/flux-video-edit",
    "prompt": "Сделай фон зимним, идёт снег",
    "input_references": [
      { "type": "video_url", "video_url": { "url": "https://example.com/source.mp4" } }
    ]
  }' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')

while true; do
  STATUS=$(curl -s "https://api.hubris.pw/v1/videos/$ID" \
    -H "Authorization: Bearer $HUBRIS_API_KEY" \
    | python3 -c 'import sys,json;print(json.load(sys.stdin)["status"])')
  echo "status=$STATUS"
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && { echo "generation failed"; exit 1; }
  sleep 5
done

curl -s "https://api.hubris.pw/v1/videos/$ID/content?index=0" \
  -H "Authorization: Bearer $HUBRIS_API_KEY" -o result.mp4

Исходник source.mp4 должен быть не длиннее 15 секунд и не меньше 160 px по стороне кадра (пределы flux-video-edit, см. «Исходное видео и фото»); duration в теле запроса не передаётся — длина результата берётся из исходника.

Скачивание — GET /v1/videos/{id}/content

GEThttps://api.hubris.pw/v1/videos/{id}/content?index=0

Отдаёт сырые байты video/mp4. Требует Bearer-ключ и доступен только владельцу задачи. В v1 доступен единственный клип — index=0 (параметр можно не передавать, по умолчанию тот же); index больше нуля вернёт 400 index_out_of_range. Запрос до готовности видео или после истечения хранения — 404 video_not_ready.

Хранение результата — 72 часа

Готовый ролик хранится у Hubris 72 часа с момента создания задачи, затем удаляется без возможности восстановления. Скачайте и сохраните файл у себя в течение этого окна. Запрос /content после удаления вернёт 404 video_not_ready — так же, как если бы видео ещё не было готово (Hubris не палит разницу между «протухло» и «не готово»).

Копия исходного видео из input_references хранится у Hubris до 72 часов и удаляется вместе с результатом задачи — а также при удалении аккаунта. Фото из input_references не копируется: провайдер модели получает его по вашей ссылке.

Каталог видео-моделей — GET /v1/videos/models

GEThttps://api.hubris.pw/v1/videos/models
curl -s https://api.hubris.pw/v1/videos/models \
  -H "Authorization: Bearer $HUBRIS_API_KEY"

Возвращает { "object": "list", "data": [...] } — активные видео-модели с поддерживаемыми разрешениями, соотношениями сторон, длительностями, доступностью звука/seed/опорных кадров и ценами в рублях за единицу (секунда или видео). Видео-модели скрыты из обычного GET /v1/models — получить их там тоже можно, но отдельным запросом: GET /v1/models?output_modalities=video.

Коды ошибок

HTTPcodetypeКогда
400invalid_requestinvalid_request_errorКривой запрос, неподдерживаемая ось каталога, callback_url, кривой или лишний input_references, исходник вне пределов модели, текст аватара дольше 180 секунд речи, неразрешённая настройка provider.options (включая два поставщика сразу) или отказ модели принять вход — её текст приходит в message
400index_out_of_rangeinvalid_request_errorindex в /content вне диапазона (v1 — только index=0)
402insufficient_balanceinvalid_request_errorНедостаточно средств для резерва под задачу — в том числе перед скачиванием исходного видео, если баланс ниже минимума
404model_not_foundinvalid_request_errorМодель с таким id не видео-модель или неактивна
404not_foundinvalid_request_errorЗадачи с таким id не существует или она принадлежит другому аккаунту
404video_not_readyinvalid_request_error/content до completed или после 72-часового TTL
413request_too_largeinvalid_request_errorТело запроса (обычно — frame_images в base64) превышает лимит
429too_many_active_jobsrate_limit_errorДостигнут лимит одновременных видео-задач аккаунта, либо уже готовится другое исходное видео этого аккаунта, либо сервер сейчас занят подготовкой — повторите запрос через минуту
429daily_limit_exceeded, key_limit_exceededrate_limit_errorУдержание под ролик не помещается в остаток лимита расхода ключа или приложения, подключённого через OAuth; в тексте — удержание и остаток. Если ролик дороже всего лимита, Retry-After нет: после сброса он тоже не пройдёт. Повтор с тем же Idempotency-Key отдаёт уже созданную задачу и в лимит не упирается
502 / 504upstream_errorapi_errorПровайдер модели не принял задачу (502, включая отказ по собственным ключам Hubris — HTTP 401/403 от провайдера) или не ответил вовремя (504)
503exchange_rate_unavailableapi_errorКурс ЦБ РФ временно недоступен — нельзя посчитать резерв под задачу

Полный формат ошибок и стратегия retry — на странице Ошибки.

Приватность и хранение данных

Видео-генерация выполняется на инфраструктуре внешних провайдеров моделей и не покрывается режимом Zero Data Retention: провайдеры временно хранят входные данные (промпт, кадры frame_images) и результат на своей стороне на время генерации. У Hubris готовый ролик хранится 72 часа, затем удаляется.

Privacy Mode на видео не распространяется. Автоматическое маскирование персональных данных (Privacy Mode) работает только для текстовых запросов Chat Completions; промпты и кадры видео уходят провайдеру как есть — не передавайте туда персональные данные третьих лиц.

Трансграничная передача (152-ФЗ). Если вы вызываете /v1/videos от имени своих пользователей, вы отвечаете за наличие их согласия на обработку и трансграничную передачу входов и выходов (промпты, кадры, результат) за пределы РФ, а также на 72-часовое хранение результата у Hubris. Условия использования API фиксируют это в оферте.

Hubris не логирует содержимое ваших запросов: prompt, байты видео и frame_images не попадают в логи — только метаданные (модель, стоимость, статус, латентность).

Что дальше

Обновлено: