Seedance video generation

Generate videos with Volcengine Ark's Seedance models: text to video, first-frame and first-and-last-frame video, video from reference images or a reference video, plus video editing and extension. Video generation runs as an asynchronous task: you get a task ID right after submitting, poll until the task completes, then download the video. The endpoints follow the shape of OpenAI's Videos API, and the gateway converts the common parameters into Volcengine Ark's format. The official OpenAI SDK's videos.create() can submit tasks directly; for querying and downloading, call the HTTP endpoints directly. For the task lifecycle in general, see Asynchronous tasks.

Supported models

Model ID Highlights Duration and resolution Billing basis
bytedance/doubao-seedance-2.5 Most capable: up to 30 reference images, 1080p (10-bit) and mov output, and the task type can be declared with omni_reference_task_type 4–30 s; 480p, 720p, 1080p Per output token; tiered by resolution (480p / 720p in one tier, 1080p in another) and by whether a video is supplied as input
bytedance/doubao-seedance-2.0-fast 2.0 series; up to 9 reference images; mp4 output only 4–15 s; 480p, 720p Per output token; tiered by whether a video is supplied as input
bytedance/doubao-seedance-2.0-mini Same capabilities as 2.0-fast 4–15 s; 480p, 720p Per output token; tiered by whether a video is supplied as input

All three models support text to video, first-frame and first-and-last-frame video, image reference, video reference, video editing and extension, and video with sound. Output aspect ratios are 21:9, 16:9, 4:3, 1:1, 3:4, 9:16 or adaptive. A model must be enabled in the workspace's model marketplace before you can call it; the model marketplace shows the current prices.

Endpoints

Method Path Purpose
POST /v1/videos Submit a video generation task; returns a task ID right away
GET /v1/videos/{id} Query the task status
GET /v1/videos/{id}/content Download the video (302 redirect to the video URL)
DELETE /v1/videos/{id} Delete a finished task; a task in progress cannot be canceled

The full URL is https://api.lyrouter.com followed by the path in the table.

Authentication

Send your API key in a request header:

Authorization: Bearer <API Key>

Bearer is case-sensitive. You can use x-api-key: <API Key> instead; when both are present, only Authorization is read. Create API keys on the workspace's API Keys page. Your API key is used only to authenticate with LYRouter and is never forwarded to the upstream provider. See Base URLs and authentication.

Any API key in the same workspace can query and download that workspace's tasks; a key from another workspace gets 404.

Headers

Header Required Description
Content-Type Required when submitting application/json. POST /v1/videos also accepts multipart/form-data; see "Submitting a form" below
X-Gateway-Strict-Parameters No When true, a request containing parameters that would be dropped returns 400 instead, without submitting a task or charging; see Gateway headers and extensions

Submit a task

POST/v1/videos

Submits a video generation task. The gateway validates the parameters, converts them into Volcengine Ark's format and submits the task to the provider, then returns a task object with status queued right away; the video is generated in the background. The submit request itself incurs no cost.

Request body

