88API88API
使用指南AI 应用API 文档帮助支持
视频(Video)

视频 API 完整调用规范

适用于 88API 视频模型的统一接口,涵盖模型发现、素材上传、视频生成、轮询、下载和跨模型引用。

本页是 88API 对客户开放的统一视频生成规范,适用于 88API 提供的各类视频模型。客户只需从自己的 API Key 可见的模型列表中取得 model ID,再使用本页的统一接口。

当前视频任务共用“模型发现→提交→轮询→下载”协议。不同账号看到的模型 ID 可能不同,模型支持的时长、比例、分辨率和参考素材数量也可能不同;请勿把一个模型的能力直接套用到另一个模型。

更新于 2026-10-10。各品牌模型的专属参数和示例已拆分为独立页面,见下方 各品牌调用规范。推荐流程:先调用 GET /v1/models 获取当前 Key 可用的模型 ID;已有可用 HTTPS 素材直接引用;本地素材先上传 → 提交生成 → 轮询取得结果 URL → 播放、下载或作为下一轮参考。上传成功不代表模型一定支持该素材类型;模型能力与存储能力是两回事。

统一接入规则:先使用 GET /v1/models 返回的精确 id 填写 model,再使用 POST /v1/videos 创建任务,保存响应中的 id,通过 GET /v1/videos/{id} 轮询。新接入不要使用旧 /v1/video/generations,不要依赖 task_id,也不要把 callback_url 当作所有模型都支持的能力;需要稳定兼容时统一采用轮询。

1. 统一接入最小规则

如果同一套客户端需要接入多个视频模型,只需固定下面这套流程:

  1. 使用当前 API Key 请求 GET /v1/models。
  2. 从返回结果中选择客户实际有权限的模型 id;不要把模型广场名称、历史名称或其他账号看到的名称硬编码成全局配置。
  3. 使用同一个 POST /v1/videos 请求格式提交任务。
  4. 只使用响应中的公开 id 查询任务;不要解析或拼接任何其他任务 ID。
  5. 使用完成响应中的 url、video_url 或 result_url 获取视频。

推荐使用的通用字段是:

用途推荐字段说明
模型model必须是当前 Key 从 /v1/models 获取的精确模型 ID
文本prompt纯文生视频通常必填;是否可留空取决于模型
时长duration 或 seconds二选一,单位为秒;优先使用 duration
比例ratio 或 aspect_ratio推荐用于设置画面比例;具体可用值以目标模型能力为准
参考图images推荐使用公网 HTTPS 或已上传的 assets.88api.ai URL
参考视频metadata.referenceVideos仅在目标模型能力支持时使用
参考音频metadata.referenceAudios仅在目标模型能力支持时使用
首尾帧metadata.firstFrame、metadata.lastFrame必须同时遵守目标模型的首尾帧和素材互斥规则

上表是多数模型通用的写法。部分品牌的字段不同(例如万相的首帧、尾帧和参考素材推荐使用 media 数组,无法识别的素材字段会返回 400),请以 各品牌调用规范 为准。

size 是部分模型和历史客户端使用的兼容字段。新客户端应优先使用 ratio 或 aspect_ratio,并根据目标模型的能力说明选择具体值。

本页后续示例中的 <MODEL_ID> 均表示上述步骤从 /v1/models 获取的真实模型 ID,不是要求所有客户使用同一个固定字符串。

2. 接口速查

用途方法与路径说明
获取模型GET https://api.88api.ai/v1/models返回当前 API Key 可见的模型 ID;以返回结果为准
提交生成POST https://api.88api.ai/v1/videos提交文本和参考素材,返回任务 id
查询任务GET https://api.88api.ai/v1/videos/{id}查询状态、进度和错误
申请素材上传POST https://api.88api.ai/v1/media/uploads发送文件摘要、字节数和 MIME,取得短期上传凭证
上传素材二进制PUT {upload_url}使用返回的专用上传请求头,不发送 API Key
获取视频GET {url}优先使用完成响应的结果 URL,不自行拼接路径
旧内容入口GET https://api.88api.ai/v1/videos/{id}/content已归档视频的兼容跳转;不作为所有结果的通用下载方式
终态回调提交时设置 callback_url仅支持回调的型号;其余使用轮询

请求头

Authorization: Bearer sk-你的API密钥
Content-Type: application/json
Accept: application/json

API 密钥必须保存在服务端,不得写入浏览器、小程序或 App 前端包。

操作鉴权
创建、查询任务;申请上传凭证Authorization: Bearer ...,只发到 api.88api.ai API
文件上传原样使用 headers 中的 X-Media-Upload-Token 与 Content-Type
读取公共媒体assets.88api.ai 地址不需要 Bearer、Cookie 或登录

匿名读取不等于匿名上传,也不开放任务详情。获得公共媒体 URL 的人可以在有效期内读取文件;不要把 URL 当作严格的隐私隔离机制。不要向媒体地址、官方结果地址或重定向目标转发自己的 API Key。

先获取当前账号可用的模型 ID

模型 ID 不是所有账号都固定相同。客户端首次接入或定期刷新模型列表时,先执行:

curl 'https://api.88api.ai/v1/models' \
  --header 'Authorization: Bearer sk-xxxx'

从响应中的 data[].id 选择模型,并将该值原样放入后续 POST /v1/videos 的 model 字段。不要根据模型显示名称自行拼接 ID,也不要把其他账号或历史文档中的模型名当作当前账号一定可用。

{
  "object": "list",
  "data": [
    {
      "id": "<MODEL_ID>",
      "object": "model",
      "owned_by": "88api"
    }
  ]
}

3. 标准提交请求

最小文生视频请求

curl --request POST 'https://api.88api.ai/v1/videos' \
  --header 'Authorization: Bearer sk-xxxx' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "model": "<MODEL_ID>",
    "prompt": "雨夜城市中的银色跑车,低机位跟拍,霓虹倒影,电影感",
    "duration": 8,
    "ratio": "16:9"
  }'

duration 也可以写成字符串形式的 seconds:

{
  "model": "<MODEL_ID>",
  "prompt": "日出时的海岸航拍,云层缓慢移动",
  "seconds": "8",
  "ratio": "16:9"
}

公共请求字段

字段类型必填说明
modelstring是当前 API Key 通过 GET /v1/models 获取的精确模型 ID
promptstring条件必填文本提示词;仅部分模型允许有参考素材时留空
negative_promptstring否多参考模型的负面提示词;Veo 也可写成 metadata.negativePrompt
durationinteger否视频时长,单位秒;与 seconds 二选一
secondsstring否字符串形式的视频时长,例如 "8"
ratiostring否推荐的画面比例字段,例如 16:9、9:16
aspect_ratiostring否ratio 的兼容别名;具体可用值以目标模型能力为准
sizestring否兼容字段;新接入不要依赖它传递比例
resolutionstring否仅在目标模型支持时使用,例如 480p、720p、1080p、4k
imagestring否单张参考图片 URL
imagesstring[]否参考图片 URL 数组,推荐用法
videostring否单个参考视频 URL;Gemini Omni 必须使用此顶层字段
videosstring[]否参考视频数组;其他多参考模型使用 metadata.referenceVideos
seedinteger否随机种子
generateAudio / generate_audioboolean否支持的模型上开启同步生成音频;仅在模型能力允许时使用
camera_controlobject否镜头控制;仅在对应模型明确支持时使用
callback_urlstring否仅在目标模型明确支持回调时使用;新接入默认不要发送
metadataobject否参考视频、音频、首尾帧等扩展字段

分辨率如何请求

统一创建接口相同,但不同模型的分辨率选择方式可能不同。请先以 GET /v1/models 返回的模型 ID 和模型能力说明为准。

固定分辨率模型:通过模型 ID 选择分辨率

部分模型已经在模型名称或模型 ID 中固定了输出档位。客户应先从 GET /v1/models 获取当前账号真正可用的 ID,然后直接选择对应档位的模型:

{
  "model": "<FIXED_RESOLUTION_MODEL_ID>",
  "prompt": "城市夜景中的汽车缓慢驶过街道",
  "duration": 8,
  "ratio": "16:9"
}

对于固定分辨率模型,resolution 不能临时改变输出档位。即使客户端携带了 resolution,也不要把它当作覆盖模型能力的方式。

可选分辨率模型:使用 resolution 字段

部分模型可能支持多个分辨率,此时在请求中传入小写的 resolution:

{
  "model": "<MULTI_RESOLUTION_MODEL_ID>",
  "prompt": "城市夜景中的汽车缓慢驶过街道",
  "duration": 8,
  "resolution": "720p",
  "ratio": "16:9"
}

实际可用的分辨率和取值仍以当前 API Key 的 /v1/models、模型说明及服务端校验为准。如果客户端需要兼容多个模型,可以保留 resolution 字段:支持该字段的模型用它选择输出档位,固定档位模型则以 model 为准。不要使用 quality、vquality 或自行修改模型名来绕过模型能力限制;不支持的组合会返回 400。

4. 参考图片、视频和音频

3.1 参考图片

多张图片统一使用顶层 images 数组:

{
  "model": "<MODEL_ID>",
  "prompt": "保持人物面部、服装和配色一致,在海边缓慢行走,电影级柔光",
  "duration": 10,
  "ratio": "16:9",
  "images": [
    "https://cdn.example.com/character-front.jpg",
    "https://cdn.example.com/character-side.jpg"
  ]
}

单图也可以使用 image 或 input_reference,但新接入建议统一使用 images 数组。

3.2 参考视频

对能力表中支持多视频参考的模型,放在 metadata.referenceVideos:

{
  "model": "<MODEL_ID>",
  "prompt": "参考镜头节奏和运镜,但保持主体外观不变",
  "duration": 8,
  "ratio": "16:9",
  "images": ["https://cdn.example.com/subject.jpg"],
  "metadata": {
    "referenceVideos": [
      "https://cdn.example.com/camera-motion.mp4"
    ]
  }
}

3.3 参考音频

对能力表中支持音频参考的模型,放在 metadata.referenceAudios:

{
  "model": "<MODEL_ID>",
  "prompt": "人物按照参考音频的节奏演唱,口型自然",
  "duration": 10,
  "ratio": "16:9",
  "images": ["https://cdn.example.com/singer.jpg"],
  "metadata": {
    "referenceAudios": [
      "https://cdn.example.com/voice-reference.mp3"
    ]
  }
}

MiniMax H3 的参考图片不要用顶层 images,请按 MiniMax 视频 API 规范 使用 metadata.referenceImages。

3.4 多参考模型的混合素材

