视频生成 API 接入文档
版本 v1.1 | 更新日期 2026-09-27 | 网关:https://www.20saminos.cn
目录
1. 概述
本网关提供 Seedance / MiniMax 系列视频生成能力,接口兼容 OpenAI Chat Completions 协议——任何支持该协议的 SDK 或客户端都可以直接调用。
| 项目 |
值 |
| 网关地址 |
https://www.20saminos.cn |
| 提交端点 |
POST /v1/chat/completions |
| 调用方式 |
两段式:先提交任务拿 TASK_ID,再轮询状态取视频地址 |
| 数据格式 |
JSON,Content-Type: application/json |
调用链路:
1
| 调用方 → 本网关(鉴权 · 计费 · 限流)→ 协议适配层 → 视频模型上游
|
2. 鉴权
所有请求需在请求头携带平台令牌:
1
| Authorization: Bearer sk-xxxxxxxxxxxxxxxx
|
注意:令牌在平台后台「令牌管理」创建,创建时分组必须选择 default,否则请求会返回「无可用渠道」。
3. 可用模型
| 模型 ID |
时长(秒) |
分辨率 |
画面比例 |
说明 |
seedance-2.0 |
5 / 15 |
720p |
9:16、1:1、3:4、4:3、16:9 |
综合质量最好 |
seedance-2.5 |
14 / 30 |
720p |
同上 |
长视频,最长 30 秒 |
minimaxh3 |
4 ~ 15(整数) |
2K |
同上 |
支持参考视频/音频,任意时长同价 |
分辨率由模型固定,不需要也不能覆盖(传入会被忽略)。
4. 提交任务
4.1 请求
1 2 3
| POST /v1/chat/completions Authorization: Bearer sk-xxx Content-Type: application/json
|
1 2 3 4 5 6 7 8 9 10
| { "model": "seedance-2.0", "messages": [ { "role": "user", "content": "一只小狗在草地上奔跑,镜头缓慢推进,阳光自然,画面稳定" } ], "duration": 5, "ratio": "9:16", "mode": "text-to-video", "request_id": "order_20260927_001" }
|
4.2 参数说明
| 参数 |
必填 |
类型 |
说明 |
model |
✅ |
string |
见第 3 节模型表 |
messages |
✅ |
array |
提示词写在最后一条 user 消息的 content 里,长度 1–10000 字符 |
duration |
|
int |
秒数,取值受模型限制;不传则用默认值(2.0 → 5、2.5 → 14、minimaxh3 → 15) |
ratio |
|
string |
画面比例,默认 9:16 |
mode |
|
string |
text-to-video(默认)、reference(参考图生视频)、first-frame(首帧生视频) |
images |
|
array |
参考图/首帧图 URL 数组,最多 9 张,详见 4.4 |
request_id |
|
string |
幂等键,建议传唯一订单号。同一 request_id 重复提交不会重复扣费 |
兜底写法:如果客户端不方便传顶层参数,可以写在提示词末尾,例如
...影调偏暖 [duration=5 ratio=16:9]
4.3 响应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "id": "chatcmpl-c8e39d2d90f045bc95a7", "object": "chat.completion", "created": 1790473750, "model": "koko", "choices": [ { "index": 0, "finish_reason": "stop", "message": { "role": "assistant", "content": "TASK_ID: ee9e3705d6dd4693a50ebb8602a863ba\nSTATUS: queued\nPOINTS: 8\nBALANCE: 1064" } } ] }
|
content 中是若干行 键: 值,按行解析即可:
| 字段 |
含义 |
TASK_ID |
任务编号,务必保存,查询和取片都用它 |
STATUS |
提交后的即时状态,通常为 queued 或 running |
POINTS |
本次扣除的积分 |
BALANCE |
账户剩余积分 |
4.4 参考图与首帧图
除了纯文生视频,还支持上传图片作为参考或首帧:
| 参数 |
说明 |
images |
图片 URL 数组,1–9 张 |
mode |
first-frame:首帧生视频(取 images 第 1 张)
reference:参考图生视频 不传且带 images 时默认按 reference 处理 |
图片要求(重要)
上游服务器会主动去下载你提供的图片,因此必须满足:
| 检查项 |
要求 |
| 协议 |
必须 HTTPS,http 不支持 |
| 可达性 |
公网可直连,不能是内网地址、不能需要登录/Cookie/Basic Auth |
| 防盗链 |
不能有 Referer 校验,否则上游会拿到 403 |
| Content-Type |
必须是 image/*(见下方常见坑) |
| 格式 |
JPG / PNG / WebP |
| 大小 |
单张 ≤ 50 MB |
| 数量 |
最多 9 张 |
⚠️ 最常见的坑:只看状态码 200 是不够的。
很多反向代理站点对不存在的路径会返回 200 + text/html(前端首页),看起来”能访问”,但上游拿到的是网页不是图片,会直接报错。必须确认 Content-Type 是 image/*。
自检命令:
1
| curl -sI "https://your-cdn.com/first.png" | grep -iE 'HTTP|content-type|content-length'
|
| 返回 |
判定 |
200 + content-type: image/png |
✅ 可用 |
200 + content-type: text/html |
❌ 拿到的是网页 |
403 / 404 / 超时 |
❌ 上游拿不到 |
示例:首帧生视频
1 2 3 4 5 6 7 8 9 10 11
| { "model": "seedance-2.0", "messages": [ { "role": "user", "content": "冰淇淋变成一个跳舞的粑粑" } ], "duration": 5, "ratio": "9:16", "mode": "first-frame", "images": ["https://img.example.com/first.png"], "request_id": "ff_test_001" }
|
示例:参考图生视频
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "model": "seedance-2.0", "messages": [ { "role": "user", "content": "画面中的猫咪缓缓睁开眼睛,镜头轻微推近" } ], "duration": 5, "ratio": "1:1", "mode": "reference", "images": [ "https://img.example.com/cat.png", "https://img.example.com/style.png" ] }
|
最佳实践
- 比例对齐:
ratio 要与图片比例一致(竖图用 9:16,横图用 16:9),否则首帧会被裁切或拉伸。
prompt 描述动作和镜头运动,不要重复描述静态画面——首帧已经决定了画面起点。
- 图片放独立静态站点或对象存储(公共读),不要放在有登录态、防盗链或反向代理兜底成 HTML 的站点上。
- 图片处理失败会自动退还积分,不用担心扣费。
5. 查询任务状态
沿用同一个端点,把 model 换成带 -status 后缀的模型(该模型不计费),消息体中放入 TASK_ID:
1 2 3 4 5 6
| { "model": "seedance-2.0-status", "messages": [ { "role": "user", "content": "TASK_ID: ee9e3705d6dd4693a50ebb8602a863ba" } ] }
|
响应:
1 2 3 4 5 6 7 8 9
| { "choices": [ { "message": { "content": "TASK_ID: ee9e3705d6dd4693a50ebb8602a863ba\nSTATUS: completed\nVIDEO_URL: https://example.com/video.mp4\nFAILURE_REASON: " } } ] }
|
状态说明
STATUS |
含义 |
处理方式 |
queued |
已扣费,等待提交上游 |
继续轮询 |
running |
生成中 |
继续轮询 |
completed |
生成完成 |
读取 VIDEO_URL |
failed |
生成失败,积分已自动退回 |
查看 FAILURE_REASON |
轮询建议:提交后每隔 5–10 秒查询一次,直到 completed 或 failed。多数任务在 1–5 分钟内完成,请勿高频轮询。
⚠️ 关于 VIDEO_URL
返回的 VIDEO_URL 是带签名的临时下载链接,有有效期。过期后再次访问会返回:
1 2 3 4 5 6
| { "error": { "code": "INVALID_DOWNLOAD_SIGNATURE", "message": "视频下载链接无效或已过期,请重新查询任务状态获取新链接" } }
|
处理方式:
- 不要直接把该链接交付终端用户长期使用;
- 拿到后请立即下载并转存到自己的对象存储/CDN;
- 如果链接已失效,重新调用本接口查询任务即可获取新链接(任务本身不会失效)。
6. 完整示例
6.1 cURL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| curl -s https://www.20saminos.cn/v1/chat/completions \ -H "Authorization: Bearer sk-xxx" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance-2.0", "messages": [{"role": "user", "content": "一只小狗在草地上奔跑,镜头缓慢推进"}], "duration": 5, "ratio": "9:16", "request_id": "order_001" }'
curl -s https://www.20saminos.cn/v1/chat/completions \ -H "Authorization: Bearer sk-xxx" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance-2.0-status", "messages": [{"role": "user", "content": "TASK_ID: ee9e3705d6dd4693a50ebb8602a863ba"}] }'
|
6.2 首帧生视频(cURL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| cat > /tmp/first.json <<'EOF' { "model": "seedance-2.0", "messages": [{"role": "user", "content": "冰淇淋变成一个跳舞的粑粑"}], "duration": 5, "ratio": "9:16", "mode": "first-frame", "images": ["https://img.example.com/first.png"], "request_id": "ff_test_002" } EOF
curl -s https://www.20saminos.cn/v1/chat/completions \ -H "Authorization: Bearer sk-xxx" \ -H "Content-Type: application/json" -d @/tmp/first.json
|
6.3 Python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import re import time import uuid import requests
BASE = "https://www.20saminos.cn/v1/chat/completions" TOKEN = "sk-xxx" HEAD = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
def submit(prompt, model="seedance-2.0", duration=5, ratio="9:16", mode="text-to-video", images=None): """提交任务,返回 TASK_ID""" body = { "model": model, "messages": [{"role": "user", "content": prompt}], "duration": duration, "ratio": ratio, "mode": mode, "request_id": uuid.uuid4().hex, } if images: body["images"] = images resp = requests.post(BASE, json=body, headers=HEAD, timeout=60) text = resp.json()["choices"][0]["message"]["content"] return re.search(r"TASK_ID:\s*(\S+)", text).group(1)
def wait(task_id, model="seedance-2.0", interval=8, timeout=900): """轮询直到完成,返回视频地址""" t0 = time.time() while time.time() - t0 < timeout: resp = requests.post(BASE, headers=HEAD, timeout=30, json={ "model": f"{model}-status", "messages": [{"role": "user", "content": f"TASK_ID: {task_id}"}], }) text = resp.json()["choices"][0]["message"]["content"] status = re.search(r"STATUS:\s*(\S+)", text).group(1) if status == "completed": return re.search(r"VIDEO_URL:\s*(\S+)", text).group(1) if status == "failed": raise RuntimeError(re.search(r"FAILURE_REASON:\s*(.*)", text).group(1)) time.sleep(interval) raise TimeoutError("生成超时")
if __name__ == "__main__": task = submit("一只小狗在草地上奔跑,镜头缓慢推进") print("视频地址:", wait(task))
task = submit("冰淇淋变成一个跳舞的粑粑", mode="first-frame", images=["https://img.example.com/first.png"]) print("视频地址:", wait(task))
|
7. 错误码
错误响应为 OpenAI 风格:
1 2 3 4 5 6 7
| { "error": { "message": "seedance-2.0 只支持 [5, 15],收到 8", "type": "koko_error", "code": "INVALID_DURATION" } }
|
| HTTP |
code |
说明 |
| 400 |
invalid_request |
请求体结构问题(如缺少 messages) |
| 400 |
MODEL_NOT_SUPPORTED |
模型 ID 不在支持列表 |
| 400 |
INVALID_PROMPT |
提示词为空或超过 10000 字符 |
| 400 |
INVALID_DURATION |
时长不符合该模型规格 |
| 400 |
INVALID_RATIO |
画面比例不支持 |
| 400 |
INVALID_MODE |
mode 取值不支持 |
| 400 |
INVALID_REFERENCE_MEDIA |
参考素材不是公网 HTTPS URL |
| 400 |
INVALID_REFERENCE_IMAGES |
参考图字段、数量或格式不符合要求 |
| 400 |
REFERENCE_UPLOAD_FAILED |
参考图下载/处理失败,积分已自动退回 |
| 401 |
INVALID_API_KEY |
令牌无效或已停用 |
| 402 |
INSUFFICIENT_POINTS |
积分不足,未扣费 |
| 404 |
TASK_NOT_FOUND |
任务不存在或不属于当前账号 |
| 429 |
RATE_LIMITED |
超过每分钟 120 次请求 |
| 502 |
UPSTREAM_ERROR |
上游提交/查询失败;若 message 含「参考图处理失败」,通常是图片地址返回的不是图片 |
| 503 |
MODEL_MAINTENANCE |
模型维护中,未扣积分 |
| 504 |
UPSTREAM_TIMEOUT |
上游超时,积分已退回 |
| 4xx |
无可用渠道 |
令牌分组与模型不匹配 |
8. 计费
- 按次计费,与 token 数、视频文件大小、参考图数量无关。
- 扣费发生在提交成功时;提交失败、参数校验失败、参考图处理失败、模型维护、任务最终失败都会自动退回。
- 同一
request_id 最多成功扣费一次。
| 模型 |
积分/次 |
折合人民币 |
seedance-2.0 |
8 |
约 ¥0.8 |
seedance-2.5 |
待确认 |
— |
minimaxh3 |
待确认 |
— |
*-status(查询) |
0 |
免费 |
价格以平台后台实时配置为准。
9. 限制与注意事项
- 同一提示词不保证结果一致,视频生成具有随机性。
VIDEO_URL 是临时签名链接,会过期,请立即下载转存(详见第 5 节)。
- 参考图/首帧图必须是公网 HTTPS 且能被上游直接访问,
Content-Type 必须为 image/*,单张 ≤ 50 MB、最多 9 张。内网地址、需鉴权地址、防盗链地址都会导致失败(积分自动退回)。
- 参考图比例需与
ratio 一致,否则首帧会被裁切或拉伸。
- 单令牌限速 120 次/分钟,批量生成请做好排队与退避重试。
- 幂等重试:网络超时时,请使用相同的
request_id 重发,不会重复扣费。
- 上游异常格式的视频会被平台自动转为浏览器可播放的 H.264 MP4。
附录 A:服务端实现说明
内部内容,不需要发给接入方。
A.1 功能实现状态
| 功能 |
状态 |
说明 |
| 提交任务(文生视频) |
✅ 已实现 |
/v1/chat/completions |
查询任务(*-status) |
✅ 已实现 |
消息中识别 TASK_ID |
参考图 / 首帧图(images) |
✅ 已实现 |
映射为上游 reference_images |
取片代理(/koko/content/{id}) |
⬜ 待添加 |
见 A.3,可解决链接过期问题 |
A.2 New API 侧配置
- 渠道类型:高级自定义
- Base URL:
http://172.18.0.1:8787
- 上游路径:
http://172.18.0.1:8787/v1/chat/completions
- 认证:无认证(key 已移到适配层环境变量)
- 透传请求体:开启
- 参数覆盖:留空
- 渠道模型:
seedance-2.0、seedance-2.5、minimaxh3,以及 seedance-2.0-status、seedance-2.5-status、minimaxh3-status
- 计费:按次固定价;
*-status 三个模型价格设为 0,否则每次轮询都会扣一整次视频费用
A.3 取片代理端点(待添加,推荐)
上游的 VIDEO_URL 是临时签名链接,客户侧体验差。加一个代理端点即可提供稳定取片:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| from fastapi.responses import StreamingResponse
@app.get("/koko/content/{task_id}") async def content(task_id: str): client = httpx.AsyncClient(timeout=300) req = client.build_request( "GET", f"{KOKO_BASE}/videos/{task_id}/content", headers={"Authorization": f"Bearer {KOKO_KEY}"}) r = await client.send(req, stream=True) if r.status_code not in (200, 206): await r.aclose(); await client.aclose() return fail(502, "CONTENT_FAILED", "取视频失败") return StreamingResponse( r.aiter_bytes(), media_type=r.headers.get("content-type", "video/mp4"), headers={"Content-Disposition": f'inline; filename="{task_id}.mp4"'})
|
优势:不需要 key、不受签名过期影响、支持大文件流式传输。
两种用法:
| 用法 |
说明 |
| 后端转存(推荐) |
后端调 http://172.18.0.1:8787/koko/content/{task_id} 下载后转存到自己的存储,对外发自己的永久链接 |
| 直接对外 |
需 nginx 反代 + 额外鉴权,否则视频会裸露在公网 |
A.4 重启与验证
1 2
| systemctl restart koko-adapter journalctl -u koko-adapter -n 20 --no-pager
|
附录 B:运维备忘
内部内容,不需要发给接入方。
| 项目 |
位置 / 命令 |
| 适配层代码 |
/opt/koko-adapter/koko_adapter.py |
| 上游密钥 |
/etc/koko-adapter.env(权限 600) |
| 重启 |
systemctl restart koko-adapter |
| 日志 |
journalctl -u koko-adapter -f |
| 监听地址 |
172.18.0.1:8787(baota_net 网关,仅容器可访问) |
| 参考图静态站点 |
img.20saminos.cn(纯静态,图片放站点根目录) |
| New API 部署 |
compose 项目 /www/dk_project/dk_app/newapi/newapi_25CJ,容器 newapi_25cj-new-api-1 |
| ⚠️ 防火墙 |
需放行网桥 br-b5debb1b700d 入站,重启或重载防火墙后会失效(Ubuntu:ufw allow from 172.18.0.0/16 to any port 8787 proto tcp,或在宝塔面板加规则)。更稳的做法是把适配层容器化,走容器互访 |
| ⚠️ 密钥轮换 |
sk-hIIr... 与 koko_live_a23c... 曾明文暴露,需在 New API 和 kokoai 后台分别重建 |
更新日志
| 版本 |
日期 |
变更 |
| v1.0 |
2026-09-27 |
初版:概述、鉴权、模型、提交、查询、示例、错误码、计费、限制 |
| v1.1 |
2026-09-27 |
新增「4.4 参考图与首帧图」;补充 VIDEO_URL 临时签名链接说明;错误码表新增 4 项参考图相关;新增附录 A.3 取片代理端点 |