Wan
Wan is Alibaba’s video generation family (Tongyi Wanxiang), served through DashScope channels. Tasks go through the same endpoints as every other video model — see Create a task and Get a task. This page covers the Wan 3.0 adaptor end to end: the request fields it reads, how it builds the DashScope request, the media rules, how to read the task record, and how the per-second charge is computed.
Video generation is asynchronous: submit, keep the task_id, poll every 10–15 seconds, then download when the status is SUCCESS.
Models
Section titled “Models”| Model ID | Resolutions | Duration | Notes |
|---|---|---|---|
wan3.0-video | 480P, 720P, 1080P | 2–30 seconds, or -1 smart | Standard model: text-to-video, first/last frame, and multi-reference material |
wan3.0-video-prime | 480P, 720P, 1080P | 2–30 seconds, or -1 smart | Faster variant with the same capabilities |
Both default to 1080P and 5 seconds when the request omits those values. Both support text-to-video, first-frame and first/last-frame image-to-video, reference image/video/audio, video editing and video extension. Model IDs are the ones returned by GET /v1/models; which models your account can use and their prices are set in the RouteAPI console.
The channel also serves older Wan 2.x models (wan2.7-i2v, wan2.7-t2v, wan2.5-i2v-preview, wan2.2-*, wanx2.1-*), which use different rules and are not covered here.
How the common envelope maps
Section titled “How the common envelope maps”| Envelope field | What the Wan 3.0 adaptor does |
|---|---|
prompt | Sent as input.prompt. Like every task request, a blank prompt is rejected with 400 before submission |
image / images | The first frame is image when set, otherwise the first non-empty entry of images; the last frame is the second non-empty entry of images. Further images are ignored |
input_reference | Single-image shorthand: it replaces the images array with one URL, but a top-level image still wins for the first frame |
resolution | Sent as parameters.resolution, upper-cased and suffixed with P if needed |
ratio | Sent as parameters.ratio, lower-cased |
size | Folded into resolution and ratio, then dropped. 1920x1080 and 1920*1080 are both accepted; a *-style size outside the supported table is a 400, while a bare tier such as 720p is used as the resolution |
duration | Sent as parameters.duration. A missing value becomes 5 |
seconds | Parsed as an integer duration when duration is absent |
generate_audio | Sent as parameters.audio; an explicit false is forwarded |
metadata | Merged over the built request, so metadata.parameters.* and metadata.input.* override the mapped values |
When both are sent, an explicit resolution / ratio wins over the value derived from size; size is always dropped from the upstream body. The adaptor then sends input, parameters, and model to DashScope’s video-synthesis endpoint; you never call that endpoint yourself.
Because the body uses DashScope’s shape, the same values can be supplied in metadata:
{ "metadata": { "input": { "negative_prompt": "blurry, low quality" }, "parameters": { "duration": 10, "resolution": "720p", "ratio": "9:16" } }}Metadata cannot change the model: a metadata.model that differs from the resolved upstream model is rejected with 400.
Parameters
Section titled “Parameters”These can be written at the top level of the request body. Model-specific keys go in metadata.
| Field | Type | Values | Notes |
|---|---|---|---|
model | string | wan3.0-video, wan3.0-video-prime | Required |
prompt | string | — | Required and non-blank. In reference mode, address material as “image 1”, “video 1”, “audio 1” |
resolution | string | 480P, 720P, 1080P | Case-insensitive; the trailing P is optional. Default 1080P |
ratio | string | adaptive, 21:9, 16:9, 4:3, 1:1, 3:4, 9:16 | Case-insensitive. When omitted, DashScope applies adaptive |
size | string | 1280*720, 1280x720, or 720p | Folded into resolution and ratio, then dropped. Only sizes in the adaptor’s table are accepted |
duration | integer | 2–30 | Output seconds. Default 5. A negative or above-3600 value is rejected by the shared task validation; -1 smart duration only works in metadata.parameters |
seconds | string | integer string | Used in place of duration when duration is absent or 0. A non-integer value is a 400 |
image | string | URL | Single first-frame image; it wins over images for the first frame |
images | array | URLs | 1 image = first frame; 2 images = first frame + last frame |
input_reference | string | URL | Replaces images with a single first-frame URL; a top-level image still wins for the first frame |
generate_audio | boolean | true / false | Sent as parameters.audio; an explicit false is forwarded as false |
metadata | object | DashScope input / parameters | Overrides the mapped values; see below |
When size is a WIDTH*HEIGHT pair, it must appear in the adaptor’s table: 480P accepts 832*480, 480*832, 624*624; 720P accepts 1280*720, 720*1280, 960*960, 1088*832, 832*1088; 1080P accepts 1920*1080, 1080*1920, 1440*1440, 1632*1248, 1248*1632. The ratio is reduced from the pair and kept only if it matches one of the supported values; otherwise the request is left to DashScope’s default ratio.
Metadata parameters
Section titled “Metadata parameters”metadata mirrors DashScope’s input and parameters objects. Every key below is optional.
| Metadata key | Type | Notes |
|---|---|---|
input.media | array | Material list of {"type", "url"} items. When present, top-level images are not used |
input.prompt | string | Replaces the top-level prompt upstream, but the top-level prompt is still required by the shared validation |
input.audio_url | string | Shorthand for appending a reference_audio item |
input.negative_prompt | string | Negative prompt |
input.template | string | DashScope effect template, forwarded as is |
parameters.resolution | string | Same as top-level resolution |
parameters.ratio | string | Same as top-level ratio |
parameters.size | string | Same as top-level size |
parameters.duration | integer | -1 for smart duration, otherwise 2–30 |
parameters.audio | boolean | Same as generate_audio |
parameters.seed | integer | -1 or 0–2147483647. 0 is kept as an explicit value; the same seed does not guarantee the same result |
parameters.watermark | boolean | Provider-side watermark; off by default |
parameters.prompt_extend | boolean | Accepted but has no effect — prompt rewriting is always on (see below) |
Values in metadata.parameters.* win over the top-level equivalents. Unknown keys inside metadata are ignored, and a key with the wrong type is a 400 before anything is reserved.
prompt_extend cannot be turned off. The top level has no such field, so a top-level prompt_extend is ignored outright; under metadata.parameters the adaptor always starts from true and stores the value in a non-pointer boolean with omitempty, so false is dropped on the way to DashScope and the provider default (on) applies. Send it only if you want to restate the default; it changes nothing.
Input media
Section titled “Input media”Wan 3.0 takes material through input.media, an array of {"type": "...", "url": "..."} items. The adaptor builds it from the top-level image fields and merges in anything you pass explicitly:
type | Meaning | Limit (DashScope) |
|---|---|---|
first_frame | First frame of the video | 1 |
last_frame | Last frame of the video; requires a first frame | 1 |
reference_image | Reference image for subject/style guidance | 10 |
reference_video | Reference video for reference generation, editing, or extension | 5, 15 seconds in total |
reference_audio | Reference audio. input.audio_url is appended as this type | 5, 15 seconds in total |
A media array you pass explicitly in metadata.input.media is kept as is; when it is absent the adaptor builds one from the top-level image fields, in the order first_frame, last_frame. The fallback also reads the legacy metadata.input.img_url / first_frame_url / last_frame_url keys, and metadata.input.audio_url always appends a reference_audio item. All four legacy fields are folded into media and then cleared — Wan 3.0 only understands media.
The counts and durations above are DashScope’s rules. RouteAPI does not pre-check them; a violation is rejected upstream and refunded — either as a fail_to_fetch_task error or as a FAILURE task — not as a local 400.
Material rules:
- Material URLs must be publicly downloadable http(s) addresses without a login. Images may also be sent as
data:image/png;base64,.... - Per-asset limits: images JPEG / JPG / PNG (no alpha) / BMP / WEBP, 240–8000 px per side, aspect ratio at most 8:1, at most 20 MB; videos mp4 / mov, 1–15 seconds each, at least 16 fps, 240–4096 px per side, at most 100 MB; audio wav / mp3, 1–15 seconds each, at most 15 MB.
- First/last-frame mode and reference-material mode are mutually exclusive. Mixing frames with
reference_*items in onemediaarray is rejected upstream: the task is refunded. reference_image,reference_video, andreference_audiocan be combined freely.- In the prompt, refer to material by ordinal — “image 1”, “video 1”, “audio 1” — counted per type in the order the items appear in
media.
Two rules are enforced locally, before submission, so neither reaches the provider or the wallet:
metadata.input.audio_urltogether with afirst_frameorlast_frameitem is rejected with400and the messagewan3 does not support audio_url together with first/last frame images. The check looks only atinput.audio_url; areference_audioitem placed directly inmedianext to a frame is caught by DashScope instead (taskFAILURE, refunded).- After the metadata merge, a request with an empty prompt and an empty
mediaarray is rejected with400(wan3 requires prompt or input.media). In practice the shared task validation already rejects a blankprompt, so a prompt is always required.
Text to video
Section titled “Text to video”Only a prompt and video parameters:
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "An orange cat walking on a rooftop, cinematic lighting", "resolution": "480P", "ratio": "16:9", "duration": 2 }'First and last frame
Section titled “First and last frame”Put images in the top-level images array: one image is the first frame, two images are the first and last frame in that order.
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "The scene shifts naturally from morning to sunset while the camera moves smoothly", "images": [ "https://cdn.example.com/first.jpg", "https://cdn.example.com/last.jpg" ], "resolution": "720P", "ratio": "adaptive", "duration": 5 }'You can also spell the frames out in metadata.input.media with an explicit type; a last_frame must be sent together with a first_frame.
Reference image + video + audio
Section titled “Reference image + video + audio”For reference material, write each item in metadata.input.media. Once media is present, the top-level image fields are ignored.
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "The character in image 1 performs the motion from video 1, dancing to the rhythm of audio 1", "resolution": "720P", "ratio": "adaptive", "duration": 8, "metadata": { "input": { "media": [ { "type": "reference_image", "url": "https://cdn.example.com/character.jpg" }, { "type": "reference_video", "url": "https://cdn.example.com/motion.mp4" }, { "type": "reference_audio", "url": "https://cdn.example.com/music.mp3" } ] } } }'A request that contains a reference_video item reserves 30 seconds at submit regardless of the requested duration, because the final output length is unknown then — see Billing.
Video editing and extension
Section titled “Video editing and extension”Both modes pass the source video as reference_video and express the intent in the prompt.
- Editing — for example “convert video 1 entirely into a claymation style”. Send
ratio: "adaptive"so the output keeps the source aspect ratio. - Extension — for example “extend video 1 forward, keeping the character motion and camera style consistent”.
ratiomust beadaptive. Setdurationto the source length plus the extension: extending a 5-second video by 2 seconds means7. You can also use-1and let the model decide.
With a video input, the source video length plus the output length cannot exceed 30 seconds. That limit is enforced by DashScope; a request that breaks it fails and is refunded.
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "Convert video 1 entirely into a claymation style", "resolution": "480P", "ratio": "adaptive", "duration": 2, "metadata": { "input": { "media": [ { "type": "reference_video", "url": "https://cdn.example.com/input.mp4" } ] } } }'To extend the same 5-second clip by 2 seconds, change the prompt and the duration:
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "Extend video 1 forward by two seconds, keeping the character motion and camera style consistent", "resolution": "480P", "ratio": "adaptive", "duration": 7, "metadata": { "input": { "media": [ { "type": "reference_video", "url": "https://cdn.example.com/input.mp4" } ] } } }'Smart duration
Section titled “Smart duration”With smart duration the model picks the length. Send -1 in metadata.parameters.duration; a negative top-level duration is rejected by the shared task validation with invalid_seconds.
curl https://api.routeapi.ai/v1/video/generations \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "wan3.0-video", "prompt": "An orange cat walking on a rooftop, cinematic lighting", "resolution": "480P", "ratio": "16:9", "metadata": { "parameters": { "duration": -1 } } }'A smart-duration task reserves 30 seconds at submit and settles down to the actual output length on completion.
Query the task
Section titled “Query the task”curl https://api.routeapi.ai/v1/video/generations/task_FQvcRkHE9wjSWwyISDcrWCKoqCPNdkCx \ -H "Authorization: Bearer $ROUTEAPI_KEY"{ "code": "success", "message": "", "data": { "id": 4512, "created_at": 1792412645, "updated_at": 1792412780, "task_id": "task_FQvcRkHE9wjSWwyISDcrWCKoqCPNdkCx", "platform": "17", "user_id": 1024, "group": "default", "channel_id": 18, "quota": 50000, "action": "textGenerate", "status": "SUCCESS", "progress": "100%", "fail_reason": "", "result_url": "https://dashscope-result.oss.example/video.mp4", "submit_time": 1792412645, "start_time": 1792412650, "finish_time": 1792412780, "properties": { "input": "", "origin_model_name": "wan3.0-video", "upstream_model_name": "wan3.0-video" }, "data": { "request_id": "42a9c287-db7b-9edb-b613-xxxxxxxx", "output": { "task_id": "dd40fcb4-34f4-4292-a9e0-xxxxxxxx", "task_status": "SUCCEEDED", "video_url": "https://dashscope-result.oss.example/video.mp4", "orig_prompt": "An orange cat walking on a rooftop, cinematic lighting" }, "usage": { "video_count": 1, "duration": 2, "input_video_duration": 0, "output_video_duration": 2, "fps": 30, "SR": 480, "ratio": "16:9" } } }}The outer fields are RouteAPI’s. data.status is the value to trust; data.progress is a coarse stage marker for Wan (20% queued, 30% generating, 100% finished), not a provider percentage. data.quota is the amount charged so far — after a successful task it reflects the settled amount. data.action is generate when the request carried a top-level image field (image, images, or input_reference) and textGenerate otherwise.
data.data is DashScope’s own task payload, stored as returned. The fields worth reading:
| Field | Meaning |
|---|---|
data.data.output.task_id | DashScope task ID, different from the outer data.task_id |
data.data.output.video_url | Provider video URL; the same value as the outer data.result_url |
data.data.output.orig_prompt | Prompt DashScope received |
data.data.usage.output_video_duration | Actual output seconds — this is the billed quantity |
data.data.usage.input_video_duration | Input video seconds; free. 0 for text-to-video |
data.data.usage.duration | Input + output seconds; informational only, never used for settlement |
data.data.usage.SR | Actual output resolution tier as a number (480, 720, 1080); used for settlement |
data.data.usage.fps | Output frame rate, 30 for these models |
data.data.usage.ratio | Actual aspect ratio. With adaptive it is the source ratio, for example 427:240 |
data.data may be incomplete until the task finishes, so judge completion by data.status, not by the presence of usage.
Statuses: NOT_START and QUEUED mean keep polling, IN_PROGRESS means generating, SUCCESS means download, FAILURE means read fail_reason — the reserved amount has already been refunded.
Download the video
Section titled “Download the video”data.result_url points at the Alibaba-hosted file. That URL is temporary — Alibaba expires it on its own schedule, so download it promptly — or stream it through RouteAPI, which does not depend on the URL’s remaining lifetime:
curl -L -o out.mp4 \ https://api.routeapi.ai/v1/videos/task_FQvcRkHE9wjSWwyISDcrWCKoqCPNdkCx/content \ -H "Authorization: Bearer $ROUTEAPI_KEY"The content endpoint only serves finished tasks owned by the caller: an unfinished task is a 400 naming the current status, an unknown or foreign task_id is a 404, and a provider fetch failure is a 502. See Get a task for its full error list. A still-valid result_url can also be reused directly as the reference_video or reference_image of the next request.
Status mapping
Section titled “Status mapping”| DashScope status | Task record status |
|---|---|
PENDING | QUEUED |
RUNNING | IN_PROGRESS |
SUCCEEDED | SUCCESS |
FAILED, CANCELED, UNKNOWN | FAILURE |
Billing
Section titled “Billing”Wan 3.0 is billed per output second, scaled by resolution. The console price is the per-second price for 480P; the other tiers are that price times the model’s resolution ratio, 1 : 2 : 4 for 480P : 720P : 1080P by default.
Both the reservation and the final charge use the same shape:
base quota = model price (per 480P second) × group ratioreserved quota = base quota × reserved seconds × resolution ratiosettled quota = min(base quota × output seconds × effective ratio, reserved quota)effective ratio is the ratio of the requested tier, or — when DashScope reports a lower SR than requested — the lower tier’s ratio. It is never raised above the requested tier.
Reservation and settlement:
| Request | Reserved at submit | On completion |
|---|---|---|
Explicit duration, no reference_video | the requested seconds | actual output seconds |
Contains a reference_video item | 30 seconds | actual output seconds, difference refunded |
metadata.parameters.duration: -1 (smart duration) | 30 seconds | actual output seconds, difference refunded |
Settlement rules verified in the adaptor:
- Only
usage.output_video_durationis billed. Input video seconds, reference images and audio, and first/last frames are free, and generating audio does not change the price. - The unit price is the one snapshotted at submit. A console price change while the task runs does not affect it; the reservation stays as charged.
- Settlement only refunds. A task that reports more seconds than reserved, a higher
SR, or a malformedusagekeeps the reservation rather than charging extra; a missingusageon a successful task also keeps the reservation. - A failed task — moderation rejection, provider timeout, cancellation — refunds the reservation. A
400is rejected before submission and never charges. - Requests that reserve 30 seconds need enough balance or Key quota to cover that reservation, otherwise the submit is rejected with
403insufficient_user_quota.
The submit response carries an X-New-Api-Other-Ratios header with the multipliers that were applied, for example {"resolution-1080P":4,"seconds":5} for an explicit 5-second 1080P request. For a reference-video or smart-duration request, seconds is 30.
Worked example (hypothetical prices; use the console for real ones). Suppose the model is priced at $0.05 per 480P second, so 720P is $0.10 and 1080P is $0.20 under the default 1 : 2 : 4 table, and the group ratio is 1:
- Request
1080P,duration: 5. Reserved: 0.20 × 5 = $1.00. - DashScope reports
usage.SR: 720andoutput_video_duration: 5, so the output came back one tier lower. Settled: 0.10 × 5 = $0.50, and $0.50 is refunded. - Had it reported
SR: 1080for 5 seconds, the charge would have stayed at $1.00 — settlement never charges above the reservation.
Second example: 480P with a 2-second source video, duration: 2. Because a reference_video is present, the reservation is 30 × $0.05 = $1.50; the actual output is 2 seconds, so the charge settles to 2 × $0.05 = $0.10 and $1.40 is refunded.
Limits and errors
Section titled “Limits and errors”Parameter errors are caught before submission: they return 400, are not sent upstream, and cost nothing.
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or blank prompt (prompt is required); audio_url combined with first/last frame (wan3 does not support audio_url together with first/last frame images); empty prompt and empty media; a metadata field with the wrong type; a metadata.model that differs from the resolved model (can't change model with metadata) |
| 400 | invalid_request | Parameter validation failures — see the message table below |
| 400 | invalid_json | The body cannot be parsed |
| 400 | invalid_seconds | Top-level duration / seconds is negative or above 3600 (seconds must be between 1 and 3600) |
| 400 | missing_model | No model in the body (model field is required) |
| 400 | task_not_exist | The task_id is unknown or belongs to another account |
| 403 | insufficient_user_quota | The balance or Key quota cannot cover the reservation; nothing is submitted |
| 429 | — | Upstream capacity exhausted; the message is replaced with Upstream load is saturated, please try again later |
| 500 | build_request_failed, do_request_failed, invalid_response | Conversion failed, the upstream was unreachable, or DashScope returned no task ID |
| upstream status | ali_api_error | DashScope returned an error code and message in the response body |
| upstream status | fail_to_fetch_task | DashScope rejected the submission; the HTTP status is DashScope’s |
Common 400 messages from the Wan 3.0 validation, exactly as returned:
message | Cause |
|---|---|
invalid duration: 40, wan3 supports -1 or 2-30 | Duration outside 2–30 and not -1 |
invalid resolution: 4KP, wan3 supports 480P, 720P, 1080P | Resolution not in the supported tiers (shown after upper-casing and adding P) |
invalid ratio: 1:2, wan3 supports adaptive, 21:9, 16:9, 4:3, 1:1, 3:4, 9:16 | Ratio not in the supported list |
invalid seed: 3000000000, wan3 supports -1 or 0-2147483647 | Seed outside -1 or 0–2147483647 |
invalid size: 1000*1000 | A WIDTH*HEIGHT size not in the table |
wan3 does not support audio_url together with first/last frame images | Frames plus reference audio sent as input.audio_url |
wan3 requires prompt or input.media | No prompt and no media after the metadata merge |
can't change model with metadata | metadata.model differs from the resolved upstream model |
convert seconds to int failed: ... | seconds is not an integer string |
seconds must be between 1 and 3600 | Top-level duration is negative or too large; use metadata.parameters.duration for -1 |
Failures that happen after submission — moderation, undownloadable material, an invalid material combination, input + output over 30 seconds — do not surface as HTTP errors. They appear on the task record as FAILURE with a fail_reason, and the reserved amount is refunded.
Differences from the DashScope API
Section titled “Differences from the DashScope API”- The endpoint is different. RouteAPI does not expose
/api/v1/services/aigc/video-generation/video-synthesis; pointing the DashScope SDK at our base URL does not work. - DashScope’s native
input/parametersobjects go undermetadata.input/metadata.parameters. The common parameters (prompt,resolution,ratio,size,duration,images,generate_audio) can also be sent at the top level and are mapped for you. - The returned
task_idis RouteAPI’s ID, not DashScope’s. DashScope’s ID is indata.data.output.task_id. - Prompt rewriting (
prompt_extend) is always on and cannot be disabled. - Legacy per-model image fields (
img_url,first_frame_url,last_frame_url,audio_url) are dropped; onlyinput.mediareaches the provider. - Callback notification is not supported — poll the task endpoint.
- Billing is per output second with the resolution ratio; the provider’s per-request or tiered billing does not apply.
Complete script
Section titled “Complete script”Requires bash, curl, and jq.
#!/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":"wan3.0-video","prompt":"An orange cat walking on a rooftop, cinematic lighting","resolution":"480P","ratio":"16:9","duration":2}' \ | 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=$(printf '%s' "$RESP" | jq -r .data.status) echo "status: $STATUS $(printf '%s' "$RESP" | jq -r .data.progress)"
case "$STATUS" in SUCCESS) echo "billed seconds: $(printf '%s' "$RESP" | jq -r .data.data.usage.output_video_duration)" curl -sSL -o "$TASK_ID.mp4" "$BASE/v1/videos/$TASK_ID/content" -H "$AUTH" echo "saved $TASK_ID.mp4" exit 0 ;; FAILURE) echo "failed: $(printf '%s' "$RESP" | jq -r .data.fail_reason)" exit 1 ;; esac
sleep 15done
echo "timed out waiting for the task"exit 1