HiAPI
博客

教程

如何使用 hailuo 2.3 图生视频 API:curl、Python 与一份可运行的请求示例

2026年11月3日
如何使用 hailuo 2.3 图生视频 API:curl、Python 与一份可运行的请求示例

摘要

  • 这是什么:一份在 hiapi 上使用 hailuo-2.3/image-to-video API 的可运行配方——把一张静态图片变成一段短视频:创建任务、轮询任务、下载 .mp4。
  • 端点:POST https://api.hiapi.ai/v1/tasks,请求体里写 "model": "hailuo-2.3/image-to-video",然后用 GET /v1/tasks/<taskId> 轮询,直到任务进入终态。
  • 两个必填输入字段:prompt 和 image_url——指向你首帧的单个公开 URL(不是数组)。
  • duration 是字符串,不是数字:"6" 或 "10"。传整数 6 会被 400 拒绝。
  • Schema 是严格的。没有 motion_strength、camera_movement 或 resolution 参数——传了会被 400 拒绝。运动完全通过 prompt 控制。

你要构建的是什么,以及需要准备什么

你有一张静态图片——产品图、角色渲染图、照片——你想让 hailuo-2.3 把它动起来,生成一段正好从这一帧开始的视频片段。hailuo-2.3 以物理上可信的运动(布料、头发、身体力学)著称,因此非常适合让静态图片活起来。

你需要两样东西:

  1. 一个 hiapi API key。在 dashboard 中创建一个。Key 形如 sk-...,放在 Authorization: Bearer 请求头里。
  2. 一个可公开访问的首帧图片 URL。API 会从 image_url 拉取图片,所以它必须无需认证即可访问——S3/R2/CDN 链接可以;localhost 路径或已过期的签名 URL 不行。

和 hiapi 上每个生成模型一样,这个模型也走异步任务接口:你 POST 一个任务,立即拿到 taskId,之后通过轮询或回调收取结果。视频没有同步端点——片段渲染需要时间,会超过典型 HTTP 超时。

请求 schema

hailuo-2.3/image-to-video 的完整输入 schema——已对照线上 API 验证:

字段 类型 必填 说明
prompt string ✅ 描述运动,而不是场景——场景来自你的图片。
image_url string ✅ 一个公开 URL,即首帧。是单数——不是 image_urls。
duration string 可选 "6" 或 "10"(秒)。必须是字符串。
prompt_optimizer boolean 可选 让平台在生成前扩展你的 prompt。

Schema 是严格的:不在此表中的任何内容都会被拒绝。发送 motion_strength、camera_movement 或 resolution,你会原样收到:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'resolution', 'camera_movement', 'motion_strength' not allowed"
}

有两个后果值得记住:

  • 没有运动调节旋钮。hailuo-2.3 的运动物理只由 prompt 引导——见下面的提示词编写部分。
  • 这个端点上没有分辨率或宽高比控制。不要为 API 会返回 400 的参数构建 UI。

最小可运行示例:curl

创建任务:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "hailuo-2.3/image-to-video",
    "input": {
      "prompt": "The woman turns toward the camera and smiles; wind gently moves her hair; slow cinematic push-in",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "duration": "6",
      "prompt_optimizer": true
    }
  }'

响应(节选)在 data.taskId 下包含任务 id:

{ "data": { "taskId": "..." } }

轮询它:

curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
  -H "Authorization: Bearer sk-<your-key>"

片段渲染期间,data.status 报告非终态。当它翻转为 "success" 时,视频 URL 在 data.output[0].url;如果是 "fail",data.error.code 和 data.error.message 会说明原因。

任务成功后立即下载文件。输出 URL 是签名的并带有过期时间(expireAt)——把字节存到你自己的存储中,绝不要热链任务 URL。

最小可运行示例:Python

相同流程的完整脚本——创建、轮询、下载:

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}


def create_task() -> str:
    payload = {
        "model": "hailuo-2.3/image-to-video",
        "input": {
            "prompt": (
                "The cat stretches, hops off the windowsill, and walks toward "
                "the camera; soft afternoon light, subtle handheld camera sway"
            ),
            "image_url": "https://your-cdn.example.com/first-frame.jpg",
            "duration": "6",          # string, not int: "6" or "10"
            "prompt_optimizer": True,
        },
    }
    r = requests.post(API_BASE, json=payload, headers=HEADERS, timeout=30)
    task_id = (r.json().get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed ({r.status_code}): {r.text[:300]}")
    return task_id


def wait_task(task_id: str, timeout_s: int = 900, poll_s: int = 10) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
        task = r.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task
        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} not finished after {timeout_s}s")