{
  "model": "<MODEL_ID>",
  "prompt": "使用参考图的产品外观、参考视频的镜头节奏和参考音频的声音氛围,制作科技广告",
  "duration": 12,
  "ratio": "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 要求

  1. 参考视频和音频采用匿名可下载的公网 HTTPS URL。blob:、本机路径、data:video/... 不能作为通用参考视频协议;本地文件按下节先上传。
  2. URL 不能依赖 Cookie、浏览器登录态、Referer 或自定义请求头。
  3. 不能使用 localhost、私网 IP、Docker 服务名或仅你内网可访问的地址。
  4. URL 在整个任务生成期间必须有效,建议有效期不少于 60 分钟。
  5. 图片建议使用 JPG、PNG 或 WebP;视频建议 MP4/MOV;音频建议 MP3/WAV。
  6. 视频建议不超过 50 MB/段,音频建议不超过 15 MB/段;过大素材可能在下载或审核阶段失败。
  7. 引用第三方素材前,请确保拥有必要的著作权、肖像权和使用授权。

素材页面能打开不等于视频字节可下载:目标应返回实际媒体,而不是 HTML 分享页。URL 可用也不代表其编码、时长或尺寸符合目标模型要求。Google 视频模型的图片还可以使用 Base64 或文件上传,见 Google 视频 API 规范;参考视频仍只接受 URL。

3.6 本地素材上传:先申请,再 PUT

此接口提供临时媒体存储,不会生成视频、转码、压缩或自动提取视频帧。

第一步:在服务端申请上传凭证。

POST https://api.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 一致
sha256SHA-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,不要立刻重复申请新对象。上传和引用不会产生新的生成任务。

5. 支持模型的首尾帧模式

首帧和尾帧放在 metadata.firstFrame 与 metadata.lastFrame:

下例适用于支持该字段的多参考模型。Veo 和 Gemini Omni 同样接受 metadata.firstFrame 和 metadata.lastFrame,其他写法见 Google 视频 API 规范。

{
  "model": "<MODEL_ID>",
  "prompt": "从清晨的空旷广场平滑过渡到夜晚灯光亮起的同一构图",
  "duration": 10,
  "ratio": "16:9",
  "metadata": {
    "firstFrame": "https://cdn.example.com/start.jpg",
    "lastFrame": "https://cdn.example.com/end.jpg"
  }
}

规则:

  • lastFrame 不能单独使用,必须同时提供 firstFrame。
  • 大多数支持首尾帧的模型不允许将首尾帧与普通参考图片/视频/音频混用。
  • 模型能力表标记为“互斥”时,请在首尾帧和普通参考素材之间二选一。

6. 当前视频模型能力表

以下表格用于说明已整理的公开模型能力,不是客户请求时的固定模型 ID。实际请求必须使用当前 API Key 从 /v1/models 获取的 id。不同账号可能看到不同的模型 ID;只要使用统一 /v1/videos 协议,客户端无需了解服务端如何选择模型。

各品牌调用规范

不同品牌的参数写法和素材字段不完全相同,接入前请先阅读对应品牌页:

品牌页模型
Seedance 官方版seedance-2.5官方版、seedance-2.0官方版、seedance-2.0-fast官方版、seedance-2.0-mini官方版
SD 云厂商版SD2.5、SD2.0
Seedance 官转Seedance 2.0 Mini(多分辨率)、Seedance 2.0(多分辨率)、Seedance 2.0 Fast(多分辨率)、Seedance 2.5(多分辨率)
Googleveo-3.1、veo-3.1-fast、gemini-omni-flash、gemini-omni-flash-1.1
可灵 Klingkling-3.0-turbo
MiniMaxminimax-h3(同 minimax_h3)
万相 Wanwan3.0-video、wan3.0-video-prime
Grokgrok-imagine-video、grok-imagine-video-1.5

模型能力总表

序号模型能力/公开名称时长比例输出图/视频/音频上限首尾帧可控音效
1seedance-2.5官方版4–30 秒,默认 4 秒16:9 9:16 1:1 4:3 3:4 21:9720P30 / 10 / 10支持,与普通素材互斥不可设置
2seedance-2.0官方版4–15 秒,默认 4 秒同上720P9 / 3 / 3支持,与普通素材互斥不可设置
3seedance-2.0-fast官方版4–15 秒,默认 4 秒同上720P9 / 3 / 3支持,与普通素材互斥不可设置
4seedance-2.0-mini官方版4–15 秒,默认 5 秒同上480P/720P,默认 480P9 / 3 / 3模型说明未列出首尾帧模式不提供生成音频开关
5SD2.54–30 秒必须指定:16:9 9:16 1:1 4:3 3:4 21:9480P/720P/1080P,默认 480P30 / 10 / 10支持,与普通素材互斥支持 generate_audio
6SD2.04–15 秒1:1(默认) 21:9 16:9 9:16 3:4 4:3480P/720P/1080P/4K,默认 480P9 / 3 / 3,三类合计最多12支持,与普通素材互斥不可设置
7Seedance 2.0 Mini(多分辨率)4–15 秒;支持 -1,默认 5 秒adaptive(默认) 16:9 4:3 1:1 3:4 9:16 21:9480P/720P9 / 3 / 3支持,与普通素材互斥默认带音频,可关闭
8Seedance 2.0(多分辨率)4–15 秒;支持 -1,默认 5 秒同上480P/720P/1080P/4K9 / 3 / 3支持,与普通素材互斥默认带音频,可关闭
9Seedance 2.0 Fast(多分辨率)4–15 秒;支持 -1,默认 5 秒同上480P/720P9 / 3 / 3支持,与普通素材互斥默认带音频,可关闭
10Seedance 2.5(多分辨率)4–30 秒;支持 -1,默认 -1(自动)同上480P/720P/1080P30 / 10 / 10支持,与普通素材互斥默认带音频,可关闭
11veo-3.1-fast4/6/8 秒,参考图固定 8 秒,延长每次新增 7 秒16:9 9:16720P/1080P/4K3 / 0 / 0支持首帧、首尾帧和参考图原生音频,generate_audio 可关闭
12veo-3.14/6/8 秒,参考图固定 8 秒,延长每次新增 7 秒16:9 9:16720P/1080P/4K3 / 0 / 0支持首帧、首尾帧和参考图原生音频,generate_audio 可关闭
13gemini-omni-flash3–10 秒16:9 9:16720P10 / 1 / 0支持首帧、首尾帧不支持
14gemini-omni-flash-1.13–10 秒,延长每次新增约 10 秒16:9 9:16720P/1080P/4K10 / 3 / 0支持首帧、首尾帧不支持
15kling-3.0-turbo4–15 秒16:9 9:16 1:1720P/1080P/2K/4K,默认 720P30(含首尾帧)/ 10 / 0支持,可与参考图同用支持 generate_audio
16minimax-h34–15 秒,默认 5 秒16:9(默认) 9:16 1:1 4:3 3:4 21:9768P/1080P/2K(超分),默认 768P9 / – / 3支持,与普通素材互斥成片自带音轨,不可设置
17wan3.0-video2–30 秒,默认 5 秒;支持 -1adaptive(默认) 16:9 9:16 1:1 4:3 3:4 21:9480P/720P/1080P,默认 1080P10 / 5 / 5(使用 media 数组)支持,与普通素材互斥不可设置
18wan3.0-video-prime同上同上同上同上同上不可设置
19grok-imagine-video1–15 秒,默认 8 秒16:9 9:16 1:1 4:3 3:4 3:2 2:3(用 aspect_ratio)480P/720P1 / 0 / 0不支持不支持
20grok-imagine-video-1.51–15 秒,默认 8 秒同上480P/720P/1080P1 / 0 / 0不支持不支持

表中序号从 1 开始,仅用于阅读,不是请求参数。表中的公开名称和能力系列也仅用于对照;实际调用时必须使用当前 API Key 从 /v1/models 返回的精确 id,不要自行拼接名称或推断映射关系。

模型特有规则

各品牌的字段差异较大,以下是最容易出错的几点,完整规则见上方各品牌页:

  • 万相 wan3.0-video* 的首帧、尾帧和参考素材推荐放在 media 数组;也接受 metadata.firstFrame/lastFrame/referenceImages/referenceVideos/referenceAudios(含下划线写法)、顶层 firstFrame/first_frame/lastFrame/last_frame 和顶层 images(第 1 张首帧、第 2 张尾帧,最多 2 张),都会转为 media。无法识别的素材字段会返回 400。
  • Grok 的 aspect_ratio 与 ratio 等价,同时填写多个比例字段时取值必须一致;最多 1 张输入图片。
  • minimax-h3 的参考图片用 metadata.referenceImages,首尾帧用 metadata.firstFrame/lastFrame;顶层 images 的含义会随分配线路不同,不要依赖。480P、4K 已下架。
  • Veo 和 Gemini Omni 的参考视频使用顶层 video/videos,不使用 metadata.referenceVideos。
  • Seedance 官转也接受单数字段 image、input_reference、video、audio 作为参考素材,同一素材重复填写只计一次;size 与 ratio、aspect_ratio 或 resolution 冲突时返回 400。该组模型还支持虚拟人像库、2.5 样片、尾帧、联网搜索、取消排队任务和官方任务 ID,见 Seedance 官转。
  • SD2.5 必须明确指定比例,不支持 auto。
  • Seedance 官方版、SD2.5、SD2.0 和 kling-3.0-turbo 每个系列只有一个模型名,用 resolution 选择档位并按档位计费;不传时使用该系列最低档,不支持的档位返回 400。此前带档位后缀的旧名称(如 SD2.5 720P、SD2.0 4K)仍可调用,但固定为名称中的档位,已不在模型广场展示;可灵带档位后缀的旧名称已下架。

7. 提交响应

提交成功后会立即返回公开任务 ID:

{
  "id": "task_xxxxxxxxxxxxxxxxxxxxxxxx",
  "object": "video",
  "model": "<MODEL_ID>",
  "status": "queued",
  "progress": 0,
  "created_at": 1788249600
}

请保存 id。标准接口不保证返回 task_id,新接入必须使用 id。

HTTP 200 表示“任务已被接收”,不代表视频已生成完成。

8. 轮询任务

curl 'https://api.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": "<MODEL_ID>",
  "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": "<MODEL_ID>",
  "status": "failed",
  "progress": 100,
  "error": {
    "code": "video_generation_failed",
    "message": "视频生成失败"
  }
}

轮询策略

  1. 提交后等待 3–5 秒再进行第一次查询。
  2. 建议每 10–15 秒查询一次,不要每秒轮询。
  3. 交互客户端可采用 15分钟等待预算,服务端可持久化任务ID继续查询或接收回调。客户端停止等待不等于任务失败、取消或退款。
  4. queued 或 in_progress 期间不要重复提交同一任务,否则可能重复计费。
  5. 同时配置回调时,轮询可以作为容灾备用机制。

单次HTTP请求超时、客户端总等待时间和服务端任务期限是不同概念。不要将15分钟写成服务端必然终止的保证,也不要只凭 progress=100 判成功;以 status 为准。恢复页面或网络后可继续查询原任务。

9. 回调(Webhook)

本节仅适用于明确支持回调的型号。Google 视频模型(Veo、Gemini Omni)以及其他未明确支持回调的模型请使用轮询,不设置 callback_url。

提交时设置

{
  "model": "<MODEL_ID>",
  "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.completed
  • video.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": "<MODEL_ID>",
    "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": "<MODEL_ID>",
    "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,耗时业务逻辑放入自己的异步队列。

10. 获取视频文件

任务进入 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": "<MODEL_ID>",
  "prompt": "保持参考视频的主体与动作,延续镜头",
  "duration": 4,
  "ratio": "16:9",
  "metadata": {
    "referenceVideos": ["<上一任务返回的完整HTTPS结果URL>"]
  }
}

11. JavaScript 完整轮询示例

Node.js 22+;在服务端设置 API_KEY、MODEL_ID 和 REFERENCE_FILE(MP4文件路径)。MODEL_ID 应来自当前 API Key 的 GET /v1/models 响应。设置 TASK_ID 可直接恢复旧任务,不重新上传或生成。示例会创建一次收费生成,最后仅打印下一轮请求体,不自动发起第二次生成。为便于阅读使用文件缓冲,大文件批量处理应限制并发。

// video-api-example.mjs — Node.js 22+, run with API_KEY, MODEL_ID, 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://api.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.MODEL_ID) throw new Error("Set MODEL_ID from GET /v1/models");
    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: process.env.MODEL_ID, prompt: "保持参考视频的主体与动作,延续镜头",
      duration: 4, ratio: "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: process.env.MODEL_ID, prompt: "保持参考视频的主体与动作,延续镜头",
    duration: 4, ratio: "16:9", metadata: { referenceVideos: [resultURL] },
  }, null, 2));
}

await main();

12. 错误处理与重试

情况处理建议
HTTP 400检查模型名、时长、比例、素材数量与字段位置
HTTP 401API Key 缺失、错误或已失效
HTTP 403当前 Key 无模型或分组权限
HTTP 404任务不存在、不属于当前账户,或成品尚未就绪
上传 HTTP 409指定对象已存在且不可覆盖;先HEAD确认并复用成功的地址
媒体 HTTP 410文件已到期;读取和重复引用不会续期,清理后也可能为404
上传 HTTP 415文件签名与声明MIME不符;不能仅改扩展名冒充其他格式
媒体 HTTP 416Range超出文件边界或格式不支持;重新HEAD确认大小
HTTP 429请求过快或额度限制;按 Retry-After 或指数退避
HTTP 5xx暂时服务错误;查询请求可退避重试
status=failedHTTP 查询成功但生成失败;读取 error.message
素材下载失败确认 URL 公网可达、未过期、无防盗链和鉴权依赖
unsupported_reference_media模型不支持该参考类型,上传或换域名不能解决
must be a public HTTPS URL本地文件先上传,不能提交Blob、Data URL、HTTP或需要登录的地址
input_download_failed检查目标服务实际可达性、DNS/网络、媒体有效期,不只是本机能否打开
内容安全拦截调整提示词或参考素材,不要原样无限重试

POST /v1/videos 暂不提供客户自定义的幂等键保证。如果客户在已发出完整请求后遇到网络超时,不要盲目立即重发,否则可能生成并计费多个任务。