GPT-6 Sol & Claude Opus 5.5 are liveGPT-6 Sol at half the price of 5.6 Sol

Wan 3.0 API: Text-to-Video and Image-to-Video in Python

One script made both clips: the code, the real times and Alibaba's per-second price.

Kiki, OmniaKey COO8 min read
On this page

Wan 3.0 runs on Alibaba Cloud Model Studio as wan3.0-video and the faster wan3.0-video-prime. Every call is asynchronous: create a task, poll the ID, download the MP4. Our Python script made a 5-second 720P clip from text in 2 min 42 s and one from a still in 3 min 20 s, both with sound.

  1. Put your key in OMNIAKEY_API_KEY.
  2. POST https://api.omniakey.com/v1/media/tasks with the model, the operation, the prompt, the parameters and a new Idempotency-Key.
  3. Poll GET /v1/media/tasks/{id} until the status reads succeeded, then download assets[0].url at once.

Is there an official Wan 3.0 API, and which model should I use?

Yes. Alibaba Cloud Model Studio is the official API, with both wan3.0-video and wan3.0-video-prime (reference updated October 15, 2026). Its create call needs an X-DashScope-Async: enable header, and Alibaba's Python path is the DashScope SDK 2.0.1 or newer.

Alibaba Model StudioOmniaKey
Both model IDsYesYes
Async header neededYesNo
Video link lasts24 hoursAbout 1 hour, renewable
BilledPer secondPer second
PythonDashScope SDKStandard library

Both IDs take one body shape, and the model picks the mode from the media you send. The script below runs wan3.0-video, and its MODEL line switches it. We did not run Wan 3.0 Prime, which OmniaKey's docs describe as the same capabilities at roughly half the turnaround (checked October 31, 2026).

What do you need before the first call?

Three things, and the first one is the key:

  1. An OmniaKey API key, exported in your shell:

    bash
    export OMNIAKEY_API_KEY="your-key"
    
  2. Python 3. The script imports the standard library only, so python3 runs it as is.

  3. For image to video, a first frame: a JPEG about 1024 px wide, or a public HTTPS URL. The script refuses a file over 700,000 bytes, because Base64 adds a third and the body cap is 1 MiB (docs, checked October 31, 2026).

Which parameters matter?

Seven fields do most of the work, and each is a value inside parameters.

ParameterWhat it doesDefault
DurationClip length, seconds5
ResolutionPicture size tier1080P
RatioFrame shapeadaptive
AudioSound trackon
SeedRepeatability hint-1
Prompt extendRewrites your prompton
WatermarkAdds a corner markoff

The script sets duration, resolution and ratio. It asks for 720P, which costs half as much per second at list prices as Alibaba's 1080P default.

What does the Python code look like?

The script below ran unchanged on October 31, 2026: standard library only, both modes, one MODEL line to change.

python
import base64
import json
import os
import time
import urllib.error
import urllib.request
import uuid

API = "https://api.omniakey.com/v1/media/tasks"
KEY = os.environ["OMNIAKEY_API_KEY"]
MODEL = "wan3.0-video"

def request(method, url, body=None, extra_headers=None):
    headers = {"Authorization": f"Bearer {KEY}", "User-Agent": "omniakey-example/1"}
    data = None
    if body is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(body).encode()
    headers.update(extra_headers or {})
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.load(resp)

def poll(url, attempts=5):
    for attempt in range(attempts):
        try:
            return request("GET", url)
        except urllib.error.HTTPError as err:
            if err.code < 500 or attempt == attempts - 1:
                raise
        except urllib.error.URLError:
            if attempt == attempts - 1:
                raise
        time.sleep(5 * (attempt + 1))

def run_task(body, out_path):
    task = request("POST", API, body, {"Idempotency-Key": str(uuid.uuid4())})["id"]
    print("task", task, flush=True)
    for _ in range(180):  # 5-second polls, up to 15 minutes
        time.sleep(5)
        state = poll(f"{API}/{task}")
        if state["status"] not in ("queued", "processing"):
            break
    else:
        raise TimeoutError(f"{task} is still {state['status']}: poll it again later, do not resubmit")
    if state["status"] != "succeeded":
        raise RuntimeError(state.get("error") or state["status"])
    asset_url = state["assets"][0]["url"]  # signed link: short-lived, so download now
    req = urllib.request.Request(asset_url, headers={"User-Agent": "omniakey-example/1"})
    with urllib.request.urlopen(req, timeout=300) as resp, open(out_path, "wb") as f:
        f.write(resp.read())
    return task

