HiAPI
Блог

Руководство

Как использовать API Ray 3.2 для видео из изображений: curl, Python и запрос, который работает с первого раза

Oct 20, 2026
Как использовать API Ray 3.2 для видео из изображений: curl, Python и запрос, который работает с первого раза

Ray 3.2 «изображение в видео» анимирует один статичный кадр: вы даёте ему изображение и подсказку, описывающую движение, и он возвращает короткий ролик, начинающийся с вашего изображения. На hiapi id модели — ray-3.2/image-to-video, и вызывается она через тот же универсальный асинхронный API задач, что и все остальные видеомодели — POST /v1/tasks, затем поллинг или ожидание колбэка.

Это отдельная конечная точка, а не та же, что у Ray 3.2 «текст в видео», с другими подсказками — просто другая. Её входная схема меньше и строже, и двух элементов управления, к которым вы могли привыкнуть по текстовой модели, здесь нет. Это руководство даёт запрос, который сработает с первого раза: точная схема (проверено на живом API 2026-10-20), готовый к копированию curl, полный Python-скрипт отправки/опроса/загрузки, а также продакшн-детали — колбэки, идемпотентность и реальные ответы с ошибками, включая тот режим отказа, который проявляется не там, где вы ожидаете.

Что вы создадите и что вам понадобится

Цель: отправить изображение первого кадра + подсказку о движении, опросить задачу и скачать беззвучный .mp4, в котором ваше изображение оживает.

Требования:

  • API-ключ hiapi — получите его в своей панели управления. Ключ имеет вид sk-... и передаётся в заголовке Authorization: Bearer. Впервые работаете с API задач? Начните с краткого руководства.
  • Публично доступный URL изображения первого кадра (подробнее ниже). Любой HTTP-клиент — мы покажем curl и Python (requests).

Ray 3.2 «изображение в видео» тарифицируется за видеоклип. Поскольку длина клипа фиксирована (см. ниже), resolution — единственный рычаг стоимости, которым вы управляете: клип в 1080p стоит в несколько раз дороже, чем в 540p. Актуальные цены смотрите на странице цен, а при итерациях по подсказкам сначала прототипируйте на 540p.

Точная входная схема (два обязательных поля, одно необязательное)

Всё передаётся внутри объекта input. Схема строгая — она отклоняет любое нераспознанное поле. Проверено поле за полем на живом API:

Поле Тип Обязательно Допустимые значения По умолчанию
prompt string да как должно двигаться изображение (камера, движение субъекта) —
image_url string да публичный URL первого кадра —
resolution string нет 540p, 720p, 1080p 540p

Три ловушки, на которые легко попасться, придя от моделей «текст в видео»:

  1. Нет duration. Длина клипа фиксирована (около 5 секунд). Отправка duration не обрезает клип — она просто отклоняется. Это ровно наоборот по сравнению с Ray 3.2 «текст в видео», где duration — необязательное целое число (5 или 10).
  2. Нет aspect_ratio. Соотношение сторон выходного видео соответствует вашему входному изображению, так что задавать нечего. В текстовой модели вы выбираете соотношение сторон; здесь его определяет изображение. (Подтверждено реальным запуском ниже: квадратный вход дал квадратный клип.)
  3. Схема закрыта. seed, negative_prompt, prompt_optimizer и любые другие поля вернут 400 с additional properties '<field>' not allowed. Не переносите тело запроса из других моделей и не предполагайте, что лишние ключи будут проигнорированы.

Что фактически возвращает валидация

Это реальные ответы с живого эндпоинта — с намеренно неверными значениями, без создания задач и без списаний:

// POST with no image_url
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: image_url: missing required field \"image_url\""}

// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}

// POST with a "duration" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: <root>: additional properties 'duration' not allowed"}

// POST with an "aspect_ratio" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: <root>: additional properties 'aspect_ratio' not allowed"}

message указывает конкретное проблемное поле, так что если вы что-то забыли, сам API служит вам справочником по схеме.

Подготовка первого кадра

image_url должен быть URL, доступным для worker'а hiapi, — публичной ссылкой, а не локальным путём и не base64-блобом data:. Изображение определяет соотношение сторон клипа и начальную композицию, поэтому отталкивайтесь от того кадра, с которого действительно хотите начать движение.

Об одной ловушке стоит предупредить заранее, потому что её легко упустить: плохой image_url не отлавливается при создании задачи. Вызов создания проверяет только форму запроса (является ли он строкой?), а не то, разрешается ли URL. Недоступный или 404-й URL изображения вернёт совершенно нормальный 200 и taskId — а затем задача через несколько секунд переключится в fail. Точный payload отказа мы увидим в разделе устранения неполадок; суть в том, чтобы всегда ветвиться по терминальному status задачи и никогда не предполагать, что ответ 200 на создание означает успех.

Минимальный рабочий пример: curl

Создайте задачу:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ray-3.2/image-to-video",
    "input": {
      "prompt": "slow cinematic push-in, gentle parallax, soft drifting light, the subject breathing almost imperceptibly",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "resolution": "540p"
    }
  }'

Ответ создания содержит id задачи:

{ "code": 200, "data": { "taskId": "tk-hiapi-01M5..." }, "message": "success" }

Опрашивайте, пока она не достигнет терминального состояния:

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M5... \
  -H "Authorization: Bearer sk-YOUR_KEY"

Пока задача выполняется, data.status проходит через несколько промежуточных состояний (вы увидите значения вроде handling, затем archiving). Два терминальных состояния — success и fail. При успехе:

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01M5...",
    "status": "success",
    "created": 1792435735,
    "completed": 1792435781,
    "storage": "temp",
    "output": [
      { "type": "video", "artifactId": "278504",
        "expireAt": 1793040580,
        "url": "https://temp.hiapi.ai/.../clip.mp4" }
    ]
  },
  "message": "success"
}

Скачайте data.output[0].url немедленно. Это подписанный URL на temp.hiapi.ai с expireAt (Unix-временная метка, примерно через неделю) — сохраните байты в собственное хранилище, а не хотлинкайте. Ray 3.2 возвращает беззвучный MP4; ждать звуковую дорожку не нужно.

Полный Python-скрипт (отправка → опрос → загрузка)

import time
import requests

