mode subscribe on Sume images is a 30-second wait: poll instead

On Sume, mode subscribe is an alias of sync with one wait of at most 30 seconds and no events. For a longer wait, submit async and poll status in your client.

5 min readSume
All posts

On Sume, mode: "subscribe" is not a live event stream. It is an alias of sync: the server holds one request for at most 30 seconds, then answers with the result or a job envelope. For a wait that can run longer, submit with mode: "async" and poll GET /v1/jobs/{id}/status from your own client until terminal is true, then read the result.

What each mode does

The jobs docs list four modes. async returns a 202 job envelope straight away. sync and subscribe block for up to wait_timeout_seconds, capped at 30, and a client timeout does not cancel the job. webhook returns 202 and sends a terminal callback. On POST /v1/images the default is sync with a 30 second wait, not async.

If a sync wait ends before the job does, the envelope carries status_url, result_url, events_url and a sync object where timed_out is true. Poll the status URL and do not resubmit, because a second create is a second paid job.

Wait modes on Sume (read 2026-10-05)
ModeServer holds the requestEvents or SSENext step
asyncNoNoPoll status_url
syncUp to 30 sNoRead result or poll
subscribeUp to 30 s (alias of sync)NoSame as sync
webhookNoTerminal callback onlyVerify signature, poll as backup

A client-side subscribe loop

Submit async, then poll. Obey next_poll_after_seconds when it is present, stop on terminal, and fetch the result only when result_ready is true.

import os, time, requests
B = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

def main():
    body = {"model": "openai/gpt-image-2.5", "mode": "async",
            "prompt": "Wide teal banner with a white paper plane",
            "image_size": "1920x640", "quality": "high"}
    job = requests.post(f"{B}/v1/images", headers=H, json=body, timeout=60)
    job.raise_for_status()
    status_url = job.json()["status_url"]
    while True:
        s = requests.get(status_url, headers=H, timeout=30).json()
        if s["terminal"]:
            break
        time.sleep(s.get("next_poll_after_seconds") or 2)
    if s["sume_status"] != "completed":
        raise SystemExit(f"job ended as {s['sume_status']}")
    res = requests.get(job.json()["result_url"], headers=H, timeout=30)
    print(res.json())

main()

Limits to plan for

Cap your own loop, for example at ten minutes, and surface the job id to the user so support can find the job.

  • Status values are queued, processing, completed, failed and canceled.
  • Failed jobs are not billed, and an expired wait is not a failure.
  • The status poll returns no percentage, so show a state, not a bar.

Which mode to pick

Use the default sync wait for a small, fast request such as a 1024 square at medium quality, where the answer usually lands inside 30 seconds and one call is simplest. Use async plus polling for 4K, high or max quality, large n and any batch, because those fall back to a 202 anyway and the poll loop then becomes the only path. Use webhook when a server of yours, not a browser, should be told the moment the job ends. In every case, keep the job id the first response returns, since it is how you recover after a dropped connection.

One more rule from the jobs docs: when a wait comes back not terminal, poll and do not resubmit. A second create is a second paid job, and a retry with the same idempotency key is the safe way to repeat a failed network call.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume