教程
如何使用 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 |
从文生视频模型过来的人容易踩的三个坑:
- 没有
duration。片段长度固定(约 5 秒)。发送duration不会被截断——而是直接被拒绝。这与 Ray 3.2 文生视频正好相反,在那里duration是可选择的整数(5 或 10)。 - 没有
aspect_ratio。输出画幅跟随你的输入图片,所以没有可设置的东西。在文本模型里你选择宽高比;这里由图片决定。(下文用真实运行确认:方形输入生成了方形片段。) - 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 秒,无声。
这次运行告诉了你两件参数表没有说明的事:
- 输出宽高比与输入一致——方形进,方形出。这就是为什么没有
aspect_ratio字段;请按照你希望的片段构图来取景image_url。 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。
相关资源
- Ray 3.2 图生视频模型页面——playground、实时状态和完整的分辨率价格表
- Ray 3.2 文生视频模型页面——兄弟端点;从文本而不是图片开始(它增加了
duration和aspect_ratio,去掉了image_url) - 如何使用 Hailuo 2.3 图生视频 API——同一任务 API 上的另一份图生视频教程,是一个有用的 schema 对比
- 如何使用 grok-imagine 图生视频 API——用于对比的第三个 i2v 模型
- hiapi 定价——按分辨率的每片段视频价格
常见问题
模型 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 通知你,而不是你猜测间隔。