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.

5 min readSume
All posts

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.

Job statuses, read from the Sume docs 2026-10-08
StatusTerminalWhat to do
queuednoSleep, then read again
processingnoSleep, then read again
completedyesRead the result and download the files
failedyesStop; read the error on the job record
canceledyesStop

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

All Developers posts

Written by Sume