Get a Video Task
After submitting a task through Create a video task, poll GET /v1/video/generations/{task_id} until the task reaches a terminal state, then download the video from GET /v1/videos/{task_id}/content. Polling reads a task record that RouteAPI refreshes in the background, so a 10–15 second interval is enough.
Get the task record
Section titled “Get the task record”GET /v1/video/generations/{task_id}Tasks are scoped to the API Key’s user: a task_id that belongs to another account is reported as task_not_exist.
curl https://api.routeapi.ai/v1/video/generations/task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV \ -H "Authorization: Bearer $ROUTEAPI_KEY"{ "code": "success", "message": "", "data": { "id": 4512, "created_at": 1792048450, "updated_at": 1792048569, "task_id": "task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV", "platform": "54", "user_id": 1024, "group": "default", "channel_id": 18, "quota": 123456, "action": "generate", "status": "SUCCESS", "progress": "100%", "fail_reason": "", "result_url": "https://...mp4", "submit_time": 1792048458, "start_time": 1792048461, "finish_time": 1792048569, "properties": { "input": "", "origin_model_name": "seedance-2.0-fast" }, "data": { "id": "cgt-xxxxxxxx", "status": "succeeded" } }}Task record fields
Section titled “Task record fields”| Field | Description |
|---|---|
code | "success" on a readable task; failures use the task error shape described below |
data.id | Internal row ID; not used in any request |
data.task_id | RouteAPI task ID — the value you poll and download with |
data.status | NOT_START, SUBMITTED, QUEUED, IN_PROGRESS, SUCCESS, or FAILURE |
data.progress | Progress string such as "30%" or "100%" |
data.result_url | Video URL once the task succeeded. It may point at the RouteAPI download endpoint or directly at an upstream URL with a limited lifetime |
data.fail_reason | Failure reason; empty otherwise |
data.submit_time / start_time / finish_time | Unix seconds. start_time and finish_time are 0 until the task starts and finishes |
data.created_at / updated_at | Unix seconds, the row’s creation and last-update time |
data.quota | Quota charged for the task |
data.platform | Numeric upstream channel type of the channel that ran the task |
data.group / data.channel_id / data.user_id | Billing group, channel, and owning account. Useful when asking support about a task |
data.action | What the task does: generate, textGenerate, firstTailGenerate, referenceGenerate, or remixGenerate |
data.properties | Stored task properties: origin_model_name, upstream_model_name, and the upstream request snapshot in input when the adaptor records one. Intended for debugging |
data.data | The upstream provider’s latest task payload, stored for debugging. Its shape is provider-specific |
Statuses
Section titled “Statuses”| Status | Meaning | What to do |
|---|---|---|
NOT_START | The row exists but upstream has not been polled yet | Keep polling |
SUBMITTED / QUEUED | Accepted, waiting in the provider’s queue | Keep polling |
IN_PROGRESS | Generating | Keep polling |
SUCCESS | Terminal success | Download the video |
FAILURE | Terminal failure | Read fail_reason; the reserved amount has already been refunded |
Only SUCCESS is terminal-success; FAILURE is terminal-failure and the reserved amount has already been refunded when you see it. Everything else means keep polling.
Polling example
Section titled “Polling example”import osimport timeimport requests
BASE = "https://api.routeapi.ai"HEADERS = {"Authorization": f"Bearer {os.environ['ROUTEAPI_KEY']}"}
task_id = requests.post( f"{BASE}/v1/video/generations", headers=HEADERS, json={ "model": "seedance-2.0-fast", "prompt": "An orange cat stretching on a windowsill at sunrise", "duration": 5, "resolution": "720p", "ratio": "16:9", }, timeout=60,).json()["task_id"]
while True: task = requests.get(f"{BASE}/v1/video/generations/{task_id}", headers=HEADERS, timeout=30).json()["data"] print(task["status"], task["progress"])
if task["status"] == "SUCCESS": video = requests.get(f"{BASE}/v1/videos/{task_id}/content", headers=HEADERS, timeout=300) video.raise_for_status() with open(f"{task_id}.mp4", "wb") as f: f.write(video.content) break if task["status"] == "FAILURE": raise SystemExit(f"task failed: {task['fail_reason']}")
time.sleep(15)Complete shell example
Section titled “Complete shell example”#!/usr/bin/env bashset -euo pipefail: "${ROUTEAPI_KEY:?set ROUTEAPI_KEY first}"BASE="${ROUTEAPI_BASE:-https://api.routeapi.ai}"AUTH="Authorization: Bearer $ROUTEAPI_KEY"
TASK_ID=$(curl -sS -X POST "$BASE/v1/video/generations" \ -H "$AUTH" -H "Content-Type: application/json" \ -d '{"model":"seedance-2.0-fast","prompt":"An orange cat stretching on a windowsill at sunrise","duration":4,"resolution":"480p","ratio":"16:9"}' \ | jq -er .task_id)
echo "task: $TASK_ID"
for _ in $(seq 1 60); do RESP=$(curl -sS "$BASE/v1/video/generations/$TASK_ID" -H "$AUTH") STATUS=$(echo "$RESP" | jq -r .data.status) echo "status: $STATUS $(echo "$RESP" | jq -r .data.progress)"
case "$STATUS" in SUCCESS) curl -sSL -o "$TASK_ID.mp4" "$BASE/v1/videos/$TASK_ID/content" -H "$AUTH" echo "saved $TASK_ID.mp4" exit 0 ;; FAILURE) echo "failed: $(echo "$RESP" | jq -r .data.fail_reason)" exit 1 ;; esac
sleep 15done
echo "timed out waiting for the task"exit 1Polling GET /v1/video/generations/{task_id} normally just reads the local task record, so keep the interval at 10–15 seconds rather than hammering the endpoint.
Download the video
Section titled “Download the video”GET /v1/videos/{task_id}/contentStreams the video bytes through RouteAPI, so the caller does not need to reach the provider’s storage. The response carries Cache-Control: public, max-age=86400 and the upstream Content-Type (defaulting to video/mp4 for inline Base64 results).
curl -L -o out.mp4 \ https://api.routeapi.ai/v1/videos/task_G2RdWGt4t1nck5fYqTjwREKJo8Ze4RAV/content \ -H "Authorization: Bearer $ROUTEAPI_KEY"This route accepts either an API Key or a console session, and it only returns data for tasks owned by the caller. Its errors use the {"error": {"message": "...", "type": "..."}} object shape:
| Status | Meaning |
|---|---|
| 400 | The task is not finished yet; the message contains the current status |
| 404 | Unknown task, or a task that belongs to another account |
| 403 | The resolved video URL was rejected by the outbound protection rules |
| 500 | The task row or its channel could not be read, or the video URL could not be parsed |
| 502 | The provider could not be reached, returned a non-200 status, or the video URL was empty |
Errors
Section titled “Errors”| Status | Code | Meaning |
|---|---|---|
| 400 | task_not_exist | The task_id is unknown or belongs to another account |
| 500 | get_task_failed | RouteAPI could not read its own task row |
| 500 | marshal_response_failed, copy_response_body_failed | RouteAPI could not serialize or write the response |
Requests rejected before routing use the standard middleware error body {"error": {"message": ..., "type": "error", "code": ...}}, not the task shape.
Next steps
Section titled “Next steps”- Create a video task — submit a task and the shared request envelope.
- OpenAI video format — the
/v1/videosdialect of the same task, withremix. - Assets — a
404from the asset API means the account has no asset space yet. - Common Errors — status codes shared by all model endpoints.