Fall back to a second image model after three 502s: a Python breaker
Sume returns 502 when an image job fails inside the wait budget. Count them, switch to a second model id after three, and use a new idempotency key per model.

Count consecutive 502 responses from POST /v1/images, and after three switch to a second Sume model id. Sume returns 502 when a job reaches a terminal failure inside the sync wait; a slow job returns 202 and is not a failure. A swap is a model-id string, so the fallback is a list of ids and an index.
Use a separate Idempotency-Key per model. The same key with a changed payload returns 409 idempotency_conflict, so a key that contains the model id keeps the retry legal.
The breaker
The sample is sticky on purpose: once it moves to the fallback it stays there until a human resets it, so a flapping provider cannot bounce traffic between two styles every few seconds.
import json, os, urllib.error, urllib.request
MODELS = ["openai/gpt-image-2.5-sunburst", "google/nano-banana-2"]
state = {"i": 0, "fails": 0}
def post(model, prompt, key):
req = urllib.request.Request(
"https://api.sume.com/v1/images",
json.dumps({"model": model, "prompt": prompt}).encode(),
{"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json", "Idempotency-Key": key})
with urllib.request.urlopen(req, timeout=60) as r:
return r.status, json.load(r)
def render(prompt, job_id):
model = MODELS[state["i"]]
try:
out = post(model, prompt, f"{job_id}:{model}")
state["fails"] = 0
return out
except urllib.error.HTTPError as err:
if err.code == 502:
state["fails"] += 1
if state["fails"] >= 3 and state["i"] < len(MODELS) - 1:
state["i"] += 1
state["fails"] = 0
raiseWhich errors count
| Response | Meaning | Counts toward the breaker |
|---|---|---|
| 200 | Images in data[].url | No, reset the counter |
| 202 | Job envelope, still running | No, poll status_url |
| 502 | Terminal failure inside the wait budget | Yes |
| 400 | Parameter the model does not list | No, fix the request |
| 404 model_not_found | Id unknown to Sume | No, fix the id |
| 409 idempotency_conflict | Same key, different payload | No, change the key |
What the fallback must not hide
A fallback changes the look of the output. Log the model id with each result, and alert when the index moves, so the switch is a decision someone made. Do not use sume/auto as the fallback if you need to know the model: it never discloses it, and job.model stays sume/auto. See the model-map post for the config side.
Sources
Related posts
More in Developers
- Gemini CLI and the hosted Sume server: do not rely on env in headers
Add the hosted Sume server to Gemini CLI with httpUrl and a bearer header. Gemini expands env vars only in the env block; set a timeout above jobs_wait.
- Get the video URL from a Sume webhook: pick the artifact by type
A Sume job.completed payload lists artifacts with id, url, type and content_type. Select the video by content_type, not array index. TypeScript for Node.
- Go net/http client for Sume images: handle 200 and 202 on a model swap
A Go program that posts to Sume /v1/images with the model id from an env var and branches on 200 versus 202, so a gpt-image-1 swap is not a rebuild.
- gpt-image-1 returns 404 model_not_found on Sume: which id to send
gpt-image-1, gpt-image-1.5 or gemini-2.5-flash-image sent to Sume /v1/images return 404 model_not_found. Ids to send instead, plus a lookup.
Written by Sume