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 failed is 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 failed and 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.