Parameter Type Required Description
model string Yes A model ID from the table above. It must be a specific model ID: a virtual model @vmodel/<name> or lyrouter/auto returns 403, and a failure does not fall back to the workspace's Default Model
prompt string Yes The prompt. Refer to reference materials as @image1 and @video1; video editing and extension tasks rely on the prompt to express the intent; see "Task types and constraints" below
seconds integer No Video duration in seconds; must be a positive integer. 4–30 for 2.5 and 4–15 for the 2.0 series; when omitted, the provider default applies. Do not send it for video editing tasks
size string No Resolution, written as WxH (such as 1280x720) or a tier name (480p, 720p, 1080p); see "The two ways to write size" below. 1080p is supported only by 2.5
first_frame string No URL of the first-frame image (first-frame video); 1 image. The output aspect ratio follows the first-frame image
last_frame string No URL of the last-frame image, used together with first_frame (first-and-last-frame video)
reference_image string | string[] No URLs of reference images: a string for one image, an array for several. Up to 30 for 2.5 and up to 9 for the 2.0 series
input_reference string No URL of a reference video, used for reference-to-video, video editing and video extension. Only one URL is accepted; an array returns 400
generate_audio boolean No Whether to generate a video with sound; supported by all three models. On by default when omitted: the generated video has an audio track
watermark boolean No When true, an "AI生成" (AI-generated) label is added to the bottom-right corner of the video; off by default
output_format string No Output format: mp4 (default) or mov. mov is supported only by 2.5
omni_reference_task_type string No Designed for 2.5; the 2.0 series currently does not reject it, and the task is generated and charged as usual. Declares the task type explicitly: auto (default), reference, edit or extend; see "Task types and constraints" below
  • Fields not in the table are dropped, and the X-Gateway-Unsupported-Parameters response header lists their names; with X-Gateway-Strict-Parameters: true, the request returns 400 instead. This includes Volcengine Ark's native duration, resolution, ratio and content; use seconds, size and the material fields above instead.
  • This endpoint does not support n; each request generates one video.
  • reference_audio (reference audio) is an exception: it is currently not forwarded to Seedance, sending it raises no error, and it does not appear in X-Gateway-Unsupported-Parameters; the reference audio is simply ignored. If you need reference audio, use MiniMax H3 video generation instead. source_video is only for MiniMax H3 video upscaling; sending it to Seedance returns 400 source_video is not supported for this model.
  • The provider must be able to reach the material URLs. Use public URLs for large files rather than Base64; the request body must not exceed 64 MB in total.

Submitting a form

POST /v1/videos also accepts multipart/form-data, which is what the official OpenAI SDK's videos.create() uses. In a form, the gateway reads these fields: model, prompt, seconds, size, input_reference, watermark, generate_audio, output_format and omni_reference_task_type:

  • input_reference accepts only a URL; an uploaded file raises no error but is ignored: pass a publicly reachable https:// URL. Currently, when you upload a file, the task is generated as if there were no reference video and is charged as usual.
  • watermark and generate_audio accept only the literal values true or false.
  • Submit first_frame, last_frame and reference_image in a JSON request body. Currently these fields in a form are replaced with a placeholder value and forwarded to the provider; with first_frame, for example, the provider returns 400 The parameter content[1].image_url specified in the request is not valid, and the error does not point to the real cause.

Request examples

The examples read the API key from the LYROUTER_API_KEY environment variable; to set it, see Your first model call.

curl https://api.lyrouter.com/v1/videos \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bytedance/doubao-seedance-2.0-mini",
    "prompt": "A corgi running across a lawn, with sunlight shining through the leaves",
    "size": "1280x720",
    "seconds": 5,
    "generate_audio": true
  }'

Response

A successful submission returns HTTP 200 and a task object with status queued; for the fields, see "Retrieve a task" below. The submit response has no usage, because the cost is settled when the task completes. Below, the 400 is a seconds value that is not a positive integer, the 403 is a model that is not enabled in this workspace (or a virtual model or lyrouter/auto), and the 429 means the provider account has reached its limit of concurrent tasks.

{
  "id": "aigwt_volcengine_videos_MXgno8VlzuzZtJl0kq83jA",
  "object": "video",
  "model": "doubao-seedance-2-0-mini-260615",
  "status": "queued",
  "progress": 0,
  "created_at": 1791950400,
  "expires_at": 1792555200,
  "seconds": 5,
  "size": "1280x720"
}
  • Keep the returned id. If a query hits a network error or times out, keep querying with the same ID rather than resubmitting: resubmitting creates a new task and charges again.
  • The concurrency limit behind 429 upstream_concurrency_full is shared per provider account, so switching to another API key does not help; resubmit later.

Retrieve a task

GET/v1/videos/{id}

Reads the task's current status. The gateway polls the provider for progress in the background, and this endpoint only returns the status the gateway has recorded, so querying more often does not get you the result sooner.

Path parameters

Parameter Type Required Description
id string Yes The task ID returned at submission, starting with aigwt_. The provider's own task ID cannot be queried and returns 404

Request examples

curl https://api.lyrouter.com/v1/videos/$TASK_ID \
  -H "Authorization: Bearer $LYROUTER_API_KEY"

Response

When the task exists, the response is always HTTP 200, even if the task itself failed; rely on status for the outcome. A task ID that does not exist, is older than 7 days or belongs to another workspace returns 404.

{
  "id": "aigwt_volcengine_videos_MXgno8VlzuzZtJl0kq83jA",
  "object": "video",
  "model": "doubao-seedance-2-0-mini-260615",
  "status": "in_progress",
  "progress": 0,
  "created_at": 1791950400,
  "expires_at": 1792555200,
  "seconds": 5,
  "size": "1280x720"
}
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 the video
failed Failed Read error; you are not charged
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, such as doubao-seedance-2-0-mini-260615, which is written differently from the model ID in the request
status One of queued, in_progress, completed or failed
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
error Present only when the task is failed: code and message, plus provider_request_id (the provider's request identifier) and retryable, each present only when it has a value
usage Present after the task completes: duration_seconds is the video length in seconds, and completion_tokens is the output token count reported by the provider, which is what Seedance bills on

For failed tasks, error.code is usually the provider's error code, returned as is:

error.code Meaning and fix
InvalidParameter.TaskTypeConstraint The task type conflicts with the parameters, for example a video editing task that sent seconds. Fix the parameters against "Task types and constraints" below
InvalidParameter.TaskTypeMismatch The type declared with omni_reference_task_type disagrees with what the model inferred. Write the prompt with the keywords in the task type table, or switch to auto
OutputVideoSensitiveContentDetected and similar Content moderation did not pass; change the prompt or materials and resubmit
upstream_error The provider reported a failure without a recognizable error code
  • error.retryable false means retrying unchanged will not succeed, so you need to change the request; true means you can retry; a missing field means the gateway cannot tell, so do not treat it as false. For all values, see Asynchronous tasks.
  • Polling interval: start querying about 10 seconds after submitting, then gradually widen to once every 30 seconds.
  • Waiting limit: a task still unfinished 24 hours after submission will never produce a result and is not charged, so you can stop polling. At that point the query may still show queued or in_progress; to see the task's actual state, search by task ID on the workspace's Multimedia generation page in the console (State shows Expired).

Download the video

GET/v1/videos/{id}/content

Call it after the task becomes completed. It returns 302, and the Location response 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.

Request examples

# Download to a local file
curl -L https://api.lyrouter.com/v1/videos/$TASK_ID/content \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -o video.mp4

# Get only the video URL without downloading
curl -s -o /dev/null -w '%{redirect_url}\n' \
  https://api.lyrouter.com/v1/videos/$TASK_ID/content \
  -H "Authorization: Bearer $LYROUTER_API_KEY"

Response

Status: 302
Location: <signed video URL provided by the provider>
  • The video URL is fixed when the task completes, and every later call 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.
  • Calling it before the task completes (or after it failed) 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.

Delete a task

DELETE/v1/videos/{id}

For a finished task (completed or failed), returns HTTP 200 and the task object with "deleted": true; the task record is not deleted, and you can still query and download it until expires_at. Calling it on a task in progress returns 409 cancel_not_supported: the provider offers no cancel endpoint, so wait for the task to end. The official OpenAI SDK's videos.delete() reads this deleted field.

Seedance-specific parameters and limits

The two ways to write size

Notation What the gateway does
WxH (such as 1280x720) Takes the short edge as the resolution tier (1280x720 → 720p); when the aspect ratio is within 2% of a ratio the provider supports, that ratio is used, otherwise the reduced ratio is sent as is and the provider returns 400 (for 1000x480: the parameter ratio specified in the request is not valid ...)
Tier name (such as 720p, 1080p) Passed through; the provider picks the aspect ratio from the prompt
  • For first-frame and first-and-last-frame video, the output aspect ratio follows the first-frame image.
  • Seedance 2.5 renders 1080p as 10-bit H.265/HEVC, which some players cannot open; mov is H.264 + yuv444p + PCM. Use VLC, mpv, ffplay or IINA to play them.

Task types and constraints

Seedance infers the task type from the materials and the prompt, and each type has its own constraints:

Task type How it is triggered Constraints
Text to video prompt only None
First-frame / first-and-last-frame video first_frame (+ last_frame) The output aspect ratio follows the first-frame image
Reference to video reference_image / input_reference None
Video editing input_reference, with an editing intent in the prompt Do not send seconds; the output aspect ratio and duration follow the source video; the source video must be 4–30 s
Video extension input_reference, with an extension intent in the prompt The output aspect ratio follows the source video

The duration of a video editing task is fixed to the source video, and the gateway's seconds accepts only positive integers, so simply omit seconds for editing tasks; sending it is rejected by the provider (InvalidParameter.TaskTypeConstraint).

With Seedance 2.5, you can declare the task type explicitly with omni_reference_task_type, which turns the provider's asynchronous failure into a synchronous error at submission:

Value Meaning Keywords the prompt must contain
auto (default) The model decides —
reference Reference to video —
edit Video editing 编辑视频 / 增加 / 加上 / 删除 / 去掉 / 修改 / 替换 / 改成 (edit video / add / add on / delete / remove / modify / replace / change to)
extend Video extension 向前延长 / 向后延长 / 延续 / 续写 (extend forward / extend backward / continue / write a continuation)

edit and extend apply only to scenarios with reference materials: used for text to video (without reference materials), the submission returns 400 right away with the error code InvalidParameter.TaskTypeConstraint, and the error message contains This parameter is only supported in the multimodal reference scenario, and is not applicable to text-to-video generation or first-frame / first-last-frame generation.

If the declared type disagrees with what the model infers, the task may fail asynchronously (InvalidParameter.TaskTypeMismatch).

Input material limits (Seedance 2.5)

Material Limits
Images jpeg, png, webp, bmp, tiff, gif, heic, heif; sides [300, 6000] px; aspect ratio [0.4, 2.5]; ≤ 30 MB each; 1 first frame / 2 for first-and-last frame / 1–30 reference images
Video mp4, mov (H.264 or H.265; audio AAC / MP3 / PCM); resolution 480p or 720p; duration [2, 30] s (editing tasks [4, 30] s); sides [300, 6000] px; aspect ratio [0.4, 2.5]; ≤ 200 MB each; frame rate [24, 60]
Request body ≤ 64 MB in total; use public URLs for large files rather than Base64

Seedance does not accept reference images or reference videos containing real human faces; they are blocked by input moderation.

The provider's web search and last-frame return have no corresponding gateway parameters, so they are not available through LYRouter.

Billing

Seedance bills on the output tokens reported by the provider (usage.completion_tokens on the task object), tiered by resolution and by whether a video is supplied as input:

Model Tiers
bytedance/doubao-seedance-2.5 480p / 720p and 1080p, each split again by whether a video is supplied as input
bytedance/doubao-seedance-2.0-fast Two tiers, split only by whether a video is supplied as input
bytedance/doubao-seedance-2.0-mini Two tiers, split only by whether a video is supplied as input
  • A video input moves the task to a different tier: the unit price is lower, but the total is higher: tasks with input_reference (reference video, video editing, video extension) are metered at roughly twice the tokens of the same task without a reference video (see "equivalent seconds" below), and the lower unit price does not make up for the extra usage. First-frame / first-and-last-frame image to video does not count as a video input.
  • Tokens = a fixed overhead + tokens per second × equivalent seconds, so token counts already reflect the differences in resolution and duration (measured: 480p 4 s ≈ 40,594 tokens, 8 s ≈ 80,770, 720p 4 s ≈ 87,300), and there is no separate duration tier. Without a reference video, the equivalent seconds are simply the output seconds.
  • With a reference video, the equivalent seconds are not the output duration: for an output of up to 6 seconds, equivalent seconds = output seconds + reference video seconds; above 6 seconds, equivalent seconds = 1.5 × output seconds + 2, regardless of the reference video's length. 6 s is the break point. For example, with a 5-second reference video, a 5-second output is metered as 10 s and a 10-second output as 17 s. Video editing and extension are metered by the same rule (measured).
  • The price is locked at submission; when the task becomes completed, it is settled on the token count actually reported; failed tasks (including timeouts) are not charged. To see a task's cost in the logs, see Asynchronous tasks.

Complete example

Submits a first-frame video task, polls until it ends, then downloads the video. Replace first_frame with your own public image URL. 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": "bytedance/doubao-seedance-2.0-mini",
    "prompt": "The camera slowly pushes in as the kitten turns to look out of the window",
    "first_frame": "https://example.com/cat.jpg",
    "size": "720p",
    "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 the same ID again later"; 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 query again
    *) echo "Query error: $TASK"; exit 1 ;;
  esac
done

# 3. Download on success; 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
400 seconds must be a positive whole number of seconds invalid_parameter seconds is not a positive integer, for example 0, a negative number or a decimal
400 input_reference must be a non-empty URL string invalid_parameter input_reference is empty or an array. Seedance accepts only one reference video URL
400 generate_audio must be a boolean invalid_parameter In JSON, generate_audio (or watermark) is not a boolean
400 source_video is not supported for this model invalid_parameter source_video is only for MiniMax H3 video upscaling; remove it from the request
400 unsupported parameters for this model: <parameter name> unsupported_parameters The request has X-Gateway-Strict-Parameters: true and contains parameters that would be dropped. Remove the listed parameters or use the gateway's parameter names
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
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
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. 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
  • 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 resolution, duration, number of materials and so on exceed the model's range, the provider rejects the request, and the status code and error text are set by the provider; problems found only after submission come back as failed tasks, as described under "Retrieve a task" above. The full list is in Errors and request identifiers.

FAQ

A video editing task failed with InvalidParameter.TaskTypeConstraint. What should I do?

Answer: The duration of a video editing task is fixed to the source video. Remove seconds from the request and resubmit. For the constraints of the other task types, see "Task types and constraints" above.

A task failed with InvalidParameter.TaskTypeMismatch. What should I do?

