Skip to content

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.

Model IDResolutionsDurationNotes
wan3.0-video480P, 720P, 1080P2–30 seconds, or -1 smartStandard model: text-to-video, first/last frame, and multi-reference material
wan3.0-video-prime480P, 720P, 1080P2–30 seconds, or -1 smartFaster 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.

Envelope fieldWhat the Wan 3.0 adaptor does
promptSent as input.prompt. Like every task request, a blank prompt is rejected with 400 before submission
image / imagesThe 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_referenceSingle-image shorthand: it replaces the images array with one URL, but a top-level image still wins for the first frame
resolutionSent as parameters.resolution, upper-cased and suffixed with P if needed
ratioSent as parameters.ratio, lower-cased
sizeFolded 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
durationSent as parameters.duration. A missing value becomes 5
secondsParsed as an integer duration when duration is absent
generate_audioSent as parameters.audio; an explicit false is forwarded
metadataMerged 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.

These can be written at the top level of the request body. Model-specific keys go in metadata.

FieldTypeValuesNotes
modelstringwan3.0-video, wan3.0-video-primeRequired
promptstring—Required and non-blank. In reference mode, address material as “image 1”, “video 1”, “audio 1”
resolutionstring480P, 720P, 1080PCase-insensitive; the trailing P is optional. Default 1080P
ratiostringadaptive, 21:9, 16:9, 4:3, 1:1, 3:4, 9:16Case-insensitive. When omitted, DashScope applies adaptive
sizestring1280*720, 1280x720, or 720pFolded into resolution and ratio, then dropped. Only sizes in the adaptor’s table are accepted
durationinteger2–30Output seconds. Default 5. A negative or above-3600 value is rejected by the shared task validation; -1 smart duration only works in metadata.parameters
secondsstringinteger stringUsed in place of duration when duration is absent or 0. A non-integer value is a 400
imagestringURLSingle first-frame image; it wins over images for the first frame
imagesarrayURLs1 image = first frame; 2 images = first frame + last frame
input_referencestringURLReplaces images with a single first-frame URL; a top-level image still wins for the first frame
generate_audiobooleantrue / falseSent as parameters.audio; an explicit false is forwarded as false
metadataobjectDashScope input / parametersOverrides 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 mirrors DashScope’s input and parameters objects. Every key below is optional.

Metadata keyTypeNotes
input.mediaarrayMaterial list of {"type", "url"} items. When present, top-level images are not used
input.promptstringReplaces the top-level prompt upstream, but the top-level prompt is still required by the shared validation
input.audio_urlstringShorthand for appending a reference_audio item
input.negative_promptstringNegative prompt
input.templatestringDashScope effect template, forwarded as is
parameters.resolutionstringSame as top-level resolution
parameters.ratiostringSame as top-level ratio
parameters.sizestringSame as top-level size
parameters.durationinteger-1 for smart duration, otherwise 2–30
parameters.audiobooleanSame as generate_audio
parameters.seedinteger-1 or 0–2147483647. 0 is kept as an explicit value; the same seed does not guarantee the same result
parameters.watermarkbooleanProvider-side watermark; off by default
parameters.prompt_extendbooleanAccepted 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.

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:

typeMeaningLimit (DashScope)
first_frameFirst frame of the video1
last_frameLast frame of the video; requires a first frame1
reference_imageReference image for subject/style guidance10
reference_videoReference video for reference generation, editing, or extension5, 15 seconds in total
reference_audioReference audio. input.audio_url is appended as this type5, 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 one media array is rejected upstream: the task is refunded.
  • reference_image, reference_video, and reference_audio can 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_url together with a first_frame or last_frame item is rejected with 400 and the message wan3 does not support audio_url together with first/last frame images. The check looks only at input.audio_url; a reference_audio item placed directly in media next to a frame is caught by DashScope instead (task FAILURE, refunded).
  • After the metadata merge, a request with an empty prompt and an empty media array is rejected with 400 (wan3 requires prompt or input.media). In practice the shared task validation already rejects a blank prompt, so a prompt is always required.

Only a prompt and video parameters:

Terminal window
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
}'

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.

Terminal window
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.