def main() -> None:
    task_id = create_task()
    print("task created:", task_id)
    task = wait_task(task_id)
    url = task["output"][0]["url"]
    # The URL is signed and expires -- download immediately, store the bytes yourself.
    video = requests.get(url, timeout=120).content
    with open("hailuo-output.mp4", "wb") as f:
        f.write(video)
    print("saved hailuo-output.mp4")


if __name__ == "__main__":
    main()

视频任务比图片任务运行时间明显更长,所以脚本每 10 秒轮询一次,并设置 15 分钟的宽裕上限。不要比每 ~5 秒更激进地轮询——这不会让渲染更快。

编写能控制运动的 prompt

因为没有任何运动参数,prompt 就是你唯一的方向盘——而对 hailuo-2.3 来说这已经够了。三条经验法则:

  • 描述运动,而不是外观。模型已经看到了你的图片。"a beautiful woman in a red dress" 浪费了 prompt;"she turns her head slowly to the left as the camera pushes in" 才用好了它。
  • 用平实语言说明镜头运动。"slow push-in"、"orbit right"、"handheld sway"、"static camera"——这些应写在 prompt 文本里,而不是写在(并不存在的)camera_movement 字段里。
  • 拿不准时让 prompt_optimizer 帮忙。设置 "prompt_optimizer": true 后,平台会在生成前扩展简短的 prompt——对短输入是很好的默认值。当你希望严格遵循精心设计的措辞时,就关掉它。

生产环境注意事项

优先用回调而不是轮询。创建任务时附上回调,hiapi 会把终态结果 POST 到你的端点,这样你无需保持开放循环:

{
  "model": "hailuo-2.3/image-to-video",
  "input": { "...": "..." },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

"when": "final" 只在终态触发一次。轮询适合脚本和 notebook;回调才是服务器的正确形态——视频渲染足够长,一个阻塞在轮询上的 worker 是实打实的成本。

显式处理这些错误:

  • 401 且 "code": "permission_denied"——key 有问题,或该 key 无权使用此模型。消息中包含一个 request_id,可以发给支持团队。
  • 400 INVALID_REQUEST——schema 违规。消息很精确,例如 duration: got number, want string 或 image_url: missing required field "image_url"。原样记录它。
  • 创建任务时 402——余额不足。注意视频每个任务的计费高于图片,所以一个仍能正常跑图片任务的 key 可能先开始创建视频失败。充值,或在 dashboard 中检查用量。
  • 任务进入 "fail"——生成侧失败;检查 data.error。特别是图生视频的一个常见原因:image_url 无法拉取(私有 bucket、过期的签名 URL)。

围绕重试做到幂等。如果你的进程在创建与下载之间死掉,天真的重启会创建(并付费)第二次渲染。在 create_task 返回后立即持久化 taskId,重启时继续轮询而不是重新创建。

相关阅读

常见问题

hailuo-2.3 图生视频支持哪些时长? 两个:"6" 和 "10" 秒,以字符串传入。任何其他值——或把相同值写成数字——都会返回 400 INVALID_REQUEST。

为什么 "duration": 6 会失败,而 "duration": "6" 可以? Schema 把 duration 定义为字符串枚举。API 会明确告诉你:duration: got number, want string。这是此端点上最常见的 400。

我能设置输出分辨率或宽高比吗? 不能。端点不暴露 resolution 或 aspect_ratio 字段,发送其中一个就是 400——schema 会拒绝未知属性,而不是忽略它们。

如何控制镜头运动或运动强度? 通过 prompt。没有 camera_movement 或 motion_strength 参数——用平实语言写镜头和运动指令("slow push-in"、"she walks toward the camera")。

我能传多张参考图片吗? 不能——image_url 是单个字符串,即首帧。这与 hiapi 上其他一些接收 image_urls 数组的模型不同;schema 是按模型定义的,所以务必查看模型页。

首帧支持哪些图片格式? 标准 Web 格式(JPEG/PNG),从可公开访问的 URL 提供。如果平台无法拉取该 URL,任务会在创建后失败——所以提交前先验证链接可公开访问。

这和 hailuo-2.3/text-to-video 有什么不同? 文生视频仅凭你的 prompt 发明场景;图生视频把首帧钉在你的图片上并从它开始动画。任务生命周期完全相同——只有输入 schema 不同(这里 image_url 是必填的,而文生视频根本没有图片字段)。

费用是多少? 视频按输出秒数计费,费率会变化——查看 hailuo-2.3/image-to-video 模型页和定价页获取当前数字。

立即开始构建持久 AI 产物

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

阅读 API 文档