# OpenRouter 视频生成 API：一份代码优先的接入指南

- 来源：OpenRouter：Announcements（RSS）
- 作者：OpenRouter
- 发布时间：2026-08-25 08:00
- AIHOT 分数：66
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmt8zzh0w05nirolyozmvq7i0
- 原文链接：https://openrouter.ai/blog/tutorials/video-generation-api

## 精选理由

将视频生成抽象为提交、轮询、下载的异步作业，模型切换只改标识符，同时覆盖幂等、webhook 与并发控制，可迁移到其他异步生成 API 的集成。

## AI 摘要

OpenRouter 推出统一的异步视频生成 API，通过 POST /api/v1/videos 提交任务、轮询状态并下载 MP4，支持 Seedance、Veo、Wan 等模型，切换模型只需更改 model 标识符。

## 正文

在测试单个模型时，为应用添加视频生成功能是直截了当的。但当你想要尝试另一个模型时，复杂性就显现出来了。每个提供商都可能有自己的端点、请求参数、任务状态、轮询逻辑和输出格式。这就把一次简单的模型更换变成了又一个需要构建和维护的集成工作。

我们将这一工作流整合到了一个异步视频 API 之后。你向 POST /api/v1/videos 提交提示词，收到一个任务 ID，轮询直到生成完成，然后下载生成好的视频。

在本指南中，我们将从头到尾构建这一流程。我们将使用 Seedance 提交任务，安全地轮询，保存 MP4 文件，然后用 Veo 和 Wan 运行相同的集成。

摘要

一个端点，多种视频模型。通过 POST /api/v1/videos 使用 Seedance、Veo、Wan 及其他受支持的模型进行生成。

该工作流是异步的。提交任务，轮询其状态，然后下载完成的视频。

通过更改模型标识符来切换模型。某些特定于模型的设置（如时长和宽高比）仍需要针对每个模型进行调整，更多细节见第 4 步，但端点、认证、轮询循环和下载逻辑始终不变。

为什么异步 API 比替代方案更好

视频生成所需的时间比典型的 API 响应更长。模型必须生成并协调许多帧，保持它们之间的视觉一致性，有时还要生成匹配的音频。根据模型和请求的设置，这一过程可能需要几秒到几分钟。

在整个期间保持原始 HTTP 请求打开是脆弱的。浏览器会话可能关闭，无服务器函数可能达到其执行限制，或者代理可能在视频准备好之前超时。

异步 API 将提交与完成分离开来：

提交生成请求。

立即收到任务 ID。

单独检查任务状态。

生成完成后下载视频。

模型在后台工作时，你的应用可以继续运行。它还可以在重启后恢复任务，因为生成过程绑定在一个持久化的任务 ID 上，而不是一个长时间保持的连接。

直接与单个提供商集成

当你已经明确知道自己想要哪个模型，并且预期不会改变时，直接与单一提供商集成可以运作得很好。你直接使用该提供商的认证方式、请求格式、任务状态、轮询端点和输出响应。

当你想要对比另一个模型时，额外的工作量就显现出来了。新的提供商可能对时长和分辨率使用不同的字段名，或者返回带有不同终态状态的不同任务对象。它可能还要求用另一种方法来下载生成完成的资产。这样一来，你的应用程序就需要第二个客户端、另一组环境变量，以及更多针对特定提供商的错误处理逻辑。

这种方法本身并没有什么问题。它只是意味着切换模型变成了一次集成变更，而不是配置变更，这会让实验速度变慢，并且随着模型列表的增长，维护成本也会上升。

在本地运行视频模型

本地生成能给你最大的控制权。你可以选择模型权重、自定义工作流、将资产保留在自己的环境中，并且无需为每次生成向托管提供商付费。

这种控制权也伴随着基础设施方面的责任。你需要合适的 GPU 算力，以及正确的 Python 和 CUDA 依赖。你还需要足够的存储空间，并为每个模型家族维护一个可用的运行环境。更高的分辨率和更长的视频会增加内存和处理需求，而添加另一个模型可能意味着要下载更多权重或维护另一个工作流。

对于已经运营 GPU 基础设施或需要本地处理的团队来说，这可能是值得的。但如果你的目标是快速添加视频生成功能并测试多个模型，这是一个较重的起点。而通过 OpenRouter 的托管路径则省去了大部分这类搭建工作，这也是本指南其余部分将要介绍的内容。

通过 OpenRouter 使用一个托管 API

我们在所有支持的视频模型之间保持一致的生成生命周期。无论所选模型是 Seedance、Veo、Wan 还是目录中的其他模型，应用程序都使用相同的 API 密钥、POST /api/v1/videos 端点、任务状态流程和输出获取过程。

这些模型仍具备不同的能力。有的可能支持更长的时长，而另一些则提供额外的宽高比、更高分辨率、音频生成或特定于提供商的控制选项。我们通过视频模型端点来呈现这些差异，而不是强行让每个模型都具备完全相同的功能集。

这样既能为你提供稳定的集成体验，又不会掩盖每个模型的独特之处。你的应用可以查询当前的能力、构建有效的请求，并在不替换周边任务基础设施的情况下更换模型。

前提条件与设置

你只需要一个 OpenRouter API 密钥和一个能够发送 HTTP 请求的工具。这里的示例使用 Python 搭配 requests 库，以及 TypeScript 搭配内置的 fetch API，但该工作流程适用于任何能够发起 HTTP 请求的语言。

首先从你的 OpenRouter 账户创建一个 API 密钥，然后将其存储在环境变量中，而不是直接写入源代码：

export OPENROUTER_API_KEY="sk-or-..."

对于 Python 示例，如果你尚未安装 requests，请先安装：

pip install requests

OpenRouter 使用 bearer token 对 API 请求进行身份验证。在 Python 中，我们将一次性定义共享值，并在整个指南中重复使用：

import os import requests

API_KEY = os.environ["OPENROUTER_API_KEY"] BASE_URL = "https://openrouter.ai/api/v1"

HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }

在提交任务之前，你还可以查询视频模型端点，查看当前可用的模型以及每个模型支持的功能：

curl "https://openrouter.ai/api/v1/videos/models" \ -H "Authorization: Bearer $OPENROUTER_API_KEY"

响应中包含每个模型支持的时长、分辨率、宽高比、帧图像支持、音频能力、定价 SKU 以及特定于提供商的参数。这比假设某个视频模型接受的设置也适用于另一个模型要可靠得多。

步骤 1：提交视频生成任务

向 /api/v1/videos 发送一个 POST 请求，并指定视频模型。同时包含一个描述你希望生成内容的提示词。

每个请求都必须包含 model，而 text-to-video 则必须包含 prompt。支持仅凭图像输入生成视频的模型可以省略 prompt。你还可以在所选模型支持的情况下，提供可选设置，例如时长、分辨率、宽高比、音频生成、参考图像和随机种子。

我们将在整个指南中使用相同的提示词：

PROMPT = ( "A paper boat drifting down a rain-slicked gutter at night, " "neon reflections, slow tracking shot, cinematic lighting" )

以下函数使用 Seedance 2.0 提交任务：

def submit_video(model: str, prompt: str) -> dict: response = requests.post( f"{BASE_URL}/videos", headers=HEADERS, json={ "model": model, "prompt": prompt, "duration": 4, "resolution": "720p", "aspect_ratio": "16:9", "generate_audio": False, }, timeout=60, )

response.raise_for_status() return response.json()

job = submit_video( model="bytedance/seedance-2.0", prompt=PROMPT, )

print("Job ID:", job["id"]) print("Status:", job["status"]) print("Polling URL:", job["polling_url"])

对应的 cURL 请求是：

curl "https://openrouter.ai/api/v1/videos" \ -H "Authorization: Bearer $OPENROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "bytedance/seedance-2.0", "prompt": "A paper boat drifting down a rain-slicked gutter at night, neon reflections, slow tracking shot, cinematic lighting", "duration": 4, "resolution": "720p", "aspect_ratio": "16:9", "generate_audio": false }'

请求成功会返回 HTTP 202 Accepted。该响应表示的是一个后台任务，而非已完成的视频：

{ "id": "job-abc123", "status": "pending", "polling_url": "https://openrouter.ai/api/v1/videos/job-abc123" }

在继续之前，请先保存返回的任务 ID。如果你的进程重启，你应该能够恢复跟踪现有任务，而不是重新提交并支付另一次生成费用。

步骤 2：轮询任务直至完成

