HiAPI
博客

教程

Happyhorse 1.1 文生视频 API:可运行的 curl 与 Python 教程

2026年11月3日
Happyhorse 1.1 文生视频 API:可运行的 curl 与 Python 教程

本页将带你走完用 happyhorse-1.1/text-to-video 生成视频的最小请求,说明每个输入字段实际接受什么值,以及两种等待结果的方式(轮询和回调)。以下所有内容均已对线上 API 实测,请求结构和错误消息均为原文照录。

你将构建什么

一个函数:接收文本提示词,返回可下载的 MP4 URL。模型为 happyhorse-1.1/text-to-video,通过统一的 /v1/tasks 端点提供服务。你提交任务,立即拿回一个 taskId,然后要么轮询直到完成,要么让 hiapi 把最终结果 POST 到你的 webhook。

前置条件:

  • 一个 hiapi API key。新账户赠送 1 美元免费额度——在 API Keys 页面获取。
  • 一种发起 HTTPS 请求的方式。无需安装 SDK。

不要把 key 放进客户端代码,也不要提交到 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 拒绝,而不是被静默丢弃。以下是 API 实际接受的 input 内字段,直接取自校验错误信息:

字段 类型 是否必填 允许值 默认行为
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。

获取结果:轮询

取回输出最简单的方式是按 id GET 任务,直到 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")

获取结果:回调(生产模式)

轮询适合脚本和 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" }

设计健壮的接收端

三条规则保证回调路径安全:

  1. 幂等性。 按 taskId 持久化。如果你两次收到同一个回调——最终一定会,因为重试——重复处理必须是无操作。
  2. 把确认与处理解耦。 一旦把 payload 入队,就从 webhook 返回 200 OK。实际的下载 / 重传 / 写数据库放在后台任务中。慢 webhook 会招致超时和重复投递。
  3. 回退到轮询。 把你提交的每个 taskId 连同 created_at 存入自己的数据库。运行一个清扫器,对任何超过约 30 分钟且尚无终态的行轮询 /v1/tasks/{id}。回调是快路径,不是唯一路径。

一个最小的 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 未知字段、枚举值错误、duration 超出 3–15 查看 message,它会指出字段——例如 aspect_ratio: value must be one of '16:9'...
401 — key 缺失或错误,或 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 会生成音频吗? 通过任务 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。选择能捕捉所需运动的最小值——时长就是你付费的部分。

能否传 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 和更长时长。

轮询还是回调——该用哪个? 两者都用,按此顺序。先轮询,因为少一个活动部件。当你第一次拥有无法承受让 worker 阻塞在 wait_for_task 上的后端时,就转向回调。无论如何都保留轮询作为回退清扫器;回调是尽力而为,不是保证送达。

为什么我有额度调用却返回 402? API 会估算任务成本上限——时长 × 每秒价格——并预先预留。如果余额无法覆盖该估算,就立即返回 402。充值、重试,成功后只扣实际成本。

立即开始构建持久 AI 产物

获取 API 密钥,通过一个基于任务(task)的 API 调用图像、视频和音频模型。

阅读 API 文档