Documentation
Video generation
Video is an asynchronous task: submit a request, poll the task ID, fetch the result. Includes task expiry and how billing settles.
Submitting a task
Video is the one invocation mode on this platform that is not request-and-response. You submit a task, get an identifier back immediately, and collect the result later. There is no streaming variant and no blocking variant — polling is the only way to learn the outcome.
1import os, requests23response = requests.post(4 "https://www.realrelay.ai/v1/video/generations",5 headers={"Authorization": f"Bearer {os.environ['REALRELAY_API_KEY']}"},6 json={7 "model": "alibaba/wan2.1-t2v-plus",8 "prompt": "A paper boat drifting down a rain gutter",9 "duration": 5,10 },11)1213task_id = response.json()["task_id"]14print(task_id)1const response = await fetch("https://www.realrelay.ai/v1/video/generations", {2 method: "POST",3 headers: {4 Authorization: `Bearer ${process.env.REALRELAY_API_KEY}`,5 "Content-Type": "application/json",6 },7 body: JSON.stringify({8 model: "alibaba/wan2.1-t2v-plus",9 prompt: "A paper boat drifting down a rain gutter",10 duration: 5,11 }),12});1314const { task_id } = await response.json();15console.log(task_id);1curl https://www.realrelay.ai/v1/video/generations \2 -H "Authorization: Bearer $REALRELAY_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{5 "model": "alibaba/wan2.1-t2v-plus",6 "prompt": "A paper boat drifting down a rain gutter",7 "duration": 58 }'910# {"task_id": "task_9f2c...", "status": "queued"}duration, width,
height, fps,
seed and image (a URL or
base64 frame, for image-to-video models) are accepted, and
metadata carries vendor-specific fields unchanged.
Which durations and resolutions a model accepts differs per model and is
enforced by the provider, so an unsupported combination fails the submit
rather than the task.
Polling for status
The task_id you get back is ours, not the
provider's. Poll it on the same path you submitted to:
1import os, time, requests23task_id = "task_9f2c..."4headers = {"Authorization": f"Bearer {os.environ['REALRELAY_API_KEY']}"}56while True:7 task = requests.get(8 f"https://www.realrelay.ai/v1/video/generations/{task_id}",9 headers=headers,10 ).json()1112 if task["status"] in ("completed", "failed"):13 break1415 time.sleep(10)1617if task["status"] == "failed":18 raise RuntimeError(task["error"]["message"])1920print(task["url"], task["format"])1const task_id = "task_9f2c...";2const headers = {3 Authorization: `Bearer ${process.env.REALRELAY_API_KEY}`,4};56let task;7do {8 await new Promise((resolve) => setTimeout(resolve, 10_000));9 const response = await fetch(10 `https://www.realrelay.ai/v1/video/generations/${task_id}`,11 { headers },12 );13 task = await response.json();14} while (task.status !== "completed" && task.status !== "failed");1516if (task.status === "failed") throw new Error(task.error.message);1718console.log(task.url, task.format);1curl https://www.realrelay.ai/v1/video/generations/task_9f2c... \2 -H "Authorization: Bearer $REALRELAY_API_KEY"34# in progress:5# {"task_id": "task_9f2c...", "status": "in_progress"}67# finished:8# {9# "task_id": "task_9f2c...",10# "status": "completed",11# "url": "https://...",12# "format": "mp4",13# "metadata": { "duration": 5, "fps": 30, "width": 1280, "height": 720 }14# }1516# failed:17# {"task_id": "task_9f2c...", "status": "failed",18# "error": { "code": 500, "message": "..." }}status moves through
queued → in_progress →
completed, or ends at
failed. Poll on an interval of seconds, not
milliseconds: generation takes as long as the provider takes, and polling
harder does not make it finish sooner.
Fetching the result
A completed task carries a url, a
format, and a metadata
block describing what was actually produced — the real duration, frame rate
and dimensions, which can differ from what you asked for.
1{2"task_id": "task_9f2c...",3"status": "completed",4"url": "https://...",5"format": "mp4",6"metadata": { "duration": 5, "fps": 30, "width": 1280, "height": 720 }7}Do not treat that URL as durable. It may point at our own proxy, at the
provider directly, or be an inline data: payload,
depending on the model — and in the first two cases it expires. Download the
file when the task completes and store it yourself.
Billing and failures
Video is billed per call, adjusted by what you actually generated: duration and resolution are part of the price. A charge is placed when you submit and settled when the task ends, so the final amount can differ from the estimate at submit time.
A task that ends in
failedis refunded automatically. You do not need to ask.A task that has not finished within the platform's task timeout — 24 hours by default — is marked
failedand refunded on the same rule. A task is never left pending forever.Submitting without ever polling still costs you: the work runs and settles regardless of whether you collect the result.
Per-model prices are on the catalog.
