文字生视频
提示词,生成视频。
创建任务,跟踪进度,获取视频成片。
参考图生视频
参考图,让画面动起来。
延续人物与场景的视觉方向,生成新视频。
脚本分镜
脚本分镜,逐镜成片。
按镜头组织创作,让整段故事连贯呈现。
工作台能力说明
能做什么测连通 / 拉模型 / 加入任务 / 立即刷新 / 取消任务 / 清除任务 / 刷新列表 / 全部提交上游 / 清空任务记录,外加任务列表每行的「提交上游 · 取消 · 获取状态 · 清除」和「当前任务 / 任务列表」双页签。
安全与能力边界「测连通」走的是需要令牌的 /v1/status,不会返回账号明细;任务记录同时承担计费与退款凭据,因此「清除」采用软删除、保留账务依据;服务端只允许启动排队中的任务,失败或已取消的任务会显示为不可启动。
从旧工作台过来本页已完整承接原用户工作台的能力:令牌登录、余额、卡密兑换、批量创建、素材库、任务筛选。数据始终在同一个后台,任务与积分按令牌可直接查到,无需迁移。
令牌安全与计费令牌默认记住在本机,创建任务会扣除积分
新建视频任务
兼容本地 V1 接口的 JSON 与图片 multipart 请求。
任务
列表自动刷新:有任务在跑时约 4 秒一次,空闲时 20 秒一次;提交后任务会立刻出现在列表里(不用手动点刷新)。
阶段进度按状态估算,不是上游回报的百分比 —— 本服务不保留上游的逐条进度事件。
.mp4 即可正常播放。
- 提示词
- —
- 模型与时长
- —
- 画幅
- —
- 成片
- —
- 流水
- —
这里只展示任务的状态、阶段、失败原因与成片归档情况。本服务的任务行不保存上游的逐条对话事件,因此不会出现一条看起来像上游对话的日志。
任务原始 JSON
连接后可读取你自己的任务数据。
我的任务
尚未加载| ID | 状态 | 预计耗时 | 时长 | 评分 | 提示词 | 错误 | 操作 |
|---|---|---|---|---|---|---|---|
| 连接令牌后加载任务。 | |||||||
脚本分镜
主题生成多镜头脚本,再逐条创建视频任务。
脚本
点一条切换,或在上方生成新的分镜
选择一个脚本,或先生成一份新脚本
API 对接文档
对外开放的是 /v1 这一层。字段形状照抄参考站 dola2api 2.3.13,照着旧客户端改过来的代码基本可以直接跑。
令牌就是「视频任务」页顶部那个 dv_ 开头的值 —— 页面和 API 用的是同一个令牌、同一个余额。下面示例里的 dv_你的令牌 可以直接替换;点右上角「复制」时会自动换成当前已连接的令牌。
① 鉴权
除 /v1/healthz 与票据地址外,所有接口都要在请求头带令牌:
Authorization: Bearer dv_你的令牌
- 没有共享密钥那一层 —— 直接用你自己的用户令牌当 Bearer,一个令牌只能碰自己的任务。
- 令牌不存在 →
401 INVALID_API_KEY;被停用 →403 TOKEN_INACTIVE;过期 →403 TOKEN_EXPIRED。 - 任务归属按令牌隔离:查别人的任务号一律回
404 TASK_NOT_FOUND(不会告诉你「存在但不是你的」)。
{"code":0,"data":...};失败一律 {"error":{"message","type":"invalid_request_error","code"?}}。判断成败请用 HTTP 状态码 + 有没有 error 字段,别只看 code(成功信封的 code 恒为 0)。② 接口一览
| 方法 | 路径 | 说明 | 鉴权 |
|---|---|---|---|
| GET | /v1/healthz | 存活探测,纯文本 ok | 不需要 |
| GET | /v1/status | 令牌余额、可用档位、当日额度、就绪度 | 令牌 |
| GET | /v1/models | 模型清单(含可用秒数) | 令牌 |
| GET | /v1/model-groups | 模型分组,形状与参考站一致 | 令牌 |
| POST | /v1/videos | 创建任务(JSON 或 multipart) | 令牌 |
| GET | /v1/videos | 任务列表,?limit= 默认 20、最大 100 | 令牌 |
| GET | /v1/videos/:id | 任务详情(顺带补做失败退款) | 令牌 |
| POST | /v1/videos/:id/start | 启动一条排队中的任务 | 令牌 |
| POST | /v1/videos/start | 批量启动,ids 最多 100 | 令牌 |
| POST | /v1/videos/:id/cancel | 取消(还在列表里,状态变成已取消) | 令牌 |
| DELETE | /v1/videos/:id | 清除 = 取消 + 从列表隐藏(软删除) | 令牌 |
| DELETE | /v1/videos | 批量清除,ids 最多 200;不带 ids = 全部 | 令牌 |
| GET | /v1/videos/:id/content | 302 跳到成片地址 | 令牌 |
| GET | /v1/files/:ticket | 成片字节流,支持 Range(拖动进度条) | 票据自带 |
| GET | /v1/videos/:id/reference-images | 这条任务带了哪些参考图 | 令牌 |
| GET | /v1/videos/:id/reference-images/:name | 取一张参考图的字节 / 缩略图 | 票据 或 令牌 |
| POST | /v1/redeem | 卡密兑换积分(只充给令牌自己) | 令牌 |
| GET | /v1/accounts | 令牌自身信息 | 令牌 |
/v1 下任何没列出的路径都回 404 NOT_FOUND 且是 JSON,不会掉进 SPA 返回一坨 HTML。③ 创建任务
| 字段 | 必填 | 说明 |
|---|---|---|
| prompt | 是 | 提示词,1~12000 字,trim 后不能为空 |
| seconds | 否 | 15 或 30,默认 30(10 / 20 秒档位已下线) |
| model | 否 | seedance_v2.0(15 秒)/ seedance_v2.5(30 秒)。不传就按 seconds 自动选;传了与 seconds 不匹配的组合直接 400 |
| size | 否 | 16:9 / 9:16 / 1:1,也接受像素 720x1280(自动约分)。默认 16:9。别名 ratio |
| auto_start | 否 | 默认 true。传 false 只建任务并冻结积分,之后要自己调 /start |
| images | 否 | JSON 专用:base64 数组,最多 9 张,整包 ≤ 50 MiB |
| input_reference | 否 | multipart 专用:参考图文件字段,可重复;也认 images / image |
| mode | 否 | standard / expert;不传按 seconds 推(15 秒 → expert) |
| account_id | 否 | 指定用哪个账号;配 strict_account=true 表示只用这一个、不够就不跑 |
| force_seconds | 否 | 会真的改变上游时长并据此计价,慎用 |
| reference_source_task | 否 | 复用某条任务的参考图(失败重试用),配 reference_source_keep 可只保留其中几张 |
| audio | 不支持 | 传了直接 400 —— 本服务没有音频能力,静默忽略等于骗人 |
| points | 不支持 | 由服务端计价,传了直接 400 |
请求(JSON)
curl -X POST https://admin.fei85.cn/v1/videos \
-H "Authorization: Bearer dv_你的令牌" \
-H "Content-Type: application/json" \
-d '{"prompt":"海边日落,镜头缓慢横移","seconds":30,"size":"16:9","auto_start":true}'请求(multipart 带参考图)
curl -X POST https://admin.fei85.cn/v1/videos \ -H "Authorization: Bearer dv_你的令牌" \ -F "prompt=海边日落,镜头缓慢横移" \ -F "seconds=30" \ -F "size=16:9" \ -F "input_reference=@./ref1.png" \ -F "input_reference=@./ref2.png"
响应 202 Accepted
{
"code": 0,
"data": {
"id": 1234,
"task_id": 1234,
"object": "video",
"status": "queued",
"stage": "排队中",
"model": "seedance_v2.5",
"seconds": 30,
"ratio": "16:9",
"auto_start": true,
"started": true,
"charged_points": 1,
"balance": 99,
"charge_ref": "...",
"reference_source_task": null,
"reused_reference_count": 0,
"created_at": "2026-10-01T08:00:00.000Z"
}
}id(= task_id)后面查询和取片都用它;charged_points 是本次实扣积分、balance 是扣完的余额。stage 是给界面看的中文描述,会随流程变,不要拿它判断状态。GET /v1/videos/:id)时若发现该退未退会顺手补退,响应里的 refunded / refunded_points 就是这次补退的结果。已经提交到上游的任务取消不退 —— 上游额度已经花掉了。④ 查询任务
{
"code": 0,
"data": {
"id": 1234,
"object": "video",
"status": "ready",
"stage": "已归档",
"prompt": "海边日落,镜头缓慢横移",
"ratio": "16:9",
"seconds": 30,
"duration_sec": 30.08,
"bytes": 21800000,
"archived": true,
"is_unwatermarked": false,
"unwatermark_note": null,
"error": null,
"ready": true,
"done": true,
"url": "/v1/videos/1234/content",
"url_source": "archive",
"url_expires": null,
"watermarked_url": null,
"unwatermarked_url": null,
"created_at": "...", "updated_at": "...", "finished_at": "...",
"cleared": false, "refunded": false, "refunded_points": 0, "balance": 99
}
}| status | 含义 | 下一步 |
|---|---|---|
| queued | 排队中,还没提交到上游 | 等;auto_start=false 建的要先调 /start |
| submitting | 正在提交给上游 | 等 |
| generating | 上游生成中 | 等 |
| resolving | 整理成片 | 等 |
| ready | 已完成,可取片 | GET /content |
| failed | 失败(已自动退款) | 看 error,改参数重提 |
| cancelled | 已取消 | 只能重提 |
ready/failed/cancelled是终态;done字段就是这三个的合集,轮询到done=true就可以停。archived=true(url_source=archive)表示成片已存到本服务,取片最稳。url_expires="upstream-temporary"表示用的是上游临时直链,会过期。cleared=true表示这条已被清除,列表里不再出现,但按 id 仍查得到(它是扣费凭据)。
列表
返回 { items: [...], count },按时间倒序,已清除的不在列表里。列表项字段是详情的子集(不带 url 那几个取片字段)。
⑤ 取成片
这个接口不返回文件本身,而是 302 到一个现签的地址。客户端永远不要缓存它返回的链接:
- 有本地归档 → 302 到
/v1/files/<ticket>,票据 10 分钟过期,支持 Range; - 没有归档但有上游直链 → 302 到上游地址,它带签名、会过期,这是事实不是缺陷;
- 任务不是
ready→409 CONTENT_NOT_READY;压根没有成片 →404 CONTENT_MISSING。
curl -L -o video-1234.mp4 \ -H "Authorization: Bearer dv_你的令牌" \ https://admin.fei85.cn/v1/videos/1234/content
/v1/files/<ticket> 本身不需要 Authorization(票据就是鉴权)—— 播放器拖进度条时发的是裸请求,带不上自定义头,这是刻意设计。代价是链接 10 分钟后失效:别把链接存下来当长期地址用,要用就重新调一次 /content 现签。播放器报网络错误又刚过 10 分钟,先重签再怀疑文件坏了。<video src="/v1/videos/1234/content"> 交给浏览器,302 会自动跟随。若自己 fetch,记得开 redirect: 'follow',并不要把整段读成 blob 再喂给播放器 —— 30 秒的片子有几十 MB,那样必须整段下完才有画面。⑥ 启动 / 取消 / 清除
只能启动 queued 的任务。已是终态或已在跑 → 409 TASK_NOT_STARTABLE。启动不起来时会自动取消并退款。
POST /v1/videos/start
{ "ids": [1234, 1235, 1236] }
→ { "code": 0, "data": { "started": [{ "id": 1234, "status": "queued", "started": true }],
"errors": [{ "id": 1235, "code": "TASK_NOT_STARTABLE", "error": "..." }] } }逐条处理、不整体回滚:started 是成功的,errors 是失败的,重复的 id 自动去重。
停掉任务,仍在列表里,状态变 cancelled。只有「还没提交到上游」和「上游明确失败」两种状态退款,响应里带 refunded / refunded_points / balance。
= 取消 + 打 cleared_at,从列表里消失。重复清除是幂等的,不会报错也不会重复退款(响应里的 refunded_already 就是「此前已退过、本次没再退」)。
body 给 { "ids": [...] }(最多 200)清指定的;不给 ids 清当前令牌名下全部未清除的任务。返回 cleared_count / refunded_points / refunded_already_count / errors。
cleared:true。⑦ 参考图
最多 9 张,整包请求 ≤ 50 MiB(multipart 侧留到 52 MiB)。两种提交方式:
- multipart:字段名
input_reference(也认images/image),可重复; - JSON:
images传 base64 数组。不认识的文件字段会 400,不会静默丢掉。
{
"code": 0,
"data": {
"task_id": 1234,
"count": 2,
"cleared": false,
"recorded_count": 2,
"expires_in_minutes": 10,
"items": [
{ "name": "ref1.png", "size": 4470000,
"url": "https://admin.fei85.cn/v1/videos/1234/reference-images/ref1.png?ticket=...",
"thumb_url": "https://admin.fei85.cn/v1/videos/1234/reference-images/ref1.png?ticket=...&w=256" }
]
}
}url/thumb_url带 10 分钟票据,不需要 Authorization,可以直接塞进<img src>。- 展示位用
thumb_url(缩略图约 16 KB);原图动辄 4 MB 一张,直接铺会拉爆。thumb_url取不到时回退到url。 cleared=true表示记录里说带过图、但暂存目录空了 → 需要重新上传,不是「本来就没带图」。
reference_source_task,服务端会自己从暂存目录读出、复检、按新任务落盘,浏览器一个字节都不用再传。配 reference_source_keep(文件名数组)可只保留其中几张。⑧ 状态码与错误码
{
"error": {
"message": "积分不足,任务未提交",
"type": "invalid_request_error",
"balance": 0,
"need": 1
}
}失败信封里除 message / type 外,还会按情况带下面这些字段(白名单,只会多不会乱):code、retryAfterSeconds、duplicateTaskId、balance、need、quota、supportedSeconds、generation。
| HTTP | code | 含义 / 怎么处理 |
|---|---|---|
| 401 | MISSING_API_KEY | 没带 Authorization 头 |
| 401 | INVALID_API_KEY | 令牌不存在,换一个 |
| 403 | TOKEN_INACTIVE | 令牌被停用 |
| 403 | TOKEN_EXPIRED | 令牌已过期 |
| 400 | MISSING_PROMPT | prompt 为空 |
| 400 | invalid_parameter | 参数不合法(例如 seconds 写了小数) |
| 400 | UNSUPPORTED_MODEL | model 不认识 |
| 400 | UNSUPPORTED_MODEL_SECONDS | model 与 seconds 不匹配(未建任务、未扣积分) |
| 400 | DURATION_RETIRED | 档位已下线(10 / 20 秒),带 supportedSeconds |
| 400 | DURATION_UNSUPPORTED | seconds / forceSeconds 只能是 15 或 30 |
| 400 | EXPERT_MODE_REQUIRED | 15 秒必须走专家模式(seconds=15 会自动带) |
| 400 | UNSUPPORTED_PARAMETER | 传了 audio、points 或不认识的 multipart 文件字段 |
| 400 | REFERENCE_IMAGE_* | INVALID / TOO_LARGE / LIMIT:图坏了、太大、超 9 张或超 50 MiB |
| 402 | (无 code) | 积分不足,带 balance / need。任务未提交、未扣积分 |
| 409 | PROMPT_COOLDOWN | 相同提示词刚提交过,等 retryAfterSeconds 秒;duplicateTaskId 是那一条 |
| 409 | GENERATION_SUBMISSION_UNRESOLVED | 同提示词的原任务还待核对,先查原任务,别重提 |
| 409 | CONTENT_NOT_READY | 成片还没验收通过 |
| 409 | TASK_NOT_STARTABLE | 任务不在可启动状态(终态或已在跑) |
| 409 | GENERATION_QUOTA_EXHAUSTED | 指定账号最新回执显示额度为 0 |
| 409 | GENERATION_NO_ACCOUNT | 账号池没有可用账号(持久性问题,不是等一会儿就好) |
| 409 | GENERATION_ACCOUNT_CHANGED | 选号期间账号/代理状态变了,直接重试即可 |
| 429 | DAILY_POINTS_LIMIT | 今日积分上限,带 quota(limit / used / remaining / day) |
| 429 | GENERATION_QUEUE_FULL | 生成队列已满,稍后重试 |
| 503 | GATEWAY_DISABLED | 对外网关在后台被关了 |
| 404 | TASK_NOT_FOUND | 任务不存在,或不属于你的令牌 |
| 404 | CONTENT_MISSING | 这条任务没有可下载的成片 |
| 401 | TICKET_INVALID | 票据无效或已过 10 分钟,重新取地址 |
| 404 | NOT_FOUND | /v1 下没有这个接口(路径或方法写错了) |
| 500 | INTERNAL_ERROR | 服务端异常,稍后重试并保留请求参数 |
402 / 429 也都是扣费前拦下的。⑨ 完整示例:提交 → 等 → 下载
Node(内置 fetch,无依赖)
const BASE = 'https://admin.fei85.cn/v1';
const TOKEN = 'dv_你的令牌';
const api = (p, init = {}) => fetch(BASE + p, {
...init,
headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json', ...init.headers },
}).then(async (r) => {
const body = await r.json().catch(() => ({}));
if (!r.ok) throw new Error(`[${r.status}] ${body.error?.code || ''} ${body.error?.message || ''}`);
return body.data;
});
// 1) 建任务
const { id } = await api('/videos', {
method: 'POST',
body: JSON.stringify({ prompt: '海边日落,镜头缓慢横移', seconds: 30, size: '16:9' }),
});
console.log('任务号', id);
// 2) 轮询到终态(有任务在跑 4 秒一次就够)
for (;;) {
const v = await api(`/videos/${id}`);
console.log(v.status, v.stage || '');
if (v.done) { if (v.status !== 'ready') throw new Error(v.error || '未出片'); break; }
await new Promise((r) => setTimeout(r, 4000));
}
// 3) 取成片:/content 是 302,让 fetch 自己跟,别缓存这个链接
const res = await fetch(`${BASE}/videos/${id}/content`, {
headers: { Authorization: `Bearer ${TOKEN}` },
redirect: 'follow',
});
const buf = Buffer.from(await res.arrayBuffer());
await import('node:fs').then((fs) => fs.writeFileSync(`video-${id}.mp4`, buf));
console.log('已保存', buf.length, '字节');Python
import time, requests
BASE = "https://admin.fei85.cn/v1"
TOKEN = "dv_你的令牌"
H = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post(f"{BASE}/videos", headers=H, json={
"prompt": "海边日落,镜头缓慢横移", "seconds": 30, "size": "16:9",
})
r.raise_for_status()
vid = r.json()["data"]["id"]
print("任务号", vid)
while True:
v = requests.get(f"{BASE}/videos/{vid}", headers=H).json()["data"]
print(v["status"], v.get("stage") or "")
if v.get("done"):
if v["status"] != "ready":
raise SystemExit(v.get("error") or "未出片")
break
time.sleep(4)
# allow_redirects=True 才会跟到真正的成片地址
with requests.get(f"{BASE}/videos/{vid}/content", headers=H, stream=True) as resp:
resp.raise_for_status()
with open(f"video-{vid}.mp4", "wb") as f:
for chunk in resp.iter_content(1 << 20):
f.write(chunk)
print("已保存 video-%s.mp4" % vid)GET /v1/status 一次拿全:令牌余额 token.points、单条消耗 points_per_task、今天还能用多少 daily.remaining(日结算按服务器本地日,见 daily.day)、15 秒与 30 秒是否就绪 expert_seconds_ready / fixed_seconds_ready。别等被 429 拒绝才知道有日上限。