视频 API 完整调用规范
从本地素材上传到视频生成、结果下载和跨模型引用,涵盖鉴权、30天保存规则、轮询和回调。
本页是 88API 对客户开放的统一视频生成规范。客户无需关心模型的内部接入方式;请始终使用本页的 88API 域名、对外模型名和公开任务 ID。
当前公开视频模型共用“提交→轮询→下载”任务协议;回调仅适用于明确支持该能力的型号,但参考素材字段和能力边界按模型族区分。请勿将某一模型族的素材示例盲目套用到所有模型。
更新于 2026-09-14。推荐流程:已有可用 HTTPS 素材直接引用;本地素材先上传 → 提交生成 → 轮询或回调取得结果 URL → 播放、下载或作为下一轮参考。上传成功不代表模型一定支持该素材类型;模型能力与存储能力是两回事。
任务接口(2026-09-15):SD2.0/SD2.5(480P、720P、1080P)、Seedance 官方版与 Mini、Wan3(480p、720p、1080p)、Kling Turbo 和 MiniMax H3 使用
POST /v1/videos创建,保存返回的id,通过GET /v1/videos/{id}轮询。不要依赖task_id,不要发送callback_url;这些型号的新请求暂不支持回调,非空回调参数会在提交前被拒绝。模型名和鉴权方式不变。旧/v1/video/generations仅保留部分型号的过渡支持,不用于新接入。
1. 接口速查
| 用途 | 方法与路径 | 说明 |
|---|---|---|
| 提交生成 | POST https://88api.ai/v1/videos | 提交文本和参考素材,返回任务 id |
| 查询任务 | GET https://88api.ai/v1/videos/{id} | 查询状态、进度和错误 |
| 申请素材上传 | POST https://88api.ai/v1/media/uploads | 发送文件摘要、字节数和 MIME,取得短期上传凭证 |
| 上传素材二进制 | PUT {upload_url} | 使用返回的专用上传请求头,不发送 API Key |
| 获取视频 | GET {url} | 优先使用完成响应的结果 URL,不自行拼接路径 |
| 旧内容入口 | GET https://88api.ai/v1/videos/{id}/content | 已归档视频的兼容跳转;不作为所有结果的通用下载方式 |
| 终态回调 | 提交时设置 callback_url | 仅支持回调的型号;其余使用轮询 |
请求头
Authorization: Bearer sk-你的API密钥
Content-Type: application/json
Accept: application/jsonAPI 密钥必须保存在服务端,不得写入浏览器、小程序或 App 前端包。
| 操作 | 鉴权 |
|---|---|
| 创建、查询任务;申请上传凭证 | Authorization: Bearer ...,只发到 88api.ai API |
| 文件上传 | 原样使用 headers 中的 X-Media-Upload-Token 与 Content-Type |
| 读取公共媒体 | assets.88api.ai 地址不需要 Bearer、Cookie 或登录 |
匿名读取不等于匿名上传,也不开放任务详情。获得公共媒体 URL 的人可以在有效期内读取文件;不要把 URL 当作严格的隐私隔离机制。不要向媒体地址、官方结果地址或重定向目标转发自己的 API Key。
2. 标准提交请求
最小文生视频请求
curl --request POST 'https://88api.ai/v1/videos' \
--header 'Authorization: Bearer sk-xxxx' \
--header 'Content-Type: application/json' \
--data-raw '{
"model": "SD2.5 720P",
"prompt": "雨夜城市中的银色跑车,低机位跟拍,霓虹倒影,电影感",
"duration": 8,
"size": "16:9"
}'duration 也可以写成字符串形式的 seconds:
{
"model": "SD2.5 720P",
"prompt": "日出时的海岸航拍,云层缓慢移动",
"seconds": "8",
"size": "16:9"
}公共请求字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
model | string | 是 | 88API 模型广场中显示的完整模型名 |
prompt | string | 条件必填 | 文本提示词;仅部分模型允许有参考素材时留空 |
negative_prompt | string | 否 | 多参考模型的负面提示词;Veo 使用 metadata.negativePrompt |
duration | integer | 否 | 视频时长,单位秒;与 seconds 二选一 |
seconds | string | 否 | 字符串形式的视频时长,例如 "8" |
size | string | 否 | 画面比例或尺寸,例如 16:9、1280x720 |
image | string | 否 | 单张参考图片 URL |
images | string[] | 否 | 参考图片 URL 数组,推荐用法 |
video | string | 否 | 单个参考视频 URL;Gemini Omni 必须使用此顶层字段 |
videos | string[] | 否 | 参考视频数组;其他多参考模型使用 metadata.referenceVideos |
seed | integer | 否 | 随机种子 |
generate_audio | boolean | 否 | 支持的模型上开启同步生成音频 |
camera_control | object | 否 | 镜头控制;仅在对应模型明确支持时使用 |
callback_url | string | 否 | 仅支持回调的型号可用;上方列出的轮询型号不接受此字段 |
metadata | object | 否 | 参考视频、音频、首尾帧、比例等扩展字段 |
分辨率由销售模型名锁定。例如
wan3.0-video-1080p始终输出 1080P;不要依靠自行传入resolution绕过模型档位。
3. 参考图片、视频和音频
3.1 参考图片
多张图片统一使用顶层 images 数组:
{
"model": "SD2.5 720P",
"prompt": "保持人物面部、服装和配色一致,在海边缓慢行走,电影级柔光",
"duration": 10,
"size": "16:9",
"images": [
"https://cdn.example.com/character-front.jpg",
"https://cdn.example.com/character-side.jpg"
]
}单图也可以使用 image 或 input_reference,但新接入建议统一使用 images 数组。
3.2 参考视频
对能力表中支持多视频参考的 SD、Seedance、H3、Wan 和 Kling 系列,放在 metadata.referenceVideos:
{
"model": "SD2.0 720P",
"prompt": "参考镜头节奏和运镜,但保持主体外观不变",
"duration": 8,
"size": "16:9",
"images": ["https://cdn.example.com/subject.jpg"],
"metadata": {
"referenceVideos": [
"https://cdn.example.com/camera-motion.mp4"
]
}
}3.3 参考音频
对能力表中支持音频参考的 SD、Seedance、H3 和 Wan 系列,放在 metadata.referenceAudios:
{
"model": "SD2.0 720P",
"prompt": "人物按照参考音频的节奏演唱,口型自然",
"duration": 10,
"size": "16:9",
"images": ["https://cdn.example.com/singer.jpg"],
"metadata": {
"referenceAudios": [
"https://cdn.example.com/voice-reference.mp3"
]
}
}H3 的参考类型和组合限制须以账户当前开放能力为准;不能把其他模型的音频或视频参考支持直接套用到 H3。
3.4 多参考模型的混合素材
{
"model": "SD2.5 720P",
"prompt": "使用参考图的产品外观、参考视频的镜头节奏和参考音频的声音氛围,制作科技广告",
"duration": 12,
"size": "16:9",
"images": [
"https://cdn.example.com/product-front.jpg",
"https://cdn.example.com/product-detail.jpg"
],
"metadata": {
"referenceVideos": [
"https://cdn.example.com/motion-reference.mp4"
],
"referenceAudios": [
"https://cdn.example.com/sound-reference.mp3"
]
}
}同一类素材不要同时使用多个别名字段。例如不要同时提交顶层 images 和 metadata.referenceImages;当多个兼容字段同时存在时,网关会选择其中一组,不保证合并。
3.5 素材 URL 要求
- 参考视频和音频采用匿名可下载的公网 HTTPS URL。
blob:、本机路径、data:video/...不能作为通用参考视频协议;本地文件按下节先上传。 - URL 不能依赖 Cookie、浏览器登录态、Referer 或自定义请求头。
- 不能使用
localhost、私网 IP、Docker 服务名或仅你内网可访问的地址。 - URL 在整个任务生成期间必须有效,建议有效期不少于 60 分钟。
- 图片建议使用 JPG、PNG 或 WebP;视频建议 MP4/MOV;音频建议 MP3/WAV。
- 视频建议不超过 50 MB/段,音频建议不超过 15 MB/段;过大素材可能在下载或审核阶段失败。
- 引用第三方素材前,请确保拥有必要的著作权、肖像权和使用授权。
素材页面能打开不等于视频字节可下载:目标应返回实际媒体,而不是 HTML 分享页。URL 可用也不代表其编码、时长或尺寸符合目标模型要求。Veo 图片的特殊 Base64 输入规则见下文,不要把它误当成参考视频支持 Base64。
3.6 本地素材上传:先申请,再 PUT
此接口提供临时媒体存储,不会生成视频、转码、压缩或自动提取视频帧。
第一步:在服务端申请上传凭证。
POST https://88api.ai/v1/media/uploads
Authorization: Bearer sk-xxxx
Content-Type: application/json{
"size": 1234567,
"mime_type": "video/mp4",
"sha256": "<文件原始字节的SHA-256,Base64URL无padding编码>"
}| 字段 | 规则 |
|---|---|
size | 原始文件字节数,整数,1–100,000,000;不是 Base64 字符串长度 |
mime_type | 必须与真实文件格式及上传时 Content-Type 一致 |
sha256 | SHA-256 的32字节摘要做 Base64URL 编码,不带 =;不是64位十六进制字符串 |
支持上传:video/mp4、video/webm、video/quicktime;audio/mpeg、audio/wav、audio/ogg、audio/mp4、audio/flac;image/png、image/jpeg、image/webp、image/gif。上传上限不等于模型参考上限,仍须满足目标模型的更严格限制。
HTTP 200 返回示意(尖括号为占位符,不是可调用的真实值):
{
"url": "https://assets.88api.ai/media/reference-media/<object-id>.mp4",
"upload_url": "https://assets.88api.ai/media/reference-media/<object-id>.mp4",
"method": "PUT",
"headers": {
"Content-Type": "video/mp4",
"X-Media-Upload-Token": "<scoped-upload-token>"
},
"expires_at": 1789373400
}这里的 expires_at 是 上传凭证到期时间(Unix秒,约10分钟),不是文件保存期限;url 在此时也不代表文件已上传完成。凭证仅允许写入指定文件,不能访问整个存储桶。
第二步:在凭证到期前上传原始二进制。 原样使用响应的 upload_url、method 和 headers:
# 以下变量来自申请凭证的响应;不要把 API_KEY 发给上传地址。
curl --request PUT "$UPLOAD_URL" \
--header 'Content-Type: video/mp4' \
--header "X-Media-Upload-Token: $UPLOAD_TOKEN" \
--data-binary @reference.mp4不是 multipart/form-data,不使用 -F,也不是 JSON Base64。必须发送与申请时一致的 Content-Length、MIME 和文件字节;浏览器可直接发送 File/Blob,由浏览器生成 Content-Length。服务端流式客户端也必须给出正确长度,不使用未知长度的 chunked 上传。
HTTP 201 表示上传完成,返回 url、size、mime_type、expires_at。这次 expires_at 才是素材到期时间(Unix秒,自该对象上传起30天)。确认成功后,将 url 放入目标模型要求的参考字段。浏览器应由自己的后端申请凭证,然后直接上传,不能在前端暴露长期 API Key。
同一上传地址不允许覆盖:重复 PUT 返回409,应复用已经成功上传的地址;遇到不确定的网络中断,先匿名 HEAD 检查该 URL,不要立刻重复申请新对象。上传和引用不会产生新的生成任务。
4. 支持模型的首尾帧模式
首帧和尾帧放在 metadata.firstFrame 与 metadata.lastFrame:
下例适用于支持该字段的多参考模型。Veo 使用 images 加 metadata.video_mode,不要套用本节字段。
{
"model": "Seedance-2.5-720p官方版",
"prompt": "从清晨的空旷广场平滑过渡到夜晚灯光亮起的同一构图",
"duration": 10,
"size": "16:9",
"metadata": {
"firstFrame": "https://cdn.example.com/start.jpg",
"lastFrame": "https://cdn.example.com/end.jpg"
}
}规则:
lastFrame不能单独使用,必须同时提供firstFrame。- 大多数支持首尾帧的模型不允许将首尾帧与普通参考图片/视频/音频混用。
- 模型能力表标记为“互斥”时,请在首尾帧和普通参考素材之间二选一。
5. 当前视频模型能力表
以下只使用 88API 对外模型名。可用模型和价格以 模型广场 及账户当前权限为准。
Veo 调用差异
Veo 不接受参考视频或参考音频。当前网关另提供显式图片模式,不能再将所有 Veo 请求理解为“只读取第一张图”:
metadata.video_mode | images | 规则 |
|---|---|---|
| 不传 | 旧单图兼容方式,仅第一张 | 新接入建议显式选择模式 |
frames | 第1张为首帧,第2张为尾帧,最多2张 | 首尾帧能力要求 Veo 3.1,并以实际开放模型支持为准 |
reference | 最多3张主体参考图 | 有参考图时必须8秒;要求 Veo 3.1,并以实际开放模型支持为准 |
显式模式的图片使用 PNG/JPEG 的 Base64 或 data:image/...;base64,...,每张解码后不超过20 MiB。当前此适配路径不自动把 HTTPS 图片下载成 Base64;不要直接把图片 URL 当作该模式的输入。下例 Base64 为占位符,调用时替换为完整文件数据。网关字段支持不代表每个 Fast/其他变体都开放相同上游能力。
{
"model": "veo-3.1-fast",
"prompt": "保持参考图中的主体,镜头缓慢靠近,背景云层自然移动",
"duration": 8,
"size": "1920x1080",
"images": ["data:image/jpeg;base64,<完整图片Base64>"],
"metadata": {
"video_mode": "frames",
"negativePrompt": "blur, distortion",
"generateAudio": true
}
}横屏可使用 1280x720/1920x1080,竖屏可使用 720x1280/1080x1920。基础时长为4/6/8秒;参考图模式固定8秒,其他组合还受模型限制。负面提示词、种子和音频开关使用 metadata.negativePrompt、metadata.seed、metadata.generateAudio。首尾帧请求按顺序放两张图;主体参考请求选择 reference,不是 metadata.referenceVideos。
Grok 视频调用差异
Grok 视频支持文生视频和最多一张首图,不支持多图、参考视频或参考音频。
{
"model": "grok-imagine-video-1.5",
"prompt": "保持参考图主体,从近景缓慢拉远,电影感",
"duration": 8,
"size": "16:9",
"images": ["https://cdn.example.com/start.jpg"],
"metadata": {
"resolution": "720p"
}
}grok-imagine-video 和 grok-imagine-video-1.5 可选 480P/720P;grok-imagine-video-1.5-1080p 固定 1080P,客户不应覆盖分辨率。
Gemini Omni 视频调用差异
gemini-omni-flash 固定输出 720P,支持最多 10 张图片或最多 1 个参考视频。该模型的参考视频必须使用顶层 video/videos,不是 metadata.referenceVideos。
{
"model": "gemini-omni-flash",
"prompt": "保持参考视频的镜头节奏,将主体替换为参考图中的产品",
"duration": 6,
"size": "16:9",
"images": ["https://cdn.example.com/product.jpg"],
"video": "https://cdn.example.com/reference.mp4"
}参考视频只支持 MP4/MOV,时长不得超过 10 秒,文件不得超过 64 MiB。该模型不支持参考音频和首尾帧。
| 对外模型名 | 时长 | 比例 | 输出 | 图/视频/音频上限 | 首尾帧 | 可控音效 |
|---|---|---|---|---|---|---|
veo-3.1-fast | 基础4/6/8秒,模式限制见上文 | 16:9 9:16 | 720P/1080P | 图片按模式;0视频/0音频 | 按模式及开放能力 | 原生音频,可用 metadata.generateAudio |
veo-3.1 | 基础4/6/8秒,参考图8秒 | 16:9 9:16 | 720P/1080P | 图片按模式;0视频/0音频 | 按模式及开放能力 | 原生音频,可用 metadata.generateAudio |
grok-imagine-video | 1–15 秒 | 16:9 9:16 1:1 4:3 3:4 3:2 2:3 | 480P/720P | 1 / 0 / 0 | 不支持 | 不支持 |
grok-imagine-video-1.5 | 1–15 秒 | 同上 | 480P/720P | 1 / 0 / 0 | 不支持 | 不支持 |
grok-imagine-video-1.5-1080p | 1–15 秒 | 同上 | 1080P | 1 / 0 / 0 | 不支持 | 不支持 |
gemini-omni-flash | 3–10 秒 | 16:9 9:16 | 720P | 10 / 1 / 0 | 不支持 | 不支持 |
SD2.5 720P | 4–30 秒 | auto + 6 种常用比例 | 720P | 30 / 10 / 10 | 支持,与普通素材互斥 | 支持 generate_audio |
SD2.5 480P | 4–30 秒 | auto + 6 种常用比例 | 480P | 30 / 10 / 10 | 支持,与普通素材互斥 | 支持 generate_audio |
SD2.0 720P | 4–15 秒 | 1:1 21:9 16:9 9:16 3:4 4:3 | 720P | 9 / 3 / 3,三类合计最多 12 | 支持,与普通素材互斥 | 不可设置 |
SD2.5 1080P | 4–30 秒 | auto + 6 种常用比例 | 1080P | 30 / 10 / 10 | 支持,与普通素材互斥 | 支持 generate_audio |
SD2.0 480P | 4–15 秒 | 同 SD2.0 720P | 480P | 9 / 3 / 3,三类合计最多 12 | 支持,与普通素材互斥 | 不可设置 |
SD2.0 1080P | 4–15 秒 | 同 SD2.0 720P | 1080P | 9 / 3 / 3,三类合计最多 12 | 支持,与普通素材互斥 | 不可设置 |
minimax-h3-768p | 以当前开放能力为准 | 以当前开放能力为准 | 768P | 参考视频及数量须确认,见下文 | 按开放能力 | 不可设置 |
Seedance-2.5-720p官方版 | 4–30 秒 | 16:9 9:16 1:1 4:3 3:4 21:9 | 720P | 30 / 10 / 10 | 支持 | 不可设置 |
Seedance-2.0-720p官方版 | 4–15 秒 | 同上 | 720P | 9 / 3 / 3 | 支持,与普通素材互斥 | 不可设置 |
Seedance-2.0-fast-720p官方版 | 4–15 秒 | 同上 | 720P | 9 / 3 / 3 | 支持,与普通素材互斥 | 不可设置 |
seedance-2.0-mini-480p | 4–15 秒 | 同上 | 480P | 9 / 3 / 3 | 未公开,建议不传 | 不可设置 |
seedance-2.0-mini-720p | 4–15 秒 | 同上 | 720P | 9 / 3 / 3 | 未公开,建议不传 | 不可设置 |
wan3.0-video-480p | 4–30 秒 | 16:9 9:16 1:1 4:3 3:4 | 480P | 30 / 10 / 10 | 支持 | 不可设置 |
wan3.0-video-720p | 4–30 秒 | 16:9 9:16 1:1 4:3 3:4 | 720P | 10 / 5 / 5 | 支持,与普通素材互斥 | 不可设置 |
wan3.0-video-1080p | 4–30 秒 | 同上 | 1080P | 10 / 5 / 5 | 支持,与普通素材互斥 | 不可设置 |
kling-3.0-turbo-720p | 4–15 秒 | 16:9 9:16 1:1 | 720P | 30 / 10 / 0 | 支持 | 支持 generate_audio |
kling-3.0-turbo-1080p | 4–15 秒 | 同上 | 1080P | 30 / 10 / 0 | 支持 | 支持 generate_audio |
kling-3.0-turbo-2k | 4–15 秒 | 同上 | 2K | 30 / 10 / 0 | 支持 | 支持 generate_audio |
kling-3.0-turbo-4k | 4–15 秒 | 同上 | 4K | 30 / 10 / 0 | 支持 | 支持 generate_audio |
SD2.5 720P 的“6 种常用比例”是 1:1、21:9、16:9、9:16、3:4、4:3。
模型特有规则
- Veo、Grok 和 Gemini Omni 不使用多参考模型的素材字段;必须按上方各自示例提交。
- SD2.5 的480P/720P/1080P三档支持
generate_audio;参考视频单个2–30秒,合计不超过30秒。 - SD2.0 的480P/720P/1080P三档参考素材合计不超过12,输出固定带音频。
minimax-h3-768p:同一公开名称的不同服务路径可能具有不同参考能力,不能将历史10图/5视频/5音频作为所有请求的保证。使用视频参考前,请确认账户当前可用服务支持该能力;支持视频参考的路径仍要求匿名公网HTTPS视频直链。上传素材只能解决地址可访问性,不能增加模型能力。参考音频应搭配视觉素材,并遵守当前服务限制。wan3.0-video-*:有参考素材时允许省略prompt,但为了结果可控,生产请求仍建议写明动作和镜头要求。kling-3.0-turbo-*:不支持参考音频;支持generate_audio。图片使用顶层images,视频参考仍建议使用metadata.referenceVideos。- 带分辨率后缀的模型名必须精确匹配;不要删除
-720p、-1080p、-2k或-4k。
6. 提交响应
提交成功后会立即返回公开任务 ID:
{
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"object": "video",
"model": "SD2.5 720P",
"status": "queued",
"progress": 0,
"created_at": 1788249600
}请保存 id。标准接口不保证返回 task_id,新接入必须使用 id。
HTTP 200 表示“任务已被接收”,不代表视频已生成完成。
7. 轮询任务
curl 'https://88api.ai/v1/videos/task_xxxxxxxxxxxxxxxxxxxxxxxx' \
--header 'Authorization: Bearer sk-xxxx'统一状态
status | 含义 | 客户端动作 |
|---|---|---|
queued | 已接收,正在排队 | 继续等待 |
in_progress | 正在生成 | 继续等待 |
completed | 已完成 | 读取返回的 url,播放、下载或再次引用 |
failed | 已失败 | 读取 error.code 和 error.message |
unknown | 未知状态 | 短暂重试,持续出现时联系支持 |
完成响应示例:
{
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"object": "video",
"model": "SD2.5 720P",
"status": "completed",
"progress": 100,
"created_at": 1788249600,
"completed_at": 1788250200,
"url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
"video_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
"result_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4"
}失败响应示例:
{
"id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"object": "video",
"model": "SD2.5 720P",
"status": "failed",
"progress": 100,
"error": {
"code": "video_generation_failed",
"message": "视频生成失败"
}
}轮询策略
- 提交后等待 3–5 秒再进行第一次查询。
- 建议每 10–15 秒查询一次,不要每秒轮询。
- 交互客户端可采用 15分钟等待预算,服务端可持久化任务ID继续查询或接收回调。客户端停止等待不等于任务失败、取消或退款。
queued或in_progress期间不要重复提交同一任务,否则可能重复计费。- 同时配置回调时,轮询可以作为容灾备用机制。
单次HTTP请求超时、客户端总等待时间和服务端任务期限是不同概念。不要将15分钟写成服务端必然终止的保证,也不要只凭 progress=100 判成功;以 status 为准。恢复页面或网络后可继续查询原任务。
8. 回调(Webhook)
本节仅适用于明确支持回调的型号,例如 Veo。上方列出的 SD、Seedance、Wan3、Kling Turbo、MiniMax H3 新请求请使用轮询,不设置 callback_url。
提交时设置
{
"model": "veo-3.1-fast",
"prompt": "赛博朋克城市中的无人机航拍",
"duration": 8,
"size": "16:9",
"callback_url": "https://api.customer.example/webhooks/88api/video"
}callback_url 必须:
- 是公网可访问的 HTTP(S) URL,生产环境强烈建议 HTTPS;
- 不超过 2048 个字符;
- 不能指向私网 IP、
localhost、链路本地地址或受限制端口; - 能在 15 秒内返回任意
2xx响应。
回调只在终态发送:
video.completedvideo.failed
回调请求头
Content-Type: application/json
User-Agent: NewAPI-Task-Callback/1.0
X-NewAPI-Event: video.completed
X-NewAPI-Delivery: evt_task_xxx_completed
X-NewAPI-Timestamp: 1788250200
X-NewAPI-Signature: sha256=<hex-hmac>完成事件
{
"id": "evt_task_xxxxxxxxxxxxxxxxxxxxxxxx_completed",
"type": "video.completed",
"created_at": 1788250200,
"data": {
"task_id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"model": "veo-3.1-fast",
"status": "completed",
"progress": "100%",
"output_url": "https://assets.88api.ai/media/task-videos/2026/09/<object-key>.mp4",
"created_at": 1788249600,
"completed_at": 1788250200
}
}失败事件
{
"id": "evt_task_xxxxxxxxxxxxxxxxxxxxxxxx_failed",
"type": "video.failed",
"created_at": 1788250200,
"data": {
"task_id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
"model": "veo-3.1-fast",
"status": "failed",
"progress": "100%",
"error": "视频生成失败",
"created_at": 1788249600,
"completed_at": 1788250200
}
}验证回调签名
签名秘钥是提交该任务时使用的完整 API Key(包含 sk- 前缀)。签名原文是:
X-NewAPI-Timestamp + "." + 原始请求体字节Node.js 验证示例:
import crypto from "node:crypto";
import express from "express";
const app = express();
// 必须先保留原始请求体,再解析 JSON。
app.use(
express.json({
verify: (req, _res, buffer) => {
req.rawBody = buffer;
},
}),
);
app.post("/webhooks/88api/video", (req, res) => {
const timestamp = req.header("X-NewAPI-Timestamp") || "";
const timestampNumber = Number(timestamp);
if (
!Number.isFinite(timestampNumber) ||
Math.abs(Date.now() / 1000 - timestampNumber) > 300
) {
return res.status(401).send("stale timestamp");
}
const received = (req.header("X-NewAPI-Signature") || "").replace(
/^sha256=/,
"",
);
const secret = process.env.API_KEY; // 完整 sk-xxxx
const signed = Buffer.concat([
Buffer.from(`${timestamp}.`, "utf8"),
req.rawBody,
]);
const expected = crypto
.createHmac("sha256", secret)
.update(signed)
.digest("hex");
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).send("invalid signature");
// 使用 X-NewAPI-Delivery 或 req.body.id 幂等去重。
console.log(req.body.type, req.body.data.task_id);
return res.sendStatus(204);
});回调重试和幂等
- 回调超时、网络错误或返回非
2xx时,88API 会指数退避重试。 - 单次请求超时为 15 秒,最多尝试 8 次,退避上限为 15 分钟。
- 同一事件可能被投递多次。客户端必须使用
X-NewAPI-Delivery或事件id做幂等去重。 - 建议拒绝与本机时间相差超过 5 分钟的回调,降低重放风险。
- 建议先验签、保存事件并立即返回
2xx,耗时业务逻辑放入自己的异步队列。
9. 获取视频文件
任务进入 completed 后,优先使用查询返回的 url;兼容客户端可回退读取 video_url / result_url,回调使用 data.output_url。这些字段是服务端交付结果,不要从任务ID或上游ID自行构造媒体路径。
# RESULT_URL 原样取自完成响应,不向媒体服务器发送 API Key。
curl --fail --location "$RESULT_URL" --output result.mp4说明:
- 客户端应允许 HTTP 3xx 跳转,
curl需要带--location。 - 本站归档结果使用
https://assets.88api.ai/media/...匿名地址;符合安全直连条件的官方结果保持原始URL,不强制改成本站域名。 - 其他无法直接公开或无法明确映射的产物可能继续返回带访问能力参数的兼容地址。应完整保留服务器返回的URL和必要参数,不得自行删参数或替换域名;不要给assets地址另加查询参数(会返回400)。
- 旧
/v1/videos/{id}/content对已归档视频提供307兼容跳转,不应作为官方直链或所有产物的必经入口;返回404时重新查询任务取得实际结果地址,不要盲目重复生成。 - 回调中的
output_url是不透明结果地址,可能具有有效期;不要解析其域名或路径结构。 - 不要拼接、保存或依赖任何内部任务 ID;只使用提交响应返回的公开
id。 - 如果你需要长期保存成品,应在完成后下载到自己的对象存储。
9.1 保存、播放与跨模型引用
- 本站上传素材与归档视频自各自对象上传起保存30天,重复读取、查询旧任务、重复引用均不续期,也不重新上传一份。官方结果URL按来源自身有效期处理,不承诺30天。
- 没有签名参数、无需登录,不代表永久保存;文件到期返回410,已被生命周期清理后可能返回404。任务记录存在也不代表二进制文件仍存在。
- 公共媒体支持 GET、HEAD、单段 Range 和浏览器跨域读取。可读取
X-Media-Expires-At(Unix秒);Range 成功通常为206。缓存不会延长原到期时间,也不保证所有地区都有相同下载速度。 - Veo生成的结果可作为支持视频参考的其他模型的输入,前提是地址仍有效,且格式、尺寸、时长满足目标模型要求。直接把结果URL放入该模型的参考字段,无需先下载再上传;这不表示Veo本身支持输入参考视频。
{
"model": "SD2.0 720P",
"prompt": "保持参考视频的主体与动作,延续镜头",
"duration": 4,
"size": "16:9",
"metadata": {
"referenceVideos": ["<上一任务返回的完整HTTPS结果URL>"]
}
}10. JavaScript 完整轮询示例
Node.js 22+;在服务端设置 API_KEY 和 REFERENCE_FILE(MP4文件路径)。设置 TASK_ID 可直接恢复旧任务,不重新上传或生成。示例会创建一次收费生成,最后仅打印下一轮请求体,不自动发起第二次生成。为便于阅读使用文件缓冲,大文件批量处理应限制并发。
// video-api-example.mjs — Node.js 22+, run with API_KEY and REFERENCE_FILE.
import { readFile, stat } from "node:fs/promises";
import { createHash } from "node:crypto";
const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error("Set API_KEY on your server");
const baseUrl = "https://88api.ai";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function apiJSON(path, body) {
// No automatic POST retry: a timed-out create may already have been accepted.
const response = await fetch(baseUrl + path, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(120000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
return response.json();
}
async function uploadMedia(file, mimeType) {
const info = await stat(file);
if (!info.isFile() || info.size < 1 || info.size > 100000000) {
throw new Error("File must contain 1–100000000 bytes");
}
const bytes = await readFile(file);
const permit = await apiJSON("/v1/media/uploads", {
size: bytes.length, mime_type: mimeType,
sha256: createHash("sha256").update(bytes).digest("base64url"),
});
const target = new URL(permit.upload_url);
if (target.origin !== "https://assets.88api.ai" || permit.method !== "PUT") {
throw new Error("Unexpected upload destination; do not send credentials");
}
const response = await fetch(target, {
method: "PUT", headers: permit.headers, body: bytes,
redirect: "error", signal: AbortSignal.timeout(120000),
});
// A 409/ambiguous upload is not automatically retried; HEAD permit.url first.
if (response.status !== 201) throw new Error(`Upload HTTP ${response.status}`);
return response.json();
}
async function waitForVideo(id, budgetMs = 15 * 60 * 1000) {
const deadline = Date.now() + budgetMs;
let delay = 4000;
while (Date.now() < deadline) {
await sleep(Math.min(delay, deadline - Date.now()));
const remaining = deadline - Date.now();
if (remaining <= 0) break;
let response, state;
try {
response = await fetch(`${baseUrl}/v1/videos/${encodeURIComponent(id)}`, {
headers: { Authorization: `Bearer ${apiKey}` },
signal: AbortSignal.timeout(Math.min(30000, remaining)),
});
if (response.ok) state = await response.json();
} catch {
delay = Math.min(delay * 2, 60000);
continue; // Retry a read, never create a replacement task.
}
if (response.status === 429 || response.status >= 500) {
const retryAfter = response.headers.get("Retry-After");
const seconds = retryAfter && /^\d+$/.test(retryAfter)
? Number(retryAfter)
: (Date.parse(retryAfter || "") - Date.now()) / 1000;
delay = Number.isFinite(seconds) && seconds > 0
? Math.max(1000, seconds * 1000)
: Math.min(delay * 2, 60000);
await response.body?.cancel();
continue;
}
if (!response.ok) throw new Error(`Task ${id}: HTTP ${response.status}`);
if (state.status === "completed") {
const result = state.url || state.video_url || state.result_url;
if (typeof result !== "string" || new URL(result).protocol !== "https:") {
throw new Error(`Task ${id} completed without a usable HTTPS result URL`);
}
return result; // Keep official URLs and capability query parameters intact.
}
if (state.status === "failed") {
throw new Error(`Task ${id}: ${state.error?.message || "video generation failed"}`);
}
if (!["queued", "in_progress", "unknown"].includes(state.status)) {
throw new Error(`Task ${id}: unexpected status ${state.status}`);
}
delay = 12000;
}
throw new Error(`Stopped waiting for ${id}; save this ID and query later. Not cancelled.`);
}
async function main() {
// Resume a saved task without uploading or creating another paid task.
let id = process.env.TASK_ID;
if (!id) {
if (!process.env.REFERENCE_FILE) throw new Error("Set REFERENCE_FILE");
const media = await uploadMedia(process.env.REFERENCE_FILE, "video/mp4");
const task = await apiJSON("/v1/videos", {
model: "SD2.0 720P", prompt: "保持参考视频的主体与动作,延续镜头",
duration: 4, size: "16:9", metadata: { referenceVideos: [media.url] },
});
id = task.id;
if (!id) throw new Error("Create response did not contain a task ID; do not blindly retry");
console.log("Save task ID:", id); // Persist to your database in production.
}
const resultURL = await waitForVideo(id);
console.log("Result URL:", resultURL); // This grants media access; do not log publicly.
// This only shows the NEXT request body; it does not create a second paid task.
console.log(JSON.stringify({
model: "SD2.0 720P", prompt: "保持参考视频的主体与动作,延续镜头",
duration: 4, size: "16:9", metadata: { referenceVideos: [resultURL] },
}, null, 2));
}
await main();11. 错误处理与重试
| 情况 | 处理建议 |
|---|---|
| HTTP 400 | 检查模型名、时长、比例、素材数量与字段位置 |
| HTTP 401 | API Key 缺失、错误或已失效 |
| HTTP 403 | 当前 Key 无模型或分组权限 |
| HTTP 404 | 任务不存在、不属于当前账户,或成品尚未就绪 |
| 上传 HTTP 409 | 指定对象已存在且不可覆盖;先HEAD确认并复用成功的地址 |
| 媒体 HTTP 410 | 文件已到期;读取和重复引用不会续期,清理后也可能为404 |
| 上传 HTTP 415 | 文件签名与声明MIME不符;不能仅改扩展名冒充其他格式 |
| 媒体 HTTP 416 | Range超出文件边界或格式不支持;重新HEAD确认大小 |
| HTTP 429 | 请求过快或额度限制;按 Retry-After 或指数退避 |
| HTTP 5xx | 暂时服务错误;查询请求可退避重试 |
status=failed | HTTP 查询成功但生成失败;读取 error.message |
| 素材下载失败 | 确认 URL 公网可达、未过期、无防盗链和鉴权依赖 |
unsupported_reference_media | 模型不支持该参考类型,上传或换域名不能解决 |
must be a public HTTPS URL | 本地文件先上传,不能提交Blob、Data URL、HTTP或需要登录的地址 |
input_download_failed | 检查上游实际可达性、DNS/网络、媒体有效期,不只是本机能否打开 |
| 内容安全拦截 | 调整提示词或参考素材,不要原样无限重试 |
POST /v1/videos 暂不提供客户自定义的幂等键保证。如果客户在已发出完整请求后遇到网络超时,不要盲目立即重发,否则可能生成并计费多个任务。