> ## Documentation Index
> Fetch the complete documentation index at: https://omnirelay.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Image generation and editing

> Create and edit images with OpenAI-compatible endpoints, billed per image by size tier.

`POST /v1/images/generations` creates images from a text prompt. `POST /v1/images/edits` changes one or more images you upload. Both follow the OpenAI Images API, so the official OpenAI SDKs work with base URL `https://api.omnirelay.ai/v1` and an OmniRelay API key.

The same key rules apply as for text models: key expiry, [IP allowlist](https://omnirelay.ai/docs/overview/rate-limits.md#key-ip-allowlist), [model limits](https://omnirelay.ai/docs/overview/authentication.md#limit-a-key-to-some-models), budgets and [rate and concurrency limits](https://omnirelay.ai/docs/overview/rate-limits.md). You can also try both endpoints in the console playground before writing code.

## Models

| Model ID | Generate | Edit | Images per call |
| - | - | - | - |
| `gpt-image-2` | Yes | Yes | 1–4 |
| `gpt-image-2.5` | Yes | Yes | 1–4 |

Image models appear in your own [`GET /v1/models`](https://omnirelay.ai/docs/llms/listing-models#image-models) list with `capabilities.image_generation: true` and their per-image prices for your workspace.

## Pricing

Images are billed **per image returned**, not per token. The requested `size` sets the price tier of every image in the call:

| Tier | Requested `size` | List price per image |
| - | - | - |
| 1K | Longest side up to 1024 px, for example `1024x1024` or `1024x768` | USD 0.134 |
| 2K | Longest side up to 2048 px, for example `1536x1024` or `2048x2048`; also `size` omitted or `auto` | USD 0.201 |
| 4K | Longest side up to 4096 px, for example `3840x2160` | USD 0.268 |

`quality` does not change the price. Your discount applies as for text models. A call reserves `n` images at its tier before it is sent and settles only the images that come back; the response reports the charge in `cost_microusd`. See [Usage and billing](https://omnirelay.ai/docs/general/usage-billing.md#image-models).

## Generate an image

```bash cURL theme={null}
curl https://api.omnirelay.ai/v1/images/generations \
  -H "Authorization: Bearer $OMNIRELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-image-2","prompt":"A red bicycle on a white background","size":"1024x1024","quality":"low"}' \
  | jq -r '.data[0].b64_json' | base64 --decode > bicycle.png
```

```python Python theme={null}
import base64, os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OMNIRELAY_API_KEY"], base_url="https://api.omnirelay.ai/v1")

result = client.images.generate(
    model="gpt-image-2",
    prompt="A red bicycle on a white background",
    size="1024x1024",
    quality="low",
)
open("bicycle.png", "wb").write(base64.b64decode(result.data[0].b64_json))
```

```javascript JavaScript theme={null}
import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({ apiKey: process.env.OMNIRELAY_API_KEY, baseURL: "https://api.omnirelay.ai/v1" });

const result = await client.images.generate({
  model: "gpt-image-2",
  prompt: "A red bicycle on a white background",
  size: "1024x1024",
  quality: "low",
});
fs.writeFileSync("bicycle.png", Buffer.from(result.data[0].b64_json, "base64"));
```

## Edit an image

Send the source image as `multipart/form-data` in the field `image` (or several as `image[]`), with an optional `mask` whose transparent areas mark what to change. The whole request may be up to 20 MiB.

```bash cURL theme={null}
curl https://api.omnirelay.ai/v1/images/edits \
  -H "Authorization: Bearer $OMNIRELAY_API_KEY" \
  -F model=gpt-image-2 \
  -F prompt="Make the bicycle blue" \
  -F size=1024x1024 \
  -F image=@bicycle.png \
  | jq -r '.data[0].b64_json' | base64 --decode > blue-bicycle.png
```

```python Python theme={null}
result = client.images.edit(
    model="gpt-image-2",
    image=open("bicycle.png", "rb"),
    prompt="Make the bicycle blue",
    size="1024x1024",
)
open("blue-bicycle.png", "wb").write(base64.b64decode(result.data[0].b64_json))
```

```javascript JavaScript theme={null}
const result = await client.images.edit({
  model: "gpt-image-2",
  image: fs.createReadStream("bicycle.png"),
  prompt: "Make the bicycle blue",
  size: "1024x1024",
});
fs.writeFileSync("blue-bicycle.png", Buffer.from(result.data[0].b64_json, "base64"));
```

## Parameters

| Field | Rule |
| - | - |
| `model` | An image model ID from `GET /v1/models`. |
| `prompt` | Required text, up to 32,000 characters. |
| `size` | `WIDTHxHEIGHT` or `auto` (the default). Sets the price tier; a size whose longest side exceeds 4096 px is refused with `400 invalid_parameter`. |
| `quality` | Passed to the model, for example `low`, `medium`, `high` or `auto`. Higher quality takes longer but costs the same. |
| `n` | Number of images, 1–4. Default 1. |
| `response_format` | Only `b64_json`. `url` is refused with `400 unsupported_parameter` before any charge. |
| `stream` | Not supported for images. |
| `image`, `image[]`, `mask` | Edits only: the uploaded files, at most 17 files and 20 MiB in total. |
| Other fields | `background`, `output_format`, `output_compression`, `moderation` and `user` are passed to the model, which accepts or refuses them. A refusal returns `400` with `error.param` and is not charged. |

## Response

```json theme={null}
{
  "created": 1791734841,
  "model": "gpt-image-2",
  "data": [{ "b64_json": "iVBORw0KGgo…", "revised_prompt": "…" }],
  "background": "opaque",
  "output_format": "png",
  "quality": "low",
  "size": "1024x1024",
  "usage": { "input_tokens": 18, "output_tokens": 196, "total_tokens": 214 },
  "cost_microusd": 134000
}
```

* `data[].b64_json` is the image, base64-encoded in `output_format`. Images are never returned as links.
* `usage` reports the model's token counts for information only; the charge is `cost_microusd`, per image.
* Send `X-Gateway-Metadata: enabled` to add a `gateway_metadata` block (also returned as `omnirelay_metadata`), as for text models.

## Long calls

Image calls usually take 15–90 seconds, and high quality or 4K can take longer. If a call has not finished after about 25 seconds, OmniRelay answers `200` immediately and sends spaces until the JSON body is ready, so proxies do not close an idle connection. JSON parsers and the OpenAI SDKs ignore the leading spaces.

Because the status is already sent, **a failure after that point arrives as an error object in a `200` body**. Always check the parsed body for `error` before reading `data`:

```python theme={null}
import json, requests

response = requests.post(
    "https://api.omnirelay.ai/v1/images/generations",
    headers={"Authorization": f"Bearer {key}"},
    json={"model": "gpt-image-2", "prompt": "A lighthouse at dusk", "quality": "high"},
    timeout=300,
)
body = json.loads(response.text)
if "error" in body:
    raise RuntimeError(body["error"]["code"], body.get("request_id"))
```

Set your HTTP client timeout to at least 3 minutes.

## Failures and charges

| Situation | Response | Charged? |
| - | - | - |
| Invalid request, refused parameter, budget or limit | `400`, `402`, `403`, `413` or `429` with an error code | No |
| The model provider refused the call | `502 upstream_rejected` | No |
| Timeout, dropped connection, provider server error, or an answer that could not be verified | `502` or `504 image_call_held` | Held for review; charged only if the images were produced |

A provider finishes and bills an image even when the answer does not reach you, so OmniRelay does not release `image_call_held` calls automatically. The reservation stays held until the call is reconciled; contact support with the `request_id` if it does not clear. Do not resend the same request in a loop: each call is billed separately. See [Errors](https://omnirelay.ai/docs/general/errors.md).

## Limits

* At most 4 images per call; at most 20 MiB per request; prompts up to 32,000 characters.
* Workspace, member and key requests per minute and concurrent calls count image calls like text calls.
* Images are not stored by OmniRelay; save `b64_json` on your side.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.