Swap the AI image model without a redeploy: JSON config hot reload

Read the Sume image model id from a JSON file that reloads when it changes, so a gpt-image-1 shutdown fix is a one-line edit with no deploy. Python, stdlib.

5 min readSume
All posts

Yes, you can swap the image model without a redeploy: keep the model id in a small JSON file, re-read it when the file's modified time changes, and pass it as the model field of POST /v1/images. OpenAI's deprecations page lists gpt-image-1 for shutdown on October 23, 2026, and gpt-image-1-mini, gpt-image-1.5 and chatgpt-image-latest for December 1, 2026. Both name gpt-image-2.5-sunburst or gpt-image-2.5-flare as the replacement.

The point of the file is that the model id stops being a code constant. On Sume the id is a request field, so the whole migration is one string: openai/gpt-image-2.5-sunburst instead of the retired id.

What goes in the file?

One key is enough. Keep the id in the canonical org/slug form that GET /v1/images/models returns, so the file and the catalog use the same spelling. Sume also accepts bare legacy aliases such as gpt-image-2.5, but the canonical form diffs cleanly against the catalog.

Image model ids for the config file, read 2026-10-06
Config valueSume idNotes
Defaultopenai/gpt-image-2.5-sunburstChatGPT Image 2.5, up to 16 input references, mask_url, background
Same familyopenai/gpt-image-2.5The Flare variant, same limits as Sunburst
Cheaper draftgoogle/nano-banana-2Different prompt style, test before switching

The reload code

The reader below checks the file's modified time on each call. A call that fails after a bad edit is a plain Python error, so validate the value before you save it. A 200 response has data as a list of images. A 202 response is the job envelope, so branch on the status code, not the body.

import json, os, urllib.request

CONFIG = "image-model.json"  # {"model": "openai/gpt-image-2.5-sunburst"}
_cache = {"mtime": 0.0, "model": None}

def current_model():
    mtime = os.path.getmtime(CONFIG)
    if mtime != _cache["mtime"]:
        with open(CONFIG) as f:
            _cache.update(mtime=mtime, model=json.load(f)["model"])
    return _cache["model"]

def call(model, prompt):
    body = json.dumps({"model": model, "prompt": prompt}).encode()
    req = urllib.request.Request("https://api.sume.com/v1/images", body, {
        "Authorization": "Bearer " + os.environ["SUME_API_KEY"],
        "Content-Type": "application/json"})
    with urllib.request.urlopen(req, timeout=60) as r:
        return r.status, json.load(r)

if __name__ == "__main__":
    if not os.path.exists(CONFIG):
        json.dump({"model": "openai/gpt-image-2.5-sunburst"}, open(CONFIG, "w"))
    status, out = call(current_model(), "a ceramic mug on linen")
    print(status, out["data"][0]["url"] if status == 200 else out["data"]["status_url"])

What a config swap does not fix

A config swap changes the id, not the parameters. If your old requests sent output_compression, seed or stream, Sume answers 400 unsupported_parameter or streaming_not_supported, because it rejects fields a model does not list instead of dropping them. Send one test render per model before you edit the production file, and keep the scan from the repo-scan post in CI so a hard-coded id cannot come back.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume