# GPT Image 2.5

GPT Image 2.5 comes as two models, Flare and Sunburst, and both take exactly the same request body. A request returns a task ID straight away; the images arrive on that task when rendering finishes. Text-to-image, reference edits and masked edits all go to the same endpoint.

## Model IDs

| Model ID                          | Model                                               | Billing                               |
| --------------------------------- | --------------------------------------------------- | ------------------------------------- |
| `gpt-image-2.5-flare`             | Flare, the faster of the two                        | Fixed price per image, set by quality |
| `gpt-image-2.5-sunburst`          | Sunburst, slower, holds detail better through edits | Fixed price per image, set by quality |
| `gpt-image-2.5-flare-official`    | Flare                                               | Input and output tokens               |
| `gpt-image-2.5-sunburst-official` | Sunburst                                            | Input and output tokens               |

Switching between the four IDs never requires a change to the request body. Current rates for each one are on the [GPT Image 2.5 page](https://toklab.ai/models/gpt-image-2-5#pricing).

## Quick example

```bash
curl https://api.toklab.ai/v1/images/generations \
  -H "Authorization: Bearer $TOKLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2.5-flare",
    "prompt": "A stack of three linen-bound notebooks on a walnut desk, morning side light",
    "size": "1024x1024",
    "quality": "low"
  }'
```

```python
import os
import requests

response = requests.post(
    "https://api.toklab.ai/v1/images/generations",
    headers={"Authorization": f"Bearer {os.environ['TOKLAB_API_KEY']}"},
    json={
        "model": "gpt-image-2.5-flare",
        "prompt": "A stack of three linen-bound notebooks on a walnut desk, morning side light",
        "size": "1024x1024",
        "quality": "low",
    },
    timeout=60,
)
response.raise_for_status()
task_id = response.json()["id"]
```

```javascript
const response = await fetch('https://api.toklab.ai/v1/images/generations', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TOKLAB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gpt-image-2.5-flare',
    prompt:
      'A stack of three linen-bound notebooks on a walnut desk, morning side light',
    size: '1024x1024',
    quality: 'low',
  }),
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const { id: taskId } = await response.json();
```

```go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
    "strings"
    "time"
)

func main() {
    body := strings.NewReader(`{
      "model": "gpt-image-2.5-flare",
      "prompt": "A stack of three linen-bound notebooks on a walnut desk, morning side light",
      "size": "1024x1024",
      "quality": "low"
    }`)
    req, err := http.NewRequest("POST", "https://api.toklab.ai/v1/images/generations", body)
    if err != nil { panic(err) }
    req.Header.Set("Authorization", "Bearer " + os.Getenv("TOKLAB_API_KEY"))
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{Timeout: 60 * time.Second}
    res, err := client.Do(req)
    if err != nil { panic(err) }
    defer res.Body.Close()
    if res.StatusCode != http.StatusOK { panic(res.Status) }
    var task struct { ID string `json:"id"` }
    if err := json.NewDecoder(res.Body).Decode(&task); err != nil { panic(err) }
    fmt.Println(task.ID)
}
```

## Endpoint

```text
POST https://api.toklab.ai/v1/images/generations
```

| Header        | Value                 |
| ------------- | --------------------- |
| Authorization | `Bearer YOUR_API_KEY` |
| Content-Type  | `application/json`    |

What the request contains decides the operation: a prompt alone generates, a prompt with `images` edits, and adding `mask` limits the edit to a region. The response is a task, never image bytes. Call the endpoint from your server so the key stays out of the browser.

## Parameters

| Name                 | Type      | Required | Default        | Notes                                                                                        |
| -------------------- | --------- | -------- | -------------- | -------------------------------------------------------------------------------------------- |
| `model`              | string    | Yes      | —              | One of the four IDs above.                                                                   |
| `prompt`             | string    | Yes      | —              | Must contain non-whitespace text; at most 32,000 characters.                                 |
| `images`             | object\[] | No       | —              | 1 to 16 entries, each `{"image_url":"https://..."}`. Sending this makes the request an edit. |
| `mask`               | object    | No       | —              | `{"image_url":"https://..."}`. Only valid together with `images`.                            |
| `size`               | string    | No       | `auto`         | `auto` or `WIDTHxHEIGHT` within the size rules below.                                        |
| `quality`            | enum      | No       | `auto`         | `auto`, `low`, `medium`, `high`, `xhigh`, `max`.                                             |
| `background`         | enum      | No       | `auto`         | `auto`, `opaque`, `transparent`.                                                             |
| `output_format`      | enum      | No       | `png`          | `png` or `jpeg`.                                                                             |
| `output_compression` | integer   | No       | `100` for JPEG | 0 to 100, JPEG only. 0 is a valid value.                                                     |
| `n`                  | integer   | No       | `1`            | 1 to 10 images per request.                                                                  |
| `moderation`         | enum      | No       | `auto`         | `auto` or `low`.                                                                             |
| `user`               | string    | No       | —              | Your own identifier for the end user. Keep personal data out of it.                          |

### Size rules

`1024x1024`, `1536x1024` and `1024x1536` are the usual picks. A custom size passes only if all four of these hold:

* Width and height are both multiples of 16.
* Neither side is longer than 3840 pixels.
* Width divided by height is between 1:3 and 3:1.
* Width times height is between 655,360 and 8,294,400 pixels, both ends included.

So `3840x2160` and `2880x2880` pass, while `4096x2304` fails on the edge limit. With `auto` the model picks the dimensions. Ratio strings such as `16:9` are not accepted as `size`.

In the Playground, Ratio mode turns an aspect ratio plus a 1K, 2K or 4K preset into a single `size` value and shows it before you submit. There is no `resolution` or `aspect_ratio` field in the API; send the computed `size`. OpenAI labels output above 2560×1440 experimental: those sizes are accepted, but a bigger canvas does not promise more detail.

### Quality

GPT Image 2 tops out at `high`; GPT Image 2.5 adds `xhigh` and `max`. Each step up renders more slowly. On the token-billed IDs it also produces more output tokens: at 1024x1024 we recorded 196 for `low`, 439 for `medium`, 1,756 for `high`, 3,122 for `xhigh` and 7,024 for `max`. Treat those as single observations. Size and image content move the count.

`auto` hands the choice to the model, so neither the tier nor the cost is fixed in advance. Iterating at `low` and moving up only for the keeper is the cheapest way to work.

### Transparent backgrounds

Set `background: "transparent"` with PNG output. A transparent request with `output_format: "jpeg"` is rejected, because JPEG has no alpha channel. `output_compression` is likewise rejected unless the format is JPEG.

### Other request rules

* The scalar options (`n`, `size`, `quality`, `background`, `output_format`, `output_compression`, `moderation`) treat `null` as not sent.
* Any field not listed above is rejected, including `style`, `response_format` and `input_fidelity`.
* The Playground does not send `user`; it exists for API callers.

## Modes

| Operation      | Send                         |
| -------------- | ---------------------------- |
| Text to image  | `prompt`                     |
| Reference edit | `prompt` + `images`          |
| Masked edit    | `prompt` + `images` + `mask` |

### Edit example

```bash
curl https://api.toklab.ai/v1/images/generations \
  -H "Authorization: Bearer $TOKLAB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2.5-sunburst",
    "prompt": "Recolour the mug to deep forest green. Keep the table, the light and the steam exactly as they are.",
    "images": [{"image_url": "https://example.com/mug.png"}],
    "mask": {"image_url": "https://example.com/mug-mask.png"},
    "output_format": "jpeg",
    "output_compression": 90
  }'
```

Point both URLs at images you host. Drop `mask` to let the model edit the whole picture.

## Media inputs

Inputs are URLs only. Base64, `data:` and `blob:` URLs, multipart uploads, raw bytes and OpenAI Files IDs are all refused. The Playground uploads the files you pick and sends their URLs for you.

* Reference images: public HTTP(S) URLs to PNG, JPEG or WebP, each under 50 MB.
* Mask: a PNG under 4 MB with the same pixel dimensions as the first reference image. Transparent pixels mark the area to change. With several references, the mask applies to the first.

The model treats the mask as guidance, so edges are not guaranteed to be pixel-exact. Media is fetched while the task runs; a URL that cannot be read fails the task, and a failed task is not charged.

## Billing

The Standard IDs charge a fixed amount for each delivered image, and the amount depends on the model and the quality you pick. The `-official` IDs charge for the input and output tokens the render actually uses, so quality, size, reference images, prompt length and `n` all change the total. Rates are listed on the [model page](https://toklab.ai/models/gpt-image-2-5#pricing) and read from the same ledger that bills your account.

The Playground estimate combines a measured sample with the current rate. It is a guide, not a quote; the actual charge for each task is in your usage history.

## Response

Submitting returns the task:

```json
{
  "id": "task_...",
  "model": "gpt-image-2.5-flare",
  "status": "processing",
  "created_at": 1789970508
}
```

### Poll the task

```bash
curl https://api.toklab.ai/v1/tasks/YOUR_TASK_ID \
  -H "Authorization: Bearer $TOKLAB_API_KEY"
```

Check every few seconds until `status` becomes `completed` or `failed`. If a poll times out, the render has not failed; ask about the same task ID again. Submitting a second request to find out where the first one is just creates a second paid task.

### Polling example

This continues the Python example above. It stops waiting after ten minutes, which ends only the local loop: the task keeps running and the same ID can be queried later.

```python
import time

print(f"Task ID: {task_id}")
deadline = time.monotonic() + 600
while time.monotonic() < deadline:
    result = requests.get(
        f"https://api.toklab.ai/v1/tasks/{task_id}",
        headers={"Authorization": f"Bearer {os.environ['TOKLAB_API_KEY']}"},
        timeout=30,
    )
    result.raise_for_status()
    task = result.json()
    if task["status"] == "completed":
        for image in task["output"]["data"]:
            print(image["url"])
        break
    if task["status"] == "failed":
        raise RuntimeError(task["error"]["message"])
    time.sleep(3)
else:
    raise TimeoutError(f"Still waiting. Resume polling task {task_id}.")
```

### Completed task

```json
{
  "id": "task_...",
  "model": "gpt-image-2.5-flare",
  "status": "completed",
  "created_at": 1789970508,
  "finished_at": 1789970538,
  "output": {
    "created": 1789970532,
    "data": [{"url": "https://.../task_example/0.png"}],
    "usage": {
      "input_tokens": 29,
      "output_tokens": 196,
      "total_tokens": 225
    }
  }
}
```

| Field                                                                        | Meaning                                                                                                       |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `id`                                                                         | The task ID to query.                                                                                         |
| `status`                                                                     | `processing`, `completed` or `failed`.                                                                        |
| `created_at`, `finished_at`                                                  | Unix seconds. `finished_at` is empty or zero until the task ends.                                             |
| `output.data[].url`                                                          | Hosted image URLs, present once the task completes. Copy them to your own storage if you need them long term. |
| `output.size`, `output.quality`, `output.background`, `output.output_format` | What was actually rendered, when the model reports it.                                                        |
| `output.usage`                                                               | Token usage, when reported. May include a text and image breakdown.                                           |
| `error`                                                                      | Present on failed tasks.                                                                                      |

Delivery is asynchronous, so this is not a drop-in for the synchronous OpenAI Images SDK, and `stream` and `partial_images` are not available.

## Errors

A request refused before a task exists gets an HTTP error status and an `error` object. A task that fails later still answers the poll with HTTP 200, `status: "failed"` and an `error` object:

```json
{
  "id": "task_...",
  "status": "failed",
  "error": {
    "code": 60002,
    "message": "Generation could not be completed. Please try again."
  }
}
```

Every model shares one error format; codes, statuses and when to retry are in the [error catalog](https://toklab.ai/docs/api/errors). If the submit call itself times out, look in your task history before retrying, since the first request may already have been accepted.

## Pricing

Every rate this model is billed at, read live from the price list billing uses. A tier applies when its condition matches, checked top to bottom.

### gpt-image-2.5-flare · gpt-image-2.5-sunburst

| When | Image (per image) |
| --- | --- |
| Quality: xhigh or Quality: max | $0.0225 |
| Otherwise | $0.021 |

### gpt-image-2.5-flare-official · gpt-image-2.5-sunburst-official

| Input (per 1M tokens) | Output (per 1M tokens) | Cache read (per 1M tokens) | Image input (per 1M tokens) | Image cache read (per 1M tokens) |
| --- | --- | --- | --- | --- |
| $3.75 | $22.50 | $0.9375 | $6.00 | $1.50 |

## Related

* [GPT Image 2.5 Playground and pricing](https://toklab.ai/models/gpt-image-2-5)
* [Task lifecycle](https://toklab.ai/docs/api/tasks)
* [OpenAPI schema](https://toklab.ai/docs/gpt-image-2-5.openapi.json)
* [This page as Markdown](https://toklab.ai/docs/gpt-image-2-5.md)
