# OpenAI Image API Tutorial: Images API vs Responses API

> Use the Images API for one-prompt image generation or edits and the Responses image_generation tool for multi-turn editing, with GPT Image 2.5 code.

- Source: https://www.aifreeapi.com/en/posts/openai-image-api-tutorial
- Language: en
- Published: 2026-03-22
- Updated: 2026-09-25
- Publisher: AI Free API (https://www.aifreeapi.com)

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](https://developers.openai.com/api/docs/guides/image-generation#choosing-the-right-api).

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](https://www.aifreeapi.com/posts/en/openai-image-api-tutorial/img/request-shapes.webp)

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 API | Responses API + `image_generation` tool |
|---|---|---|
| Best fit | One prompt in, one image (or a few) out: generate or edit | Chat-style products where users refine an image over several turns, or flows that mix images with other tools |
| Where the image model goes | Top-level `model`, for example `gpt-image-2.5-flare` | Top-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 back | `data[i].b64_json` | `output` items with `type == "image_generation_call"`, field `result` |
| Several images per request | `n` from 1 to 10 | No `n` parameter. The model decides how many times to call the tool |
| Follow-up edits | New request to `/v1/images/edits` with the image attached | `previous_response_id`, or the earlier `image_generation_call` ID as an input item |
| What you pay for | Image 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?](/en/posts/chat-completions-vs-responses-api) 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](/en/posts/gpt-image-2-5).

| Model ID | Status as of September 25, 2026 | What to do |
|---|---|---|
| `gpt-image-2.5-flare` | Current, fast everyday generation | Default for new generation code |
| `gpt-image-2.5-sunburst` | Current, aimed at precise edits | Use when edits must keep a subject intact |
| `gpt-image-2` | Earlier model, not deprecated | Keep for an existing integration or when you need Batch |
| `gpt-image-1.5`, `gpt-image-1-mini`, `chatgpt-image-latest` | Shut down December 1, 2026 | Migrate before that date |
| `gpt-image-1` | Shuts down October 23, 2026 | Migrate now. This is also the Responses tool's default |
| `dall-e-2`, `dall-e-3` | Removed May 12, 2026 | Replace. This is also the Images API's default |

Dates come from OpenAI's [deprecations page](https://developers.openai.com/api/docs/deprecations), 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](https://help.openai.com/en/articles/10910291-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](https://developers.openai.com/api/reference/python/resources/images/methods/generate) 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](/en/posts/openai-image-editing-api) 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](https://developers.openai.com/api/docs/models) 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](https://developers.openai.com/api/reference/resources/responses/methods/create), 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](https://www.aifreeapi.com/posts/en/openai-image-api-tutorial/img/responses-result-flow.webp)

### 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](https://developers.openai.com/api/docs/guides/tools-image-generation) 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](https://developers.openai.com/api/docs/pricing#image-generation): $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, estimate | Output tokens | Cost at $30 per 1M |
|---|---|---|
| GPT Image 2.5, `low` | 196 | $0.00588 |
| GPT Image 2.5, `medium` | 439 | $0.01317 |
| GPT Image 2.5, `high` | 1,756 | $0.05268 |
| GPT Image 2.5, `xhigh` | 3,122 | $0.09366 |
| GPT Image 2.5, `max` | 7,024 | $0.21072 |
| `gpt-image-2`, `medium` | 1,756 | $0.05268 |
| `gpt-image-2`, `high` | 7,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](/en/posts/openai-image-generation-api-pricing).

## OpenAI image API errors and access checks

| Symptom | Likely cause | What to do |
|---|---|---|
| Error mentioning organization verification | GPT Image models may require [API Organization Verification](https://help.openai.com/en/articles/10910291-api-organization-verification) | Complete verification in the developer console. See [Fix GPT Image 2's 'Your organization must be verified' Error](/en/posts/openai-image-generation-api-verification), or [Can You Use GPT Image 2 Without Organization Verification?](/en/posts/gpt-image-2-without-organization-verification) |
| Images API call fails and you didn't pass `model` | The default is `dall-e-2`, which was removed | Add `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 change | Don't retry automatically. Read `error.code` and optional `moderation_details`, then change the request |
| Rate-limit or 5xx errors | Transient | Retry with backoff and log the request ID. Don't auto-retry quota errors |
| Timeouts on complex prompts | Complex prompts can take up to 2 minutes | Raise your HTTP client, proxy, or serverless timeout above that |
| Invalid size | Custom sizes must be multiples of 16, with a 1:3 to 3:1 ratio and no edge above 3840 pixels | See [GPT Image 2 Invalid Size: Fix 1024×576 and Export Exact 16:9](/en/posts/gpt-image-2-invalid-size) |
| Transparent background ignored or rejected | Needs `background: "transparent"` with `png` or `webp`, not `jpeg` | See [GPT Image 2: Generate and Validate Transparent PNG or WebP](/en/posts/gpt-image-2-transparent-background) |
| Responses call returns text and no image | The model chose not to call the tool | Add `tool_choice: {"type": "image_generation"}` |

The error-handling guidance comes from the image guide and OpenAI's [error codes guide](https://developers.openai.com/api/docs/guides/error-codes). 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](https://developers.openai.com/api/docs/supported-countries).

### 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](https://docs.laozhang.ai/api-capabilities/gpt-image-2-5) 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.