def text_to_video(prompt, out_path, duration=5, resolution="720P", ratio="16:9"):
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "text_to_video",
        "input": {"prompt": prompt},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": ratio},
    }
    return run_task(body, out_path)

def image_to_video(prompt, image_path, out_path, duration=5, resolution="720P"):
    with open(image_path, "rb") as f:
        raw = f.read()
    if len(raw) > 700_000:  # base64 adds a third, and the whole body must stay under 1 MiB
        raise ValueError("first frame too large: save it as a JPEG about 1024 px wide")
    frame = "data:image/jpeg;base64," + base64.b64encode(raw).decode()
    body = {
        "model": MODEL,
        "type": "video",
        "operation": "image_to_video",
        "input": {"prompt": prompt, "media": [{"type": "first_frame", "url": frame}]},
        "parameters": {"duration": duration, "resolution": resolution, "ratio": "adaptive"},
    }
    return run_task(body, out_path)

if __name__ == "__main__":
    print("done", text_to_video(
        "A blacksmith hammers a glowing horseshoe on an anvil in a dark forge. Sparks fly at each "
        "strike and light his leather apron. The camera slowly pushes in. Sound of the hammer ringing "
        "on steel and the fire crackling. No dialogue, no music, no text.",
        "forge.mp4",
    ))
    print("done", image_to_video(
        "Image 1 is the first frame. The paper boat drifts slowly to the right across the puddle. "
        "Light rain makes small ripples and the cafe lights shimmer in the water. The camera stays "
        "low and still. Sound of light rain. No dialogue, no music, no text.",
        "boat.jpg",
        "boat-drifts.mp4",
    ))

What each part does:

  • run_task mints a new Idempotency-Key for each job. Resending the same key with the same body returns the original task and never charges twice (docs, checked October 31, 2026).
  • It polls every 5 seconds for up to 15 minutes, then raises TimeoutError.
  • A timeout means poll the same ID again later; a resubmit mints a new key, creates a second job and bills it.
  • It downloads the first asset URL at once, because the signed link is short-lived.
  • image_to_video refuses a frame over 700,000 bytes: Base64 adds a third, and the body cap is 1 MiB.
  • The image call sets ratio to adaptive, so the clip follows the still's shape.

What it printed:

text
task task_<id>
done task_<id>
task task_<id>
done task_<id>

What did the two calls produce?

Both files came back as 5.04-second MP4s, 150 frames at 30 fps, H.264, with an AAC stereo track at 44.1 kHz.

Text to video: the forge

Four frames from the Wan 3.0 forge clip
Text to video, 5 s at 720P: frames at 0.3, 2.2, 3.5 and 4.7 seconds.

The clip follows the prompt: a blacksmith in a dark shirt and leather apron hammers a glowing orange horseshoe, sparks burst at 2.2 seconds, and the camera pushes in a little. The flaw is a stamped maker's mark of garbled letters on the anvil, though the prompt asked for no text. Start to file took 2 min 42 s; the MP4 is 1280×720 at 6.6 MB.

Image to video: the paper boat

The first-frame still and three frames from the Wan 3.0 paper-boat clip
Image to video: the still we sent (top left), then the clip at 1.6, 3.5 and 4.7 seconds.

The first frame was a still we made with Nano Banana 2.1, a red paper boat on a rain puddle, resized to a 1024×571 JPEG of 158 KB and sent inline as a data URL. The clip keeps that dusk light and the wet cobblestones, drifts the boat left to right as asked, ripples at 1.6 seconds, and holds the camera low and still. It came back as 1284×716 because adaptive followed the still's shape, in 3 min 20 s at 7.4 MB.

Is the sound on, and does "no music" work?

Sound is on by default. Gemini 3.8 Flash described the forge track as metallic clinking with no speech and no music, and the paper-boat track as a melancholic, calm piano melody, although that prompt ended with "no music". Alibaba's prompt guide (updated October 16, 2026) suggests writing "No BGM" or "No background music", and our prompt used neither.

With "audio": false the file carries a video track only: our 2-second 480P lantern job had no sound track, and Alibaba charges the same either way.

ClipTimeSizeSound
Forge (text)2 min 42 s1280×720Clinking, no music
Paper boat (image)3 min 20 s1284×716Piano despite "no music"
Lantern, audio off4 min 36 s854×480No sound track

Queue time varies: the 2-second audio-off job took 4 min 36 s, longer than either 5-second 720P clip. For prompt wording, the Wan 3.0 prompt guide has the structure and example prompts.

Poll GET /v1/media/tasks/{id} until the status reads succeeded, failed or canceled; while the job runs it reads queued or processing. A finished task returns a signed asset URL that lasts about an hour (OmniaKey docs, checked October 31, 2026). Download it at once, then poll the task again for a fresh URL whenever the old one expires, because the record lives for 24 hours.

The file is an MP4 with H.264 video at 30 fps, and Alibaba's own video URL lasts 24 hours. Cancelling a Wan 3.0 job does not stop the render, and a job that succeeds after a cancel is still billed, so check duration and resolution before you submit.

Which errors will you hit?

Most of these come back before a task exists, and each has a fix in the request or the key.

  • 400 model_price_error — duration outside 2 to 30 seconds. Our "duration": 1 probe failed in 0.9 s with "media duration must be between 2 and 30 seconds"; no task was created and nothing was billed.
  • 400 missing_idempotency_key — no Idempotency-Key header. Add one, up to 255 characters.
  • 409 idempotency_key_reused — same key, different body. Mint a new key for each prompt.
  • 403 model_not_allowed — the key may not call that model. Check the model value, then the key's settings.
  • 403 with a balance or spending-cap message — low balance or a per-key cap. Top up or raise the cap.
  • 404 task_not_found — wrong ID, or a task over 24 hours old. Poll the ID the create call returned.
  • 409 task_state_conflict — the job already finished, as when a cancel lands after success. Read the status first.
  • 429 — too many requests. OmniaKey publishes no fixed rate limits, so back off with exponential delay and jitter.
  • 503 no_available_channel — no channel for that operation, such as video to video. Use text to video or image to video.
  • TimeoutError — still running after 15 minutes. Poll the same ID later; a resubmit makes and bills a second job.

How much does a Wan 3.0 clip cost?

Alibaba bills per second of video, and a failed request is not billed (pricing page updated October 29, 2026). The table holds Alibaba's Singapore list prices per second, checked October 31, 2026, from Alibaba's Model Studio pricing page.

Model480P720P1080P
Wan 3.0$0.05$0.1$0.2
Wan 3.0 Prime$0.068$0.14$0.28

A 5-second 720P clip is $0.50 on Wan 3.0 (5 × $0.1) and $0.70 on Prime (5 × $0.14). The three Wan 3.0 prices carry a "Limited-time 30% off" marking, and the page does not say whether the figures include it. Sound on or off costs the same.

The free quota is 30 seconds of video, input plus output, on wan3.0-video in Singapore; Prime has none. China (Beijing) lists lower dollar rates for the standard model: $0.041256, $0.082513 and $0.165025 per second at 480P, 720P and 1080P.

OmniaKey bills per second too, with current rates on the Wan 3.0 and Wan 3.0 Prime model pages.

Frequently asked questions

How long does a call take?

Alibaba says a job typically takes 1 to 5 minutes and suggests polling every 15 seconds (reference, checked October 31, 2026). Our three jobs took 2 min 42 s, 3 min 20 s and 4 min 36 s.

How do I turn the sound off?

Set "audio": false inside parameters, and the MP4 arrives with a video track only, as our lantern job did. The price stays the same.

Should I use wan3.0-video or wan3.0-video-prime?

Pick wan3.0-video for Alibaba's lower list rate and its 30-second free quota in Singapore, and Prime when turnaround matters more. OmniaKey describes Prime as the same capabilities at roughly half the time.

About an hour on OmniaKey, and polling the task again returns a fresh link while the record lasts 24 hours. Alibaba's own video URL lasts 24 hours.

Sources checked

Models in this article

Wan 3.0video APIPythontext-to-videoimage-to-video

Related articles

Recent posts