Sume /v1/images returns 200 or 202: branch on the status code

A slow 4K or xhigh image request on Sume returns 202 with a job envelope, not the image body. A Python client that handles 200, 202 and 502 correctly.

5 min readSume
All posts

POST /v1/images waits up to 30 seconds. If the image is ready, you get 200 with data[].url. If it is not, you get 202 with a job envelope, and the image comes later from the job result endpoint. Branch on the status code, not on the shape of the body. Sizes and tiers that push you over the wait, such as 4K, high quality and large n, are the likely cause.

The three outcomes

The docs define the sync path as a route-level default of mode: "sync" and wait_timeout_seconds: 30. A terminal failure inside the wait comes back as 502 with the error envelope; the same code, message and next-action fields appear on the job status. A 202 is not an error.

Status codes from POST /v1/images (Sume docs, read 2026-10-07) (read 2026-10-07)
StatusMeaningWhat to do
200Image ready inside the waitRead data[].url and usage.cost
202Wait budget expired, or mode was async or webhookPoll status_url, then fetch result_url
502Generation failed inside the waitRead error.code and next_action; failed jobs are not billed
400Parameter the model does not listFix the request; do not retry unchanged

A client that handles all three

This script posts a request, returns URLs on 200, polls on 202 with backoff, and raises on anything else. It never resubmits the paid request just because a local timeout fired.

import os
import time
import requests

BASE = "https://api.sume.com"
HEAD = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}


def generate(body):
    r = requests.post(BASE + "/v1/images", headers=HEAD, json=body, timeout=45)
    if r.status_code == 200:
        return [d["url"] for d in r.json()["data"]]
    if r.status_code != 202:
        raise RuntimeError("status %s: %s" % (r.status_code, r.text[:300]))
    env = r.json()["data"]
    delay = 2
    while True:
        st = requests.get(env["status_url"], headers=HEAD, timeout=30).json()
        if st["data"]["terminal"]:
            break
        time.sleep(delay)
        delay = min(delay * 2, 20)
    return requests.get(env["result_url"], headers=HEAD, timeout=30).json()


print(generate({"model": "openai/gpt-image-2.5", "quality": "xhigh",
                "prompt": "studio photo of a red kettle", "resolution": "4K"}))

What to tighten in production

The loop stops when data.terminal is true in the status response. After it stops, check data.sume_status: only completed has images, while failed and canceled do not, and /result returns 409 job_not_completed until the result is ready. The job result keeps the generic job shape, not the image body, so parse result according to the Jobs and results page.

If you prefer not to poll, send mode: "webhook" with a public HTTPS webhook_url and verify the signed event on your side.

Not a progress stream

mode: "subscribe" is an alias of sync, a single bounded wait. It is not a progress feed. For progress, submit with mode: "async" and read GET /v1/jobs/{id}/events.

Testing the branch

Exercise all three paths before launch:

  • 200: a small low quality request.
  • 202: send mode: "async" and confirm you follow the envelope.
  • 400: send a parameter the model does not list and confirm you do not retry.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume