Overview
The Wan 3.0 API generates videos with the same models, credits and limits as the workspace. Each request acts as your account: it spends your credits, follows your membership, and its videos appear in your history.
The API is REST over HTTPS with JSON bodies. Every URL below is relative to https://wan3.video/api/v1. Videos render asynchronously: you create a video, poll it until its status is completed or failed, then download it. Rendering usually takes a few minutes. Videos made through the API send no completion email.
Call the API from your own server. Browsers cannot call it directly, and an API key in web or app code can be copied and used to spend your credits.
Authentication
Create a key on the API keys page while signed in. The full key is shown only once, so store it in a secret manager or an environment variable. An account can hold up to 10 active keys; revoke a key there as soon as it may have leaked.
Send the key as a bearer token with every request. A missing, invalid or revoked key returns 401 UNAUTHORIZED.
curl https://wan3.video/api/v1/account \
-H "Authorization: Bearer $WAN3_API_KEY"Quickstart
1. Create a five-second 720P video from a prompt. The response is the new video, with status set to processing.
curl https://wan3.video/api/v1/videos \
-H "Authorization: Bearer $WAN3_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "w3.0-video",
"prompt": "A paper boat drifting down a rain-soaked street at dusk, cinematic lighting",
"resolution": "720P",
"duration": 5,
"aspectRatio": "16:9"
}'2. Poll the video every 10–15 seconds until it completes or fails.
curl https://wan3.video/api/v1/videos/VIDEO_ID \
-H "Authorization: Bearer $WAN3_API_KEY"3. Download the MP4. The URL may redirect to our file storage, so follow redirects.
curl -L -o video.mp4 https://wan3.video/api/v1/videos/VIDEO_ID/content \
-H "Authorization: Bearer $WAN3_API_KEY"The same flow in Python:
import os
import time
import uuid
import requests
API = "https://wan3.video/api/v1"
KEY = os.environ["WAN3_API_KEY"]
def api(method, path, headers=None, **kwargs):
response = requests.request(
method,
f"{API}{path}",
headers={"Authorization": f"Bearer {KEY}", **(headers or {})},
timeout=60,
**kwargs,
)
body = response.json()
if body["error"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
return body["data"]
video = api(
"POST",
"/videos",
headers={"Idempotency-Key": str(uuid.uuid4())},
json={
"model": "w3.0-video",
"prompt": "A paper boat drifting down a rain-soaked street at dusk",
"resolution": "720P",
"duration": 5,
},
)
while video["status"] == "processing":
time.sleep(10)
video = api("GET", f"/videos/{video['id']}")
if video["status"] == "failed":
raise RuntimeError(video["error"]["message"])
download = requests.get(
video["videoUrl"], headers={"Authorization": f"Bearer {KEY}"}, timeout=300
)
download.raise_for_status()
with open(f"{video['id']}.mp4", "wb") as file:
file.write(download.content)Responses and errors
Every JSON response has the same envelope. A success carries { "data": …, "error": null }; a failure carries { "data": null, "error": { "code", "message" } }. Branch on error.code, which is stable; message is meant for people and may change. Some errors add fields, such as the credits a video needs.
Every response also has an X-Request-Id header. Quote it when you contact support about a request.
HTTP/1.1 402 Payment Required
X-Request-Id: 3f1c9a52-6a0e-4c55-9d7e-0f6f1d2b8c41
{
"data": null,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Not enough credits for this generation",
"requiredCredits": 10,
"availableCredits": 3.5
}
}| Status | Meaning |
|---|---|
200, 201 | The request succeeded; 201 means a resource was created. |
400 | A parameter is missing or invalid. The message names it. |
401 | The API key is missing, invalid or revoked. |
402 | The account does not have enough credits. |
403 | The account cannot do this, for example without a membership. |
404 | The video does not exist or belongs to another account. |
409 | The video is not in a state that allows this request. |
422 | The request was refused, for example by content safety review. |
429 | Too many videos are rendering, or too many safety checks ran. |
451 | The service is not available in your region. |
5xx | A temporary failure. Retry later; failed videos are refunded. |
Idempotent requests
A network error can leave you unsure whether a video was created. Send an Idempotency-Key header with POST /videos, for example a UUID, and retry with the same key: the retry returns the video the first attempt created, with status 200 and an Idempotent-Replayed: true header, instead of starting and charging a second one.
Keys may be 1–255 printable ASCII characters without spaces and are scoped to your account. Use a new key for every new video: reusing one with different parameters returns 422 IDEMPOTENCY_KEY_MISMATCH. When the first attempt was refused before a video existed, for example for lack of credits, a retry with the same key runs again.
Get account
/account
Returns the account's credit balance, membership, and how many of its videos are rendering now against the concurrency limit.
{
"data": {
"credits": 128.5,
"membership": {
"active": true,
"plan": "pro",
"expiresAt": "2026-11-06T08:00:00.000Z"
},
"activeGenerations": 2,
"maxActiveGenerations": 20
},
"error": null
}Create an upload
/uploads
Image-to-video and video edits take files you upload first. Uploading takes three steps: create an upload here, PUT the file to the returned uploadUrl, then complete it. Pass the upload's key when you create a video. Uploads belong to your account and are deleted after seven days.
| filename | The file's name, 1–255 characters. Only used to name the stored file. |
|---|---|
| contentType | image/jpeg, image/png, image/webp, image/bmp, video/mp4 or video/quicktime. |
| bytes | The file size in bytes. |
| model | The model the file is for. Pass doubao-seedance-2.5 to check Seedance 2.5 media rules; any other value, or none, checks the W3.0 rules. |
curl https://wan3.video/api/v1/uploads \
-H "Authorization: Bearer $WAN3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filename": "first-frame.png",
"contentType": "image/png",
"bytes": 1048576
}'{
"data": {
"key": "temp/5f0c…/2026-10-06/7d1e…-first-frame.png",
"uploadUrl": "https://…r2.cloudflarestorage.com/…",
"uploadMethod": "PUT",
"uploadHeaders": { "Content-Type": "image/png" },
"uploadExpiresAt": "2026-10-06T08:15:00.000Z",
"expiresAt": "2026-10-13T08:00:00.000Z"
},
"error": null
}Then send the file's bytes to uploadUrl with the method and headers given, within 15 minutes. Do not send your API key to this URL.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
--upload-file first-frame.pngComplete an upload
/uploads/complete
Checks the uploaded file against the model's media requirements and returns a video's duration. Creating a video checks its files again, so this step is optional, but it reports a bad file before you create a video. Files are not resized or converted: send media that already meets the requirements.
| key | The key returned when the upload was created. |
|---|---|
| contentType | The content type the upload was created with. |
| bytes | The size the upload was created with. |
| model | The model the file is for. Pass doubao-seedance-2.5 to check Seedance 2.5 media rules; any other value, or none, checks the W3.0 rules. |
curl https://wan3.video/api/v1/uploads/complete \
-H "Authorization: Bearer $WAN3_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"key": "temp/5f0c…/2026-10-06/7d1e…-first-frame.png",
"contentType": "image/png",
"bytes": 1048576
}'{
"data": {
"key": "temp/5f0c…/2026-10-06/7d1e…-first-frame.png",
"contentType": "image/png",
"bytes": 1048576,
"expiresAt": "2026-10-13T08:00:00.000Z"
},
"error": null
}Videos also return durationSeconds. A file that is missing, differs from the declared type or size, or breaks a media rule returns 400 UPLOAD_INVALID with the reason.
Create a video
/videos
Starts rendering a video and charges its credits. If rendering fails, the credits are refunded automatically. Prompts first pass a content safety review; a refused prompt costs nothing.
| prompt | What the video should show, up to 20,000 characters. |
|---|---|
| model | Defaults to w3.0-video. See models and credits. |
| mode | text-to-video, image-to-video or video-edit. When omitted it is video-edit with a videoKey, image-to-video with images, and otherwise text-to-video. |
| resolution | One the model supports. Defaults to 720P, or 1080P for Pro models. Above 720P requires an active membership. |
| duration | Seconds of output; defaults to 5. W3.0 models render 2–30 seconds and Seedance 2.5 renders 4–30. A W3.0 edit and its source video may last 30 seconds together. A Seedance 2.5 edit lasts as long as its source, so omit duration for it. |
| aspectRatio | 16:9, 9:16, 1:1, 4:3, 3:4; defaults to 16:9. W3.0 edits keep the source's ratio unless you set one. Seedance 2.5 chooses the ratio itself for first-frame videos and edits. |
| seed | 0–2147483647. Reusing a seed with the same inputs gives similar results. Random when omitted. |
| images | Uploaded images as { "key", "role" }. For image-to-video, send 1–10 images that each have a role: one first_frame image opens the video, or up to 10 reference_image images guide its subjects and style; the two roles cannot be combined. For a video edit, send up to 10 reference images; their role may be omitted. |
| videoKey | The uploaded source video of a video edit. |
curl https://wan3.video/api/v1/videos \
-H "Authorization: Bearer $WAN3_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 8b0f6f5e-0c55-4a2b-9a7d-3f1e2d4c5b6a" \
-d '{
"model": "w3.0-video",
"prompt": "The camera slowly pushes in as she turns toward the window",
"images": [
{ "key": "temp/5f0c…/first-frame.png", "role": "first_frame" }
],
"duration": 5
}'{
"model": "w3.0-video",
"prompt": "Turn the street into a snowy winter night",
"videoKey": "temp/5f0c…/source.mp4",
"images": [{ "key": "temp/5f0c…/style.jpg" }],
"duration": 5
}Returns 201 with the new video object, whose status is processing. Common refusals are 402 INSUFFICIENT_CREDITS, 403 MEMBERSHIP_REQUIRED, 422 NSFW_PROMPT and 429 ACTIVE_GENERATION_LIMIT; see error codes.
The video object
| id | The video's ID. |
|---|---|
| status | processing, completed or failed. |
| model, mode, prompt | What was requested. |
| resolution | The output resolution. |
| duration | The output length in seconds. |
| aspectRatio | The output ratio, adaptive when it follows the first frame, or source when an edit keeps its source's ratio. |
| seed | The seed used. |
| creditCost | Credits charged. A failed video's credits are refunded. |
| videoUrl | Once completed, the download URL, which takes your API key; otherwise null. |
| error | For a failed video, { "code", "message" } with code GENERATION_FAILED, GENERATION_TIMED_OUT or PROVIDER_REQUEST_REJECTED. |
| createdAt, updatedAt | ISO 8601 timestamps. |
{
"data": {
"id": "video_1759737600000_4f2a9c1e",
"status": "completed",
"model": "w3.0-video",
"mode": "image-to-video",
"prompt": "The camera slowly pushes in as she turns toward the window",
"resolution": "720P",
"duration": 5,
"aspectRatio": "16:9",
"seed": 1844673015,
"creditCost": 5,
"videoUrl": "https://wan3.video/api/v1/videos/video_1759737600000_4f2a9c1e/content",
"error": null,
"createdAt": "2026-10-06T08:00:00.000Z",
"updatedAt": "2026-10-06T08:02:41.000Z"
},
"error": null
}Retrieve a video
/videos/{id}
Returns the video object. For a video that is still processing, this also asks the provider for its latest progress. Poll no more often than every 5 seconds.
List videos
/videos
Returns your videos, newest first.
| limit | 1–100 videos per page; defaults to 20. |
|---|---|
| status | Only processing, completed or failed videos. |
| cursor | The previous page's nextCursor, to fetch the page after it. |
curl "https://wan3.video/api/v1/videos?limit=20&status=completed" \
-H "Authorization: Bearer $WAN3_API_KEY"{
"data": {
"videos": [ { "id": "video_…", "status": "completed", … } ],
"nextCursor": "video_1759737600000_4f2a9c1e"
},
"error": null
}nextCursor is null on the last page.
Download a video
/videos/{id}/content
Returns the completed video as an MP4 file. Archived videos answer with a 307 redirect to our file storage, so follow redirects; the file URL itself needs no key. Range requests are supported. A video that has not completed returns 409 VIDEO_NOT_READY.
Delete a video
/videos/{id}
Permanently deletes a completed video and its stored file. Videos that are processing or failed cannot be deleted and return 409 VIDEO_NOT_DELETABLE.
{
"data": { "id": "video_1759737600000_4f2a9c1e", "deleted": true },
"error": null
}Models and credits
A video costs credits in proportion to its length and resolution. The table shows the credits for a 5-second video; a 10-second video costs twice as much. Buy credits or a membership on the pricing page.
| Model | Description | Credits for 5 seconds | Membership |
|---|---|---|---|
w3.0-video | W3.0 | 480P: 2.5 · 720P: 5 · 1080P: 10 | For 1080P |
w3.0-video-prime | W3.0, faster rendering | 480P: 3.5 · 720P: 7 · 1080P: 14 | For 1080P |
w3.0-video-pro | W3.0 Pro, super-resolution | 1080P: 10 · 2K: 10 · 4K: 12.5 | Required |
w3.0-video-prime-pro | W3.0 Pro, faster rendering | 1080P: 14 · 2K: 14 · 4K: 17.5 | Required |
doubao-seedance-2.5 | Seedance 2.5 | 480P: 6.47 · 720P: 14.55 · 1080P: 25.92 | For 1080P |
W3.0 edits cost the same as new videos of the requested length. A Seedance 2.5 edit is billed for its source video's length twice, as input and as output, at a lower rate: editing a 10-second source at 720P costs 38.78 credits. The creditCost of each video is the exact charge.
Media requirements
Uploads are checked against the rules of the model they are for. Files are not resized or converted, so prepare them before uploading.
| W3.0 models | Seedance 2.5 | |
|---|---|---|
| Image formats | JPEG, PNG without transparency, WebP, BMP | JPEG, PNG, WebP, BMP |
| Image size | Up to 20 MB | Under 30 MB |
| Image sides | 240–8,000 px | 300–6,000 px |
| Video formats | MP4, MOV | MP4, MOV |
| Video size | Up to 100 MB | Under 200 MB |
| Video length | 1–15 seconds | 4–30 seconds |
| Video sides | 240–4,096 px | 300–6,000 px, 409,600–8,295,044 pixels in total |
| Frame rate | At least 16 FPS | 24–60 FPS |
| Aspect ratio | Up to 8:1 either way | Width ÷ height between 0.4 and 2.5 |
Limits and storage
- Up to 20 videos per account can render at the same time. More return
429 ACTIVE_GENERATION_LIMITuntil one finishes. - Prompts that need a full content safety review are limited to 30 a minute and 200 an hour per account. Beyond that, creating a video returns
429 PROMPT_MODERATION_RATE_LIMITED. - A video still rendering after 3 hours fails with
GENERATION_TIMED_OUT. Failed videos are always refunded. - Upload URLs expire after 15 minutes, and uploaded files are deleted after 7 days.
- Videos made while your account holds a membership, or after it has bought credits, are stored permanently. Other videos stay with the video provider and may expire, so download them soon after they complete.
- Prompts can be up to 20,000 characters, with up to 10 images per video.
Error codes
| Code | Status | Meaning |
|---|---|---|
INVALID_JSON | 400 | The body is not a JSON object. |
INVALID_REQUEST | 400 | A parameter is missing or invalid; the message names it. |
INVALID_CURSOR | 400 | The cursor is not a nextCursor from a previous page. |
UPLOAD_INVALID | 400 | The upload is missing, differs from what was declared, or breaks a media rule. |
UNAUTHORIZED | 401 | The API key is missing, invalid or revoked. |
INSUFFICIENT_CREDITS | 402 | Not enough credits; requiredCredits and availableCredits say how many. |
ACCOUNT_BANNED | 403 | The account is banned. |
MEMBERSHIP_REQUIRED | 403 | Pro models and resolutions above 720P need an active membership. |
VIDEO_NOT_FOUND | 404 | No video with this ID exists for this account. |
IDEMPOTENCY_KEY_REUSED | 409 | The key's video was deleted; use a new Idempotency-Key. |
VIDEO_NOT_READY | 409 | The video has not completed yet. |
VIDEO_NOT_DELETABLE | 409 | Only completed videos can be deleted. |
IDEMPOTENCY_KEY_MISMATCH | 422 | The Idempotency-Key was used with different parameters. |
NSFW_PROMPT | 422 | The prompt failed content safety review. Nothing was charged. |
PROVIDER_REQUEST_REJECTED | 422 | The provider refused the same prompt and media within the last day; change them first. |
ACTIVE_GENERATION_LIMIT | 429 | Too many videos are rendering; activeLimit gives the limit. |
PROMPT_MODERATION_RATE_LIMITED | 429 | Too many content safety checks; wait a minute. |
REGION_UNAVAILABLE | 451 | The service is not available in your region. |
INTERNAL_ERROR | 500 | An unexpected error; quote X-Request-Id to support. |
PROVIDER_ERROR | 502 | The video provider failed; retry later. |
VIDEO_UNAVAILABLE | 502 | The video file could not be fetched; retry shortly. |
SERVICE_UNAVAILABLE | 503 | A service is temporarily unavailable; retry later. |
PROMPT_MODERATION_UNAVAILABLE | 503 | Content safety review is unavailable; retry later. |