Руководство
Happyhorse 1.1 API генерации видео из текста: рабочий туториал по curl и Python
Эта страница проведёт вас через минимальный запрос на генерацию видео с помощью happyhorse-1.1/text-to-video, объяснит, какие значения реально принимает каждое поле ввода, и опишет два способа дождаться результата (опрос и callback). Всё ниже проверено на живом API, структура запросов и тексты ошибок приведены дословно.
Что вы создадите
Функцию, которая принимает текстовый промпт и возвращает URL загружаемого MP4. Модель — happyhorse-1.1/text-to-video, доступная через единый эндпоинт /v1/tasks. Вы отправляете задачу, сразу получаете taskId, а затем либо опрашиваете статус до завершения, либо позволяете hiapi отправить финальный результат POST-запросом на ваш webhook.
Требования:
- API-ключ hiapi. Новым аккаунтам даётся 1 доллар бесплатного лимита — получите его на странице API Keys.
- Любой способ отправки HTTPS-запросов. SDK устанавливать не нужно.
Не помещайте ключ в клиентский код и не коммитьте его в git. Читайте его из переменной окружения:
export HIAPI_API_KEY="sk-..."
Минимальный рабочий запрос
Единственное обязательное поле ввода — prompt. У всех остальных полей есть значения по умолчанию.
curl
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "a calm ocean wave at sunset, slow motion, golden hour"
}
}'
Ответ:
{
"code": 200,
"data": { "taskId": "tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC" },
"message": "success"
}
Этот taskId — ваш пропуск к работе с задачей. Сохраните его.
Python (только стандартная библиотека)
import os
import urllib.request
import json
API_KEY = os.environ["HIAPI_API_KEY"]
def submit_task(prompt: str, **input_overrides) -> str:
body = {
"model": "happyhorse-1.1/text-to-video",
"input": {"prompt": prompt, **input_overrides},
}
req = urllib.request.Request(
"https://api.hiapi.ai/v1/tasks",
method="POST",
headers={
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
},
data=json.dumps(body).encode("utf-8"),
)
with urllib.request.urlopen(req) as resp:
payload = json.loads(resp.read())
if payload.get("code") != 200:
raise RuntimeError(payload)
return payload["data"]["taskId"]
task_id = submit_task("a calm ocean wave at sunset, slow motion, golden hour")
print(task_id) # e.g. tk-hiapi-01KW23VDG2EZ20J7RQH8S67MNC
Полная таблица параметров
Структура параметров компактна и строга — неизвестные поля отклоняются с кодом 400, а не молча отбрасываются. Ниже перечислены поля внутри input, которые API реально принимает, взятые напрямую из сообщений об ошибках валидации:
| Поле | Тип | Обязательно | Допустимые значения | Поведение по умолчанию |
|---|---|---|---|---|
prompt |
string | Да | любой непустой текст, описывающий сцену | — |
aspect_ratio |
string | Нет | 16:9, 9:16, 3:4, 4:3, 4:5, 5:4, 1:1, 9:21, 21:9 |
значение модели по умолчанию |
resolution |
string | Нет | 720p, 1080p |
значение модели по умолчанию |
duration |
integer | Нет | от 3 до 15 (секунд) | значение модели по умолчанию |
Поля, которые кажутся очевидными, но сейчас не принимаются: size, audio, seed, negative_prompt. Отправка любого из них вернёт additional properties '<field>' not allowed. Если вы работали с другими моделями hiapi — qwen-image-2.0 использует size, z-image использует aspect_ratio — учтите, что happyhorse-1.1/text-to-video относится к семейству с соотношением сторон, а не к семейству с явными размерами.
Запрос со всеми поддерживаемыми параметрами:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "a vintage paper plane gliding above neon Tokyo at night, cinematic, 24fps",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 8
}
}'
О стоимости и длительности
Модель тарифицируется по секундам сгенерированного видео — текущий тариф смотрите на странице цен.duration — это рычаг стоимости, resolution — нет. Переход с 5 до 15 секунд утроит стоимость; а вот замена 720p на 1080p на момент написания статьи не влияет на цену. Во время разработки используйте по умолчанию duration: 3 и более низкое разрешение; повышайте до 1080p только на окончательно утверждённых промптах.
Получение результата: опрос
Самый простой способ получить результат — делать GET задачи по id, пока status не перейдёт в терминальное состояние.
import time
import urllib.request
import json
def get_task(task_id: str) -> dict:
req = urllib.request.Request(
f"https://api.hiapi.ai/v1/tasks/{task_id}",
headers={"Authorization": f"Bearer {API_KEY}"},
)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())["data"]
def wait_for_task(task_id: str, timeout_s: int = 600, poll_interval_s: int = 10) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
task = get_task(task_id)
status = task["status"]
if status == "success":
return task
if status == "fail":
raise RuntimeError(f"task failed: {task}")
time.sleep(poll_interval_s)
raise TimeoutError(f"task {task_id} still {status} after {timeout_s}s")
task = wait_for_task(submit_task("a quiet coffee shop on a rainy afternoon"))
video_url = task["output"][0]["url"]
print(video_url)
Несколько важных деталей:
statusпоследовательно проходитqueued → handling → archiving → success(илиfail). Любое состояние доsuccessилиfailне должно считаться терминальным.- Время генерации растёт с
durationи текущей глубиной очереди. Задача на 5 секунд 720p обычно завершается за несколько минут; для 1080p 15 секунд закладывайте больше времени. output[].urlхранится во временном хранилище. Сразу скачайте байты и разместите их самостоятельно; не сохраняйте только URL для передачи конечным пользователям — он истекает.
import urllib.request
def download(video_url: str, out_path: str) -> None:
with urllib.request.urlopen(video_url) as resp, open(out_path, "wb") as f:
f.write(resp.read())
download(video_url, "ocean.mp4")
Получение результата: callback (продакшен-режим)
Опрос подходит для скриптов и notebook, но в реальном бэкенде вы не хотите, чтобы worker был занят минутами на каждое видео. Передайте при отправке объект callback, и hiapi ровно один раз отправит тело завершённой задачи POST-запросом на ваш URL.
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "happyhorse-1.1/text-to-video",
"input": {
"prompt": "an astronaut planting a flag on a glass beach",
"aspect_ratio": "16:9",
"resolution": "1080p",
"duration": 6
},
"callback": {
"url": "https://example.com/hooks/hiapi",
"when": "final"
}
}'
Сейчас поддерживается только when: "final" — отправка других значений вернёт:
{ "code": 400, "error_code": "INVALID_REQUEST",
"message": "invalid callback.when: only 'final' is supported" }
Проектирование надёжного получателя
Три правила обеспечат безопасность пути через callback:
- Идемпотентность. Сохраняйте состояние по
taskId. Если вы получите один и тот же callback дважды — что в итоге обязательно случится из-за повторов — повторная обработка должна быть no-op. - Отделите подтверждение от обработки. Как только payload поставлен в очередь, верните из webhook
200 OK. Фактическое скачивание / переразмещение / запись в базу выполняйте в фоновой задаче. Медленный webhook провоцирует таймауты и повторную доставку. - Откат к опросу. Сохраняйте каждый отправленный
taskIdвместе сcreated_atв своей базе. Запускайте сканер, который опрашивает/v1/tasks/{id}для любой строки, которая старше примерно 30 минут и ещё не имеет терминального состояния. Callback — это быстрый путь, а не единственный.
Минимальный получатель на FastAPI:
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/hooks/hiapi")
async def hiapi_callback(req: Request):
body = await req.json()
task = body.get("data", body) # callback wraps the same shape as GET
task_id = task["taskId"]
if task["status"] == "success":
url = task["output"][0]["url"]
# enqueue, don't download here
await queue.put({"task_id": task_id, "url": url})
return {"ok": True}
Ошибки, с которыми вы реально столкнётесь
Форма конверта ошибок всегда одна и та же — {code, error_code, message, data} — поэтому можно написать один обработчик. Несколько случаев, заслуживающих особой обработки для этой модели:
| HTTP | error_code |
Условие возникновения | Способ исправления |
|---|---|---|---|
| 400 | INVALID_REQUEST |
неизвестное поле, неверное значение enum, duration вне 3–15 |
посмотрите message, оно укажет поле — например, aspect_ratio: value must be one of '16:9'... |
| 401 | — | ключ отсутствует или неверен, либо permission_denied |
перепроверьте заголовок Authorization: Bearer sk-... |
| 402 | — | баланса не хватает для покрытия оценки стоимости | пополните баланс на hiapi.ai/en/pricing и повторите |
| 429 | — | вы слишком часто обращаетесь к эндпоинту | используйте экспоненциальную задержку с джиттером |
Реальный пример ошибки валидации — можно скопировать как тестовую фикстуру:
{
"code": 400,
"data": null,
"error_code": "INVALID_REQUEST",
"message": "invalid input: aspect_ratio: value must be one of '16:9', '9:16', '3:4', '4:3', '4:5', '5:4', '1:1', '9:21', '21:9'; resolution: value must be one of '720p', '1080p'; duration: maximum: got 99, want 15"
}
Обратите внимание, что все поля сообщаются в одном ответе. Валидируйте на стороне клиента, где это возможно, но когда не получается — полагайтесь на это.
Связанные материалы на hiapi
- Страница модели happyhorse-1.1/text-to-video — песочница, полный UI параметров и актуальные цены
- happyhorse-1.1/image-to-video — то же семейство моделей, но оживляет статичное изображение
- veo-3.1-fast/text-to-video и seedance-2.0 — альтернативные модели генерации видео из текста на том же API
/v1/tasks - Справочник по единому API задач — авторитетный источник по
POST /v1/tasks,GET /v1/tasks/:id, callback и конверту ошибок - Просмотреть все модели и цены
Часто задаваемые вопросы
Генерирует ли happyhorse-1.1/text-to-video аудио?
Через API задач — нет; в структуре запроса сейчас нет поля audio, и отправка его вернёт additional properties 'audio' not allowed. Выход — немой MP4. Накладывайте музыку на последующих этапах.
Какие соотношения сторон доступны для вертикальных / коротких видео?
Для вывода в стиле TikTok и Reels стандартным выбором будет 9:16. API также принимает 4:5 (вертикаль для ленты Instagram) и 9:21 (сверхдлинная вертикаль), если вам нужен более узкий кадр.
Какая минимальная и максимальная длительность?
duration — целое число от 3 до 15 секунд включительно. Отправка 0 даст duration: minimum: got 0, want 3; отправка 99 даст duration: maximum: got 99, want 15. Выбирайте минимальное значение, capturing нужное движение — вы платите именно за длительность.
Можно ли передать seed для воспроизводимого результата?
Эта модель сейчас это не поддерживает. Запрос {"input": {"prompt": "...", "seed": 42}} завершится ошибкой additional properties 'seed' not allowed. Если воспроизводимость важна, записывайте полное тело запроса и повторяйте с тем же промптом; вы получите похожий — но не попиксельно идентичный — результат.
Поддерживается ли negative_prompt?
Нет. Направляйте вывод, добавляя ограничения в сам prompt (например, "...clean composition, no text, no watermarks").
Как долго действует URL вывода?
Для задач на тарифах по умолчанию поле storage имеет значение temp, а output[].url — краткосрочная подписанная ссылка. Скачивайте файл, как только задача достигает success, и размещайте его заново. Не сохраняйте исходный URL для последующей выдачи.
Как контролировать расходы во время разработки?
Две привычки для экономии: держите duration: 3, пока не зафиксируете промпт, и отправляйте с resolution: "720p", пока не утвердите раскадровку. Повышайте до 1080p и более длинной длительности только для финального рендера.
Опрос или callback — что выбрать?
Используйте оба, в таком порядке. Сначала опрос — на один движущийся компонент меньше. Переходите к callback, когда у вас впервые появится бэкенд, где worker не может себе позволить блокироваться на wait_for_task. В любом случае сохраняйте опрос как резервный сканер; callback — это best-effort, а не гарантированная доставка.
Почему при наличии лимита вызовов возвращается 402? API оценивает верхнюю границу стоимости задачи — длительность × цена за секунду — и резервирует её заранее. Если баланс не покрывает эту оценку, сразу возвращается 402. Пополните баланс, повторите, и после успеха списывается только фактическая стоимость.
Похожие статьи
Все статьи →-
Руководство
Как вызвать 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
Практическая демонстрация с упором на запросы: построение вызова от промпта до видеоклипа, отправка запроса, опрос статуса завершения и скачивание результата