GPT Image 1 to GPT Image 2.5 on Sume: what changes in the output
Moving from GPT Image 1 to ChatGPT Image 2.5 on Sume changes the response (URL, not base64), default quality, size grid and failures.

Moving from GPT Image 1 to ChatGPT Image 2.5 on Sume changes five things in your code: the response carries a URL in data[].url and not base64 in b64_json, the default quality is high, sizes follow a short-side grid, the 30-second wait can return a 202 job envelope, and failed jobs are not billed. Prompts and the ratio field carry over; the model id becomes openai/gpt-image-2.5.
OpenAI's deprecations page is where the shutdown date for GPT Image 1 is announced; check it for the date that applies to you. This post covers the migration itself and says nothing about quality differences between models.
The five changes
Each row below is a change a migrating integration will meet. The right column says what to do.
If you store results in a database, add a column for the model id and tier next to each image. When prices or defaults change, you can then explain a cost movement from the data and not from memory.
| Area | What changes | What to do |
|---|---|---|
| Response body | data[].url instead of b64_json | Download from the URL; do not decode base64 |
| Default quality | high when quality is omitted | Set quality explicitly |
| Sizes | Aspect ratio plus the model's grid | Use aspect_ratio or a valid image_size |
| Slow requests | 202 job envelope after 30 seconds | Poll the job on 202 |
| Failures | Failed jobs are not billed | Retry without a refund workflow |
The response shape
Sume mirrors generated media and returns a URL. Code that did base64.b64decode(item['b64_json']) needs to fetch the URL instead. Download soon after the call and store the file in your own bucket if you need it later, since a URL is a link to a hosted copy and not a promise of permanence.
import os, requests
r = requests.post("https://api.sume.com/v1/images",
headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]},
json={"model": "openai/gpt-image-2.5", "prompt": "A lighthouse at dusk",
"aspect_ratio": "4:3", "quality": "medium"}, timeout=60)
body = r.json()
print(r.status_code)
if r.status_code == 200:
img = requests.get(body["data"][0]["url"], timeout=60)
open("out.png", "wb").write(img.content)Quality and cost
The default is the surprise. With quality omitted on ChatGPT Image 2.5, Sume uses high, which is $0.0659 per 1024 square image; medium is $0.0165. A migrated job that never set quality can cost four times what you expect. Set it to the tier you actually used on GPT Image 1.
Cost is per image, shown as usage.cost in the response, and is the billed amount.
A migration order that works
Do the changes in this order. First, swap the model id behind a config flag and set quality to the tier you used before. Second, change the response handler to read the URL and handle 202. Third, replace any fixed pixel sizes with an aspect_ratio unless you need exact pixels. Fourth, run a small batch on both models and compare the usage.cost totals with the price you expect. Only then move traffic.
Keep the old path reachable until the shutdown date on OpenAI's deprecations page has passed and you are sure nothing depends on it. GPT Image 1 is not in Sume's image catalog, so on Sume the migration is a one-way change of id.
Sizes and the 202 case
ChatGPT Image 2.5 sizes are multiples of 16 on a short-side grid, so a custom image_size has to land on it; use aspect_ratio when you do not need an exact size. If a call takes longer than 30 seconds, you get a 202 job envelope. Treat 202 as a normal result and poll the job; a code path that only handles 200 will drop slow images.
Failed jobs are not billed, so a retry loop does not pay twice. Cap the retries anyway, and log the model id and quality with each result so a later cost review can group by them.
Finally, test with a few real prompts at the ratio you ship. Check that the file you download opens, that the dimensions match your layout, and that usage.cost agrees with the table in this post. Those three checks catch nearly every migration surprise.
One more habit helps with both. Log the model id, tier and billed cost with every finished job, then review the log weekly for a mismatch between the tier you meant and the cost you paid. A mismatch is the earliest sign of a missing field or an unintended default.
Sources
Related posts
More in Developers
- What is a partial transcript in streaming speech to text?
A partial is a provisional transcript a streaming model revises as audio arrives. Why subtitles for a finished clip only need final text and word times.
- What to show a viewer while an avatar video job is queued
Avatar jobs on Sume are async: queued, processing, then completed, failed or canceled. A status-to-UI map for waiting screens, with polling rules.
- When is async TTS the right choice? Sync wait, poll or webhook
Async TTS is right for voiceovers, batches and anything a person is not watching a spinner for. Sume's sync wait stops at 30 seconds; Flash claims 45 ms.
- Which Sume API calls are safe to retry blindly, and which need a key?
Reads, cancels and redelivers retry safely; paid submits retry only under the same Idempotency-Key. A call-by-call table, plus the codes that mean wait or stop.
Written by Sume