API_BASE = "https://api.hiapi.ai/v1"
API_KEY = "sk-YOUR_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task(image_url: str, prompt: str, resolution: str = "540p") -> str:
    resp = requests.post(
        f"{API_BASE}/tasks",
        headers=HEADERS,
        json={
            "model": "ray-3.2/image-to-video",
            "input": {
                "prompt": prompt,
                "image_url": image_url,     # public URL of the first frame (required)
                "resolution": resolution,   # "540p" | "720p" | "1080p"
                # no "duration" and no "aspect_ratio" — both are rejected
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 900, poll_interval: int = 8) -> dict:
    """Poll until terminal. Tolerates transient network blips by re-polling
    the SAME saved task_id instead of aborting the whole job."""
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        try:
            resp = requests.get(f"{API_BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
            resp.raise_for_status()
        except requests.RequestException:
            time.sleep(poll_interval)      # a dropped poll is not a failed task
            continue
        task = resp.json()["data"]
        if task["status"] == "success":
            return task
        if task["status"] == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
        time.sleep(poll_interval)           # any non-terminal status = still running
    raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")


task_id = create_task(
    image_url="https://your-cdn.example.com/first-frame.jpg",
    prompt="slow cinematic push-in, gentle parallax, soft drifting light",
)
print("task created:", task_id)

task = wait_task(task_id)
video_url = task["output"][0]["url"]

# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-i2v.mp4", "wb") as f:
    f.write(video)
print(f"saved ray32-i2v.mp4 ({len(video) / 1e6:.1f} MB)")

Запустите его после pip install requests, подставьте свой ключ и реальный image_url — и когда задача завершится, на диске будет .mp4.

Как выглядит реальный запуск

Чтобы цифры стали конкретными, вот реальный запуск в 540p на живом API 2026-10-20. Первый кадр: изображение 1600×1600 (квадратное); подсказка: медленный кинематографичный наезд.

  • Задача прошла через handling → archiving → success, серверная работа заняла около 45 секунд (created — создание, completed — завершение) — с 8-секундным циклом опроса это около 60 секунд реального времени. Видеозадачи занимают десятки секунд-минуты, а не мгновение; закладывайте щедрый дедлайн.
  • На выходе — 720×720 MP4, около 1,3 МБ, 5,04 секунды, без звука.

Этот запуск показал две вещи, которые не описаны в таблице параметров:

  1. Соотношение сторон выхода совпадает с входом — квадрат на входе, квадрат на выходе. Именно поэтому нет поля aspect_ratio; кадрируйте image_url в соответствии с желаемой композицией клипа.
  2. resolution — это уровень качества, а не буквальная высота в пикселях. Запрос 540p для входа 1600×1600 вернул 720×720 — уровень масштабируется по форме входа, а не навязывает фиксированный размер. Если вам нужен точный выходной размер, масштабируйте сданный клип самостоятельно.

Производственные замечания: колбэки, идемпотентность, ошибки

Предпочитайте колбэки опросу

Для любого сценария за пределами скрипта регистрируйте webhook при создании, а не опрашивайте:

{
  "model": "ray-3.2/image-to-video",
  "input": {
    "prompt": "...",
    "image_url": "https://your-cdn.example.com/first-frame.jpg",
    "resolution": "720p"
  },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

С "when": "final" hiapi вызовет вашу конечную точку один раз, когда задача достигнет терминального состояния — никакого трафика опроса и никаких пропущенных завершений между интервалами. Затем ваш обработчик выполняет GET /v1/tasks/<id>, чтобы получить output[0].url. Для CLI и notebook'ов, не способных открыть HTTPS-эндпоинт, опрос по-прежнему подходит.

Сделайте повторы идемпотентными

Сохраняйте taskId в момент возврата вызова создания. Если ваш процесс упадёт посреди генерации, восстановитесь, опрашивая сохранённый id — не пересоздавайте задачу, иначе вы заплатите за второй клип. Пересоздавайте только если исходный запрос создания провалился до возврата id задачи. Устойчивый цикл опроса выше уже следует этому правилу: при обрыве сетевого запроса повторно опрашивает тот же id, а не начинает заново.

Ошибки, которые вы реально увидите

Каждая строка в таблице ниже — проверенная форма живого ответа:

Симптом Ответ Исправление
Отсутствующий/неверный ключ 401, вложенный {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"...","message":"This API key is invalid..."}} Проверьте заголовок Authorization: Bearer sk-... и наличие доступа у ключа. При обращении в поддержку указывайте request_id.
Отсутствует image_url 400 INVALID_REQUEST, message: "image_url: missing required field" Этой модели требуется image_url — режима только с текстом нет.
Неверное перечисление / лишнее поле 400 INVALID_REQUEST, плоский {"code":400,"error_code":"INVALID_REQUEST","message":"..."} Прочитайте поле, указанное в message. Для duration/aspect_ratio там будет additional properties ... not allowed — удалите их.
Неверный id задачи при опросе 404, плоский {"code":404,"data":null,"message":"task not found"} (примечание: у 404 нет error_code) Неверный id, или вы опрашиваете с ключом другого аккаунта.
image_url недоступен Создание возвращает 200 + taskId; задача затем завершается с data.status:"fail" и data.error:{"code":"INVALID_REQUEST","message":"Invalid request parameters for this model..."} Это и есть ловушка. Вызов создания не отличает мёртвую ссылку от живой. Убедитесь, что ваше изображение публично доступно, и всегда читайте терминальный data.status.

Последнюю строку стоит запомнить: даже если задача провалилась, HTTP-обёртка опроса всё равно 200 — отказ находится в data.status и data.error, а не в HTTP-коде. Неудачные задачи возвращаются, но ваш код всё равно должен их замечать. Ветвитесь по data.status == "fail", не доверяйте 200.

Связанные ресурсы

Частые вопросы

Какой id модели и какую конечную точку вызывать? ray-3.2/image-to-video, отправка на POST https://api.hiapi.ai/v1/tasks, параметры в input. API асинхронный — вы получаете taskId, затем опрашиваете GET /v1/tasks/<id> (или регистрируете колбэк).

Что требуется на входе? Ровно два обязательных поля — prompt (движение) и image_url (публичный URL первого кадра) — плюс необязательный resolution (540p/720p/1080p). Всё остальное будет отклонено.

Почему я не могу задать длительность или соотношение сторон? Длина клипа фиксирована (около 5 секунд), поэтому нет поля duration; соотношение сторон выхода наследуется от входного изображения, поэтому нет поля aspect_ratio. Их отправка вернёт 400. Если нужна выбираемая длительность, используйте Ray 3.2 «текст в видео».

Мой вызов создания вернул 200, но видео так и не появилось. Почему? Чаще всего image_url недоступен. Вызов создания проверяет только форму запроса, поэтому мёртвая ссылка всё равно вернёт taskId — а затем задача завершится с fail. Опрашивайте до терминального состояния и читайте data.error. Убедитесь, что изображение публично доступно для fetching (без аутентификации, не локальный путь, не base64).

Есть ли в клипе звук? Нет — Ray 3.2 «изображение в видео» возвращает беззвучный MP4. Добавьте звук при постобработке или используйте отдельную аудиомодель.

Сколько стоит один клип? Тарификация за клип, и из-за фиксированной длины единственный рычаг — resolution: клип в 1080p стоит в несколько раз дороже, чем в 540p. Цены меняются, поэтому смотрите страницу цен, а не хардкодьте цифры, и прототипируйте на 540p.

Сколько ждать, прежде чем сдаться? Десятки секунд-минуты — нормально. Терпите нетерминальные data.status в цикле опроса и используйте щедрый дедлайн (10–15 минут), либо зарегистрируйте callback с "when": "final", чтобы hiapi уведомил вас, а не вы угадывали интервалы.

Похожие статьи

Все статьи →

Начните работать с постоянными артефактами ИИ

Получите API-ключ и вызывайте модели изображений, видео и аудио через один API на основе tasks.

Читать документацию API