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.

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.
| Config value | Sume id | Notes |
|---|---|---|
| Default | openai/gpt-image-2.5-sunburst | ChatGPT Image 2.5, up to 16 input references, mask_url, background |
| Same family | openai/gpt-image-2.5 | The Flare variant, same limits as Sunburst |
| Cheaper draft | google/nano-banana-2 | Different 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
- Test a Sume poll loop without waiting: inject sleep, assert delays
Unit test a job poll loop in milliseconds by injecting the fetch and the sleep. Assert that next_poll_after_seconds is obeyed and the 20-minute deadline holds.
- Text-to-speech API with curl and jq: one shell script to an MP3
Call the Sume TTS Router from a shell: submit with curl, loop on the status URL with jq until terminal, then download the audio artifact to voiceover.mp3.
- Sume Timeline output: H.264, AAC 192k and 1-second keyframes
What a Timeline 1.0 MP4 contains for a Reel, Short or TikTok upload: libx264 CRF 20, yuv420p, AAC 192k, faststart, 1-second keyframes, and what you cannot set.
- Timeline 400 on codec, crf or ffmpeg_args: what a Reel spec can send
Sume Timeline refuses codec, crf, preset, filter_complex and ffmpeg_args with a 400. Which output fields it does accept for a 9:16 Reel or Short and why.
Written by Sume