HiAPI
博客

教程

如何使用 Ray 3.2 图生视频 API:curl、Python 与一次可用的请求

2026年11月3日
如何使用 Ray 3.2 图生视频 API:curl、Python 与一次可用的请求

Ray 3.2 图生视频会为一帧静态画面制作动画:你给它一张图片,再加一段描述运动方式的提示词,它就会返回一段从你的图片开始的短视频。在 hiapi 上,模型 id 是 ray-3.2/image-to-video,通过与其他所有视频模型相同的统一异步任务 API 调用——POST /v1/tasks,然后轮询或接收回调。

它与 Ray 3.2 文生视频 是不同的端点,而不只是提示词不同。它的输入 schema 更小、更严格,你从文本模型那里可能习惯的两个控制项也不见了。本指南给你一个一次就能跑通的请求:确切的 schema(已于 2026-10-20 对照线上 API 验证)、可直接复制的 curl、完整的 Python 提交/轮询/下载脚本,以及生产细节——回调、幂等性,还有真实的错误响应,包括那种不会出现在你预期位置的失败模式。

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

目标:提交一张首帧图片 + 一段运动提示词,轮询任务,然后下载一个让你的图片动起来的无声 .mp4。

前置条件:

  • 一个 hiapi API key——从你的仪表盘获取一个。密钥形如 sk-...,放在 Authorization: Bearer 请求头中。刚接触任务 API?请先看快速入门。
  • 首帧图片的一个可公开访问的图片 URL(详见下文)。任意 HTTP 客户端——我们会展示 curl 和 Python(requests)。

Ray 3.2 图生视频按视频片段计费。由于片段长度固定(见下文),resolution 是你唯一能控制的成本杠杆——1080p 片段的成本是 540p 的数倍。当前价格请查看定价,在迭代提示词时先用 540p 做原型。

确切的输入 schema(两个必填字段,一个可选)

所有内容都放在 input 对象内。schema 是严格的——它会拒绝任何无法识别的字段。已逐字段对照线上 API 验证:

字段 类型 是否必填 允许值 默认值
prompt string 是 画面应如何运动(镜头、主体运动) —
image_url string 是 首帧的公开 URL —
resolution string 否 540p、720p、1080p 540p

从文生视频模型过来的人容易踩的三个坑:

  1. 没有 duration。片段长度固定(约 5 秒)。发送 duration 不会被截断——而是直接被拒绝。这与 Ray 3.2 文生视频正好相反,在那里 duration 是可选择的整数(5 或 10)。
  2. 没有 aspect_ratio。输出画幅跟随你的输入图片,所以没有可设置的东西。在文本模型里你选择宽高比;这里由图片决定。(下文用真实运行确认:方形输入生成了方形片段。)
  3. schema 是封闭的。seed、negative_prompt、prompt_optimizer 以及其他任何字段都会返回 400,并附带 additional properties '<field>' not allowed。不要从其他模型移植请求体,并假设多余键会被忽略。

校验实际返回什么

这些是来自线上端点的真实响应——故意提交无效值,没有创建任务,也没有计费:

// POST with no image_url
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: image_url: missing required field \"image_url\""}

// POST with "resolution": "2k"
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: resolution: value must be one of '540p', '720p', '1080p'"}

// POST with a "duration" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: <root>: additional properties 'duration' not allowed"}

// POST with an "aspect_ratio" field
{"code":400,"data":null,"error_code":"INVALID_REQUEST",
 "message":"invalid input: <root>: additional properties 'aspect_ratio' not allowed"}

message 会指出具体的问题字段,所以当你忘记时,API 本身就是你的 schema 参考。

准备首帧

image_url 必须是 hiapi worker 能抓取的 URL——一个公开链接,不是本地路径,也不是 data: base64 blob。图片决定了片段的长宽比和开场构图,所以请从你真正希望运动开始的那一帧出发。

有一个值得提前说明的坑,因为很容易忽略:坏的 image_url 在创建任务时不会被捕获。创建调用只校验请求的形状(它是不是字符串?),而不校验 URL 是否可解析。一个不可达或 404 的图片 URL 会返回完全正常的 200 和 taskId——然后任务在几秒后翻转为 fail。我们会在故障排查部分看到确切的失败载荷;要点是始终根据任务的终态 status 进行分支,绝不要假设创建返回 200 就意味着成功。

最小可用示例:curl

创建任务:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ray-3.2/image-to-video",
    "input": {
      "prompt": "slow cinematic push-in, gentle parallax, soft drifting light, the subject breathing almost imperceptibly",
      "image_url": "https://your-cdn.example.com/first-frame.jpg",
      "resolution": "540p"
    }
  }'

创建响应会携带一个任务 id:

{ "code": 200, "data": { "taskId": "tk-hiapi-01M5..." }, "message": "success" }

轮询直到它到达终态:

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01M5... \
  -H "Authorization: Bearer sk-YOUR_KEY"

任务运行期间,data.status 会经过若干进行中状态(你会看到像 handling 这样的值,然后是 archiving)。两个终态是 success 和 fail。成功时:

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-01M5...",
    "status": "success",
    "created": 1792435735,
    "completed": 1792435781,
    "storage": "temp",
    "output": [
      { "type": "video", "artifactId": "278504",
        "expireAt": 1793040580,
        "url": "https://temp.hiapi.ai/.../clip.mp4" }
    ]
  },
  "message": "success"
}

立即下载 data.output[0].url。它是 temp.hiapi.ai 上的签名 URL,带有 expireAt(Unix 时间戳,大约一周后)——请把字节持久化到你自己的存储,而不是热链它。Ray 3.2 返回无声 MP4;没有音轨需要等待。

完整 Python 脚本(提交 → 轮询 → 下载)

import time
import requests

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


def create_task(image_url: str, prompt: str, resolution: str = "540p") -> str:
    resp = requests.post(
        f"{API_BASE}/tasks",
        headers=HEADERS,
        json={
            "model": "ray-3.2/image-to-video",
            "input": {
                "prompt": prompt,
                "image_url": image_url,     # public URL of the first frame (required)
                "resolution": resolution,   # "540p" | "720p" | "1080p"
                # no "duration" and no "aspect_ratio" — both are rejected
            },
        },
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()["data"]["taskId"]


def wait_task(task_id: str, timeout_s: int = 900, poll_interval: int = 8) -> dict:
    """Poll until terminal. Tolerates transient network blips by re-polling
    the SAME saved task_id instead of aborting the whole job."""
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        try:
            resp = requests.get(f"{API_BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
            resp.raise_for_status()
        except requests.RequestException:
            time.sleep(poll_interval)      # a dropped poll is not a failed task
            continue
        task = resp.json()["data"]
        if task["status"] == "success":
            return task
        if task["status"] == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')}: {err.get('message')}")
        time.sleep(poll_interval)           # any non-terminal status = still running
    raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")


task_id = create_task(
    image_url="https://your-cdn.example.com/first-frame.jpg",
    prompt="slow cinematic push-in, gentle parallax, soft drifting light",
)
print("task created:", task_id)

task = wait_task(task_id)
video_url = task["output"][0]["url"]

# Output URLs expire — download right away and store the bytes yourself.
video = requests.get(video_url, timeout=120).content
with open("ray32-i2v.mp4", "wb") as f:
    f.write(video)
print(f"saved ray32-i2v.mp4 ({len(video) / 1e6:.1f} MB)")

用 pip install requests 运行它,换上你的密钥和真实的 image_url,任务完成后你磁盘上就会有一个 .mp4。

一次真实运行是什么样的

为了让数字具体化,这里给出 2026-10-20 对线上 API 的一次真实 540p 运行。首帧:一张 1600×1600(方形)图片;提示词:缓慢的电影式推近。

  • 任务经历了 handling → archiving → success,服务端工作大约在 45 秒内完成(created 创建,completed 完成)——算上 8 秒轮询循环,实际耗时约 60 秒。视频任务需要几十秒到几分钟,而不是瞬间完成;请预留充裕的截止时间。
  • 输出是一个 720×720 MP4,约 1.3 MB,5.04 秒,无声。

这次运行告诉了你两件参数表没有说明的事:

  1. 输出宽高比与输入一致——方形进,方形出。这就是为什么没有 aspect_ratio 字段;请按照你希望的片段构图来取景 image_url。
  2. resolution 是质量档位,不是字面像素高度。对 1600×1600 输入请求 540p,返回的是 720×720——该档位按输入形状缩放,而不是强制固定尺寸。如果你需要确切的输出尺寸,请自行调整交付片段的尺寸。

生产注意事项:回调、幂等性、错误

优先使用回调而非轮询

对于脚本之外的任何场景,请在创建时注册 webhook,而不是轮询:

{
  "model": "ray-3.2/image-to-video",
  "input": {
    "prompt": "...",
    "image_url": "https://your-cdn.example.com/first-frame.jpg",
    "resolution": "720p"
  },
  "callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}

使用 "when": "final" 时,hiapi 会在任务到达终态时调用你的端点一次——没有轮询流量,也不会在间隔之间错过完成。然后你的处理器执行 GET /v1/tasks/<id> 来获取 output[0].url。对于无法暴露 HTTPS 端点的 CLI 和 notebook,轮询仍然没问题。

让重试具备幂等性

创建调用返回的那一刻就持久化 taskId。如果你的进程在生成中途崩溃,请通过轮询已存储的 id 恢复——不要重新创建任务,否则你会为第二个片段付费。只有在原始创建请求在返回任务 id 之前失败时,才重新创建。上面的容错轮询循环已经遵循这条规则:网络请求中断时重新轮询同一个 id,而不是从头开始。

你实际会看到的错误

下表中的每一行都是已验证的线上响应形状:

症状 响应 修复
缺失/无效密钥 401,嵌套 {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"...","message":"This API key is invalid..."}} 检查 Authorization: Bearer sk-... 请求头,以及密钥是否有访问权限。联系支持时请提供 request_id。
缺少 image_url 400 INVALID_REQUEST,message: "image_url: missing required field" 此模型要求 image_url——没有纯文本模式。
错误枚举 / 多余字段 400 INVALID_REQUEST,扁平 {"code":400,"error_code":"INVALID_REQUEST","message":"..."} 阅读 message 中指出的字段。对于 duration/aspect_ratio,它会说 additional properties ... not allowed——删除它们。
轮询时任务 id 错误 404,扁平 {"code":404,"data":null,"message":"task not found"}(注意:404 上没有 error_code) id 错误,或者你使用了不同账户的密钥进行轮询。
image_url 不可达 创建返回 200 + 一个 taskId;任务随后以 data.status:"fail" 结束,并带有 data.error:{"code":"INVALID_REQUEST","message":"Invalid request parameters for this model..."} 这就是陷阱。创建调用无法区分死链和活链。验证你的图片可公开访问,并始终读取终态 data.status。

最后一行值得牢记:即使任务失败,轮询的 HTTP 外层仍然是 200——失败存在于 data.status 和 data.error 中,而不是 HTTP 状态码。失败任务会退款,但你的代码仍然必须注意到它们。根据 data.status == "fail" 分支,不要相信 200。

相关资源

常见问题

模型 id 是什么,我该调用哪个端点? ray-3.2/image-to-video,提交到 POST https://api.hiapi.ai/v1/tasks,参数放在 input 下。它是异步的——你会拿回一个 taskId,然后轮询 GET /v1/tasks/<id>(或注册回调)。

输入要求什么? 恰好两个必填字段——prompt(运动)和 image_url(首帧的公开 URL)——外加可选的 resolution(540p/720p/1080p)。其他任何内容都会被拒绝。

为什么我不能设置时长或宽高比? 片段长度固定(约 5 秒),所以没有 duration 字段;输出宽高比继承自你的输入图片,所以没有 aspect_ratio 字段。发送它们都会返回 400。如果你需要可选择的时长,请改用 Ray 3.2 文生视频。

我的创建调用返回了 200,但视频一直没来。为什么? 最常见的是 image_url 不可达。创建调用只校验请求形状,所以死链仍会返回 taskId——然后任务以 fail 结束。轮询到终态并读取 data.error。确保图片可公开抓取(无需认证、不是本地路径、不是 base64)。

片段有音频吗? 没有——Ray 3.2 图生视频返回无声 MP4。请在后处理中添加声音,或使用单独的音频模型。

一个片段多少钱? 按片段计费,而且由于长度固定,resolution 是唯一的杠杆——1080p 片段的成本是 540p 的数倍。价格会变动,所以请查看定价页,而不是硬编码数字,并先用 540p 做原型。

我应该等多久才放弃? 几十秒到几分钟是正常的。在轮询循环中容忍非终态 data.status,并使用充裕的截止时间(10–15 分钟),或者注册带有 "when": "final" 的 callback,让 hiapi 通知你,而不是你猜测间隔。

立即开始构建持久 AI 产物

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

阅读 API 文档