步骤 1 中返回的 polling_url 指向与你通过 GET /api/v1/videos/{id} 访问的同一个任务资源，它们是同一个端点。视频任务会经历以下状态：

状态含义

pending（待处理）任务已被接受，正在等待运行

in_progress（进行中）服务商正在生成视频

completed（已完成）视频已准备好可供下载

failed（失败）生成失败

cancelled（已取消）任务已被取消

expired（已过期）任务超出了其允许的存活时间

你的轮询循环应在 completed 状态时返回，并在 failed、cancelled 或 expired 状态时停止并报错。否则，应用程序可能会持续检查一个永远不会产出视频的任务。

文档中记录的响应会将 polling_url 作为完整 URL 返回。下面的 urljoin 调用是防御性编码，也能处理相对路径，因此循环在两种情况下都能正常工作：

import time from urllib.parse import urljoin

TERMINAL_ERROR_STATES = { "failed", "cancelled", "expired", }

def poll_video( initial_job: dict, interval: float = 30.0, timeout: float = 3600.0, ) -> dict: """Poll until the video completes or reaches an error state."""

polling_url = urljoin( "https://openrouter.ai", initial_job["polling_url"], )

deadline = time.monotonic() + timeout job = initial_job

while True: status = job["status"] print("Status:", status)

if status == "completed": return job

if status in TERMINAL_ERROR_STATES: error = job.get("error") or "No error details were returned." raise RuntimeError( f"Video generation ended with status '{status}': {error}" )

if status not in {"pending", "in_progress"}: raise RuntimeError( f"Received unexpected job status: {status}" )

if time.monotonic() >= deadline: raise TimeoutError( f"Job {job['id']} did not complete within " f"{timeout} seconds." )

time.sleep(interval)

response = requests.get( polling_url, headers={ "Authorization": f"Bearer {API_KEY}", }, timeout=30, )

response.raise_for_status() job = response.json()

completed_job = poll_video(job)

这个循环包含了两项快速示例中常被省略的保障措施。首先，它处理了所有文档中记录的终态，而不仅仅是等待 completed。其次，它设置了一小时的超时时间，这样任务就不会让进程无限期运行。有一个值得了解的边界情况：由于截止时间是在每次 sleep 之前检查，而非之后，在最坏情况下，任务在循环捕获到超时之前，最多可能比名义超时时间多运行一个轮询间隔。对于后台任务来说，这是一个可以接受的权衡。如果你需要硬性上限，也可以在从 sleep 中醒来后立即再次检查截止时间。

我们目前的建议是使用 30 秒的轮询间隔。视频任务通常需要大约 30 秒到几分钟的时间，每秒检查一次并不会让服务商更快完成。该间隔和上述超时上限都属于操作层面的建议，并非端点本身文档化的契约，因此你可以根据自己的工作负载进行调整。

同样的轮询流程，使用 TypeScript 实现：

type VideoJobStatus = | "pending" | "in_progress" | "completed" | "failed" | "cancelled" | "expired";

type VideoJob = { id: string; polling_url: string; status: VideoJobStatus; error?: string; unsigned_urls?: string[]; };

const apiKey = process.env.OPENROUTER_API_KEY;

if (!apiKey) { throw new Error("OPENROUTER_API_KEY is not set"); }

const terminalErrorStates = new Set<VideoJobStatus>([ "failed", "cancelled", "expired", ]);

async function pollVideo( initialJob: VideoJob, intervalMs = 30_000, timeoutMs = 3_600_000, ): Promise<VideoJob> { const pollingUrl = new URL( initialJob.polling_url, "https://openrouter.ai", );

const deadline = Date.now() + timeoutMs; let job = initialJob;

while (true) { console.log(`Status: ${job.status}`);

if (job.status === "completed") { return job; }

if (terminalErrorStates.has(job.status)) { throw new Error( job.error ?? `Video generation ${job.status}`, ); }

if (Date.now() >= deadline) { throw new Error( `Video job ${job.id} did not complete before the timeout`, ); }

await new Promise((resolve) => setTimeout(resolve, intervalMs), );

const response = await fetch(pollingUrl, { headers: { Authorization: `Bearer ${apiKey}`, }, });

if (!response.ok) { throw new Error( `Polling failed: ${response.status} ${await response.text()}`, ); }

job = (await response.json()) as VideoJob; } }

将状态请求失败与视频任务失败区别对待。轮询时出现的临时超时并不能证明生成本身失败。应针对同一任务 ID 重试状态请求，而不是提交新任务。

第 3 步：检索并保存视频

当状态变为 completed 时，任务响应中会包含一个已填充的 unsigned_urls 数组。每个条目指向该任务经过身份验证的内容端点：

GET /api/v1/videos/{jobId}/content?index=0

索引默认为 0。仅当模型返回多个视频输出时才需要更改。尽管字段名如此，这些 URL 并非预签名 URL，因此在轮询时需要在 Authorization 请求头中发送你的 API 密钥，方式与之前相同。

下面的辅助函数在存在未签名 URL 时使用第一个，并在极少数情况下根据任务 ID 重建内容 URL。

def download_video( job: dict, output_path: str = "out.mp4", index: int = 0, ) -> None: unsigned_urls = job.get("unsigned_urls") or []

download_url = ( unsigned_urls[index] if len(unsigned_urls) > index else ( f"{BASE_URL}/videos/" f"{job['id']}/content?index={index}" ) )

with requests.get( download_url, headers={ "Authorization": f"Bearer {API_KEY}", }, stream=True, timeout=180, ) as response: response.raise_for_status()

with open(output_path, "wb") as output_file: for chunk in response.iter_content( chunk_size=1024 * 1024 ): if chunk: output_file.write(chunk)

print(f"Saved {output_path}")

download_video(completed_job)

以分块方式流式传输响应，可避免在将整个 MP4 写入磁盘之前将其全部加载到内存中。

以下是 TypeScript 等效实现。请注意，此版本会将下载内容缓冲到内存中，而不是流式传输到磁盘；对于短视频片段来说这没问题，但如果你经常下载长视频或高分辨率视频，建议改用管道流：

import { writeFile } from "node:fs/promises";

async function downloadVideo( job: VideoJob, outputPath = "out.mp4", index = 0, ): Promise<void> { const downloadUrl = job.unsigned_urls?.[index] ?? `https://openrouter.ai/api/v1/videos/` + `${job.id}/content?index=${index}`;

const response = await fetch(downloadUrl, { headers: { Authorization: `Bearer ${apiKey}`, }, });

if (!response.ok) { throw new Error( `Download failed: ${response.status} ` + `${await response.text()}`, ); }

const videoBuffer = Buffer.from( await response.arrayBuffer(), );

await writeFile(outputPath, videoBuffer); console.log(`Saved ${outputPath}`); }

此时，你已获得一个生成的 MP4 文件并保存在磁盘上。请将已完成的视频移至你控制的存储中，而不要将生成端点视为永久文件托管。已完成的任务还可能包含一个 usage 对象，其中包含最终费用，无论你使用哪种语言，该对象都是响应正文的一部分：

usage = completed_job.get("usage") or {}

print("Generation cost:", usage.get("cost")) print("Used BYOK:", usage.get("is_byok"))

将该值存储在你的内部任务记录中，以便跟踪每次生成的实际成本。

第 4 步：一行代码切换模型

提交、轮询和下载函数并不绑定 Seedance。要使用其他受支持的视频模型，只需更改模型标识符：

# Seedance MODEL = "bytedance/seedance-2.0"

# Veo # MODEL = "google/veo-3.1"

# Wan # MODEL = "alibaba/wan-2.7"

job = submit_video( model=MODEL, prompt=PROMPT, )

completed_job = poll_video(job) download_video(completed_job)

端点、身份验证、响应结构、状态处理和下载逻辑在所有三个模型中保持一致。不会自动延续的是所有可选设置。切换模型在代码上只是一行改动，但这并不保证任何给定的时长、分辨率或宽高比组合都能在新模型上通过校验。本指南中涉及的这一配置恰好在这三个模型上均可移植使用。

{ "duration": 4, "resolution": "720p", "aspect_ratio": "16:9", "generate_audio": false }

在撰写本文时，实时模型端点显示 Seedance 2.0、Veo 3.1 和 Wan 2.7 均支持该特定组合：四秒、720p、16:9。这是这三个示例共有的配置，并非声称每个模型上所有设置都完全相同。超出此范围，差异很快就会显现：

Veo 3.1 目前显示支持四秒、六秒和八秒的时长。

