Runway polls at 5s with jitter; Sume gives next_poll_after_seconds
Runway says poll at 5 seconds or more with jitter and backoff. Sume returns next_poll_after_seconds, so your loop can obey the server instead of guessing.

Runway's guidance for manual polling is to poll no faster than every 5 seconds, add jitter, and back off exponentially. Sume's status endpoint adds one thing: when it has an opinion, it tells you the next poll time in next_poll_after_seconds, and the docs say to obey it. When it is absent, use exponential backoff. So the client logic is one line longer, and the guess goes away.
The two polling contracts
Runway's SDK page also lists a waitForTaskOutput helper with a 10 minute default timeout, a TaskFailedError, and the statuses PENDING, SUCCEEDED, CANCELED and FAILED. Sume's job statuses are queued, processing, completed, failed and canceled, and the status response carries booleans so you do not need to compare strings.
| Item | Runway | Sume |
|---|---|---|
| Interval hint | 5s or more, with jitter and exponential backoff (client's choice) | next_poll_after_seconds in the status payload; backoff when absent |
| Stop condition | Task status SUCCEEDED, FAILED or CANCELED | terminal: true; fetch the result when result_ready: true |
| Helper timeout | waitForTaskOutput default 10 minutes | waitForJob default 20 minutes, 2s poll floor |
| Failure | TaskFailedError | Status failed; read the error from GET /v1/jobs/{id} |
A Python loop that follows the server
This sketch polls GET /v1/jobs/{id}/status. It uses the server's hint when present and doubles a local delay (capped at 30 seconds) when not. The overall deadline lives in your client: a client timeout does not cancel the job, so store the job id and resume later.
import asyncio, os
import httpx
async def wait_for_job(job_id: str, deadline_s: float = 1200.0) -> dict:
headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
loop = asyncio.get_running_loop()
end = loop.time() + deadline_s
backoff = 2.0
async with httpx.AsyncClient(base_url="https://api.sume.com/v1", headers=headers) as client:
while loop.time() < end:
r = await client.get(f"/jobs/{job_id}/status")
r.raise_for_status()
body = r.json()
s = body.get("data", body)
if s.get("terminal"):
return s
hint = s.get("next_poll_after_seconds")
delay = float(hint) if hint else backoff
backoff = min(backoff * 2, 30.0)
await asyncio.sleep(delay)
raise TimeoutError(f"{job_id} not terminal after {deadline_s}s")
async def main():
print(await wait_for_job(os.environ["SUME_JOB_ID"]))
asyncio.run(main())Rules that carry over from Runway
- Add jitter if many workers poll the same account, as Runway recommends. Sume's docs say to obey the hint when it is present, so jitter only the fallback backoff.
- Prefer a webhook for long work and keep polling as a backup. Sume job webhooks are terminal-only (
job.completed,job.failed,job.canceled). - Never resubmit a paid job because a poll loop timed out. Use the same
Idempotency-Keyif you must retry the submit itself.
Worked example: how many polls a ten-minute job costs
A fixed 5 second interval over a 10 minute (600 second) wait is 600 / 5 = 120 status reads for one job. Ten jobs polled together would be 1,200 reads. Sume documents that read, status and list endpoints can also have rate limits, and tells you to treat those as poll backpressure, so a tight loop across many jobs is the wrong default. Letting the server's next_poll_after_seconds stretch the interval lowers the count without you picking a number, and a webhook lowers it to near zero.
Whichever vendor you poll, use one shared scheduler for many jobs rather than one loop each. Group ids that share a deadline, space reads with jitter, and stop on the first terminal state. Do not read the result until the status says it is ready.
Sources
Related posts
More in Developers
- One Idempotency-Key for an Omni 360p draft and 720p final: it 409s
Reusing the draft's Idempotency-Key on the 720p final returns 409 idempotency_conflict. Key naming that keeps draft and final jobs apart, with a Python helper.
- Save a 30-second Wan 3.0 clip to Cloudflare R2 from Node
Fetch a finished Wan 3.0 render from Sume and write it to Cloudflare R2 with the AWS S3 client. Shows the R2 endpoint config and the 30 s price at three tiers.
- script_run for one TTS clip per sentence: limits to set first
Fan out one tts_create per sentence inside Sume script_run, with timeout_seconds, max_calls and max_paid_calls set, then wait on the child jobs with jobs_wait.
- script_run error script_tool_forbidden: discovery calls belong outside
script_tool_forbidden means a Sume script called a discovery tool (tools_list, tools_schema, mcp_health, search_tools) or script_run. Call them from the turn.
Written by Sume