MakerNice
← 全部文章
钟表匠工作台上精密黄铜齿轮的微距特写,暖色台灯照亮台面指南

视频生成 API 接入最佳实践:幂等、429 与轮询

MakerNice 团队 · ·

稳定接入 MakerNice 生成 API 靠五个习惯:每个任务一个 Idempotency-Key、创建前一刻再报价、按 Retry-After 轮询、只取消排队中的任务、在产物过期前转存。它们适用于所有视频和图片模型,包括 Seedance 2.5 和 Wan 3.0,因为这些模型共用同一套异步 REST 流程。

重试 POST /v1/generations 怎样才不会重复扣费?

  • POST /v1/generations 必须带 Idempotency-Key 请求头:1 到 200 个可打印 ASCII 字符,不含空格,在你的账户内唯一。
  • 每个业务任务生成一个键,发请求之前先存下来,之后每次重试都复用它。重试会返回 200 和原来的任务,不会再创建一个。
  • 同一个键搭配不同的报价或不同的计价参数会返回 422 idempotency_conflict,所以不要在不同任务之间复用键。
3D 黏土插画:一把钥匙立在一圈循环箭头中间,旁边是一个时钟和一面带对勾的盾牌
每个任务一个幂等键,让每次重试都是安全的重放,而不是第二笔扣费。

报价多久有效?应该什么时候获取?

POST /v1/generations/quote 返回的报价有效期 12 分钟,且只能使用一次。请在创建之前立刻获取,而不是用户刚打开表单时就取。报价之后如果改了 model、mode、resolution 或 duration,创建会返回 422 quote_mismatch,所以要按最终输入报价。

多久轮询一次任务?遇到 429 怎么办?

每隔几秒请求一次 GET /v1/generations/{id} 就足够,生成本来就要一段时间。遇到终态就停:succeeded、failed 或 canceled。checking 表示我们正在核对结果、金额仍被冻结,继续轮询即可。所有接口都可能返回 429 rate_limited,并在 Retry-After 响应头里给出要等待的秒数,等够再发下一个请求。账户下(所有 Key 合计)进行中的任务数达到上限时,创建还会返回 429 concurrency_limit_reached,所以请在你这一侧排队,不要一次性全部发出。

// Poll one generation until it reaches a final status
const POLL_INTERVAL_MS = 5000;
const REQUEST_TIMEOUT_MS = 30000;
const FINAL = new Set(["succeeded", "failed", "canceled"]);

async function waitFor(id) {
  for (;;) {
    const res = await fetch(API_BASE + "/generations/" + id, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
      headers: { Authorization: "Bearer " + API_KEY },
    });
    if (res.status === 429) {
      // Honour the server's Retry-After (seconds) before trying again
      const retry_after_s = Number(res.headers.get("Retry-After") ?? "5");
      await new Promise((r) => setTimeout(r, retry_after_s * 1000));
      continue;
    }
    if (!res.ok) throw new Error("poll failed: " + res.status);
    const gen = await res.json();
    if (FINAL.has(gen.status)) return gen;
    await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
  }
}

哪些 API 错误该重试,哪些该修正?

状态码错误码能否重试怎么处理
429rate_limited、concurrency_limit_reached可以等 Retry-After 秒后原样重发
410quote_expired换新报价后可以重新报价再创建
409quote_already_used换新报价后可以先确认任务复用了自己的键,再重新报价
422quote_mismatch换新报价后可以按最终输入报价
422idempotency_conflict不可以只有真正的新任务才换新键
400invalid_request不可以修正输入;用 GET /v1/models 核对模式
402insufficient_balance不可以到控制台充值
409not_cancelable不可以用 GET 查看任务当前状态
410output_expired不可以改用你自己存储里的副本

创建接口的其他错误码更适合提示给人处理,而不是自动重试:402 key_spend_limit_reached、402 daily_spend_limit_reached、403 org_frozen 和 404 quote_not_found。每个错误响应都带 error.code、error.message,通常还有 request_id;请按 code 而不是 message 做分支,并把 request_id 记进日志。

什么时候可以取消生成任务?

POST /v1/generations/{id}/cancel 只在状态为 queued 时有效,并会全额释放冻结金额。任务进入 generating 之后,取消会返回 409 not_cancelable。重复执行一次已经成功的取消同样返回 409,所以遇到 409 时,先用 GET 查看状态再判断发生了什么。

生成的视频能下载多久?

带上 Key 请求 GET /v1/generations/{id}/outputs/{n}:只有任务成功后才可用,返回指向短期签名地址的 302。每次需要文件时重新请求,不要保存跳转后的地址;想以附件形式下载可加 download=1。已转存的产物带 expiresAt(尚未转存时为 null),过期后接口返回 410 output_expired,所以任务一成功就把结果转存到你自己的存储里。

每个生成任务应该记录什么?

为每个任务记录报价 id、幂等键和任务 id。三者合起来就能还原整件事:GET /v1/generations/{id} 显示当前状态,GET /v1/generations/{id}/events 返回带时间戳的事件时间线,事件码包括 created、generating、delivered、failed_refunded 等。程序崩溃重启后,用 GET /v1/generations?status=queued,generating 列出仍在进行中的任务。这些标识也会让你向我们反馈问题时处理得快得多。

刚开始接入?先读「Seedance 2.5 API 教程」跑通四个基本调用,再把这些习惯加进去。实时价格可以调用 GET /v1/pricing,或用确切输入请求报价。

常见问题

MakerNice API 里的 checking 状态是什么意思?
checking 表示我们正在核对结果,还不是终态。金额仍被冻结,继续轮询,直到看到 succeeded、failed 或 canceled。
Idempotency-Key 能给另一个生成任务复用吗?
不能。键在你的账户内必须唯一;搭配不同报价或不同计价参数再次发送,会返回 422 idempotency_conflict。
outputs/0 跳转后的地址可以存下来吗?
不建议。跳转目标是短期有效的签名地址;每次需要文件时重新请求 /v1/generations/{id}/outputs/0,或者保留你自己的副本。
Retry-After 的单位是秒还是毫秒?
秒。每个 429 响应都带 Retry-After,表示重试前需要等待的秒数。

本文涉及的模型