OpenRouter Video Generation API 2026 Guide
On August 25, 2026, OpenRouter shipped a single async endpoint — POST /api/v1/videos — that fronts 26 video generation models from ByteDance, Google, OpenAI, Alibaba, Runway, Kling, HeyGen, Black Forest Labs, and MiniMax. Before this release, adding video to an application meant wiring one SDK per provider, each with its own auth, request shape, job statuses, polling cadence, and download URL. OpenRouter's approach collapses all of that into one workflow: submit a job, poll a polling_url, retrieve the finished MP4.
This review walks through the verified endpoint behavior from OpenRouter's published guide dated August 25, 2026, the live /api/v1/videos/models catalog (26 models, snapshot captured the same day), and the per-model pricing SKUs returned in that catalog. The relevant fact bundle — endpoint paths, model identifiers, pricing per second, polling logic, webhook payload — is reproducible from those two sources.
Why an async API, not a normal blocking call
A typical text-completion API responds in 200-800 ms. A typical image generation API responds in 1-4 seconds. Video generation is the first mainstream API category where the generation time routinely exceeds the timeout tolerance of a single HTTP request. OpenRouter's guide lists a 30-second-to-several-minutes generation range as the common case. Holding a TCP connection open that long is fragile: serverless platforms (Cloudflare Workers, AWS Lambda) cap execution at 30s-15min; corporate proxies and load balancers idle out at 60-300s; mobile networks drop after 30-60s of silence.
The asynchronous pattern is the textbook fix: separate submission from completion. You submit a job, receive an ID, poll the job until the status reaches completed, and then download the asset. If your process crashes mid-poll, the job ID persists on the server and you can resume tracking from any device.
The synchronous alternative — open a streaming WebSocket and receive frame data as the model produces it — exists for some models but requires a custom transport per provider and does not survive process restarts. OpenRouter chose the persistence-over-streaming tradeoff, which is the right one for serverless and batch use cases.
Pricing: $0.03/sec to $0.60/sec, with audio and resolution as the two cost axes
The full pricing catalog from /api/v1/videos/models returns per-model pricing_skus. Most models bill by duration-seconds at a given resolution tier; some bill by video tokens (Seedance 2.0 family) or megapixel-seconds (FLUX Video Upscale). Below is a price-comparable table covering the major models, normalized to USD per second of output at the most common 720p/1080p tier:
| Model | Pricing model | Cost at 720p/1080p | 4K tier | Audio support |
|---|---|---|---|---|
| Veo 3.1 | $0.20/sec (no audio) / $0.40/sec (audio) | $0.20-0.40/sec | $0.40-0.60/sec | ✅ |
| Veo 3.1 Fast | $0.10/sec (no audio) / $0.12/sec (audio) | $0.08-0.10/sec | $0.25-0.30/sec | ✅ |
| Veo 3.1 Lite | $0.05/sec (no audio) / $0.08/sec (audio) | $0.03-0.05/sec | — | ✅ |
| Sora 2 Pro | $0.30-0.50/sec | $0.30 (720p) / $0.50 (1080p) | — | ❌ |
| Seedance 2.5 | video_tokens: $0.0000107/token | ~$0.18/sec | — | ✅ |
| Seedance 2.0 | video_tokens: $0.000007/token | ~$0.13/sec | $0.000004/token | ✅ |
| Seedance 2.0 Fast | video_tokens: $0.0000042/token | ~$0.08/sec | — | ✅ |
| Seedance 2.0 Mini | video_tokens: $0.0000035/token | ~$0.07/sec | — | ✅ |
| Seedance 1.5 Pro | video_tokens: $0.0000024/token | ~$0.05/sec | — | ✅ |
| Wan 3.0 | $0.05-0.10/sec | $0.10 (720p) / $0.20 (1080p) | — | ✅ |
| Wan 2.7 | $0.10/sec | $0.10/sec | — | ❌ |
| Wan 2.6 | $0.04-0.08/sec | $0.08 (720p) / $0.12 (1080p) | — | ❌ |
| Kling v3.0 Pro | $0.112/sec (+ $0.056 audio) | $0.112/sec | $0.112 (1080p) | ✅ (+$0.056/sec) |
| Kling v3.0 Standard | $0.084/sec (+ $0.042 audio) | $0.084/sec | $0.084 (1080p) | ✅ |
| Kling Video O1 | $0.112/sec | $0.112/sec | — | ❌ |
| Runway Gen-4.5 | $0.12/sec | $0.12/sec | — | ❌ |
| Runway Aleph 2.0 | $0.28/sec, $0.56 minimum | $0.28/sec | — | ❌ |
| Grok Imagine Video 1.5 | $0.08-0.14/sec | $0.08 (480p) / $0.14 (720p) / $0.25 (1080p) | — | ❌ |
| Grok Imagine Video | $0.05-0.07/sec | $0.05 (480p) / $0.07 (720p) | — | ❌ |
| MiniMax H3 | $0.13/sec + $0.04 per reference image | $0.13/sec | — | ❌ |
| MiniMax Hailuo 2.3 | $0.0817/sec | $0.0817/sec | — | ❌ |
| HeyGen Avatar IV | $0.05/sec | $0.05/sec | — | ❌ |
| HappyHorse 1.1 | $0.10-0.13/sec | $0.0988 (720p) / $0.1278 (1080p) | — | ❌ |
| FLUX Video Upscale | $0.075-0.105/megapixel-second | — (upscale only) | — | ❌ |
| FLUX.3 Video | $0.17-0.53/sec | $0.17 (720p) / $0.29 (1080p) | $0.41-0.53/sec | ❌ |
Source: https://openrouter.ai/api/v1/videos/models snapshot taken August 26, 2026.
The headline numbers: Veo 3.1 Lite at $0.03-0.05/sec is the cheapest 720p model with audio, and Wan 2.6 at $0.04-0.08/sec is the cheapest 720p model without audio. At the top end, Sora 2 Pro at $0.30-0.50/sec and Veo 3.1 at $0.40-0.60/sec are 6-15× more expensive than the budget tier for comparable resolutions.
A 5-second 1080p clip with audio runs $0.40 (Wan 2.6) to $3.00 (Veo 3.1 4K audio) to $2.50 (Sora 2 Pro). Budget for $1-2 per clip as the realistic working figure across most providers.
Endpoint behavior, verified
Submit a job
The single submission endpoint is POST https://openrouter.ai/api/v1/videos. The request body is JSON, with model (required) and prompt (required for text-to-video).
The verified request shape (from the OpenRouter tutorial):
curl "https://openrouter.ai/api/v1/videos" \
-H "Authorization: Bearer *** \
-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
}'
Successful submission returns HTTP 202 Accepted with a job envelope:
{
"id": "job-abc123",
"status": "pending",
"polling_url": "https://openrouter.ai/api/v1/videos/job-abc123"
}
Poll the job
polling_url is identical to GET https://openrouter.ai/api/v1/videos/{id}. The job moves through documented statuses:
| Status | Meaning |
|---|---|
| `pending` | Accepted, waiting to run |
| `in_progress` | Provider is generating |
| `completed` | Video ready to download |
| `failed` | Generation failed (error in response body) |
| `cancelled` | Job was cancelled |
| `expired` | Job exceeded its allowed lifetime |
A recommended polling interval is 30 seconds.
Download the finished video
When status: "completed" arrives, the response includes a populated unsigned_urls array. Despite the name, these URLs require the Authorization: Bearer header:
curl "https://openrouter.ai/api/v1/videos/job-abc123/content?index=0" \
-H "Authorization: Bearer *** \
--output out.mp4
Switch models with one line
# 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)
The verified shared configuration across Seedance 2.0, Veo 3.1, and Wan 2.7 is {duration: 4, resolution: "720p", aspect_ratio: "16:9", generate_audio: false}.
Model-specific pass-through parameters
Each model exposes allowed_passthrough_parameters in the /api/v1/videos/models response. These are the keys you can send inside the provider.options pass-through block:
{
"provider": {
"options": {
"google-vertex": {
"parameters": {
"negativePrompt": "blurry, low-resolution",
"enhancePrompt": true
}
}
}
}
}
Verified examples: Veo (negativePrompt, enhancePrompt); Wan (negative_prompt, prompt_extend); HeyGen (voice_id, voice_settings, motion_prompt, expressiveness, fit).
Webhook delivery for production scale
Pass a callback_url in the submission body to receive webhook delivery when the job reaches a terminal state:
{
"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"
}
Each delivery includes an X-OpenRouter-Idempotency-Key header for deduplication.
Verified limitations and known footguns
- **Zero Data Retention (ZDR) is NOT supported** for video generation.
- **Audio capability varies wildly.** Only 9 of the 26 models support `generate_audio: true`.
- **Resolution support varies wildly.** Some models support only `720p`, some support `480p`/`720p`/`1080p`, and only Veo 3.1 supports `4k`.
- **A failed polling request ≠ a failed generation.** Always persist the job ID and retry status on the existing job.
- **`unsigned_urls` is a misleading name.** The URLs are not presigned.
Curl example: end-to-end with Seedance 2.0
RESPONSE=$(curl -s -X POST "https://openrouter.ai/api/v1/videos" \
-H "Authorization: Bearer *** \
-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","duration":4,"resolution":"720p","aspect_ratio":"16:9","generate_audio":false}')
JOB_ID=$(echo "$RESPONSE" | python3 -c "import sys, json; print(json.load(sys.stdin)['id'])")
while true; do
STATUS=$(curl -s "https://openrouter.ai/api/v1/videos/$JOB_ID" \
-H "Authorization: Bearer *** | python3 -c "import sys, json; print(json.load(sys.stdin)['status'])")
case "$STATUS" in
completed) break ;;
failed|cancelled|expired) exit 1 ;;
esac
sleep 30
done
curl -s "https://openrouter.ai/api/v1/videos/$JOB_ID/content?index=0" \
-H "Authorization: Bearer *** \
--output "out_${JOB_ID}.mp4"
Python example: full workflow
import os, time, requests
from urllib.parse import urljoin
API_KEY=os.env["OPENROUTER_API_KEY"]
BASE_URL = "https://openrouter.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
TERMINAL_ERROR_STATES = {"failed", "cancelled", "expired"}
def submit_video(model, prompt, **opts):
response = requests.post(
f"{BASE_URL}/videos",
headers=HEADERS,
json={"model": model, "prompt": prompt, **opts},
timeout=60,
)
response.raise_for_status()
return response.json()
def poll_video(job, interval=30.0, timeout=3600.0):
polling_url = urljoin("https://openrouter.ai", job["polling_url"])
deadline = time.monotonic() + timeout
current = job
while True:
status = current["status"]
if status == "completed":
return current
if status in TERMINAL_ERROR_STATES:
raise RuntimeError(f"Job ended with status '{status}'")
if time.monotonic() >= deadline:
raise TimeoutError(f"Job did not complete within {timeout}s")
time.sleep(interval)
response = requests.get(polling_url, headers={"Authorization": f"Bearer {API_KEY}"}, timeout=30)
response.raise_for_status()
current = response.json()
def download_video(job, output_path="out.mp4", index=0):
unsigned_urls = job.get("unsigned_urls") or []
download_url = (
unsigned_urls[index] if index < len(unsigned_urls)
else f"{BASE_URL}/videos/{job['id']}/content?index={index}"
)
response = requests.get(download_url, headers={"Authorization": f"Bearer {API_KEY}"}, stream=True, timeout=120)
response.raise_for_status()
with open(output_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return output_path
job = submit_video(model="bytedance/seedance-2.0", prompt="A paper boat drifting", duration=4, resolution="720p", aspect_ratio="16:9", generate_audio=False)
completed = poll_video(job)
print(f"Final cost: ${completed.get('usage', {}).get('cost', 'unknown')}")
download_video(completed, "boat.mp4")
The completed-job response can include a usage block:
{
"usage": {
"cost": 0.5,
"is_byok": false
}
}
Verdict
OpenRouter's video API is the first single-endpoint abstraction that covers the realistic price/quality spread for production video generation. 26 models is enough to A/B test budget vs. flagship output.
The expensive models are worth the cost only for hero content. Veo 3.1 at $0.40/sec is the right pick for a product launch trailer; it is the wrong pick for batch UGC rendering where Wan 2.6 at $0.04-0.08/sec produces comparable 720p output at 5-10× the volume for the same budget.
Zero Data Retention is the showstopper for regulated workloads. If your compliance posture requires ZDR, video generation through OpenRouter is not an option today.
Frequently asked questions
How much does OpenRouter video generation cost?
Per-second prices range from $0.03 (Veo 3.1 Lite, no audio) to $0.60 (Veo 3.1 4K with audio). A 5-second 1080p clip with audio runs $0.40 to $3.00 depending on the model.
How do I switch video models without rewriting code?
Change the model field in the submission body. The endpoint, authentication, response shape, status handling, and download URL are identical across all 26 supported models.
How long does AI video generation take?
Usually 30 seconds to several minutes, depending on the model, resolution, and clip length.
Is video generation eligible for Zero Data Retention?
No. The async retrieval step requires the generated output to be briefly retained so it can be downloaded, so accounts with ZDR enforced are not routed to video generation.
Can I use webhooks instead of polling?
Yes. Pass a callback_url in the submission body, and OpenRouter will POST to that URL when the job reaches a terminal state.
Which model should I pick for batch UGC?
Wan 2.6 at $0.04-0.08/sec without audio is the cheapest 720p option.
Sources
- [OpenRouter Video Generation API: A Code-First Guide](https://openrouter.ai/blog/tutorials/video-generation-api) (August 25, 2026)
- [OpenRouter video models catalog (`/api/v1/videos/models`)](https://openrouter.ai/api/v1/videos/models) (snapshot August 26, 2026)
- [OpenRouter docs quickstart](https://openrouter.ai/docs/quickstart)
- [OpenRouter pricing page](https://openrouter.ai/pricing)
- [OpenRouter affiliates](https://openrouter.ai/affiliates)
- [OpenRouter homepage](https://openrouter.ai/)