Developers

Wan 3.0 API

Generate videos from your own software with the same models, credits and limits as the Wan 3.0 workspace.

Base URLhttps://wan3.video/api/v1

On this page

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.

Shell
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.

Shell
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.

Shell
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.

Shell
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:

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.

Error response
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
  }
}
StatusMeaning
200, 201The request succeeded; 201 means a resource was created.
400A parameter is missing or invalid. The message names it.
401The API key is missing, invalid or revoked.
402The account does not have enough credits.
403The account cannot do this, for example without a membership.
404The video does not exist or belongs to another account.
409The video is not in a state that allows this request.
422The request was refused, for example by content safety review.
429Too many videos are rendering, or too many safety checks ran.
451The service is not available in your region.
5xxA 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

GET/account

Returns the account's credit balance, membership, and how many of its videos are rendering now against the concurrency limit.

Response
{
  "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

POST/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.

Body parameters
filenamestring · requiredThe file's name, 1–255 characters. Only used to name the stored file.
contentTypestring · requiredimage/jpeg, image/png, image/webp, image/bmp, video/mp4 or video/quicktime.
bytesinteger · requiredThe file size in bytes.
modelstringThe 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.
Shell
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
  }'
Response · 201 Created
{
  "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.

Shell
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --upload-file first-frame.png

Complete an upload

POST/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.

Body parameters
keystring · requiredThe key returned when the upload was created.
contentTypestring · requiredThe content type the upload was created with.
bytesinteger · requiredThe size the upload was created with.
modelstringThe 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.
Shell
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
  }'
Response
{
  "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

POST/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.

Body parameters
promptstring · requiredWhat the video should show, up to 20,000 characters.
modelstringDefaults to w3.0-video. See models and credits.
modestringtext-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.
resolutionstringOne the model supports. Defaults to 720P, or 1080P for Pro models. Above 720P requires an active membership.
durationintegerSeconds 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.
aspectRatiostring16: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.
seedinteger0–2147483647. Reusing a seed with the same inputs gives similar results. Random when omitted.
imagesarrayUploaded 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.
videoKeystringThe uploaded source video of a video edit.
Image-to-video
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
  }'
Video edit body
{
  "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

Fields
idstringThe video's ID.
statusstringprocessing, completed or failed.
model, mode, promptstringWhat was requested.
resolutionstringThe output resolution.
durationintegerThe output length in seconds.
aspectRatiostringThe output ratio, adaptive when it follows the first frame, or source when an edit keeps its source's ratio.
seedinteger | nullThe seed used.
creditCostnumber | nullCredits charged. A failed video's credits are refunded.
videoUrlstring | nullOnce completed, the download URL, which takes your API key; otherwise null.
errorobject | nullFor a failed video, { "code", "message" } with code GENERATION_FAILED, GENERATION_TIMED_OUT or PROVIDER_REQUEST_REJECTED.
createdAt, updatedAtstringISO 8601 timestamps.
Example
{
  "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

GET/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

GET/videos

Returns your videos, newest first.

Query parameters
limitinteger1–100 videos per page; defaults to 20.
statusstringOnly processing, completed or failed videos.
cursorstringThe previous page's nextCursor, to fetch the page after it.
Shell
curl "https://wan3.video/api/v1/videos?limit=20&status=completed" \
  -H "Authorization: Bearer $WAN3_API_KEY"
Response
{
  "data": {
    "videos": [ { "id": "video_…", "status": "completed", … } ],
    "nextCursor": "video_1759737600000_4f2a9c1e"
  },
  "error": null
}

nextCursor is null on the last page.

Download a video

GET/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

DELETE/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.

Response
{
  "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.

ModelDescriptionCredits for 5 secondsMembership
w3.0-videoW3.0480P: 2.5 · 720P: 5 · 1080P: 10For 1080P
w3.0-video-primeW3.0, faster rendering480P: 3.5 · 720P: 7 · 1080P: 14For 1080P
w3.0-video-proW3.0 Pro, super-resolution1080P: 10 · 2K: 10 · 4K: 12.5Required
w3.0-video-prime-proW3.0 Pro, faster rendering1080P: 14 · 2K: 14 · 4K: 17.5Required
doubao-seedance-2.5Seedance 2.5480P: 6.47 · 720P: 14.55 · 1080P: 25.92For 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 modelsSeedance 2.5
Image formatsJPEG, PNG without transparency, WebP, BMPJPEG, PNG, WebP, BMP
Image sizeUp to 20 MBUnder 30 MB
Image sides240–8,000 px300–6,000 px
Video formatsMP4, MOVMP4, MOV
Video sizeUp to 100 MBUnder 200 MB
Video length1–15 seconds4–30 seconds
Video sides240–4,096 px300–6,000 px, 409,600–8,295,044 pixels in total
Frame rateAt least 16 FPS24–60 FPS
Aspect ratioUp to 8:1 either wayWidth ÷ 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_LIMIT until 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

CodeStatusMeaning
INVALID_JSON400The body is not a JSON object.
INVALID_REQUEST400A parameter is missing or invalid; the message names it.
INVALID_CURSOR400The cursor is not a nextCursor from a previous page.
UPLOAD_INVALID400The upload is missing, differs from what was declared, or breaks a media rule.
UNAUTHORIZED401The API key is missing, invalid or revoked.
INSUFFICIENT_CREDITS402Not enough credits; requiredCredits and availableCredits say how many.
ACCOUNT_BANNED403The account is banned.
MEMBERSHIP_REQUIRED403Pro models and resolutions above 720P need an active membership.
VIDEO_NOT_FOUND404No video with this ID exists for this account.
IDEMPOTENCY_KEY_REUSED409The key's video was deleted; use a new Idempotency-Key.
VIDEO_NOT_READY409The video has not completed yet.
VIDEO_NOT_DELETABLE409Only completed videos can be deleted.
IDEMPOTENCY_KEY_MISMATCH422The Idempotency-Key was used with different parameters.
NSFW_PROMPT422The prompt failed content safety review. Nothing was charged.
PROVIDER_REQUEST_REJECTED422The provider refused the same prompt and media within the last day; change them first.
ACTIVE_GENERATION_LIMIT429Too many videos are rendering; activeLimit gives the limit.
PROMPT_MODERATION_RATE_LIMITED429Too many content safety checks; wait a minute.
REGION_UNAVAILABLE451The service is not available in your region.
INTERNAL_ERROR500An unexpected error; quote X-Request-Id to support.
PROVIDER_ERROR502The video provider failed; retry later.
VIDEO_UNAVAILABLE502The video file could not be fetched; retry shortly.
SERVICE_UNAVAILABLE503A service is temporarily unavailable; retry later.
PROMPT_MODERATION_UNAVAILABLE503Content safety review is unavailable; retry later.