Python urllib only: a Wan 3.0 test job for $0.125

No requests, no SDK: 27 lines of Python urllib submit a 2-second 480p Wan 3.0 job, poll it and save the MP4. The job costs $0.125, the cheapest Wan clip.

4 min readSume
All posts

A locked-down build box, a Lambda layer you cannot extend or a teaching repo with no dependencies all have one thing in common: you cannot pip install requests. The Sume video API does not need it. Submit, poll and download are plain HTTP, and Python's urllib follows the 302 on the content URL by itself.

The script below uses wan-3.0 because it has the smallest job in the catalog. Wan 3.0 accepts 2 to 30 seconds at 480p, 720p or 1080p. At 480p a 2-second clip is 2 x $0.0625 = $0.125, which makes it a cheap way to prove your key, your balance and your download path work before you send a real brief. Nothing here is specific to Wan, though: if you pass a different model id the same code runs, and the only things that change are the price and the limits the catalog advertises for that id. Asking for a value the model does not list, such as a 1-second clip on a model with a 2-second floor, is not silently adjusted; the API answers 400 unsupported_capability and does not start a job.

The script

Export SUME_API_KEY and run it. It was run against a local stand-in server returning the documented 202, poll and 302 shapes, with POLL_SECONDS=0; against the live API leave the default of 30 seconds between polls.

import json, os, time, urllib.request

BASE = os.environ.get("SUME_API_BASE", "https://api.sume.com")
KEY = os.environ["SUME_API_KEY"]


def call(url, body=None, headers=None):
    req = urllib.request.Request(url, headers={"Authorization": f"Bearer {KEY}", **(headers or {})})
    if body is not None:
        req.data = json.dumps(body).encode()
        req.add_header("Content-Type", "application/json")
    return urllib.request.urlopen(req)  # follows the 302 on /content


job = json.load(call(f"{BASE}/v1/videos", {
    "model": "wan-3.0", "prompt": "Rain on a neon street", "duration": 2,
    "resolution": "480p", "aspect_ratio": "16:9"}, {"Idempotency-Key": "wan-test-0001"}))
while True:
    time.sleep(int(os.environ.get("POLL_SECONDS", "30")))
    state = json.load(call(job["polling_url"]))
    if state["status"] in ("failed", "cancelled"):
        raise SystemExit(f'{state["status"]}: {state.get("error")}')
    if state["status"] == "completed":
        with open("out.mp4", "wb") as f:
            f.write(call(state["unsigned_urls"][0]).read())
        print("cost", state["usage"]["cost"])
        break

What it proves

One successful run settles four separate questions at the cost of 12.5 cents.

  • The key authenticates: a bad key would have come back as 401 on the first call.
  • The workspace has USD balance: with none, submit answers 402 insufficient_credits.
  • Your network can reach the artifact host, because the final read goes through the redirect.
  • usage.cost is in the poll body, so your accounting code has something real to parse.

Wan 3.0 price ladder

The Sume price is the provider list times 1.25. Wan 3.0 is priced by the second and the resolution tier, so it is easy to estimate: multiply seconds by the rate.

Wan 3.0 per-second price on Sume (pricing tables, read 2026-10-05)
ResolutionPer second2 s clip30 s clip
480p$0.0625$0.125$1.875
720p$0.125$0.25$3.75
1080p$0.25$0.50$7.50

When to leave the test model

Wan 3.0 takes up to 10 images, 5 videos and 5 audio files as references and has audio as an option, so it can carry real work too. Switch the model string to seedance-2.5 or kling-3 when you need their look; the rest of the script stays the same because /v1/videos has one request shape for every model.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume