Wan 3.0 sync mode waits 30 seconds: a 30-second clip needs async

Sume's sync mode holds a request at most 30 seconds, so a 30 second Wan 3.0 clip needs async or a webhook. What the timeout means and how to avoid paying twice.

5 min readSume
All posts

Sume's sync mode blocks the HTTP request for at most 30 seconds, so a 30 second Wan 3.0 clip should be submitted with async (or a webhook), then polled. If sync times out the job keeps running and billing, so read the status URL; never submit the same request again.

Two different thirty-second numbers

The 30 in wait_timeout_seconds and the 30 in a 30 second clip are unrelated numbers, and mixing them up is a common mistake. The first is a limit on how long Sume holds your HTTP request. The second is the duration of the video. The jobs docs say the API clamps wait_timeout_seconds to 0 to 30 and that the budget "sets a limit on how long the HTTP request blocks, not on how long the job can take".

The four modes

Each mode decides only how you learn the outcome. It does not change cost or run time.

Sume communication modes for generation jobs (Sume docs, read 2026-10-05)
ModeRequest blocks?Use for a 30 s Wan clip?
async (default)No, returns 202 with status_urlYes: poll until terminal
syncUp to 30 sNo: video usually runs longer
subscribeSame bounded wait as syncNo: it is an alias of sync
webhookNo, Sume calls you at the endYes: keep polling as backup

What a timed-out sync call looks like

When the wait budget ends, the response is still a 2xx and still carries the job id. The envelope includes status_url, result_url, events_url and cancel_url, and a sync object where sync.timed_out is true. Continue with GET status_url, and obey next_poll_after_seconds when it is present. Do not submit a new paid job for the same intent.

The retry rule

If you retry the submit itself because a network error hid the response, send the same Idempotency-Key. The retry returns the original job rather than creating a second one, so you are not billed twice. A fresh key is a fresh paid job.

A polling client

Submit, then poll GET /v1/jobs/{id}/status until terminal is true, sleeping for next_poll_after_seconds when present and otherwise backing off. Fetch GET /v1/jobs/{id}/result when the job completed.

import asyncio, json, os, time, urllib.request

BASE = "https://api.sume.com"
HDR = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"], "Content-Type": "application/json"}

def call(path, body=None, key=None):
    h = dict(HDR)
    if key:
        h["Idempotency-Key"] = key
    data = json.dumps(body).encode() if body else None
    req = urllib.request.Request(BASE + path, data=data, headers=h)
    return json.load(urllib.request.urlopen(req))

def run():
    job = call("/v1/videos", {"model": "wan-3.0", "prompt": "Harbor at dawn",
                              "resolution": "480p", "duration": 30}, "wan-30s-001")
    delay = 5
    while True:
        s = call(f"/v1/jobs/{job['id']}/status")
        if s.get("terminal"):
            return s
        time.sleep(s.get("next_poll_after_seconds") or delay)
        delay = min(delay * 2, 60)

async def main():
    print(await asyncio.to_thread(run))

asyncio.run(main())

Short clips are not an exception

Why not sync for short clips? A 2 second 480p Wan 3.0 clip costs $0.125 and could finish inside 30 seconds; the docs still call sync the wrong tool for work that can last longer than 30 seconds, and most video work does. Write one code path (async plus polling) and use it for every length.

What a mistake costs

Watch the cost of mistakes: a 30 second clip is $1.875 at 480p, $3.75 at 720p and $7.50 at 1080p on Sume, so a duplicate submit at 1080p costs a real $7.50. The idempotency key and the status URL are what prevent it. The webhooks docs cover the push alternative, and the jobs docs cover polling.

Three ways people pay twice

Three mistakes account for most double bills with long clips. The first is treating a timed-out sync response as a failure and calling submit again with a new key. The second is a client library that retries a POST on a timeout without a stable idempotency key. The third is a cron job that submits a clip on every run without checking whether yesterday's job finished.

The fix for all three is the same: store the job id the moment you receive it, derive the idempotency key from the business intent (for example wan-ad-2026-10-05-es), and look the job up before you submit. If the job exists, poll it. If it is terminal, read the result.

Polling without hammering

Polling needs a stop condition and a back-off. Stop on completed, failed or canceled; the docs list those as terminal. Obey next_poll_after_seconds when present. Otherwise double your delay up to a ceiling such as 60 seconds. Reads have rate limits too, and the admission docs call them poll backpressure, so a tight loop that checks every second across fifty jobs invites a 429 rate_limited.

For a batch, track every job in one loop rather than one thread per job, and sleep once per round. Log the status per job so a stuck one is visible. The events_url shows the public timeline (created, queued, started, completed) when you need to see where a job is.

Sources: Jobs and results, Webhooks and the Video Router docs, for the 2 to 30 second range and the per-second rates.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume