Videos
Generate videos.
/v1/videosGenerate a video from a text prompt. The route follows the OpenAI Videos API, so client.videos.create works unchanged with the base URL https://demo-api.routerplus.com/v1.
A video takes from ten seconds to several minutes to make. That is longer than any edge keeps a request open, so each video is a job:
- 01
POST /v1/videosanswers at once with the job. - 02
GET /v1/videos/{id}reads the job. Repeat until it iscompleted. - 03
GET /v1/videos/{id}/contentdownloads the MP4.
To try it without code, pick a video model in the playground.
Limits:
- Replicate serves every video model, on the RouterPlus account. A key bound to your own provider connection cannot make videos.
- Text-to-video only. RouterPlus refuses a request that sends an image input or reference images.
Examples
Each example creates a job, polls it and downloads the MP4.
curl -s https://dev2-api.routerplus.com/v1/videos \
-H "Authorization: Bearer $TM_API_KEY" -H "content-type: application/json" \
-d '{"model":"seedance-2.0-fast","prompt":"a paper boat drifting down a rainy street","seconds":"5"}'
# → {"id":"video_…","status":"queued",…}
curl -s https://dev2-api.routerplus.com/v1/videos/video_… -H "Authorization: Bearer $TM_API_KEY"
curl -s https://dev2-api.routerplus.com/v1/videos/video_…/content -H "Authorization: Bearer $TM_API_KEY" -o video.mp4import os
import time
from openai import OpenAI
client = OpenAI(base_url="https://demo-api.routerplus.com/v1", api_key=os.environ["TM_API_KEY"])
video = client.videos.create(model="wan-3", prompt="a lighthouse at dusk", seconds="5")
while video.status in ("queued", "in_progress"):
time.sleep(5)
video = client.videos.retrieve(video.id)
if video.status == "completed":
client.videos.download_content(video.id).write_to_file("video.mp4")import { writeFileSync } from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://demo-api.routerplus.com/v1", apiKey: process.env.TM_API_KEY });
let video = await client.videos.create({ model: "veo-3.1-fast", prompt: "a fox leaping into snow", seconds: "4" });
while (video.status === "queued" || video.status === "in_progress") {
await new Promise((r) => setTimeout(r, 5000));
video = await client.videos.retrieve(video.id);
}
if (video.status === "completed") {
const file = await client.videos.downloadContent(video.id);
writeFileSync("video.mp4", Buffer.from(await file.arrayBuffer()));
}The SDK types list OpenAI's own models and lengths. RouterPlus accepts any model id, and any whole number of seconds that the model accepts.
Send your key as Authorization: Bearer tm_vk_... or as x-api-key: tm_vk_.... A missing or invalid key returns 401 with error_type: "auth".
Create a video
POST /v1/videos takes a JSON body of at most 1 MB. RouterPlus checks every rule below before it reserves money or sends anything to a provider. A refused request has no ledger row and no charge.
| Parameter | Type | Behavior |
|---|---|---|
model | string, required | A video model id. See Model discovery. A chat model is a 400 that names the chat routes. An image model is a 400 that names /v1/images/generations. An id that is not a video model anywhere is a 404. |
prompt | string, required | Non-empty, at most 32 000 characters. |
seconds | integer, or a string of digits | The length. OpenAI's SDKs send a string ("8"). RouterPlus reads both forms the same way. Each model accepts a range (see Models). If you omit it, RouterPlus sends the model's default length, and the job is billed for the length that was reserved. |
duration | integer | Another name for seconds. Send one of the two. Both with different values is a 400. |
resolution | string | Per model, for example 480p, 720p, 1080p. |
aspect_ratio | string | Per model, for example 16:9, 9:16, 1:1. |
size | string | Exact pixels, for example 1280x720, on the models that declare sizes (Seedance). Other models drop and record it. |
generate_audio | boolean | On the models that make sound. |
seed | integer | On the models that declare one. |
user | string | Your end-user id, at most 256 characters. Kept with the job and returned on it. Never sent to the provider. |
provider | object | Routing controls, including require_parameters. RouterPlus reads them and never forwards them. See Routing policies. |
Each model publishes its accepted values in /api/models.json under supported_parameters. A value outside the set of a model is a 400. The message starts with the field name and lists what that model accepts. An explicit null means "not provided", as on the other routes.
Refused, never dropped: input_reference, frame_images and input_references. A dropped image would make, and bill, a different video from the one you asked for.
Dropped and recorded: any other key, including callback_url. RouterPlus does not send provider webhooks to your URL. The x-tm-dropped-params header and the ledger row name the dropped fields. Send "provider": {"require_parameters": true} to get a 400 instead.
The answer is HTTP 200 with the video object and status: "queued". It carries the same headers as an image response: x-request-id, x-tm-provider, x-tm-attempts, x-tm-upstream-status, and x-tm-dropped-params when a field was dropped. The provider has 60 seconds to accept the job. After that, the request is a 504 upstream_error.
The video object
{
"id": "video_7f3c2a9b1e4d4c6a8b0f2e1d3c5a7b9e",
"object": "video",
"model": "wan-3",
"status": "completed",
"progress": 100,
"created_at": 1790000000,
"completed_at": 1790000094,
"expires_at": null,
"seconds": "5",
"size": null,
"resolution": "720p",
"aspect_ratio": "16:9",
"error": null,
"usage": { "cost": 0.5 }
}| Field | Meaning |
|---|---|
id | The RouterPlus id: video_ and 32 hex digits. Never the provider's id. |
status | queued → in_progress → completed or failed. |
progress | 0 until the video is done, then 100. The providers report no progress, and RouterPlus does not estimate one. |
seconds | A string, as in OpenAI's object. |
error | On a failed job: {code, message}. The code is one of video_generation_failed, content_policy_violation, video_cancelled, video_expired, video_timeout. RouterPlus never keeps the provider's own words. |
usage.cost | The charge in USD, once the job has ended. Absent while the job runs, and on static dev keys. |
user | Your user, when you sent one. |
Read, list and download
| Call | Answer |
|---|---|
GET /v1/videos/{id} | The video object. Poll every few seconds. A job takes minutes. |
GET /v1/videos | Your organization's videos, newest first: {object: "list", data, first_id, last_id, has_more}. Query: limit (1 to 100, default 20), order (desc or asc), after (a video id). |
GET /v1/videos/{id}/content | The MP4, streamed from the provider. ?variant=video is the only variant. Any other value is a 400. RouterPlus passes a Range header on, so a player can seek. A range the provider cannot satisfy is a 416. Before the job is completed, the call is a 409. |
- RouterPlus polls the provider for you: every 5 seconds for the first two minutes, then every 10 seconds, then every 20. A read between two polls shows the last status RouterPlus has.
- A video of another organization is a 404, the same as a video that does not exist.
- The file streams through as it arrives, with the provider's
content-type(elsevideo/mp4),content-length,content-rangeandaccept-ranges. - RouterPlus never stores the file. Save it when you download it.
Billing
RouterPlus bills a video at the provider's listed price for the job: Replicate's published price per second of video, for the seconds Replicate says it billed. That is never more than 5% past the length you asked for. The price passes through with no margin. See Pricing & billing.
| Case | Charge |
|---|---|
| Before the job is submitted | RouterPlus reserves seconds × the per-second ceiling of the model. The models page shows that ceiling as Per second ≤. Your balance must cover it. |
| The job ends | The reported cost. RouterPlus releases the rest of the hold. |
| A failed, cancelled or expired job | Nothing. You get no video. |
| A completed job without a reported cost | The reservation, marked estimated in GET /v1/generation. |
| The gateway restarts while a job runs | The reservation, as for any request a crash leaves open. The job continues, and GET /v1/videos/{id} still answers for it. |
| No end after two hours of polling | The reservation. The job ends failed with the code video_timeout. |
Example on wan-3 (per-second ceiling $0.105): a 5-second request reserves $0.525 (5 × $0.105). At 480p, Replicate bills $0.025 a second, so the charge is $0.125. RouterPlus releases the rest of the hold.
Models
| Model id | Made by | Length | Resolutions | Sound | Listed price | Per second ≤ |
|---|---|---|---|---|---|---|
wan-3 | Alibaba | 2 to 30 s (default 5) | 480p, 720p, 1080p | provider default; no audio toggle | $0.025/s at 480p, $0.05/s at 720p, $0.10/s at 1080p | $0.105 |
grok-imagine-video | xAI | 1 to 15 s (default 5) | 480p, 720p | provider default; no audio toggle | $0.05/s | $0.0525 |
seedance-2.0 | ByteDance | 4 to 15 s (default 5) | 480p, 720p, 1080p | yes, at no extra cost | $0.08/s at 480p, $0.18/s at 720p, $0.45/s at 1080p | $0.48 |
seedance-2.0-fast | ByteDance | 4 to 15 s (default 5) | 480p, 720p | yes, at no extra cost | $0.07/s at 480p, $0.15/s at 720p | $0.16 |
kling-v3-video | Kuaishou | 3 to 15 s (default 5) | 720p, 1080p, 4k | generate_audio, off by default | $0.168/s at 720p ($0.252 with sound), $0.224/s at 1080p ($0.336 with sound), $0.42/s at 4k | $0.441 |
veo-3.1-fast | 4, 6 or 8 s (default 8) | 720p, 1080p | on by default; generate_audio: false turns it off | $0.15/s with sound, $0.10/s without | $0.158 |
- Wan 3 and Grok Imagine Video follow the provider's audio defaults. This API has no audio toggle for them.
- Prices are Replicate's published prices for each model, read on 2026-10-12.
- Replicate bills the video it returned. That can run a fraction of a second past the length you asked for (5.04 s for 5 s). The fraction is billed, up to 5% of the length.
- Sora 2 was retired on September 24, 2026. Existing video and usage records stay readable. See OpenAI’s discontinuation notice.
Errors
| Case | Status | error_type | Billed |
|---|---|---|---|
| A field outside the model's accepted values, an image input, a chat or image model on this route | 400 | invalid_request | no row |
| The model is listed but has no price row | 400 | model_not_priced | no row |
| Not a video model in the catalog | 404 | model_unavailable | no row |
| Request body over 1 MB | 413 | request_too_large | no row |
| Unknown id, or another organization's video | 404 | not_found | n/a |
| The file before the job is completed | 409 | invalid_request | n/a |
| Key RPM or a shared limit, balance too low for the reservation, or a monthly spend cap | 429 | rate_limit / insufficient_quota | no row |
| The provider refused the submit | the provider's | upstream_error | $0 |
| The provider did not accept the job within 60 s | 504 | upstream_error | $0 |
| The provider accepted the job but named no job id, or its answer could not be read | 502 | upstream_error | $0 |
| Every deployment serving the model is cooling down | 503 | gateway_error | nothing dispatched; retry-after: 5 |
| The provider did not send the file on a download | 502 | upstream_error | n/a |
| The provider accepted the job but it failed | 200 on the read; status: "failed" | n/a | $0 |
Error bodies use the OpenAI error envelope, with the canonical class in error.metadata.error_type. RouterPlus never relays a provider's own error body. A video model sent to POST /v1/chat/completions, POST /v1/messages or POST /v1/images/generations is a 400 that names /v1/videos.
Data policy
RouterPlus keeps the id, model, length, shape, status and charge of a job, so you can retrieve it later. The video file streams from the provider when you download it. See Data policy.
Model discovery
curl -s "https://demo-api.routerplus.com/v1/models?output_modalities=video" \
-H "Authorization: Bearer $TM_API_KEY"Every row of GET /v1/models carries architecture.output_modalities, for example ["video"]. For each video model, the public feed https://demo.routerplus.com/api/models.json carries:
supported_parametersmax_output_tokens: the per-second ceiling in cost units (one micro-dollar each)price_card: the provider's listed prices
See also: POST /v1/images/generations · Pricing & billing · Errors
For agents: /docs/api-videos.md and /llms.txt