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.

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.
| Mode | Server holds the request | Events or SSE | Next step |
|---|---|---|---|
| async | No | No | Poll status_url |
| sync | Up to 30 s | No | Read result or poll |
| subscribe | Up to 30 s (alias of sync) | No | Same as sync |
| webhook | No | Terminal callback only | Verify 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,failedandcanceled. - 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
- Model calls image-generations_create? Use generate_image
New LLMs may emit old Sume tool names. The hosted server maps dots to underscores and documents two retired aliases that now point at new names.
- 'model_params has an empty allowlist': omit it or send {}
Video Router v1 has no pass-through knobs. model_params with any key gets a 400; {} is fine. /v1/videos refuses provider.options the same way. What to change.
- Move an edit call from /v1/image-1.0/generate to POST /v1/images
Field map for moving an image edit call from the retiring Image 1.0 URL to POST /v1/images: image_urls becomes input_references, num_images becomes n.
- Move from polling to Sume webhooks in three steps, keeping the poll
Add webhook_url to submits, verify sume-v1 signatures, dedupe on job_id, and keep a slow poll for canceled and skipped runs. Roll it out one route at a time.
Written by Sume