GPT Image 2.5 returns base64; Sume returns a URL: port the code

OpenAI's image API returns base64 data for GPT Image models, and Sume's returns hosted URLs. The three lines that change when you move a decoder over.

4 min readSume
All posts

If you move a GPT Image 2.5 integration from OpenAI's API to Sume's Image API, the response parsing is the first thing that breaks. OpenAI's guide says the GPT image models return base64-encoded image data (OpenAI image generation guide, read 2026-10-04), so existing code decodes a string and writes bytes. Sume returns data[].url, a hosted link to the file, plus a media_type, so the new code downloads instead of decoding.

The change is small, but three other differences travel with it: the model id, the hosted file's lifetime assumption, and the status-code split at 30 seconds. This post puts the old and new side by side and lists what to change.

Old response, new response

A Sume image response is {created, model, data: [{url, media_type}], usage: {..., cost}}. The model field echoes the id you sent, usage.cost is the USD billed to your wallet, and token counts are always 0 because images are metered per image (Sume Image API docs). On OpenAI's side you read the image payload from the response data and decode it; the table summarises where each piece lives.

Output format is output_format on Sume with png, jpeg or webp on the rows checked for this post. OpenAI's guide also lists a 0-100 compression setting; on Sume, output_compression is part of the schema but returns 400 unsupported_parameter today.

Where the image lives in OpenAI's image API and in Sume's, from the OpenAI guide and the Sume docs (read 2026-10-04)
ItemOpenAI (GPT Image models)Sume Image API
Image payloadbase64-encoded data you decodedata[].url hosted link you download
Format controloutput format and 0-100 compressionoutput_format; output_compression returns 400
Model idgpt-image-2.5-flare, gpt-image-2.5-sunburstopenai/gpt-image-2.5, openai/gpt-image-2.5-sunburst
Slow generationsOpenAI notes complex prompts can take up to 2 minutes202 with a job envelope after 30 seconds

The port, in one function

The function below saves the images from a Sume response, and handles the 202 path by returning the job URLs instead of pretending it has files. It keeps the file extension in line with media_type. It needs requests and a Sume key.

If you stream OpenAI results straight into a decoder, switch to a download step; the hosted files are served from media.sume.com, as in the docs' example response.

import os, requests

EXT = {"image/png": "png", "image/jpeg": "jpg", "image/webp": "webp"}

def generate(prompt: str, prefix: str = "out") -> list[str]:
    r = requests.post("https://api.sume.com/v1/images", timeout=120, json={
        "model": "openai/gpt-image-2.5", "prompt": prompt, "quality": "medium"},
        headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"})
    r.raise_for_status()
    if r.status_code == 202:
        job = r.json()["data"]
        raise RuntimeError(f"still running: poll {job['status_url']}")
    paths = []
    for i, item in enumerate(r.json()["data"]):
        path = f"{prefix}-{i}.{EXT.get(item['media_type'], 'bin')}"
        with open(path, "wb") as f:
            f.write(requests.get(item["url"], timeout=60).content)
        paths.append(path)
    return paths

print(generate("a paper boat on a pond, soft morning light"))

Checklist when porting

Rename the model id, replace the decoder with a download, treat 200 and 202 as two response shapes, and drop compression settings. For the full request on Sume, see GPT Image 2.5 on Sume and the Python example.

Do not send stream: true to Sume: it returns 400 streaming_not_supported, and usage.cost replaces any token-based cost tracking you had.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume