Poll a Sume image job in Python: obey next_poll_after_seconds
When POST /v1/images returns 202, keep polling the status_url and sleep for next_poll_after_seconds. A short Python loop that stops on a terminal status.

When POST /v1/images returns 202 instead of 200, the image is not ready and the body is a job envelope. Keep reading its status_url until the response says terminal, and sleep between reads for next_poll_after_seconds when it is present. If it is absent, back off. A fixed two second sleep is the wrong default, because the docs say that when the status payload asks for a longer time, that value wins.
When you get the 202
The Image API waits up to 30 seconds by default (wait_timeout_seconds), then returns 202 for slow work. The docs name 4K output, high quality and large n as the usual causes. The wait budget limits how long the HTTP request blocks, not how long the job takes. A timed-out wait is still a 2xx response with the job id, and it is not an admission failure.
Do not submit a new paid job for the same intent. If you must retry the submit itself, send the same Idempotency-Key again and you get the original job back.
| Status | Terminal | What to do |
|---|---|---|
| queued | no | Sleep, then read again |
| processing | no | Sleep, then read again |
| completed | yes | Read the result and download the files |
| failed | yes | Stop; read the error on the job record |
| canceled | yes | Stop |
The loop
The loop reads the status URL from the envelope, obeys next_poll_after_seconds, and caps total time. On a 429 it honors retry-after.
import os, time, requests
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def wait(status_url: str, limit: float = 300.0) -> dict:
start, delay = time.monotonic(), 2.0
while time.monotonic() - start < limit:
r = requests.get(status_url, headers=H, timeout=30)
if r.status_code == 429:
delay = float(r.headers.get("retry-after", delay))
else:
r.raise_for_status()
s = r.json()
if s.get("terminal"):
return s
delay = float(s.get("next_poll_after_seconds") or min(delay * 1.5, 15))
time.sleep(delay)
raise TimeoutError(status_url)Prefer a webhook for batches
Polling is fine for one image. For hundreds, use mode webhook and verify the signature on each terminal event, because every poll counts against your requests-per-minute budget. If a wave of 429s comes in, read whether the code is rate_limited or queue_full before you decide to sleep or to shrink the wave.
Once the status says completed, read GET /v1/jobs/{id}/result. That route answers 409 job_not_completed for any other state, so do not call it early.
Edge cases
If the job fails, read the error from the job record rather than from the status payload. If it is canceled, only the member whose key created the job can cancel it, so a different key sees 404 not_found on reads for jobs it did not create. Keep one key per service so polls and cancels use the same member, and log the job id with every submit so a crashed worker can resume polling instead of paying again.
Keep the total limit longer than your slowest case. A 4K high-quality batch can take minutes, and the loop above raises TimeoutError rather than looping forever.
Sources
Related posts
More in Developers
- Poll or callback for 1,000 video jobs: request counts at 30 seconds
At a 30-second poll, 1,000 video jobs make 4,000 to 20,000 status requests by job time. A callback_url cuts that to 1,000 deliveries plus a sweep.
- Poll /v1/jobs/{id}/status, then /result, in Node with backoff
A 25-line Node function that polls a Sume job on its own next_poll_after_seconds, reads /result only when the job completed, and keeps the id on timeout.
- 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.
- 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.
Written by Sume