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.

5 min readSume
All posts

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
        raise

Which errors count

Image API outcomes and what to do, per Sume image docs read 2026-10-06
ResponseMeaningCounts toward the breaker
200Images in data[].urlNo, reset the counter
202Job envelope, still runningNo, poll status_url
502Terminal failure inside the wait budgetYes
400Parameter the model does not listNo, fix the request
404 model_not_foundId unknown to SumeNo, fix the id
409 idempotency_conflictSame key, different payloadNo, 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

All Developers posts

Written by Sume