For reference material, write each item in metadata.input.media. Once media is present, the top-level image fields are ignored.

Terminal window
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.

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”. ratio must be adaptive. Set duration to the source length plus the extension: extending a 5-second video by 2 seconds means 7. You can also use -1 and 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.

Terminal window
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:

Terminal window
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" } ]
}
}
}'

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.

Terminal window
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.

Terminal window
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:

FieldMeaning
data.data.output.task_idDashScope task ID, different from the outer data.task_id
data.data.output.video_urlProvider video URL; the same value as the outer data.result_url
data.data.output.orig_promptPrompt DashScope received
data.data.usage.output_video_durationActual output seconds — this is the billed quantity
data.data.usage.input_video_durationInput video seconds; free. 0 for text-to-video
data.data.usage.durationInput + output seconds; informational only, never used for settlement
data.data.usage.SRActual output resolution tier as a number (480, 720, 1080); used for settlement
data.data.usage.fpsOutput frame rate, 30 for these models
data.data.usage.ratioActual 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.

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:

Terminal window
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.

DashScope statusTask record status
PENDINGQUEUED
RUNNINGIN_PROGRESS
SUCCEEDEDSUCCESS
FAILED, CANCELED, UNKNOWNFAILURE

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 ratio
reserved quota = base quota × reserved seconds × resolution ratio
settled 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:

RequestReserved at submitOn completion
Explicit duration, no reference_videothe requested secondsactual output seconds
Contains a reference_video item30 secondsactual output seconds, difference refunded
metadata.parameters.duration: -1 (smart duration)30 secondsactual output seconds, difference refunded

Settlement rules verified in the adaptor:

  • Only usage.output_video_duration is 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 malformed usage keeps the reservation rather than charging extra; a missing usage on a successful task also keeps the reservation.
  • A failed task — moderation rejection, provider timeout, cancellation — refunds the reservation. A 400 is 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 403 insufficient_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:

  1. Request 1080P, duration: 5. Reserved: 0.20 × 5 = $1.00.
  2. DashScope reports usage.SR: 720 and output_video_duration: 5, so the output came back one tier lower. Settled: 0.10 × 5 = $0.50, and $0.50 is refunded.
  3. Had it reported SR: 1080 for 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.

Parameter errors are caught before submission: they return 400, are not sent upstream, and cost nothing.

StatusCodeMeaning
400invalid_requestMissing 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)
400invalid_requestParameter validation failures — see the message table below
400invalid_jsonThe body cannot be parsed
400invalid_secondsTop-level duration / seconds is negative or above 3600 (seconds must be between 1 and 3600)
400missing_modelNo model in the body (model field is required)
400task_not_existThe task_id is unknown or belongs to another account
403insufficient_user_quotaThe 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
500build_request_failed, do_request_failed, invalid_responseConversion failed, the upstream was unreachable, or DashScope returned no task ID
upstream statusali_api_errorDashScope returned an error code and message in the response body
upstream statusfail_to_fetch_taskDashScope rejected the submission; the HTTP status is DashScope’s

Common 400 messages from the Wan 3.0 validation, exactly as returned:

messageCause
invalid duration: 40, wan3 supports -1 or 2-30Duration outside 2–30 and not -1
invalid resolution: 4KP, wan3 supports 480P, 720P, 1080PResolution 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:16Ratio not in the supported list
invalid seed: 3000000000, wan3 supports -1 or 0-2147483647Seed outside -1 or 0–2147483647
invalid size: 1000*1000A WIDTH*HEIGHT size not in the table
wan3 does not support audio_url together with first/last frame imagesFrames plus reference audio sent as input.audio_url
wan3 requires prompt or input.mediaNo prompt and no media after the metadata merge
can't change model with metadatametadata.model differs from the resolved upstream model
convert seconds to int failed: ...seconds is not an integer string
seconds must be between 1 and 3600Top-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.

  • 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 / parameters objects go under metadata.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_id is RouteAPI’s ID, not DashScope’s. DashScope’s ID is in data.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; only input.media reaches 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.

Requires bash, curl, and jq.

#!/usr/bin/env bash
set -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 15
done
echo "timed out waiting for the task"
exit 1