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

视频 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/json

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

操作鉴权
创建、查询任务;申请上传凭证Authorization: Bearer ...,只发到 88api.ai API
文件上传原样使用 headers 中的 X-Media-Upload-TokenContent-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"
}

公共请求字段

字段类型必填说明
modelstring88API 模型广场中显示的完整模型名
promptstring条件必填文本提示词;仅部分模型允许有参考素材时留空
negative_promptstring多参考模型的负面提示词;Veo 使用 metadata.negativePrompt
durationinteger视频时长,单位秒;与 seconds 二选一
secondsstring字符串形式的视频时长,例如 "8"
sizestring画面比例或尺寸,例如 16:91280x720
imagestring单张参考图片 URL
imagesstring[]参考图片 URL 数组,推荐用法
videostring单个参考视频 URL;Gemini Omni 必须使用此顶层字段
videosstring[]参考视频数组;其他多参考模型使用 metadata.referenceVideos
seedinteger随机种子
generate_audioboolean支持的模型上开启同步生成音频
camera_controlobject镜头控制;仅在对应模型明确支持时使用
callback_urlstring仅支持回调的型号可用;上方列出的轮询型号不接受此字段
metadataobject参考视频、音频、首尾帧、比例等扩展字段

分辨率由销售模型名锁定。例如 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"
  ]
}

单图也可以使用 imageinput_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"
    ]
  }
}

同一类素材不要同时使用多个别名字段。例如不要同时提交顶层 imagesmetadata.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 可用也不代表其编码、时长或尺寸符合目标模型要求。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 一致
sha256SHA-256 的32字节摘要做 Base64URL 编码,不带 =;不是64位十六进制字符串

支持上传:video/mp4video/webmvideo/quicktimeaudio/mpegaudio/wavaudio/oggaudio/mp4audio/flacimage/pngimage/jpegimage/webpimage/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_urlmethodheaders

# 以下变量来自申请凭证的响应;不要把 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 表示上传完成,返回 urlsizemime_typeexpires_at这次 expires_at 才是素材到期时间(Unix秒,自该对象上传起30天)。确认成功后,将 url 放入目标模型要求的参考字段。浏览器应由自己的后端申请凭证,然后直接上传,不能在前端暴露长期 API Key。

同一上传地址不允许覆盖:重复 PUT 返回409,应复用已经成功上传的地址;遇到不确定的网络中断,先匿名 HEAD 检查该 URL,不要立刻重复申请新对象。上传和引用不会产生新的生成任务。

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

首帧和尾帧放在 metadata.firstFramemetadata.lastFrame

下例适用于支持该字段的多参考模型。Veo 使用 imagesmetadata.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_modeimages规则
不传旧单图兼容方式,仅第一张新接入建议显式选择模式
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.negativePromptmetadata.seedmetadata.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-videogrok-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:16720P/1080P图片按模式;0视频/0音频按模式及开放能力原生音频,可用 metadata.generateAudio
veo-3.1基础4/6/8秒,参考图8秒16:9 9:16720P/1080P图片按模式;0视频/0音频按模式及开放能力原生音频,可用 metadata.generateAudio
grok-imagine-video1–15 秒16:9 9:16 1:1 4:3 3:4 3:2 2:3480P/720P1 / 0 / 0不支持不支持
grok-imagine-video-1.51–15 秒同上480P/720P1 / 0 / 0不支持不支持
grok-imagine-video-1.5-1080p1–15 秒同上1080P1 / 0 / 0不支持不支持
gemini-omni-flash3–10 秒16:9 9:16720P10 / 1 / 0不支持不支持
SD2.5 720P4–30 秒auto + 6 种常用比例720P30 / 10 / 10支持,与普通素材互斥支持 generate_audio
SD2.5 480P4–30 秒auto + 6 种常用比例480P30 / 10 / 10支持,与普通素材互斥支持 generate_audio
SD2.0 720P4–15 秒1:1 21:9 16:9 9:16 3:4 4:3720P9 / 3 / 3,三类合计最多 12支持,与普通素材互斥不可设置
SD2.5 1080P4–30 秒auto + 6 种常用比例1080P30 / 10 / 10支持,与普通素材互斥支持 generate_audio
SD2.0 480P4–15 秒同 SD2.0 720P480P9 / 3 / 3,三类合计最多 12支持,与普通素材互斥不可设置
SD2.0 1080P4–15 秒同 SD2.0 720P1080P9 / 3 / 3,三类合计最多 12支持,与普通素材互斥不可设置
minimax-h3-768p以当前开放能力为准以当前开放能力为准768P参考视频及数量须确认,见下文按开放能力不可设置
Seedance-2.5-720p官方版4–30 秒16:9 9:16 1:1 4:3 3:4 21:9720P30 / 10 / 10支持不可设置
Seedance-2.0-720p官方版4–15 秒同上720P9 / 3 / 3支持,与普通素材互斥不可设置
Seedance-2.0-fast-720p官方版4–15 秒同上720P9 / 3 / 3支持,与普通素材互斥不可设置
seedance-2.0-mini-480p4–15 秒同上480P9 / 3 / 3未公开,建议不传不可设置
seedance-2.0-mini-720p4–15 秒同上720P9 / 3 / 3未公开,建议不传不可设置
wan3.0-video-480p4–30 秒16:9 9:16 1:1 4:3 3:4480P30 / 10 / 10支持不可设置
wan3.0-video-720p4–30 秒16:9 9:16 1:1 4:3 3:4720P10 / 5 / 5支持,与普通素材互斥不可设置
wan3.0-video-1080p4–30 秒同上1080P10 / 5 / 5支持,与普通素材互斥不可设置
kling-3.0-turbo-720p4–15 秒16:9 9:16 1:1720P30 / 10 / 0支持支持 generate_audio
kling-3.0-turbo-1080p4–15 秒同上1080P30 / 10 / 0支持支持 generate_audio
kling-3.0-turbo-2k4–15 秒同上2K30 / 10 / 0支持支持 generate_audio
kling-3.0-turbo-4k4–15 秒同上4K30 / 10 / 0支持支持 generate_audio

SD2.5 720P 的“6 种常用比例”是 1:121:916:99:163:44: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.codeerror.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": "视频生成失败"
  }
}

轮询策略

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

单次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.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": "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_KEYREFERENCE_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 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 暂不提供客户自定义的幂等键保证。如果客户在已发出完整请求后遇到网络超时,不要盲目立即重发,否则可能生成并计费多个任务。