# 异步视频生成 API：任务 ID、轮询与按秒计费

> 视频生成是任务队列而不是请求/响应调用，并按视频秒数计费。本文给出完整契约、轮询规则，以及决定你的队列是否会重复付费的三种失败情况。

_发布 2026-09-12 · 更新 2026-09-29 · [KeepRouter Editorial](https://keeprouter.com/editorial-policy#editorial-team) · 7 分钟阅读_

![视频异步任务流程：提交调用进入任务画布并返回任务 ID，虚线轮询回路把结果带回](https://keeprouter.com/editorial/blog/async-video-generation-api.png)

_一次提交创建任务，渲染异步完成，再通过轮询取回结果。_

**先给结论：**视频生成不适合用一次 HTTP 请求/响应来完成。一次渲染要几分钟，所以接口本质是任务队列：提交任务、拿到任务 ID、轮询直到视频就绪。KeepRouter 在 `POST /v1/video/generations` 暴露这套流程，并按**视频秒数**而不是 token 计费。文本转语音的音频输出也按时长计费，但文本输入费用需要另外计算。

这会直接影响你写的代码，而且多数问题不是在第一次测试调用时出现，而是在那之后。

## 为什么同步调用行不通

一段 5 秒的 1080p 视频可能占用 GPU 一分钟以上。如果提交接口一直挂着连接，客户端就需要远超常规请求预算的超时，而你与模型之间的任何代理最终都会把一次健康的渲染从中间切断。拆成「提交 + 轮询」能让每次 HTTP 调用都很短，并让进度可观测。

代价是状态。任务 ID 是指向仍在进行的工作的句柄，因此它必须能跨进程重启、跨重新部署，也要能应对客户端内存里的映射丢失。把任务 ID 与你应用的「工作单元」（任务行、订单、草稿）存在一起，把轮询当作可恢复的一步，而不是活在某个函数调用里的循环。

## 完整契约

提交渲染。响应里包含任务 ID 和初始状态，继续下去不需要别的东西：

```bash
curl https://keeprouter.com/v1/video/generations \
  -H "Authorization: Bearer $KEEPROUTER_KEY" -H "Content-Type: application/json" \
  -d '{"model":"wan2.7-t2v","prompt":"a paper plane gliding over a sunlit desk","duration":5}'
```

```json
{
  "id": "task_QeWgyFbrwjInjPMERqtdyFvehlMM4qgX",
  "task_id": "task_QeWgyFbrwjInjPMERqtdyFvehlMM4qgX",
  "object": "video",
  "status": "queued",
  "progress": 0,
  "created_at": 1789197093,
  "completed_at": null,
  "url": null,
  "error": null
}
```

然后轮询。**`GET /v1/videos/{id}` 与 `GET /v1/video/generations/{id}` 是同一个操作**，因为不同客户端沿用了不同约定；选一个并固定下来即可。`status` 会从 `queued`、`in_progress` 走到 `completed`、`failed` 或 `canceled`，`progress` 在上游提供时是百分比。`url` 只在视频存在后出现，而且是**会过期的签名链接**，第一次看到时就把它转存到自己的存储，不要把上游链接直接交给终端用户。

轮询是读取操作：和所有调用一样需要鉴权，但不计费，也不占你的模型预算。5 秒一次是合理默认值；更密的循环基本只是在增加请求数。

**你完全可以不轮询。** 提交时带上 `webhook_url`，任务进入终态后网关会 POST 一条签名事件，字段与轮询返回一致。投递使用时间戳与原始请求体的 HMAC 签名，因此你可以先验证再信任；接收端不可用时按固定计划重试（约 1 分钟、15 分钟、1 小时、6 小时）。只接受公网 https 地址：内网、回环与云元数据地址会在任务提交前就被拒绝，重定向也不会被跟随。

无论如何都保留轮询作为兜底。webhook 可能丢失，而轮询响应会报告投递状态（`webhook.status`、`attempts`、`last_error`），因此坏掉的目标地址是可见的而不是静默的。每次轮询还会返回 `expires_at`，即网关停止跟踪该任务、签名链接同时失效的时刻，客户端据此即可判断最晚何时必须取走文件，不必自己推算窗口。

## 计费数量就是视频秒数

视频输出按**视频秒数**计量；文本转语音的音频输出按**生成音频秒数**计量，另加目录中公布的文本输入费用。不能把这两类按时长定价的输出费率当作每百万文本 token 价格。模型页会标出输出单位（`per second of video`、`per second of audio`），机器可读价格表也提供明确的 `output_price_unit` 列。

当上游对更高分辨率收费更高时，KeepRouter 会**按档位分别发布模型 ID**：`wan2.7-t2v` 是 720p 档，`wan2.7-t2v-1080p` 是 1080p 档，各有自己的费率。网关会钉住它计费的那个档位，因此请求不可能被渲染成高于你付费档位的规格，如果你请求了该 ID 未定价的分辨率，会以 `resolution_not_priced` 被拒绝，而不是被静默升档或降档。省略 `duration` 时会钉一个默认值并据此计费，所以你拿到的片段就是你付费的那个片段。

最后一点最值得记住：**金额在渲染开始前就已确定**，因为上游在提交时就按你请求的时长收费。因此你的成本核算可以在提交那一刻就精确，而不必事后对账。

## 不重复扣费的失败处理

有三种情况需要不同处理，把它们混为一谈正是队列重复付费的常见原因。

**提交调用超时。** 你并不知道任务是否已创建。不要盲目重试：重试会创建第二次渲染并产生第二笔费用。正确做法是在自己这一层把提交做成幂等，一行任务记录对应一次提交；如果确实必须在超时后重试，就把「可能多出一个任务」当成这次决定的代价，而不是默认路径。

**任务失败。** 上游在接受任务时就收费，之后判定失败也不退款，网关如实映射这一行为而不是自行吸收。轮询会明确告诉你发生了什么：`status` 变为 `failed`，`error.message` 带上上游自己的原因，例如时长超出该模型允许的范围。先读它，再判断是不是网关把任务弄丢了。

**任务 ID 不再可解析。** 任务只能被提交它的账户读取，其他账户会得到与「不存在」相同的 `404`，这是刻意的，因为轮询响应里带有签名下载地址。网关还会在 **24 小时**后停止跟踪（与签名链接有效期一致），此后轮询返回 `task_expired`；更早的每次轮询响应里，`expires_at` 就精确标出了这个时刻。所以要存的是视频，不是任务 ID。

## 用任务 ID 设计等待界面

让一个应用任务始终关联返回的任务 ID。浏览器重载后恢复查询该 ID，不要重新提交渲染。排队、运行、完成与失败应显示为不同状态，等待时间不能证明进度。[KeepRouter API 参考](https://keeprouter.com/api/docs)定义了提交与状态操作。结果就绪后，按返回地址的有效期保存，不把临时下载地址当成永久存储。

## 上线前该确认什么

用你生产环境真正要用的时长和分辨率跑一次渲染，确认四件事：任务能到 `completed`；视频能从返回的链接下载下来；费用等于「时长 × 该档位公布的每秒单价」；以及一个故意写错的请求会在任何上游调用发生之前就被拒绝。最后一项决定了你的校验是否足够早，能不能保护账户。

[模型目录](/models) 是当前可调用的视频 ID 与每秒费率来源，[快速开始](/docs/quickstart) 覆盖账户与 Key 的基础配置。如果你正在按这类端点比较网关，[多模态模型 API 说明](/zh/features/multimodal-models) 解释了为什么端点是按模型分别发布的，而不是被压成一个万能请求；[错误参考](/docs/errors) 记录了上面提到的每个错误码。


## 常见问题

### 一次视频渲染要多久？

短片段通常一分钟内完成，更长或更高分辨率的渲染需要更久。由于提交调用会立即返回，渲染时长不影响你的请求超时；每隔几秒轮询一次，并把任务 ID 持久化，避免重启后丢失。

### 任务失败还会收费吗？

会。上游在接受任务时即收费，之后判定失败也不退款，网关如实映射而不是自行吸收差额。重试前请先轮询任务并读取 error.message，那里有上游给出的原因。

### 为什么有两个轮询路径？

GET /v1/videos/{id} 与 GET /v1/video/generations/{id} 是同一个操作。现有视频 API 两种约定都有，网关两者都接受，你不必为此改写代码。

## 参考的一手资料

_本文最近复核 2026-09-29_

1. [Google Cloud text-to-speech pricing (audio is billed per token, with a documented tokens-per-second rate)](https://cloud.google.com/text-to-speech/pricing)
2. [Google Gemini API pricing (duration-metered video and audio models)](https://ai.google.dev/gemini-api/docs/pricing)
3. [OpenAI API pricing (audio token rates for speech models)](https://platform.openai.com/docs/pricing)
4. [KeepRouter API reference](https://keeprouter.com/api/docs)

## 继续阅读

- [多模态模型 API](https://keeprouter.com/zh/features/multimodal-models.md)
- [AI Gateway 指南：它控制什么、何时需要，以及如何落地](https://keeprouter.com/zh/blog/ai-gateway-guide.md)
- [LLM 故障切换设计指南：恢复请求，同时避免不安全重试](https://keeprouter.com/zh/blog/llm-failover-design-guide.md)
- [errors](https://keeprouter.com/docs/errors.md)
- [wan2.7 t2v](https://keeprouter.com/models/wan2.7-t2v.md)

## 检查应用需要的具体行为

把文中例子用于应用时，按产品记录的请求格式与控制范围实现。

[查看产品说明](https://keeprouter.com/api/docs)

[创建 Key，测试免费模型](https://keeprouter.com/login?returnTo=%2Fconsole%2Fkeys%3Fmodel%3Dfree)

免费测试使用 free 模型；其他付费型号需要足够预付额度。

[全部文章](https://keeprouter.com/zh/blog.md) · [模型与价格](https://keeprouter.com/models.md)
