Skip to content

OpenAI Image API Tutorial: Images API vs Responses API

For one-shot images, call the Images API with gpt-image-2.5-flare. In Responses, set the tool model: it defaults to gpt-image-1, which retires October 23, 2026.

•••12 min read•API Guides
Isometric blocks comparing the Images API, which uses one model, with the Responses API, which uses a mainline model plus an image model

If you need to generate or edit an image from one prompt, call the Images API (/v1/images/generations or /v1/images/edits) and pass gpt-image-2.5-flare or gpt-image-2.5-sunburst as the model. If the image lives inside a conversation, where users ask for a picture and then say "make it warmer," use the Responses API with the built-in image_generation tool. That split is OpenAI's own rule in its image generation guide.

Two details matter more than the choice itself. First, name the image model explicitly on both paths. The Responses tool's model defaults to gpt-image-1, which shuts down on October 23, 2026. The Images API's model defaults to dall-e-2, which was removed on May 12, 2026. Second, GPT Image models return base64, never a URL. On the Images API it is in data[0].b64_json. In Responses it is the result field of an image_generation_call output item.

Side-by-side requests: the Images API puts gpt-image-2.5-flare in the top-level model field and returns base64 in b64_json, while the Responses API puts it inside the image_generation tool and returns base64 in item.result. Leaving the model out falls back to dall-e-2 or gpt-image-1

Everything below reflects OpenAI's documentation as of September 25, 2026. The code follows the documented request and response shapes. It is written for you to run with your own key, and none of it is presented as the output of a real call.

Images API or Responses API: pick by how the image is used

Images APIResponses API + image_generation tool
Best fitOne prompt in, one image (or a few) out: generate or editChat-style products where users refine an image over several turns, or flows that mix images with other tools
Where the image model goesTop-level model, for example gpt-image-2.5-flareTop-level model is a mainline text model. The image model goes inside the tool: {"type": "image_generation", "model": "gpt-image-2.5-flare"}
Where the image comes backdata[i].b64_jsonoutput items with type == "image_generation_call", field result
Several images per requestn from 1 to 10No n parameter. The model decides how many times to call the tool
Follow-up editsNew request to /v1/images/edits with the image attachedprevious_response_id, or the earlier image_generation_call ID as an input item
What you pay forImage tokens (text input, image input, image output)The same image tokens plus the mainline model's own tokens

The Responses path adds two things the Images API lacks, according to OpenAI: multi-turn editing inside a conversation, and image inputs passed as File IDs alongside URLs or base64 data. File IDs alone don't force you onto Responses. The edits endpoint also accepts a JSON images array with file_id or image_url entries.

A few situations change the default:

  • Bulk or offline jobs. Stay on the Images API. It returns up to 10 images per request with n. If you need the Batch API, gpt-image-2 is the only current GPT Image model that supports it. The 2.5 models don't.
  • You already run an assistant on Responses. Add the tool there instead of making a separate Images API call. The mainline model can decide when a picture is needed, and it rewrites your prompt before the image model sees it.
  • You only need a fixed product image or a one-shot edit. The Responses path adds a second model, more output to parse, and extra text tokens without improving the image. Use the Images API.
  • You're still on Chat Completions. It can't call OpenAI's hosted tools natively, so you either move that flow to Responses or call the Images API separately. Chat Completions vs Responses API: Which One Should You Use? covers the general migration.

Which model ID to put in your code

OpenAI's guide positions gpt-image-2.5-flare for fast everyday generation and gpt-image-2.5-sunburst for work where editing precision matters most. Both have -2026-09-08 snapshots if you want to pin a version. There is no bare gpt-image-2.5 ID. For the full comparison, see GPT Image 2.5: Flare vs. Sunburst, Pricing, and API Examples.

Model IDStatus as of September 25, 2026What to do
gpt-image-2.5-flareCurrent, fast everyday generationDefault for new generation code
gpt-image-2.5-sunburstCurrent, aimed at precise editsUse when edits must keep a subject intact
gpt-image-2Earlier model, not deprecatedKeep for an existing integration or when you need Batch
gpt-image-1.5, gpt-image-1-mini, chatgpt-image-latestShut down December 1, 2026Migrate before that date
gpt-image-1Shuts down October 23, 2026Migrate now. This is also the Responses tool's default
dall-e-2, dall-e-3Removed May 12, 2026Replace. This is also the Images API's default

Dates come from OpenAI's deprecations page, which lists gpt-image-2 as a replacement for the retiring models because that table predates 2.5. Either gpt-image-2 or a 2.5 model is a valid target. The guide recommends 2.5 for new integrations.

Before you migrate, search your code for the model names that are going away and for tool declarations that never name a model:

bash
grep -rnE "dall-e-[23]|gpt-image-1|chatgpt-image-latest|response_format" --include="*.py" --include="*.js" --include="*.ts" .
grep -rn "image_generation" --include="*.py" --include="*.js" --include="*.ts" --include="*.java" --include="*.rb" .

Check every hit from the second command. Any image_generation tool without a model key is running on gpt-image-1 today. That includes code copied from OpenAI's own Java and Ruby tool examples, which build the tool without a model. OpenAI hasn't documented what an omitted tool model will do after October 23. It might fail, or it might be remapped. Don't wait to find out.

Generate your first image with the Images API

You need the openai SDK (pip install openai or npm install openai) and an OPENAI_API_KEY environment variable. Depending on your account, OpenAI may also require API Organization Verification before GPT Image models work (see the errors section below).

Python:

python
import base64
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

result = client.images.generate(
    model="gpt-image-2.5-flare",  # always set this; the default is the removed dall-e-2
    prompt="A flat illustration of a lighthouse at dusk, teal and orange palette",
    size="1024x1024",
    quality="medium",
)

with open("lighthouse.png", "wb") as f:
    f.write(base64.b64decode(result.data[0].b64_json))

print(result.usage)  # token counts for cost tracking

Node.js (save as generate.mjs so top-level await works):

js
import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI(); // reads OPENAI_API_KEY

const result = await client.images.generate({
  model: "gpt-image-2.5-flare",
  prompt: "A flat illustration of a lighthouse at dusk, teal and orange palette",
  size: "1024x1024",
  quality: "medium",
});

fs.writeFileSync("lighthouse.png", Buffer.from(result.data[0].b64_json, "base64"));
console.log(result.usage);

cURL (needs jq):

bash
curl -s https://api.openai.com/v1/images/generations \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2.5-flare",
    "prompt": "A flat illustration of a lighthouse at dusk, teal and orange palette",
    "size": "1024x1024",
    "quality": "medium"
  }' | jq -r '.data[0].b64_json' | base64 --decode > lighthouse.png

It worked if lighthouse.png opens as a 1024×1024 image and usage shows output tokens. If the file is empty, print the raw JSON. An error object there tells you more than the decoded bytes will.

Things older tutorials get wrong on this endpoint:

  • No response_format. The Images API reference says it isn't supported for GPT Image models, which always return base64. Remove it from DALL·E-era code.
  • No URLs. The 60-minute image URLs belonged to DALL·E. If your pipeline needs a hosted URL, upload the decoded bytes to your own storage.
  • Output options are size, quality (low, medium, high, plus xhigh and max on 2.5, default auto), output_format (png by default, or jpeg/webp), output_compression (0–100, for jpeg/webp), background, and moderation. OpenAI notes that jpeg is faster than png.
  • Prompts can be up to 32,000 characters for GPT Image models.

Editing an existing image is a separate endpoint, /v1/images/edits, which takes up to 16 input images and an optional mask. OpenAI Image Editing API: GPT Image 2 Edits and Variations walks through it, including why the old variations endpoint no longer has a model to run on.

Generate images with the Responses API tool

This is the same lighthouse request through Responses. The request has three parts: a mainline model at the top level, the image model inside the tool, and tool_choice to force the tool call instead of leaving that decision to the model.

python
import base64
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",  # a mainline model that supports the tool; check its model page
    input="Draw a flat illustration of a lighthouse at dusk, teal and orange palette",
    tools=[{
        "type": "image_generation",
        "model": "gpt-image-2.5-flare",  # omit this and you get gpt-image-1
        "size": "1024x1024",
        "quality": "medium",
    }],
    tool_choice={"type": "image_generation"},
)

image_calls = [item for item in response.output if item.type == "image_generation_call"]

if not image_calls:
    # The model answered without calling the tool
    print("No image. Text output:", response.output_text)

for i, call in enumerate(image_calls):
    if call.result is None:
        print(f"{call.id}: no image data (status={call.status})")
        continue
    with open(f"lighthouse-{i}.png", "wb") as f:
        f.write(base64.b64decode(call.result))
    print(call.id, "revised prompt:", call.revised_prompt)

print("response id:", response.id)
print(response.usage)

The Node.js version has the same shape:

js
import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI();

const response = await client.responses.create({
  model: "gpt-6-astra",
  input: "Draw a flat illustration of a lighthouse at dusk, teal and orange palette",
  tools: [{ type: "image_generation", model: "gpt-image-2.5-flare", size: "1024x1024", quality: "medium" }],
  tool_choice: { type: "image_generation" },
});

const calls = response.output.filter((item) => item.type === "image_generation_call");
if (calls.length === 0) console.log("No image. Text output:", response.output_text);

calls.forEach((call, i) => {
  if (!call.result) return console.log(`${call.id}: no image data (status=${call.status})`);
  fs.writeFileSync(`lighthouse-${i}.png`, Buffer.from(call.result, "base64"));
});

The top-level model

The top-level model can't be an image model. OpenAI's image model pages list v1/responses as not supported for them. Use a mainline model that supports the image generation tool. OpenAI's own examples use gpt-6-astra. The tool guide's static "Supported models" list stops at older models and doesn't even include the one its examples use. The main guide says gpt-5 and newer should support the tool. Confirm on your model's detail page instead of trusting a fixed list.

Where the base64 lives in the response

Each image is an image_generation_call item in response.output. Its fields, per the Responses reference, are id, result (base64, or null), status (in_progress, completed, generating, or failed), and the settings that were actually used: action, background, output_format, quality, size, and revised_prompt. The last one shows how the mainline model rewrote your prompt, which helps explain an image that doesn't match what you typed.

Flow for reading a Responses result: filter output for image_generation_call items, handle the case with no image call, loop over every call, skip null results, then decode and save the base64

Why the text output is empty (or is all you get)

The image isn't in the text. When the tool runs, the picture comes back as an image_generation_call item, and nothing obliges the model to add a text message beside it. So an empty output_text is not an error. Read the image calls instead. The reverse also happens: without tool_choice, the model may reply in text and never call the tool. OpenAI's own examples fall back to printing output_text in that case, and the code above does the same.

Counting images

The tool has no n, and OpenAI doesn't document a way to get an exact number of images from one Responses call. Loop over every image call as the code does, rather than reading only [0]. If you need exactly four variants, four Images API calls or one call with n=4 is the predictable route.

Tool options

The tool accepts size, quality, output_format, output_compression, background, moderation, partial_images (0–3), input_image_mask, and action (auto by default, or generate / edit). Values match the Images API, with xhigh and max quality available on the 2.5 models. input_fidelity is documented for older models, and its effect on 2.5 isn't. Leave it out when you migrate old tool code.

Follow-up edits in a conversation

Refining an image across turns is the main reason to pick Responses. OpenAI's tool guide documents two ways to point the next turn at the earlier image. Both continue from the response object above.

Chain the response, which is simplest when you want the whole conversation carried forward:

python
follow_up = client.responses.create(
    model="gpt-6-astra",
    previous_response_id=response.id,
    input="Now make it look like an oil painting",
    tools=[{"type": "image_generation", "model": "gpt-image-2.5-flare", "action": "edit"}],
)

Reference the image call by ID, when you only want that image in context:

python
follow_up = client.responses.create(
    model="gpt-6-astra",
    input=[
        {"role": "user", "content": [{"type": "input_text", "text": "Now make it look like an oil painting"}]},
        {"type": "image_generation_call", "id": image_calls[0].id},
    ],
    tools=[{"type": "image_generation", "model": "gpt-image-2.5-flare", "action": "edit"}],
)

Read the new image from follow_up.output the same way as before. Keep these points in mind:

  • Repeat the tool on every request, with its model. A follow-up that lists the tool without a model is back on gpt-image-1.
  • action controls generate versus edit. With auto, the model decides whether to edit the image in context or start a new one. edit forces an edit, and generate forces a fresh image. This is different from the /v1/images/edits endpoint. Same word, different API.
  • Edit-heavy conversations can use Sunburst. OpenAI recommends gpt-image-2.5-sunburst for precise editing. If most turns are edits, set it as the tool model on every turn.
  • Chaining is not free. With previous_response_id, "all previous input tokens for responses in the chain are billed as input tokens." Long editing sessions get more expensive per turn.
  • Responses are stored by default (30-day application-state retention). Set store: false to turn that off.

About referenced_image_ids: it appears in some developer questions, but OpenAI's current image guide, tool guide, and Responses reference contain no parameter with that name. Use previous_response_id or the image_generation_call input item shown above.

Streaming partial images

Both APIs can stream preview frames while the final image renders. Set partial_images to 1–3 (0 means final image only). In Responses, listen for response.image_generation_call.partial_image events, which carry partial_image_index and partial_image_b64. The final image arrives in response.completed. You may get fewer partials than you asked for if the image finishes quickly. Each partial adds 100 image output tokens. At $30 per million output tokens, that's 100 × $30 / 1,000,000 = $0.003 per partial.

What a GPT Image 2.5 call costs at each quality setting

GPT Image 2.5 and gpt-image-2 share the same token rates: $8 per million image input tokens ($2 cached), $30 per million image output tokens, and $5 per million text input tokens ($1.25 cached). What differs is how many output tokens each quality setting uses.

Output only, 1024×1024, estimateOutput tokensCost at $30 per 1M
GPT Image 2.5, low196$0.00588
GPT Image 2.5, medium439$0.01317
GPT Image 2.5, high1,756$0.05268
GPT Image 2.5, xhigh3,122$0.09366
GPT Image 2.5, max7,024$0.21072
gpt-image-2, medium1,756$0.05268
gpt-image-2, high7,024$0.21072

The dollar figures are output-only estimates from OpenAI's calculator. Token counts are those figures divided by $30 per million. Prompt tokens, input images, and partial images come on top. Quality names shifted between generations: 2.5 high costs the same as gpt-image-2 medium.

The API choice changes the bill in one way. A Responses request also pays for the mainline model's tokens: reading your prompt, rewriting it, and any text it writes. OpenAI's pricing page lists no separate per-call fee for the image generation tool. So the practical difference is those mainline tokens, plus prior-turn input tokens if you chain responses. Treat any fixed "tool fee" figure you see quoted elsewhere with suspicion, and check usage on your own responses instead. For budgets at scale, see GPT Image 2 API Pricing: Cost per Image and Project Budget.

OpenAI image API errors and access checks

SymptomLikely causeWhat to do
Error mentioning organization verificationGPT Image models may require API Organization VerificationComplete verification in the developer console. See Fix GPT Image 2's 'Your organization must be verified' Error, or Can You Use GPT Image 2 Without Organization Verification?
Images API call fails and you didn't pass modelThe default is dall-e-2, which was removedAdd model="gpt-image-2.5-flare" (or another current ID)
error.type is image_generation_user_error, for example code: "moderation_blocked"The prompt or input image needs to changeDon't retry automatically. Read error.code and optional moderation_details, then change the request
Rate-limit or 5xx errorsTransientRetry with backoff and log the request ID. Don't auto-retry quota errors
Timeouts on complex promptsComplex prompts can take up to 2 minutesRaise your HTTP client, proxy, or serverless timeout above that
Invalid sizeCustom sizes must be multiples of 16, with a 1:3 to 3:1 ratio and no edge above 3840 pixelsSee GPT Image 2 Invalid Size: Fix 1024×576 and Export Exact 16:9
Transparent background ignored or rejectedNeeds background: "transparent" with png or webp, not jpegSee GPT Image 2: Generate and Validate Transparent PNG or WebP
Responses call returns text and no imageThe model chose not to call the toolAdd tool_choice: {"type": "image_generation"}

The error-handling guidance comes from the image guide and OpenAI's error codes guide. Two account notes: a ChatGPT subscription and API billing are separate systems, so ChatGPT Plus doesn't fund API calls. And the API is available in the United States. If your team or users are elsewhere, check OpenAI's supported countries list.

Using an OpenAI-compatible provider instead

Some teams route image calls through a third-party gateway for billing reasons. One example is laozhang.ai. Its GPT Image 2.5 docs describe an OpenAI-compatible base URL (https://api.laozhang.ai/v1 ) serving gpt-image-2.5-flare and gpt-image-2.5-sunburst, with per-token or per-call billing and editing through /v1/images/edits. Those are the provider's own claims. It isn't OpenAI, it offers no OpenAI SLA, and its docs don't describe support for the Responses image_generation tool. If you go that way, the Images API code above is the part that carries over, with base_url changed. The Responses examples are for OpenAI directly.

Quick answers

Where is the base64 image in a Responses API result?

In response.output, on items whose type is image_generation_call, in the result field. result can be null, so check it before decoding. On the Images API, the base64 is in data[0].b64_json.

How long do OpenAI image URLs last?

Only DALL·E returned URLs, and they were valid for 60 minutes. DALL·E has been removed from the API. GPT Image models always return base64, so there is no URL to expire. Store the decoded file yourself.

Can I set gpt-image-2.5-flare as the top-level model in Responses?

No. Image models aren't supported as top-level Responses models. Put a mainline model such as gpt-6-astra at the top level and gpt-image-2.5-flare in the tool's model field.

Is referenced_image_ids a real parameter?

It doesn't appear in OpenAI's current image guide, tool guide, or Responses reference. The documented ways to edit an earlier image are previous_response_id, or passing {"type": "image_generation_call", "id": ...} as an input item.

Is DALL·E still available through the API?

No. dall-e-2 and dall-e-3 were removed on May 12, 2026. OpenAI's DALL·E model pages now point to GPT Image 2.5 Sunburst.

Found an error, or hit a problem this article does not cover? Send the article URL and what you saw to hi@laozhang.ai. We check it and update the article.