HiAPI
Блог

Руководство

HappyHorse 1.1 API генерации видео по референсу: готовый к запуску учебник по curl + Python

Jun 26, 2026
HappyHorse 1.1 API генерации видео по референсу: готовый к запуску учебник по curl + Python

HappyHorse 1.1 R2V превращает до девяти референсных изображений плюс текстовый промпт в короткое видео, сохраняя единообразие субъекта, одежды и стиля сцены между несколькими кадрами. На HiAPI эта модель доступна через единый эндпоинт асинхронных задач, её идентификатор модели — happyhorse-1.1/reference-to-video. В этом учебнике сначала показан минимальный работающий запрос, а затем — производственная версия с callback.

Цели и предварительные условия

Вы выполните следующие действия:

  1. Загрузите (или разместите) от 1 до 9 референсных изображений по HTTPS-URL, доступным API.
  2. Отправите POST-запрос на https://api.hiapi.ai/v1/tasks с задачей, ссылающейся на эти URL.
  3. Когда задача достигнет статуса 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",
    },
}

В принимающем эндпоинте нужно реализовать две вещи:

  1. Идемпотентность по taskId. И success, и fail являются терминальными состояниями, а повторы HiAPI (или дубликаты, порождённые вашим собственным слоем повторов) будут повторно доставлять один и тот же taskId. Выполняйте дедупликацию до запуска любых побочных эффектов (копирование в S3, сообщение в Slack, событие биллинга).
  2. Резервная сверка опросом. Если ваш эндпоинт недоступен, 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. Сразу после успеха задачи скачайте файл или перенесите его в постоянное хранилище.

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

Все статьи →

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

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

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