PixVerse API 上的图生视频不是一个请求。静帧先上去,响应回一个整数,然后生成调用才存在。这个整数 img_id 是两半之间的接缝,上传侧的任何失败都会在后面以生成错误的形式重新出现。两边的鉴权要求一模一样,都必须在请求头里带上 API-KEY 和一个每个请求都唯一的 Ai-trace-id,而两次调用的职责完全不同。
图生视频的端点
概览页在第一段就说清了形态:The Image-to-Video API transforms static images into dynamic videos with motion and effects. 它给出的端点是 POST https://app-api.pixverse.ai/openapi/v2/video/img/generate。
两次调用,只有一次计费
上传调用不产出视频,它只是把字节搬到厂商那里并回一个引用。生成调用才是消耗积分的那一次,也是出现在并发上限里的那一次,因为创建 generating 状态任务的是它。两次调用需要的鉴权头写在 API 指南里。
| 项 | 上传调用 | 生成调用 |
|---|---|---|
| 路径 | openapi/v2/image/upload | openapi/v2/video/img/generate |
| 作用 | 把静帧换成引用 | 创建生成任务 |
| 返回 | Resp.img_id、Resp.img_url | Resp.video_id |
| 消耗积分 | 不计费 | 按秒计费 |
| 占并发名额 | 不占 | 占 |
为什么图片要先变成一个 id
生成端点收 JSON,并且把 img_id 校验成 integer <uint64>,所以 base64 数据块和本地路径在这里都没有位置。把它变成那个整数的是上传端点,而上传端点既收字节也收 URL,所以已经放在公开存储上的静帧同样要走一遍这个握手。两家主流转售商的做法正好绕开了这一步:一家收首帧和尾帧的 URL,另一家直接收 image_url,两边都不需要先换 id。这也是很多开发者第一次看到 img_id 时会觉得陌生的原因。
上传图片并拿回 img_id
# Step 1: POST the still as multipart/form-data and keep Resp.img_id.
curl --location --request POST 'https://app-api.pixverse.ai/openapi/v2/image/upload' \
--header "API-KEY: ${PIXVERSE_API_KEY}" \
--header "Ai-trace-id: $(uuidgen)" \
--form 'image=@"storyboard.png"' \
> /tmp/pixverse-upload.json
jq -r '.ErrCode, .Resp.img_id, .Resp.img_url' /tmp/pixverse-upload.json
文件或 URL,二选一
上传请求体有两个可选字段 image 和 image_url,参考页对两者写了同一条约束:Either image or image_url is required. URL 这一路有个值得提前知道的挑剔之处,application/octet-stream is not supported,所以服务器必须提供能被识别的图片内容类型。
上传会校验什么
上传参考页列了三条规则:maximum dimensions 10000 pixels、file size less than 20MB,以及 Supported formats: "png", "webp", "jpeg", "jpg",配套 supported mime-type "image/jpeg","image/jpg","image/png","image/webp"。像素尺寸和文件体积是分开判的,所以一张压得很狠的 12000 像素扫描件即使只有四兆也会被拒。
img_id 链路的全貌

提交、留住 video_id、轮询
每一跳只往下带一个值。img_id 从上传响应进入生成请求体;video_id 从生成响应进入状态 URL。
# The whole chain, with every documented field named.
img_id=$(curl -s --request POST \
'https://app-api.pixverse.ai/openapi/v2/image/upload' \
--header "API-KEY: ${PIXVERSE_API_KEY}" \
--header "Ai-trace-id: $(uuidgen)" \
--form 'image=@"storyboard.png"' | jq -r '.Resp.img_id')
video_id=$(curl -s --request POST \
'https://app-api.pixverse.ai/openapi/v2/video/img/generate' \
--header "API-KEY: ${PIXVERSE_API_KEY}" \
--header "Ai-trace-id: $(uuidgen)" \
--header 'Content-Type: application/json' \
--data-raw "{
\"img_id\": ${img_id},
\"model\": \"v6\",
\"prompt\": \"the camera pushes in slowly, warm light across the rim\",
\"duration\": 5,
\"quality\": \"720p\",
\"motion_mode\": \"normal\",
\"seed\": 0
}" | jq -r '.Resp.video_id')
echo "img ${img_id} produced video ${video_id}"
生成调用的字段
有五个请求体参数被标为必填:duration、img_id、model、prompt 和 quality。其余都是可选,参考页逐条注明了每个字段支持哪些模型代次。
# Step 2: the generation call, with optional fields commented out.
curl --location --request POST 'https://app-api.pixverse.ai/openapi/v2/video/img/generate' \
--header "API-KEY: ${PIXVERSE_API_KEY}" \
--header "Ai-trace-id: $(uuidgen)" \
--header 'Content-Type: application/json' \
--data '{
"img_id": 98765,
"model": "v6",
"prompt": "the camera pushes in slowly, warm light across the rim",
"duration": 5,
"quality": "720p",
"motion_mode": "normal",
"negative_prompt": "text overlay, warped faces",
"seed": 0,
"generate_audio_switch": false
}'
duration 随模型变
duration 是整数,合法取值跟着模型走,参考页把区间写全了:v.3.5/v4/v4.5 : 5/8 (v3.5 1080p cannot use 8)、v5 : 5/8、v5.5/v5.6 : 5/8/10 (1080p cannot use 10),以及 v6/c1 : 1~15。写死八秒的请求在好几代模型上都能跑,到了最新这两代却会失败,因为那里接受一到十五之间的任何值。
开关、种子与运动
generate_audio_switch 标注为 Supported in v5.5/5.6/v6/c1 models,default value : false;generate_multi_clip_switch 标注为 Supported in v5.5/v6 model,默认同样是 false。seed 的范围是 0 - 2147483647。motion_mode 和 negative_prompt 出现在官方自己的 curl 示例里,却不在参数表中;motion_mode 在别处被记为默认 normal,取值 fast 只允许五秒时长,且 1080p 不支持。带 template_id 时可以启用 sound_effect_switch,并用 sound_effect_content 描述音效,留空则自动生成;lip_sync_tts_switch、lip_sync_tts_content 和 lip_sync_tts_speaker_id 只支持 v5 及更早的模型,其中口播文本的上限写的是 ~140 (UTF-8) characters。这些字段在 v6 上不会报错,只是不起作用。
轮询到 status 1
# Step 3: poll the documented status endpoint until the task leaves status 5.
while :; do
response=$(curl -s \
"https://app-api.pixverse.ai/openapi/v2/video/result/${video_id}" \
--header "API-KEY: ${PIXVERSE_API_KEY}" \
--header "Ai-trace-id: $(uuidgen)")
status=$(jq -r '.Resp.status' <<<"$response")
case "$status" in
1) jq -r '.Resp.url' <<<"$response"; break ;;
5) sleep 4 ;;
7) echo 'moderation failure, credits refunded' >&2; break ;;
8) echo 'generation failure' >&2; break ;;
*) echo "unexpected status $status" >&2; break ;;
esac
done
状态码
端点页在示例响应旁边标注了 1: Generation successful, 5: In progress, 7: Moderation failed, 8: Generation failed. 官方索引又补了第六个,6 = deleted,而端点页没有列它,所以只按端点页写的客户端会把它当成未知状态。官方索引还给了轮询节奏,every 3–5s,比这更密的轮询除了给自己增加请求量之外没有别的效果。审核失败会退积分,状态 7 只让人白等一会儿。
出手在上传侧的失败
图片相关的错误码
把路径、文件名或者过期的整数当成 img_id 送进生成请求体时,命中的是 400032 Invalid image ID。500030 Image size cannot exceed 20M , 10000px、500031、500032、500033、500041、500042 都属于上传这一跳,其中好几个只写在错误码表里而不在上传页上,这也是上传页单独不够用的原因。图片被审核拦下时回的是 500054 Content moderation failure.,提示要换一张图再试;500042 则专门指出路径本身无效,和文件内容的对错无关。
# Read the code, then decide whether the fix is upstream.
code=$(jq -r '.ErrCode' /tmp/pixverse-upload.json)
case "$code" in
0) echo 'upload accepted, take Resp.img_id' ;;
500030) echo 'dimensions or weight over the limit' ;;
500032) echo 'format refused, check the mime type' ;;
500041) echo 'transfer failed, retry the upload' ;;
*) echo "unexpected upload error $code" ;;
esac
官方文档自己打架的一处上限
生成参考页的参数表允许 Prompt <= 5000 characters。排查页对同样这两个字段写的是 =< 2048 characters,而错误码 400018 和 400019 都是 Prompt/negative prompt length exceeds 2048 characters limit.。三处里有两点说 2048、一点说 5000,所以先用小的那个数,等厂商自己对齐。限流页讲的是提交成功之后会撞上的并发上限。
常见问题
生成请求体里能直接放图片 URL 吗? 不能。它收的是 img_id,官方描述为 Image ID from Upload image API。URL 要放进上传端点的 image_url 字段,id 从那里返回。
img_id 的有效期是多久? 官方未公布。上传页和生成页都没有写过期时间,所以按任务现取,不要缓存 id。
哪些字段是必填的? duration、img_id、model、prompt 和 quality。其余包括 seed 和音频开关都是可选。
同一个 img_id 能重复生成几条吗? 该字段只接受单个 id,参考页的描述是 single image or single-image templates。同一个 id 能否提交两次没有公布,多图特效走的是 img_ids。
为什么五秒的片子到 1080p 会变贵? 因为计费按秒算,而每秒价格取决于画质和音频。公布的价目表里有每秒数字。