Seedance 2.0 目前显示支持四秒至十五秒的时长以及更多宽高比。

Wan 2.7 目前显示支持两秒至十秒的时长以及 720p 或 1080p 分辨率。

一个五秒的请求可以通过 Seedance 和 Wan 的验证，但在 Veo 上会失败。这就是为什么你的应用应在提交请求前查询 /api/v1/videos/models，而不是假设某个模型接受的设置也适用于另一个模型。上述数字在依赖它们之前，值得对照该实时端点重新核实，因为模型能力确实会变化。

同一端点还暴露了用于模型特定功能的 allowed_passthrough_parameters。这些是允许你在请求的 provider.options 对象中发送的键，该对象以提供商标识符为键，例如 provider.options["google-vertex"].parameters。只有服务于你请求的提供商的选项会被转发，未识别的键会被丢弃。例如，Veo 目前列出了 negativePrompt 和 enhancePrompt 等控制项，而 Wan 则暴露了包括 negative_prompt 和 prompt_extend 在内的选项。

在投入生产之前，有几件事值得了解

上述代码足以生成并下载一个视频。一旦在生产环境中运行，问题就变了：你需要控制成本、区分任务失败与网络故障、避免重复处理，并在提交进程退出后持续跟踪任务。

在扩展之前先检查成本

视频生成的价格因模型和配置而异。时长、分辨率、音频生成以及服务商的计费方式都会影响最终成本。本地生成则完全改变了成本结构，没有按片段计费，但取而代之的是实实在在的前期硬件和运维成本。托管式 API 则让成本保持可变，并与使用量挂钩，具体是更便宜还是更贵，取决于你的用量规模以及你是否已经拥有相关硬件。

不要在你的应用中内置一个通用的成本计算公式。在显示估算费用或提交大批量任务之前，请查询 `/api/v1/videos/models` 接口，并读取所选模型的 `pricing_skus` 字段。当任务完成后，响应中可以包含一个 `usage` 对象，其中带有该次生成的实际成本：

{ "usage": { "cost": 0.5, "is_byok": false } }

在运行大批量任务之前，请使用当前的模型数据估算成本，然后将估算值与已完成任务返回的实际 `usage.cost` 值进行对比。这也有助于你发现因更高分辨率、更长时长、生成音频或更换不同模型而导致的意外成本变化。

处理失败时避免产生重复任务

轮询请求失败并不等同于视频生成任务失败。你的应用在检查状态时可能会失去连接，但服务商可能仍在生成视频。如果你立即重新提交相同的提示词，两个任务可能都会完成，导致一次用户请求却产生两个视频和两笔费用。

一旦提交成功，请立即持久化保存 OpenRouter 任务 ID。一个有用的任务记录可能包含如下字段：

{ "internal_request_id": "req_9f21", "openrouter_job_id": "job-abc123", "model": "bytedance/seedance-2.0", "status": "pending", "attempt_number": 1, "submitted_at": "2026-07-27T12:00:00Z", "output_location": null, "cost": null, "error": null }

当状态请求因超时、连接错误或临时性服务器响应而失败时，请使用现有的任务 ID 重试该状态请求。只有当任务本身达到失败（failed）、已取消（cancelled）或已过期（expired）状态，并且你的应用重试策略允许再次尝试时，才创建新的生成任务。

将任务重试与轮询重试分开处理。轮询重试是再次检查同一个任务，而生成重试则会创建一个新的付费任务。请限制生成重试的次数，并保留为同一个内部请求创建的所有任务 ID，这样在需要调查重复输出、服务商故障或意外成本时，你就能拥有完整的记录。

当轮询无法再扩展时，请改用 Webhook

对于脚本、原型以及少量任务来说，轮询是一个不错的默认方案。但当你的应用可能同时运行数百个生成任务时，它的效率就会下降。

要自动接收结果，请在提交任务时包含一个 HTTPS callback_url：

{ "model": "bytedance/seedance-2.0", "prompt": "A paper boat drifting through neon reflections", "duration": 4, "resolution": "720p", "aspect_ratio": "16:9", "callback_url": "https://example.com/webhooks/openrouter-video" }

你可以为单个请求设置回调，也可以为整个工作区配置默认回调。请求级别的设置优先于工作区默认值。

当任务达到终态时，我们会发送 Webhook。每次投递都包含一个 X-OpenRouter-Idempotency-Key，例如：

job-abc123-completed

在处理事件之前先存储该值。如果 Webhook 被再次投递，你的处理器就能识别出该任务已被处理过，从而避免重复下载视频或重复启动下一个工作流。

当配置了 Webhook 签名密钥时，请求还会包含一个 X-OpenRouter-Signature。在解析或重新序列化请求体之前，请先根据原始请求体验证签名。生产环境中的处理器随后应保存新的任务状态，快速返回成功响应，并将下载、转码或存储等操作移交给后台工作进程。

在持久化存储中跟踪并发任务

提交和等待是两个独立的操作，因此你的应用可以同时运行多个视频任务。不要为每个任务都启动无限制的轮询循环。请使用有界的工作进程池或任务队列，并控制状态请求和下载操作可以并发执行的数量。

在 Python 中，你可以使用线程池或异步工作队列来处理有限数量的任务。在 TypeScript 中，使用带并发控制的队列比将数千个轮询 Promise 直接传给 Promise.all() 更安全。

具体实现方式并不重要，重要的是以下这些规则：

在开始轮询之前保存每个任务 ID。

限制活跃的轮询和下载操作数量。

工作进程重启后，恢复未完成的任务。

不要仅仅因为应用重启就重新提交任务。

及时将已完成的视频转移到你自己的存储中。

任务 ID 是您的应用程序与正在进行中的生成任务之间的持久连接。请将其视为应用程序状态的一部分，而不是仅存在于某个运行进程中的值。

综合运用

我们介绍了四个步骤，无论您使用哪个模型，这些步骤都不会改变。您只需通过 POST /api/v1/videos 提交请求，在轮询 GET /api/v1/videos/{id} 时留意全部四种终态，下载结果，并在需要更换模型时修改一个字符串即可。

一旦异步生命周期处理正确，模型就变成了一个设置项，而非架构决策——无论您使用的是本文介绍的三个模型，还是之后新增的任何模型。

如果您正在选择起点，可以先浏览视频模型目录，在确定使用某个模型之前，并排查看其定价和功能。

常见问题解答

OpenRouter 支持视频生成吗？

支持，通过专用的异步 API 实现。您向 POST /api/v1/videos 提交提示词，轮询 GET /api/v1/videos/{id} 直到状态变为 completed，然后下载结果。支持的模型包括 Seedance、Veo、Wan 等，全部通过同一个端点访问。

如何通过 API 从文本生成视频？

向 /api/v1/videos 发送一个包含模型和提示词的 POST 请求。您会收到一个任务 ID 和一个 polling_url，而不是视频本身。持续轮询直到状态变为 completed，然后从 unsigned_urls 或 /content 端点下载结果。

如何轮询异步视频生成任务？

按一定时间间隔调用 GET /api/v1/videos/{id}，大约 30 秒一次比较合理，直到状态达到终态：completed、failed、cancelled 或 expired。设置一个超时上限，以免卡住的任务无限期挂起您的进程。

OpenRouter 支持哪些视频模型？

目录中包含 Seedance、Veo、Wan 等模型，并且还在持续增加。查询 GET /api/v1/videos/models 可获取当前列表，以及每个模型支持的分辨率、时长、宽高比和透传参数。

我可以在不重写代码的情况下切换视频模型吗？

是的。请求结构、身份验证和轮询循环在所有模型中都是相同的，只有模型字段会变化。模型专属参数仍然通过 provider.options 透传对象传递给提供商。

在本地生成 AI 视频和通过 API 生成，哪个更便宜？

本地生成在支付硬件费用后没有单条视频的费用，但它需要一块性能足够的 GPU、依赖管理，并且每个模型家族都需要单独配置。托管 API 按生成次数收费，但完全省去了 GPU 和配置环节。哪个对你更便宜，取决于你的使用量以及你是否已经拥有硬件。

AI 视频生成需要多长时间？

通常在三十秒到几分钟之间，具体取决于模型、分辨率和视频片段长度。这就是 API 采用异步而非普通阻塞调用的原因。

视频生成是否支持零数据保留（ZDR）？

不支持。异步检索步骤需要将生成的输出短暂保留以便下载，因此启用了 ZDR 的请求不会被路由到视频生成服务。
