Runway wait_for_task_output vs a Sume poll loop in Python

Runway's SDK blocks in wait_for_task_output and raises TaskFailedError. On Sume's /v1/videos route you poll a URL yourself; a 10-second loop does it.

5 min readSume
All posts

Runway's SDK gives you one call that blocks until a task finishes, and Sume's /v1/videos route gives you a status you poll yourself. Runway's docs show wait_for_task_output() in Python and waitForTaskOutput() in Node, which block until the task completes and return the output URLs. A failed task raises TaskFailedError, with the details on error.taskDetails. On Sume you write the loop; it is about ten lines.

Where the two differ

Runway's helper hides the polling interval and the status check. The Sume route returns a polling_url on submit, and the job moves through pending, in_progress, completed, failed or cancelled. Your loop has to decide what to do with each terminal state, which is one extra branch compared with the Runway SDK: cancelled is a status on Sume, so a loop that only checks for failed will spin on a cancelled job.

Waiting on a finished video, Runway SDK vs Sume route (read 2026-10-09)
StepRunwaySume
SubmitSDK call to /v1/image_to_video or /v1/text_to_imagePOST /v1/videos
Waitwait_for_task_output() blocksyou poll polling_url
FailureTaskFailedError with taskDetailsstatus failed with an error field
Cancellednot shown on the page readstatus cancelled
Downloadoutput URLs on the taskGET /v1/videos/{id}/content

The loop

This runs as written with a key in SUME_API_KEY. It submits a 5-second Wan 3.0 clip at 720p, polls every 10 seconds, raises on failed or cancelled, then downloads the file. At the catalog rate of $0.125 per second at 720p, the clip reserves 5 x 0.125 = $0.625.

import os, time, requests

BASE = "https://api.sume.com/v1"
HEAD = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

def run(prompt: str) -> bytes:
    job = requests.post(f"{BASE}/videos", headers=HEAD, json={
        "model": "wan-3.0", "prompt": prompt, "duration": 5,
        "resolution": "720p", "aspect_ratio": "16:9"}, timeout=60).json()
    while True:
        s = requests.get(job["polling_url"], headers=HEAD, timeout=60).json()
        if s["status"] == "completed":
            break
        if s["status"] in ("failed", "cancelled"):
            raise RuntimeError(s.get("error", s["status"]))
        time.sleep(10)
    r = requests.get(f"{BASE}/videos/{job['id']}/content", headers=HEAD, timeout=120)
    r.raise_for_status()
    return r.content

if __name__ == "__main__":
    open("clip.mp4", "wb").write(run("A paper boat on a rain-soaked street"))

When to prefer a webhook

Polling every 10 seconds from a worker is fine for tens of jobs. For hundreds, send callback_url and let Sume call you; the video docs describe the signed webhook. Sume also accepts an Idempotency-Key on the video router, so a retried submit returns the original job rather than a second charge.

Testing the loop without spending money

Point the loop at a job that you cancel from the dashboard to confirm the cancelled branch raises, and at a deliberately invalid request to confirm the 400 error surfaces before the loop starts. Add a deadline too: a loop with while True and no time limit will wait forever if a job stalls, so a production version should stop after a fixed number of minutes and report the job id.

Keep the timeouts on each request, as the code does; a hung connection would otherwise freeze the worker.

What I could not confirm

Runway's docs page I read names the SDK helper and the error class but not the raw task-polling endpoint. The Sume loop uses the documented polling URL and content endpoint. All vendor figures were read on 2026-10-09 and are listed in the sources; Sume figures come from its public catalog and docs the same day.

Sources

Related posts

More in Comparisons

All Comparisons posts

Written by Sume