Руководство
HappyHorse 1.1 API генерации видео по референсу: готовый к запуску учебник по curl + Python
HappyHorse 1.1 R2V превращает до девяти референсных изображений плюс текстовый промпт в короткое видео, сохраняя единообразие субъекта, одежды и стиля сцены между несколькими кадрами. На HiAPI эта модель доступна через единый эндпоинт асинхронных задач, её идентификатор модели — happyhorse-1.1/reference-to-video. В этом учебнике сначала показан минимальный работающий запрос, а затем — производственная версия с callback.
Цели и предварительные условия
Вы выполните следующие действия:
- Загрузите (или разместите) от 1 до 9 референсных изображений по HTTPS-URL, доступным API.
- Отправите POST-запрос на
https://api.hiapi.ai/v1/tasksс задачей, ссылающейся на эти URL. - Когда задача достигнет статуса
success, прочитаете итоговый адрес MP4 изdata.output[0].url.
Предварительные условия:
- Аккаунт HiAPI и API Key, начинающийся с
sk-. Его можно получить в консоли HiAPI, а правила заголовков запросов описаны в документации аутентификации. Ключ должен передаваться в видеAuthorization: Bearer sk-...и ни в коем случае не должен находиться в клиентском коде. - Требования к размещению изображений: каждое референсное изображение должно быть в формате JPEG, PNG или WEBP, короткая сторона ≥ 400px, размер ≤ 20MB. Подойдут публичные URL CDN; подписанные URL S3 тоже подходят, если исполнитель задачи может к ним обратиться.
Минимальный рабочий запрос (curl)
Это минимальное тело запроса, которое принимает schema. Оно использует выходные параметры по умолчанию — 1080p / 16:9 / 5 секунд, поэтому вам достаточно задать только поля prompt и reference_image.
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": "Персонаж из [Image 1] идёт по сцене в стиле [Image 2], кинематографичный кадр",
"reference_image": [
"https://static.hiapi.ai/example/ref-1.jpg",
"https://static.hiapi.ai/example/ref-2.jpg"
]
}
}'
Ответ — это конверт задачи; единственное поле, которое вам действительно нужно, — data.taskId:
{
"code": 200,
"data": { "taskId": "task_xxx", "status": "queued" }
}
Опрашивайте до терминального состояния:
curl https://api.hiapi.ai/v1/tasks/task_xxx \
-H "Authorization: Bearer sk-YOUR_KEY"
Когда data.status станет success, URL видео будет находиться в data.output[0].url. Этот URL содержит метку времени expireAt, поэтому немедленно скачайте файл или перенесите его в постоянное хранилище — не размещайте эту ссылку напрямую на своём сайте.
Параметры
Эндпоинт R2V строго проверяет входные данные; неизвестные поля возвращают 400 INVALID_REQUEST. Полная schema (проверена в реальном времени для POST /v1/tasks):
| Поле | Тип | Обязательно | Допустимые значения | По умолчанию |
|---|---|---|---|---|
model |
string | Да | happyhorse-1.1/reference-to-video (фиксированное значение) |
- |
input.prompt |
string | Да | Ссылайтесь на референсные изображения через [Image 1], [Image 2]…… в порядке, соответствующем reference_image |
- |
input.reference_image |
string[] | Да | 1-9 HTTPS-URL, формат JPEG/PNG/WEBP, короткая сторона ≥ 400px, каждое ≤ 20MB | - |
input.resolution |
enum | Нет | 720p, 1080p |
1080p |
input.aspect_ratio |
enum | Нет | 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 |
16:9 |
input.duration |
integer | Нет | 3-15 (секунд) | 5 |
callback.url |
string | Нет | HTTPS-URL для приёма событий терминального состояния задачи | - |
callback.when |
enum | Нет | final (на данный момент единственное значение) |
final |
Несколько неочевидных правил валидации, о которых стоит узнать до того, как они создадут вам проблемы:
- Даже если у вас только один URL референсного изображения, передавайте
reference_imageкак массив. Передача строки напрямую вернётreference_image: got string, want array. durationдолжно быть целым числом от 3 до 15 включительно. И999, и1вернут понятное сообщение об ошибке выхода за границы.- На верхнем уровне отсутствуют
seed,negative_prompt,fps,cfg_scaleилиsteps. API отклонит неизвестные свойства.
Полные цены по комбинациям разрешения и длительности см. на странице тарифов HiAPI — стоимость меняется одновременно в зависимости от двух параметров: resolution и duration.
Python: от начала до конца + опрос
Блок кода ниже — это самодостаточный генератор, который отправляет задачу, опрашивает до терминального состояния и скачивает видео. Он намеренно сделан без зависимостей (используется только requests), чтобы вы могли скопировать его в любой сервис. Более полное описание жизненного цикла запроса см. в быстром старте.
import os
import time
import requests
API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
def submit_r2v(prompt: str, reference_images: list[str], duration: int = 5,
resolution: str = "1080p", aspect_ratio: str = "16:9") -> str:
if not 1 <= len(reference_images) <= 9:
raise ValueError("reference_image must contain 1-9 URLs")
payload = {
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": prompt,
"reference_image": reference_images,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
"duration": duration,
},
}
r = requests.post(API, json=payload, headers=HEADERS, timeout=60)
r.raise_for_status()
data = r.json()
if data.get("code") != 200:
raise RuntimeError(f"create failed: {data}")
return data["data"]["taskId"]
def wait_for_video(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> str:
deadline = time.time() + timeout_s
while time.time() < deadline:
r = requests.get(f"{API}/{task_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
task = r.json()["data"]
status = task["status"]
if status == "success":
return task["output"][0]["url"]
if status == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
def download(url: str, path: str) -> None:
with requests.get(url, stream=True, timeout=300) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(1 << 16):
f.write(chunk)
if __name__ == "__main__":
task_id = submit_r2v(
prompt="Сохрани дизайн субъекта из [Image 1], идущего в атмосфере [Image 2]",
reference_images=[
"https://static.hiapi.ai/example/char.jpg",
"https://static.hiapi.ai/example/mood.jpg",
],
duration=5,
resolution="720p",
aspect_ratio="9:16",
)
print("submitted", task_id)
video_url = wait_for_video(task_id)
print("ready", video_url)
download(video_url, "out.mp4")
Этот код намеренно делает следующее:
- Читает ключ из переменной окружения
HIAPI_API_KEY, чтобы он никогда не попадал в git или логи контейнеров. - Использует
r.raise_for_status(), чтобы перехватить транспортные ошибки до разбора JSON. - Считает терминальными только
successиfail— все остальные статусы (queued,processingи т. д.) продолжают цикл. - Немедленно скачивает выходной URL. Выходной URL подписан и истекает; если файл понадобится позже, перенесите его в постоянное хранилище или скопируйте в собственный бакет.
Производственная среда: переход с опроса на webhook
Опрос каждые 5 секунд подходит для ноутбука или низконагруженного worker. Для реального сервиса регистрируйте callback при создании задачи, чтобы HiAPI отправлял события терминального состояния на ваш эндпоинт — так ваш worker может оставаться свободным и вам не придётся платить за пустые GET-запросы. Полную schema запроса см. в разделе создание задачи, а резервный опрос — в разделе получение сведений о задаче.
payload = {
"model": "happyhorse-1.1/reference-to-video",
"input": {
"prompt": "Персонаж из [Image 1] идёт по городской улице из [Image 2]",
"reference_image": [
"https://cdn.example.com/char.jpg",
"https://cdn.example.com/street.jpg",
],
"resolution": "1080p",
"aspect_ratio": "16:9",
"duration": 8,
},
"callback": {
"url": "https://your-domain.com/hiapi/callback",
"when": "final",
},
}
В принимающем эндпоинте нужно реализовать две вещи:
- Идемпотентность по
taskId. Иsuccess, иfailявляются терминальными состояниями, а повторы HiAPI (или дубликаты, порождённые вашим собственным слоем повторов) будут повторно доставлять один и тот жеtaskId. Выполняйте дедупликацию до запуска любых побочных эффектов (копирование в S3, сообщение в Slack, событие биллинга). - Резервная сверка опросом. Если ваш эндпоинт недоступен, callback теряется. Сохраняйте лёгкую задачу сверки, которая вызывает
GET /v1/tasks/<id>для всех задач в статусеprocessing, превысивших худшее время генерации.
Ошибки, которые действительно встретятся
Следующие стоит обрабатывать явно; остальные — временные 5xx, которые должны повторяться с экспоненциальной задержкой SDK или вашим HTTP-клиентом.
401 permission_denied— заголовокAuthorizationотсутствует, имеет неверный формат или ключ отозван. Перевыпустите ключ из консоли, а не пытайтесь исправить заголовок.400 INVALID_REQUESTс путём к полю — сообщение об ошибке точно укажет, что именно не прошло, напримерreference_image: maxItems: got 10, want 9илиaspect_ratio: value must be one of .... Проверяйте входные данные до отправки из worker, чтобы быстро падать локально.task failed(терминальное состояние) — опрос/callback возвращаетstatus: "fail"вместе сerror.codeиerror.message. Частые причины: URL референсного изображения, недоступный для worker, срабатывание модерации NSFW или короткая сторона изображения ниже порога 400px.
Часто задаваемые вопросы
Можно ли сослаться на более чем 9 изображений? Нет. Schema ограничивает reference_image максимум 9 штуками. Если нужно больше вариаций, синтезируйте более плотное референсное изображение вне платформы (сетку или раскадровку) и используйте его как единственный вход.
Обязательно ли использовать маркеры [Image N] в промпте? Эти маркеры — ваш способ указать на конкретное референсное изображение. Если в промпте никогда не указывать изображения, это тоже допустимо, но модель будет свободно смешивать все референсы — для сценариев согласованности между несколькими кадрами это обычно не то, что вам нужно.
Почему я получаю 503 nsfw_moderation_unavailable? Это временная ошибка платформы, вызванная сервисом модерации, и не связана с вашим запросом. Повторите после короткой задержки.
Использовать 720p или 1080p? 1080p — значение по умолчанию и даёт лучшее качество, но стоит дороже и рендерится медленнее. Для вертикальных фрагментов для соцсетей, которые плеер всё равно понижает по разрешению, 720p с aspect_ratio: 9:16 обычно является более подходящим компромиссом.
Можно ли передать seed для воспроизводимости вывода? Пока нет. API считает seed неизвестным свойством и отклоняет его. Если вам нужна воспроизводимость, точно зафиксируйте prompt, reference_image, aspect_ratio, resolution и duration и примите некоторую вариативность между запусками.
Где хранятся выходные URL и как долго они действительны? Это подписанные URL с коротким TTL; ответ содержит expireAt. Сразу после успеха задачи скачайте файл или перенесите его в постоянное хранилище.
Похожие статьи
Все статьи →-
Руководство
Как вызвать 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
Практическая демонстрация с упором на запросы: построение вызова от промпта до видеоклипа, отправка запроса, опрос статуса завершения и скачивание результата