Seedance 2.5 30-second job: set a 20-minute client deadline in Python

A 30-second Seedance 2.5 job outlives any HTTP timeout. Submit async to Sume, poll with next_poll_after_seconds, and set the deadline in your client.

5 min readSume
All posts

Set the long timeout in your own client, not on the HTTP request. Submit the Seedance 2.5 job with mode: "async", then poll GET /v1/jobs/{id}/status until terminal is true, with a deadline of about 20 minutes that your code enforces.

Sume's docs state the reason plainly: wait_timeout_seconds is clamped to 0-30 and limits how long the request blocks, not how long the job can take. Video jobs usually do not finish inside that window, so the loop below lives in your process.

Why 20 minutes

The TypeScript SDK's waitForJob defaults to 20 minutes for video and avatar-video jobs, and the docs call that reasonable for video. It is a ceiling, not an estimate. Most clips finish sooner, but a queued job on a busy workspace waits for a concurrency slot before it even starts.

A deadline that expires does not cancel anything. The job continues and still bills, so store the job id and read it later, or cancel it before generation starts.

Time limits that apply to one Seedance 2.5 submit (read 2026-10-07)
LimitValueWhere it applies
Seedance 2.5 clip length4-30 secondsRequest validation
sync/subscribe wait0-30 seconds, clampedHTTP request on submit
Poll cadencenext_poll_after_seconds when presentYour loop
SDK waitForJob default20 minutesClient side

The loop

Read data.next_poll_after_seconds from the status response and use it as the sleep when it is not null. The fallback is a doubling backoff capped at 30 seconds.

import os, time, requests
BASE = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
r = requests.post(f"{BASE}/v1/video-router/generate", timeout=30,
    headers={**H, "Idempotency-Key": "mug-orbit-001"},
    json={"model": "seedance-2.5", "prompt": "Slow orbit around a mug",
          "duration": 30, "resolution": "720p", "mode": "async"})
r.raise_for_status()
job_id = r.json()["data"]["request_id"]
deadline, delay = time.time() + 20 * 60, 2
while time.time() < deadline:
    s = requests.get(f"{BASE}/v1/jobs/{job_id}/status", headers=H, timeout=30)
    s.raise_for_status()
    d = s.json()["data"]
    if d["terminal"]:
        break
    time.sleep(d["next_poll_after_seconds"] or delay)
    delay = min(delay * 2, 30)
else:
    raise SystemExit(f"still running, keep {job_id} and look again later")
print(d["sume_status"], d["result_ready"])

After the loop ends

sume_status is completed, failed, or canceled. Only a completed job has a result; for the others GET /v1/jobs/{id}/result answers 409 job_not_completed, so read the failure from GET /v1/jobs/{id} instead.

If you use TypeScript, waitForJob from @sume-com/sdk is this same loop with the 20-minute default.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume