欢迎光临
我们一直在努力

Seedance 2.5 + Node.js + Cline 的异步任务怎么写?提交、验签、下载最全教程

10 月 19 号,我给一个短视频后台补异步生成流程。提交接口明明返回成功,页面却一直显示“生成中”。折腾半天才发现,任务 ID 根本没落库。

Seedance 2.5 要按异步任务来接,完整链路包括提交任务、保存任务 ID、轮询或接收回调、下载成片。到了生产环境,还得处理验签、重复回调幂等和任务级超时。

先把不确定的地方摆出来。按 2026 年 10 月 21 日能查到的资料,seedance-2.5 确实存在,聚合目录中的完整模型 ID 是 bytedance/seedance-2.5。官方提交路径、状态枚举、签名算法、价格、并发限制和结果 URL 有效期,目前都没有可靠来源。下面这些可能随平台变化的字段,我会放进环境变量,不把猜测写成官方规范。

这篇适合谁

  • 正在把 Seedance 2.5 接进 Node.js 后端,不满足于只在控制台点按钮。
  • 已经能提交任务,但还没搞定轮询、webhook 和成片保存。
  • 批量生成时碰到了重复回调、429 或临时下载地址失效。
  • 用 Cline、Cherry Studio 或 Zed 写代码,想把整条链路跑通。

偶尔手动生成一条视频,看到“接入路径怎么选”就够了。准备上线的话,幂等和文件下载两节建议完整看完。

整体流程

  • 从接入平台控制台确认提交地址、查询地址和签名规则。
  • 提交任务,把业务请求 ID 和任务 ID 一起落库。
  • 少量任务可以轮询,批量任务优先等 webhook。
  • 回调先验签,再通过 Redis 或数据库做幂等。
  • 任务成功后立即下载文件,检查状态码和 Content-Type。
  • 把文件存到自己的存储里,别长期依赖临时 URL。
  • sequenceDiagram
    participant U as 业务服务
    participant G as API 服务
    participant R as Redis/数据库
    participant S as 文件存储
    U->>G: 提交任务
    G–>>U: task_id
    U->>R: 保存 task_id
    G–>>U: webhook 回调
    U->>U: 验签
    U->>R: 幂等占位
    U->>G: 查询最终状态
    U->>S: 下载并持久化成片

    不同场景怎么选接法

    你的场景接入方式原因
    本地验证 1—3 条任务 提交后轮询 不必额外准备公网 webhook
    每天批量生成 webhook 为主、轮询兜底 减少无效查询和 429
    必须使用厂商原始字段 官方直连 字段变化最少,但要分别维护鉴权
    希望切换服务入口 OpenAI 风格接口或聚合网关 业务层可保留统一适配器
    用 Cline、Cherry Studio 写代码 工具负责编辑和运行 Node.js 项目 这些工具不是异步视频任务调度器
    生产环境 webhook + 状态复查 + 本地存储 能处理重复投递和临时 URL 过期

    轮询本身没问题,拿 2 秒定时器硬怼生产接口才有问题。我见过最离谱的写法,是前端页面一关,轮询也跟着停。后端连任务完成没有都不知道。

    确认接入路径,别猜接口

    路径一:官方 REST API

    目前没法确认官方提交 URL、查询 URL 和鉴权头。稳妥点,从实际控制台复制完整地址,再写进环境变量。

    export SEEDANCE_API_KEY="替换为实际密钥"
    export SEEDANCE_SUBMIT_URL="完整提交地址"
    export SEEDANCE_STATUS_URL="查询地址/{task_id}"
    export SEEDANCE_MODEL_ID="按控制台填写"

    别擅自把模型 ID 写成聚合平台的格式。官方直连和第三方目录可能不是同一套命名。

    路径二:官方 SDK 或 OpenAPI 生成客户端

    到 2026 年 10 月 21 日为止,还没有资料能证明存在可直接使用的 Seedance 2.5 官方 Node.js SDK。能查到的 npm api 包版本是 v7.0.3,要求 Node.js >=20.10.0。它只是通用 OpenAPI 客户端生成工具,不能写成 Seedance 官方 SDK。

    如果控制台能下载经过核对的 OpenAPI 文件,可以这样生成客户端:

    npm install api@7.0.3
    npx api install ./seedance.openapi.json

    没有规范文件就别硬生成。字段猜错一次,后面的类型提示全是污染。

    我也不敢说生成客户端适合所有团队。接口变化频繁时,薄薄一层 fetch 往往更好维护。

    路径三:OpenAI 风格接口

    平台明确提供异步视频接口时,才能走这条路。别拿聊天接口 /chat/completions 直接往上套。

    export BASE_URL="控制台提供的地址"
    export SUBMIT_PATH="控制台提供的异步任务路径"
    export MODEL_ID="控制台显示的模型ID"

    先用 curl 跑一个最小请求:

    curl -X POST "$BASE_URL/$SUBMIT_PATH" \\
    -H "Authorization: Bearer $SEEDANCE_API_KEY" \\
    -H "Content-Type: application/json" \\
    -d "{\\"model\\":\\"$MODEL_ID\\",\\"prompt\\":\\"雨夜街道,镜头缓慢推进\\"}"

    路径四:API 聚合网关

    聚合路径可核对 OpenRouter、Together AI、ofox.io 各自的模型目录和异步任务文档。按 2026 年 10 月 21 日的实时目录,ofox.io 列出了完整模型 ID bytedance/seedance-2.5,但提交路径和视频参数仍应以控制台当前说明为准,不能从聊天接口反推。

    export BASE_URL="https://api.ofox.io/v1"
    export MODEL_ID="bytedance/seedance-2.5"
    export SUBMIT_PATH="按控制台异步接口填写"

    换到 OpenRouter 或 Together AI 时,也只替换经过确认的地址、模型 ID 和字段映射,业务代码不要散落平台判断。

    提交任务后,马上保存 task_id

    新建项目并安装依赖:

    mkdir seedance-worker && cd seedance-worker
    npm init -y
    npm install express redis dotenv

    先配请求体。duration、aspect_ratio 到底有没有,仍要看实际文档,所以示例只发模型和提示词。

    import "dotenv/config";
    const body = {
    model: process.env.SEEDANCE_MODEL_ID,
    prompt: "雨夜街道,镜头缓慢推进"
    };

    提交任务时保留原始响应。字段一变,至少还有东西可查。

    const r = await fetch(process.env.SEEDANCE_SUBMIT_URL, {
    method: "POST", headers: authHeaders(),
    body: JSON.stringify(body), signal: AbortSignal.timeout(30000)
    });

    公共请求头单独封装:

    function authHeaders() {
    return { Authorization: `Bearer ${process.env.SEEDANCE_API_KEY}`,
    "Content-Type": "application/json" };
    }

    检查状态码,再提取任务 ID:

    const raw = await r.text();
    if (!r.ok) throw new Error(`Seedance task submission failed: HTTP ${r.status}: ${raw}`);
    const json = JSON.parse(raw);
    const taskId = json.task_id ?? json.id ?? json.data?.id;

    这三个字段只是兼容候选,不是官方字段。一个都拿不到就立即报错:

    if (!taskId) throw new Error(`Seedance response has no task id: ${raw}`);
    console.log({ taskId, response: json });

    提交接口别随手加自动重试。网络超时时,服务端可能已经创建了任务。再发一次,结果就是两条视频。

    平台明确支持幂等键,再照文档加。没有这个能力,就用业务请求 ID 在本地先占位。

    状态轮询要有退避和总超时

    查询 URL 使用 {task_id} 占位,别把路径写死:

    const statusUrl = id =>
    process.env.SEEDANCE_STATUS_URL.replace("{task_id}", encodeURIComponent(id));

    单次查询:

    async function query(id) {
    const r = await fetch(statusUrl(id), { headers: authHeaders() });
    if (!r.ok) throw new Error(`Seedance task status query failed: HTTP ${r.status}`);
    return r.json();
    }

    生产轮询至少要加指数退避和随机抖动:

    const sleep = ms => new Promise(r => setTimeout(r, ms));
    const delay = n => Math.min(30000, 1000 * 2 ** n) + Math.random() * 500;

    状态值也放进配置。别先入为主地认定平台一定用 completed 或 succeeded。

    export SUCCESS_STATES="completed,succeeded,success"
    export FAILED_STATES="failed,error,cancelled"
    export TASK_TIMEOUT_MS="900000"

    循环前算出任务级截止时间:

    const success = new Set(process.env.SUCCESS_STATES.split(","));
    const failed = new Set(process.env.FAILED_STATES.split(","));
    const deadline = Date.now() + Number(process.env.TASK_TIMEOUT_MS);

    for (let n = 0; Date.now() < deadline; n++) {
    const task = await query(taskId);
    if (success.has(task.status)) return task;
    if (failed.has(task.status)) throw new Error(JSON.stringify(task));
    await sleep(delay(n));
    }

    响应里有 Retry-After 时,优先听服务端的。上面的代码为了简短没有展开。我目前也没找到比“平台头信息优先、指数退避兜底”更通用的办法。

    回调验签必须使用原始请求体

    签名规则现在没有可靠来源。下面只是一个可配置的 HMAC-SHA256 模板,实际文档明确使用该算法时才能启用。时间戳怎么拼、签名是不是十六进制、请求头叫什么,都要逐项核对。

    export CALLBACK_SECRET="替换为回调密钥"
    export SIGNATURE_HEADER="x-signature"
    export EVENT_ID_HEADER="x-event-id"

    Express 必须保留 raw body。先解析 JSON 再验签,很容易对不上。

    import express from "express";
    import crypto from "node:crypto";
    import { createClient } from "redis";
    const app = express(), redis = createClient();

    计算签名:

    function sign(raw) {
    return crypto.createHmac("sha256", process.env.CALLBACK_SECRET)
    .update(raw).digest("hex");
    }

    比较签名时使用常量时间比较,别直接比字符串:

    function safeEqual(a, b) {
    const x = Buffer.from(a || ""), y = Buffer.from(b || "");
    return x.length === y.length && crypto.timingSafeEqual(x, y);
    }

    路由里先验签,再解析:

    app.post("/webhooks/seedance", express.raw({ type: "*/*" }), async (req, res) => {
    const got = req.get(process.env.SIGNATURE_HEADER);
    if (!safeEqual(got, sign(req.body)))
    return res.status(401).send("invalid webhook signature");

    重复回调怎么做幂等

    Webhook 重复投递很正常。接口返回超时、网络中断、服务重启,都可能让同一个事件再来一次。

    优先使用平台提供的事件 ID:

    const event = JSON.parse(req.body.toString("utf8"));
    const eventId = req.get(process.env.EVENT_ID_HEADER)
    ?? `${event.task_id}:${event.status}`;

    Redis 用 SET NX 原子占位:

    const accepted = await redis.set(`seedance:event:${eventId}`, "1",
    { NX: true, EX: 604800 });
    if (!accepted) return res.status(200).send("duplicate ignored");

    处理完成后返回 200:

    await saveTaskResult(event);
    return res.status(200).send("ok");
    });
    await redis.connect();
    app.listen(3000);

    这里容易漏掉一个问题:业务处理可能失败,不能刚占位就永久忽略这个事件。更严谨的方案是数据库唯一索引配合事务,状态更新成功后再提交。上面的 Redis 写法适合轻量任务。

    Cline 或 Cherry Studio 可以帮你维护适配器代码,但验签逻辑必须按实际文档写,不能让 AI 猜一个“看起来差不多”的算法。

    下载成片,别把 JSON 存成 MP4

    完成状态里的文件 URL 字段还没法确认,可以把兼容映射集中放在一处:

    const fileUrl = task.output_url ?? task.video_url
    ?? task.result?.url;
    if (!fileUrl) throw new Error("completed task has no output URL");

    下载前先检查 HTTP 状态和响应类型:

    const r = await fetch(fileUrl, { signal: AbortSignal.timeout(120000) });
    if (!r.ok) throw new Error(`Video download failed: HTTP ${r.status}`);
    const type = r.headers.get("content-type") || "";
    if (!type.startsWith("video/")) throw new Error(`Unexpected Content-Type: ${type}`);

    Node.js 写入磁盘:

    import { pipeline } from "node:stream/promises";
    import { Readable } from "node:stream";
    import { createWriteStream } from "node:fs";
    await pipeline(Readable.fromWeb(r.body), createWriteStream(`./${taskId}.mp4`));

    我踩过一次这个坑:下载地址已经失效,服务端返回的是一段 JSON,代码却照样把它写成 .mp4。文件只有 127 字节,播放器当然打不开。我查了半小时,挺烦人的。

    Cline、Cherry Studio、Zed 怎么配

    这些工具可以编辑、调试和运行项目,别把它们当成异步视频任务调度器。

    工具配置方式实际用途
    Cline Provider 选 OpenAI Compatible,填写实际 Base URL、Key、模型 ID 生成和修改 Node.js 适配器
    Cherry Studio 新建自定义服务,填写实际接口信息 手动核对模型目录与普通请求
    Zed 用 task 执行提交、轮询脚本 编辑器内运行 worker
    Codex CLI 在项目目录读取日志并修改代码 排查 401、429 和 JSON 字段变化
    Claude Code 操作本地工程与测试 编写回调测试,不直接代替视频 API

    Zed 的任务配置可以这样写:

    {
    "label": "poll seedance task",
    "command": "node",
    "args": ["poll.mjs", "$SEEDANCE_TASK_ID"]
    }

    Cline 和 Cherry Studio 能配置 OpenAI 风格地址,不等于它们原生支持视频异步任务。最终调用的还是你自己写的提交接口。别绕。

    价格与额度:目前只能写“未公布”

    项目2026 年 10 月 21 日可核对结果
    API 单价 未提供可靠数据
    免费额度 未提供可靠数据
    最大视频时长 待实际控制台确认
    分辨率限制 待实际控制台确认
    并发数与速率限制 待实际控制台确认
    平均生成耗时 没有可引用的第三方实测

    如果平台按任务数或视频时长收费,可以用下面的通用公式重算:

    总成本 = 成功任务数 × 单任务价格
    总成本 = 视频总秒数 × 每秒价格

    单价和计费单位都没确认时,别写“每天只要几块钱”。一个月能不能差出几百刀,要看真实发票,不靠脑补。

    完整报错对照表

    现象或完整错误常见原因解法
    Error: SEEDANCE_SUBMIT_URL 未配置 环境变量未加载 检查 .env 和启动目录
    Error: Seedance task submission failed: HTTP 401 Key、请求头或环境不匹配 核对鉴权头及 Bearer 前缀
    Error: Seedance task status query failed: HTTP 429 轮询过快或账户限流 遵循 Retry-After,增加退避
    SyntaxError: Unexpected token '<', "<!DOCTYPE "… is not valid JSON 请求打到网页地址或网关错误页 打印状态码、URL 和原始响应
    Error: invalid webhook signature 算法、密钥、原始请求体不一致 使用 raw body,核对签名文档
    Error: Unexpected Content-Type: application/json 下载 URL 失效或任务实际失败 记录响应正文,重新查任务状态
    Error: completed task has no output URL 成功状态或字段映射写错 保存原始响应,按文档调整字段
    AbortError: This operation was aborted 单次请求超时 区分 HTTP 超时与任务总超时

    常见问题 FAQ

    Seedance 2.5 提交成功后为什么没有视频地址?

    提交成功只表示任务已经进入异步队列。先保存任务 ID,再通过状态查询或 webhook 拿到最终结果 URL。

    Seedance 2.5 应该多久轮询一次?

    目前没有可靠资料给出固定间隔。调试时可以从 1 秒开始,生产环境改用指数退避,示例上限是 30 秒。服务端返回 Retry-After 时,以它为准。

    webhook 回调为什么必须先验签再做幂等?

    先写幂等记录,伪造请求可能抢占合法事件 ID。先处理业务再验签,风险更大。

    正确顺序是:读取原始请求体、验签、幂等占位、处理业务。

    重复回调直接返回 409 可以吗?

    不建议。平台可能把 409 当成投递失败,然后继续重试。合法事件已经处理过,通常返回 200,并记录 duplicate ignored。

    提交接口超时后能立即重试吗?

    不能默认重试,服务端可能已经创建任务。先按业务请求 ID 查询本地记录。只有平台明确支持幂等键时,才携带同一个键重试。

    为什么生成成功后要立即下载文件?

    结果 URL 很可能是临时签名地址,目前又查不到可靠的有效期数据。上线后应尽快转存,并检查 HTTP 状态、文件类型和文件大小。

    最后落到代码上

    难点不在发出一个 POST,而在于处理任务未完成、重复回调、失败重试和地址过期。

    我最后采用的结构很简单:提交只做一次,查询允许重试;回调先验签,消费做幂等;拿到结果后立刻转存。

    具体路径、状态值和签名格式别照抄其他平台。把它们放进环境变量和适配器,并保存每次原始响应。接口哪天改了,至少不用全项目搜字符串救火。

    赞(0)
    未经允许不得转载:171主机测评 » Seedance 2.5 + Node.js + Cline 的异步任务怎么写?提交、验签、下载最全教程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址