Asynchronous tasks

Video generation usually takes several minutes, so LYRouter provides it as asynchronous tasks: POST /v1/videos returns a task ID starting with aigwt_ right after you submit, you then query the status with GET /v1/videos/{id}, and once the task completes you download the video with GET /v1/videos/{id}/content. The gateway polls the provider for task progress on your behalf, and your queries only read the status the gateway has recorded. Costs are settled when the task completes, and failed or timed-out tasks are not charged. For each model's parameters, sizes and billing units, see Multimodal capabilities; this page covers only the task lifecycle.

Lifecycle

Step Method and path Returns
1. Submit POST https://api.lyrouter.com/v1/videos Task object with status queued
2. Query GET https://api.lyrouter.com/v1/videos/{id} Task object; status changes as the task progresses
3. Download GET https://api.lyrouter.com/v1/videos/{id}/content 302 redirect to the video file URL
Delete DELETE https://api.lyrouter.com/v1/videos/{id} For a finished task, returns the task object with "deleted": true; the task record is not deleted. A task in progress returns 409
  • Submit: authentication is the same as for other endpoints. The request body can be JSON or multipart/form-data (the official OpenAI SDK's videos.create() uses the latter). When you submit a form, input_reference accepts only a publicly reachable https:// URL: currently an uploaded file raises no error but is ignored, and the task is generated as if there were no reference video and charged as usual. Submit first_frame, last_frame, reference_image and reference_audio as JSON: currently these fields in a form are replaced with a placeholder value and forwarded to the provider, so the provider rejects the submission or the task fails. For parameters, see "Common video parameters" and the per-model sections in Multimodal capabilities.
  • Other tasks for the same model: some models also offer POST /v1/videos/regenerations (video upscaling) and POST /v1/videos/context (context enhancement, which produces text). They return the same task object and are queried the same way; see Multimodal capabilities.
  • Who can query: any API key in the same workspace can query and download that workspace's tasks; a key from another workspace gets 404.
  • Specific models only: virtual models and lyrouter/auto cannot be used with the video endpoints, and a failure does not fall back to the default model.

Task object

Submission and queries return the same kind of task object. Here is an example of a completed task:

{
  "id": "aigwt_volcengine_videos_MXgno8VlzuzZtJl0kq83jA",
  "object": "video",
  "model": "doubao-seedance-2-0-mini-260615",
  "status": "completed",
  "progress": 100,
  "created_at": 1791950400,
  "completed_at": 1791950538,
  "expires_at": 1792555200,
  "seconds": 5,
  "size": "1280x720",
  "usage": { "duration_seconds": 5, "completion_tokens": 108900 }
}
Field Description
id The gateway's task ID, in the format aigwt_<provider>_<task type>_<random string>. It is not the provider's task ID; when you report a problem to the provider, use error.provider_request_id
object Always video
model The upstream model name actually called, which can be written differently from the model ID in the request
status queued, in_progress, completed or failed; see the table below
progress 0 before completion and 100 after; it does not show intermediate progress, so rely on status
created_at / completed_at Submission time and end time (Unix seconds). completed_at appears only after the task ends
expires_at When the task record expires, 7 days after submission. After that, queries return 404
seconds / size Echo the values you submitted
output_text Context enhancement tasks only: the enhanced prompt
error Present only when the task is failed; see Failed tasks below
usage Metering after the task completes: duration_seconds (seconds of video) for video tasks, plus completion_tokens for token-billed models such as Seedance; completion_tokens for context enhancement tasks
status Meaning What to do
queued Accepted; generation has not started Keep polling
in_progress The provider is generating Keep polling
completed Finished Call /content to download, or read output_text
failed Failed Read error; you are not charged

Failed tasks

Querying a failed task still returns HTTP 200, with status failed and the reason in error:

{
  "id": "aigwt_volcengine_videos_Qw3hTz0bVn1sLk8pYc4dRe",
  "object": "video",
  "model": "doubao-seedance-2-0-mini-260615",
  "status": "failed",
  "progress": 0,
  "created_at": 1791950400,
  "completed_at": 1791950482,
  "expires_at": 1792555200,
  "seconds": 5,
  "error": {
    "code": "OutputVideoSensitiveContentDetected",
    "message": "The request failed because the output video may contain sensitive information.",
    "provider_request_id": "021791950538123abcdef",
    "retryable": false
  }
}
error.code Meaning
deadline_exceeded Not finished 24 hours after submission. This status is not currently propagated to the query endpoint, so GET /v1/videos/{id} may keep showing queued or in_progress; handle it as described under "Set a waiting limit" below
canceled The task was canceled. Canceling a task in progress is not currently supported, so this does not occur in normal use
upstream_error The provider reported a failure without a recognizable error code
Provider error codes Returned as is, for example InvalidParameter or OutputVideoSensitiveContentDetected
  • error.provider_request_id: the provider's own request identifier; include it when you contact the provider or LYRouter support. It is absent when there is none.
  • error.retryable: false means retrying unchanged will not succeed (for example, invalid parameters or failed content moderation), so you need to change the request; true means you can retry. When the gateway cannot tell, the field is absent; do not treat a missing field as false.
  • None of these failures are charged.

Polling recommendations

  • Interval: start querying about 10 seconds after submitting, then gradually widen to once every 30 seconds. The gateway polls the provider in the background at its own pace, so querying more often does not get you the result sooner.
  • Download only after completion: calling /content before the task completes returns 409. These 4xx responses also count toward the API key's error rate, and frequent calls can trip the circuit breaker, so query until the status is completed before downloading.
  • A failed query is not a failed task: if a query times out or hits a network error, keep the task ID and continue querying; do not resubmit. Resubmitting creates a new task and charges again.
  • Set a waiting limit: a task still unfinished 24 hours after submission will never produce a result and is not charged, so you can stop polling. In the console, search by task ID on the workspace's Multimedia generation page to see the task's State (such as Generating or Expired), Failure reason and artifact.
  • Provider concurrency full: a 429 upstream_concurrency_full at submission means the provider account has reached its limit of concurrent tasks. This limit is shared per provider account, so switching to another API key does not help; resubmit later.

Downloading the artifact

GET /v1/videos/{id}/content returns 302, and the Location header is the signed video URL provided by the provider. Let the client follow the redirect to download (add -L in cURL, set follow_redirects=True in Python httpx; Node.js fetch follows redirects by default). To get only the URL, do not follow the redirect and read the Location header:

curl -s -o /dev/null -w '%{redirect_url}\n' \
  https://api.lyrouter.com/v1/videos/$TASK_ID/content \
  -H "Authorization: Bearer $LYROUTER_API_KEY"
  • The video URL is fixed when the task completes, and every later call to /content redirects to the same URL. How long the URL stays valid is up to the provider, usually 24 hours; once it expires, it cannot be fetched again through the gateway, so download it or copy it to your own storage as soon as you get it.
  • Context enhancement tasks produce text in output_text; there is nothing to download from /content.

Billing and settlement

  • Price locked at submission: price changes after you submit do not affect this task's cost.
  • Quantity from the actual result: when the task completes, it is billed on the metering the provider actually returns (such as seconds of video or token count), not on the values in the request. For each model's billing units and tiers, see Multimodal capabilities.
  • Charged on completion: the cost is settled when the task becomes completed; failed tasks (including timeouts) are not charged. The submit request itself incurs no cost.
  • Viewing in the logs: in the logs list, turn on the Task type and Cost columns under Columns. For the submit request, Task type shows Async, and Cost shows Settling until the task is settled, then the task's cost. Opening the log details shows "The submit request itself is not charged; the cost belongs to the task it started." together with the task ID; switch to Whole task to see the task's Settlement and Task cost. Failed tasks are never settled, so the log list may keep showing Settling; rely on the task status on the Multimedia generation page.
  • Quotas: the task's cost counts toward the applicable Credits quotas when it is settled.

Complete example: submit, poll, download

Replace MODEL_ID with a video model ID from the model marketplace, and set seconds and size to values that model supports (see Multimodal capabilities). The cURL example requires jq; save it as a script file and run it with bash (it uses exit on errors).

BASE=https://api.lyrouter.com/v1
AUTH="Authorization: Bearer $LYROUTER_API_KEY"

# 1. Submit the task; without a task ID, print the response and exit instead of polling
RESP=$(curl -s "$BASE/videos" -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"model":"MODEL_ID","prompt":"A corgi running across a lawn, with sunlight shining through the leaves","seconds":5}')
TASK_ID=$(echo "$RESP" | jq -r '.id // empty')
if [ -z "$TASK_ID" ]; then echo "Submission failed: $RESP"; exit 1; fi
echo "task: $TASK_ID"

# 2. Poll: start at 10 seconds and widen gradually to 30 seconds until the task ends; wait at most 2 hours
DELAY=10
GIVE_UP_AT=$(( $(date +%s) + 2 * 3600 ))
while true; do
  if [ "$(date +%s)" -gt "$GIVE_UP_AT" ]; then
    echo "$TASK_ID is still generating; query again later with the same ID"; exit 1
  fi
  sleep $DELAY
  DELAY=$(( DELAY * 3 / 2 > 30 ? 30 : DELAY * 3 / 2 ))
  TASK=$(curl -s "$BASE/videos/$TASK_ID" -H "$AUTH")
  STATUS=$(echo "$TASK" | jq -r '.status')
  echo "status: $STATUS"
  case "$STATUS" in
    completed|failed) break ;;
    queued|in_progress|"") ;;  # An empty value is usually a network glitch: keep the task ID and keep querying
    *) echo "Query error: $TASK"; exit 1 ;;
  esac
