Polling a 5-minute video job: 150 GETs at 2 s, or 10 at 30 s
A five-minute Sume video job costs 150 status reads at a 2 s interval and 10 at 30 s. Poll /v1/videos with a loop that handles every status.

For a video job that runs five minutes, polling every 2 seconds costs 150 status reads, and polling every 30 seconds costs 10. The Sume /v1/videos guide uses a 30-second interval and says video generation usually takes from 30 seconds to several minutes. So use the long interval, honor next_poll_after_seconds when a status response carries it, and stop on a terminal status.
The read counts
The arithmetic is job seconds divided by the interval. Multiply by the number of jobs to see the load on the read budget.
| Poll interval | Reads per job | Reads for 100 jobs |
|---|---|---|
| 2 s | 150 | 15,000 |
| 10 s | 30 | 3,000 |
| 30 s | 10 | 1,000 |
| 60 s | 5 | 500 |
Read the status vocabulary carefully
Sume documents status reads as poll backpressure, separate from generation concurrency, and says status and list endpoints can be rate limited. A spelling detail is easy to miss. The /v1/videos poll response uses pending, in_progress, completed, failed and cancelled with two Ls. The job endpoints under /v1/jobs use queued, processing, completed, failed and canceled with one L. A loop that checks for only one spelling never terminates on the other surface.
| Surface | Non-terminal | Terminal |
|---|---|---|
| GET /v1/videos/{id} | pending, in_progress | completed, failed, cancelled |
| GET /v1/jobs/{id}/status | queued, processing | completed, failed, canceled |
A loop that handles both
The loop below sleeps 30 seconds by default, treats either spelling of canceled as terminal, and downloads unsigned_urls[0] on success. A client-side deadline stops the wait without canceling the job, so store the job id.
import asyncio, os
import httpx
DONE = {"completed", "failed", "cancelled", "canceled"}
async def wait_video(poll_url: str, every: float = 30, limit: float = 1200):
key = os.environ.get("SUME_API_KEY", "")
if not key:
raise SystemExit("set SUME_API_KEY")
headers = {"Authorization": f"Bearer {key}"}
waited = 0.0
async with httpx.AsyncClient(headers=headers, timeout=30) as c:
while waited < limit:
r = await c.get(poll_url)
r.raise_for_status()
s = r.json()
if s["status"] in DONE:
return s
await asyncio.sleep(every)
waited += every
raise TimeoutError("still running; the job keeps billing, keep its id")
async def main():
s = await wait_video("https://api.sume.com/v1/videos/JOB_ID")
print(s["status"], (s.get("unsigned_urls") or [None])[0])
asyncio.run(main())Prefer a webhook for long work
If you run hundreds of jobs, send callback_url (HTTPS only) and keep a slow poll as a backup. The webhook carries the standard Sume job envelope, and a missed one can be recovered from the status URL.
Use the hint when the API gives one
Status responses on the job endpoints can carry next_poll_after_seconds. When it is present, obey it. When it is absent, back off. A fixed 30-second sleep is a fine default for video, and you can start shorter for image jobs, which often finish within the 30-second wait of a synchronous call. Do not poll many jobs in a tight loop, because status endpoints are rate limited and a 429 on a read is poll backpressure.
A deadline in your client does not cancel the job. The job keeps running and keeps billing, so store the id, and ask again later or cancel it explicitly if it has not started.
If you start a wave of jobs, poll them from one scheduler with a shared sleep, not from one task per job with its own tight loop. Or send callback_url on the submit and poll only the jobs that have no event after a safe delay. On the hosted MCP side, one jobs_wait call can take up to 20 job ids, but that is a different surface from the REST routes in this post.
Sources
Related posts
More in Developers
- Polling Omni jobs can't 429 your submits on Sume
Sume gives each API key separate read and write budgets: 120 writes and 4,800 reads a minute on Free, 1,200 and 48,000 on Scale. The math for an Omni batch.
- Port an OpenRouter video client to Sume: base URL, key, model ids
Sume's /v1/videos follows the OpenRouter video wire. Three edits move a client: base URL, API key, bare model id. The six differences that still bite.
- PowerShell 7: submit, poll and save a Sume video, no SDK
A Windows-friendly script for POST /v1/videos: Idempotency-Key, a 30-second poll on polling_url, and a download from unsigned_urls[0]. Fits in 14 lines.
- PowerShell 7: verify a Sume webhook with HMACSHA256, constant-time
A PowerShell function that refuses an empty secret, checks the 300-second window and accepts any entry in a rotated sume-v1 header. Under 30 lines.
Written by Sume