GPT-6 Sol & Claude Opus 5.5 are liveGPT-6 Sol at half the price of 5.6 Sol

GPT Image 2.5 API: Generate and Edit Images in Python

One POST creates the task and one GET reads it back. Here is the request code, the sizes we ran on October 29, 2026, and one edit shown before and after.

Kiki, OmniaKey COO7 min read
On this page

Send one POST to https://api.omniakey.com/v1/media/tasks with model set to gpt-image-2.5-flare, poll the task id that comes back, and download assets[0].url once the status reads succeeded. On October 29, 2026 we generated at 1K, 2K and 1536x1024 and edited one café photo, then opened every file and read the real pixel size from it: 1024×1024, 2048×2048, 1536×1024 and 1024×682. The 1K generation took 29.5 s and the 2K one 45.2 s.

Which 2.5 model id to use

Use gpt-image-2.5-flare. That is the id we called in every run below. The GPT-Image 2.5 Flare model page (checked October 29, 2026) names OpenAI as the provider, lists text to image and image edit, and carries the note "Guide updated 2026-10-27". The same page gives a second 2.5 id, gpt-image-2.5-sunburst.

Flare handles both jobs. Generate sends a prompt; edit sends a prompt plus one source image. Quality takes low, medium or high, and the same page says the level does not change the price. Every run here used medium. We did not run sunburst, quality low or high, 4K or 3840x2160, masks, or multiple reference images, so plan around Flare at medium with a single source image until you test the rest yourself.

The request code

The script below is the one we ran, unchanged: two helpers, then one generate call and one edit call. It uses only the Python standard library, reads your key from OMNIAKEY_API_KEY, and writes each PNG into the folder you run it from.

python
import base64
import json
import os
import time
import urllib.request
import uuid

API = "https://api.omniakey.com/v1/media/tasks"
KEY = os.environ["OMNIAKEY_API_KEY"]
MODEL = "gpt-image-2.5-flare"

def request(method, url, body=None, extra_headers=None):
    headers = {"Authorization": f"Bearer {KEY}", "User-Agent": "omniakey-example/1"}
    data = None
    if body is not None:
        headers["Content-Type"] = "application/json"
        data = json.dumps(body).encode()
    headers.update(extra_headers or {})
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    with urllib.request.urlopen(req, timeout=120) as resp:
        return json.load(resp)

def run_task(body, out_path):
    task = request("POST", API, body, {"Idempotency-Key": str(uuid.uuid4())})["id"]
    for _ in range(150):
        time.sleep(4)
        state = request("GET", f"{API}/{task}")
        if state["status"] not in ("queued", "processing"):
            break
    if state["status"] != "succeeded":
        raise RuntimeError(state.get("error") or state["status"])
    asset_url = state["assets"][0]["url"]  # signed link: valid for about an hour, so download now
    req = urllib.request.Request(asset_url, headers={"User-Agent": "omniakey-example/1"})
    with urllib.request.urlopen(req, timeout=120) as resp, open(out_path, "wb") as f:
        f.write(resp.read())
    return task

def generate_image(prompt, out_path, size="1K", quality="medium"):
    body = {
        "model": MODEL,
        "type": "image",
        "operation": "generate",
        "input": {"prompt": prompt},
        "parameters": {"size": size, "quality": quality},
    }
    return run_task(body, out_path)

def edit_image(source_path, prompt, out_path, size="1K", quality="medium"):
    with open(source_path, "rb") as f:
        image = base64.b64encode(f.read()).decode()
    body = {
        "model": MODEL,
        "type": "image",
        "operation": "edit",
        "input": {"prompt": prompt, "image": {"data": image, "mime_type": "image/jpeg"}},
        "parameters": {"size": size, "quality": quality},
    }
    return run_task(body, out_path)

if __name__ == "__main__":
    task = generate_image(
        "A small glass greenhouse on a hillside at sunrise, morning fog in the valley, "
        "soft watercolor style, a wooden bench outside the door, no people, no text.",
        "greenhouse.png",
    )
    print("generate task", task)
    task = edit_image(
        "cafe-base.jpg",
        "Change the red bicycle to matte black. Keep everything else exactly the same: "
        "the table, the cup, the window frame and the daylight. Photorealistic, no text, no logos.",
        "cafe-edited.png",
    )
    print("edit task", task)

Authorization: Bearer <key> goes on every call, and the create call also needs an Idempotency-Key, which the script fills with a fresh UUID. The body sets type to "image", operation to "generate" or "edit", the words under input.prompt, and the size and quality under parameters. The API also accepts n, and only 1 is valid, so the script leaves it out. A create returns HTTP 202 with a task id, the status starts as queued, and the loop waits 4 s between checks until it leaves the two working states.

An edit adds the JPEG under input.image as an inline Base64 object with its mime type. Request bodies, Base64 included, are capped at 1 MiB, so the script sends a 1024-pixel JPEG of about 230 KB.

The sizes we ran, and what came back

Four runs, all at quality medium, on October 29, 2026. Three came back at the size we asked for. The 1K edit did not, in one of its two runs.

  • 1K, generate — Pixel size we read from the file: 1024×1024; Date: October 29, 2026
  • 2K, generate — Pixel size we read from the file: 2048×2048; Date: October 29, 2026
  • 1536x1024, generate — Pixel size we read from the file: 1536×1024; Date: October 29, 2026
  • 1K, edit — Pixel size we read from the file: 1024×682; Date: October 29, 2026

The greenhouse below is a 1K generation from the same request body as the code above.

Watercolor glass greenhouse on a hillside at sunrise, with fog in the valley and a wooden bench by the door.
Generated with GPT-Image 2.5 Flare via the OmniaKey API on October 29, 2026, size 1K, quality medium.

Two things to take from the list. First, read the size off the file you downloaded. The 1K edit asked for a square and came back at 1024×682, the shape of the 1024×683 JPEG we sent, and another run of that same edit at 1K came back at 1024×1024. Second, the size list in the image API docs (checked October 29, 2026) offers auto, 1K, 2K, 4K and 3840x2160 for this model, and says it refuses off-list sizes before any charge; our 1536x1024 call on October 29, 2026 was not refused and returned 1536×1024. Check the real file before you build a layout around one size.

An edit keeps the scene and changes one thing

An edit answers two questions: what changes, and what stays. We asked for one change and named the things that had to survive.

Prompt: "Change the red bicycle to matte black. Keep everything else exactly the same: the table, the cup, the window frame and the daylight. Photorealistic, no text, no logos."

Left: a café window with a red bicycle outside. Right: the same window after the edit, with the bicycle now dark.
Left: the source photo we sent. Right: the output of one edit on October 29, 2026, size 1K, quality medium. Only the bicycle was asked to change; the table, the cup and the plant are still in place.

The bicycle came back dark, and the table, the cup, the plant and the window frame were still in the frame. The source was a 1024×683 JPEG, the output measured 1024×682, and the run took 38.0 s. Name what changes and what stays in every prompt. For prompt structure in general, see our GPT Image 2 prompt guide.

Errors and limits

Two headers do the work before any pixels exist: the bearer token and a unique Idempotency-Key. Get them right and the rest of the call is simple to retry.

What you sendWhat comes back
Create call with no key400 missing_idempotency_key
Same key, same body, twice200 with the existing task, nothing new charged
Same key, changed body409 idempotency_key_reused

The key can be up to 255 characters, per the docs checked October 29, 2026. A reuse with the same body is the safe retry: you get the existing task and nothing new is charged. Keep the body under 1 MiB including Base64, so resize or compress the source before you send it.

When a task succeeds, assets[0].url is a signed link that lasts about an hour, per the docs, so download at once or poll the same task again for a fresh link. Task records stay for 24 hours.

One header surprised us on October 29, 2026: a POST sent with Python's default urllib User-Agent returned HTTP 403 with the body "error code: 1010" from Cloudflare. The same POST with our own User-Agent: omniakey-example/1 header reached the API, which answered 400 invalid_request for the empty body we sent, and nothing was charged. The docs name only Authorization and Idempotency-Key, so set a User-Agent in your client.

GPT Image 2.5 or GPT Image 2 for API work?

Flare is the 2.5 id we tested. GPT Image 2, on its GPT Image 2 model page, uses the id gpt-image-2. If a script already produces what you need on that id, there is no reason to change it.

For a photo that must keep its scene while one object changes, Flare at medium is what our October 29, 2026 runs cover: three generated sizes and the 1024×682 edit. The GPT Image 2.5 vs GPT Image 2 comparison has sizes and same-prompt results for both models, so you can pick with numbers in front of you. Current rates are on the pricing page.

FAQ

Which model id should I use, gpt-image-2.5-flare or gpt-image-2.5-sunburst?

We ran Flare in every run above, and its edit kept the scene we asked to keep. The model page lists Sunburst as the second 2.5 id: start with Flare, and put Sunburst through your own hardest edit before you switch.

Can I ask for a landscape size such as 1536x1024?

On October 29, 2026 a 1536x1024 generation came back at 1536×1024. The size list in the image API docs does not include that value for this model, so read the file and check the docs before you rely on it in production.

Why did my edit come back in a different size from the one I asked for?

The same 1K edit came back 1024×682 in one run and 1024×1024 in another, so treat the size you ask for as a request, not a promise. Open the PNG and read its width and height before you place it.

Do I need a User-Agent header?

On October 29, 2026 a request with Python's default User-Agent got HTTP 403 with Cloudflare error 1010. The same request with our own User-Agent header reached the API, so send one.

The docs say the signed link lasts about an hour. Poll the task again for a fresh link, and download the file as soon as the task succeeds.

Models in this article

GPT Image 2.5image APIPythonimage editingAPI

Related articles

Recent posts