Answer: The task type declared with omni_reference_task_type disagrees with what the model inferred. Rewrite the prompt with the keywords from the task type table (for an editing task, for example, say 替换 "replace" or 去掉 "remove"), or switch to auto and let the model decide.

Why did the provider reject a request for 1080p or mov?

Answer: Only bytedance/doubao-seedance-2.5 supports 1080p and mov; the 2.0 series outputs only 480p and 720p mp4. Switch to 2.5, or set size to 720p and remove output_format.

The downloaded 1080p video will not play. What should I do?

Answer: Seedance 2.5 renders 1080p as 10-bit H.265/HEVC, which some players do not support. Play it with VLC, mpv, ffplay or IINA, or transcode it yourself.

Can I use the official OpenAI SDK?

Answer: You can submit tasks with client.videos.create(); the SDK submits a form, and the gateway converts it into the format the provider needs. The Python SDK needs openai 2.x: in testing, 1.58.1 has no client.videos, while 2.48.0 works. In a form, input_reference can only be a URL: writing input_reference="https://…" in the Python SDK raises RuntimeError: Expected entry at input_reference to be bytes, an io.IOBase instance, PathLike or a tuple … locally and the request is never sent, so pass it as extra_body={"input_reference": "https://…"} instead; the Node.js SDK accepts a string at runtime, but its TypeScript types do not allow it, so TypeScript needs a type-check workaround. Tier names for size (such as 720p) are not in the SDK's type declarations either, so TypeScript needs a type-check workaround; for first frames, last frames and reference images, send a JSON request body over HTTP. For querying and downloading, call the HTTP endpoints as shown on this page.

The video URL has expired. Can I download it again?

Answer: No. The video URL is fixed when the task completes, and calling /content again returns the same URL, so after it expires you need to submit a new task. Download the video or copy it to your own storage right after the task completes.

The query keeps returning in_progress. What should I do?

Answer: Video generation usually takes several minutes. A task still unfinished 24 hours after submission will never produce a result and is not charged, so you can stop polling and check the task's state by task ID on the Multimedia generation page in the console. Do not resubmit the same request while you wait: each submission creates a new task that is charged separately.