Руководство
Как использовать 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 |
Три ловушки, на которые легко попасться, придя от моделей «текст в видео»:
- Нет
duration. Длина клипа фиксирована (около 5 секунд). Отправкаdurationне обрезает клип — она просто отклоняется. Это ровно наоборот по сравнению с Ray 3.2 «текст в видео», гдеduration— необязательное целое число (5 или 10). - Нет
aspect_ratio. Соотношение сторон выходного видео соответствует вашему входному изображению, так что задавать нечего. В текстовой модели вы выбираете соотношение сторон; здесь его определяет изображение. (Подтверждено реальным запуском ниже: квадратный вход дал квадратный клип.) - Схема закрыта.
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 секунды, без звука.
Этот запуск показал две вещи, которые не описаны в таблице параметров:
- Соотношение сторон выхода совпадает с входом — квадрат на входе, квадрат на выходе. Именно поэтому нет поля
aspect_ratio; кадрируйтеimage_urlв соответствии с желаемой композицией клипа. 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.
Связанные ресурсы
- Страница модели Ray 3.2 «изображение в видео» — playground, живой статус и полная таблица цен по разрешениям
- Страница модели Ray 3.2 «текст в видео» — родственная конечная точка; начинает с текста, а не с изображения (у неё добавляются
durationиaspect_ratio, аimage_urlубирается) - Как использовать API Hailuo 2.3 «изображение в видео» — ещё одно руководство по i2v на том же API задач, полезное для сравнения схем
- Как использовать API grok-imagine «изображение в видео» — третья i2v-модель для сравнения
- Цены hiapi — цена за видеоклип по разрешениям
Частые вопросы
Какой 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 уведомил вас, а не вы угадывали интервалы.
Похожие статьи
Все статьи →-
Руководство
Как вызвать hailuo-2.3-fast/image-to-video через HiAPI API: практика с curl и Python
Первый рабочий запрос к быстрому и недорогому уровню генерации видео по изображению — от ID модели до загрузки MP4
-
Руководство
Как вызывать hailuo-2.3/text-to-video через API HiAPI: curl, Python и рабочие примеры запросов
-
Руководство
Использование Kandinsky 6.0 Pro для преобразования текста в видео на HiAPI: руководство по curl и Python
Практическая демонстрация с упором на запросы: построение вызова от промпта до видеоклипа, отправка запроса, опрос статуса завершения и скачивание результата