done

# 3. Download on completion, or print the reason on failure
if [ "$STATUS" = "completed" ]; then
  curl -sL "$BASE/videos/$TASK_ID/content" -H "$AUTH" -o video.mp4
else
  echo "$TASK" | jq '.error'
fi

Common errors

Error responses take the form {"error":{"message":"...","type":"...","code":"..."}}.

Status error.message error.code Cause and fix
404 video task not found not_found The task ID does not exist, is older than 7 days, belongs to another workspace, or is the provider's task ID. Make sure you use the aigwt_ ID returned at submission
409 video is not ready yet not_ready /content was called before the task completed (or after it failed). Query until the status is completed first
409 cancelling an in-flight video task is not supported: ... cancel_not_supported DELETE was called on a task in progress. The provider offers no cancel endpoint; wait for the task to end
429 upstream concurrency full upstream_concurrency_full The provider account has reached its limit of concurrent tasks. Switching API keys does not help; resubmit later
400 The validation message for the specific parameter invalid_parameter The video parameters are invalid, for example a size or duration outside the range the model supports. Fix them as the error says; see Multimodal capabilities
400 unsupported parameters for this model: <parameter name> unsupported_parameters The request has X-Gateway-Strict-Parameters: true and contains parameters the model does not support; see Gateway headers and extensions
403 Model is not available in the model plaza model_not_available The model is not enabled in this workspace, or a virtual model or lyrouter/auto was used. Use a specific model ID that is enabled
  • In a form submission, input_reference accepts only a URL. Currently an uploaded file raises no error: the file is ignored, and the task is generated as if there were no reference video and charged as usual. Only a non-URL text value (such as video.mp4) returns a 400 plain-text error: input_reference must be a URL; uploading a file is not supported by this gateway (pass a publicly reachable https:// URL instead). These plain-text errors from the gateway currently still carry the response header Content-Type: application/json, so check whether the body is JSON before parsing it.
  • When the provider rejects the request at submission, the status code and error text are set by the provider. The full list is in Errors and request identifiers.