# Image generation

Generate and edit images with one request. The endpoint has the shape of the OpenAI images API, so an unchanged OpenAI client works. Only a deployment that is registered to make images can win the job.

## Quickstart

```bash
curl https://router.inference.ai.exchange/v1/images/generations \
  -H "Authorization: Bearer sk-liquid-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemini-2.5-flash-image",
    "prompt": "a cat on a bicycle",
    "n": 2,
    "size": "1024x1024"
  }'
```

```python
from openai import OpenAI

client = OpenAI(base_url="https://router.inference.ai.exchange/v1", api_key="sk-liquid-...")

result = client.images.generate(
    model="gemini-2.5-flash-image",
    prompt="a cat on a bicycle",
    n=2,
    size="1024x1024",
)
```

Each image comes back as `b64_json` or `url`. The `exchange` block of the answer holds the receipt. See [Response](#response).

## Examples

Operators of the exchange made example images with each model. The page of a model in the console lists them. The app shows two here, each with its model and its prompt.

## Find a model

A control works only when an offer for the model declares it. Read what a model supports before you send a control.

| Source                               | Use it for                                                                                     |
| ------------------------------------ | ---------------------------------------------------------------------------------------------- |
| `GET /v1/models?kind=image`          | One card for each model: `parameters`, `image_sizes` and `pricing`.                            |
| `GET /v1/exchange/models?kind=image` | The market view, with no key: `sizes`, `formats`, `response_formats` and `rate_per_image_usd`. |
| The model page in the console        | The same data, with example images and a Playground.                                           |

On the card of an image model:

- `kind` is `image_generation` and `modality` is `image`.
- `image_sizes` lists the pixel sizes that you can send as `size`. The tier and the shape come from these sizes. See [Size, tier and shape](#size-tier-and-shape).
- `parameters` lists each other control that at least one offer supports: `output_format`, `quality`, `seed`, `background`, `output_compression` and `stream`. A name that is not a key is not supported, so omit it. [Modalities](https://inference.ai.exchange/docs/modalities.md#read-a-model-card) describes each descriptor.
- `pricing` gives the price of an image. If the price depends on a size or a quality, the line names it.

A preset of the image kind buys every listed model of its kind. If a request sets a control that no offer declared, the answer is `400 unsupported_parameter` and `param` names the control or its feature, such as `image.seed`. The exchange holds nothing and sends nothing.

## How it works

1. **Send the request.** The exchange picks the offers that can serve it.
2. **The exchange fixes the price cap.** The price is for one image, so the cap is exactly the charge.
3. **The provider makes the images.** The exchange dispatches the size that it resolved.
4. **You get the answer**, or events if you set `stream`. The charge is for the images that the provider returned.

## Size, tier and shape

Send `size` when you know the pixel size. Send `resolution` or `aspect_ratio` when you do not. The exchange then picks a pixel size that an offer declared. It buys, prices and dispatches that size, as if you had sent it as `size`.

```json
{ "model": "<model>", "prompt": "a cat on a bicycle", "resolution": "2K", "aspect_ratio": "16:9" }
```

| Tier   | Sizes that have it                    |
| ------ | ------------------------------------- |
| `512`  | `512x512`                             |
| `768`  | `1024x576`                            |
| `1K`   | `1024x1024`, `1536x1024`, `1024x1536` |
| `1.5K` | `1920x1080`                           |
| `2K`   | `2048x2048`, `3840x2160`              |
| `4K`   | `4096x4096`                           |

- A tier is the pixel count of a square of that side. The tier of a size is the tier that is nearest to its area.
- A shape matches a size when the two ratios differ by 1% or less. `aspect_ratio: "auto"` asks for nothing.
- Without `resolution`, the exchange picks the size that is nearest to `1K`.
- The size that the exchange picks sets the price. A model can charge more for a larger size.
- If no declared size has the tier and the shape, the answer is `400 unsupported_parameter`. It lists the sizes that offers declare. A model with no declared size cannot be asked for a tier or a shape.
- If you send `size` as well and it has another tier or shape, the answer is `400 conflicting_parameters` and names both fields.

## Controls

`seed`, `background`, `output_compression` and `stream` are offer features: `image.seed`, `image.background`, `image.output_compression` and `image.stream`. An offer wins a request that sets one only when it declared the feature. An offer that did not declare it cannot win, because it would ignore the control and still charge for the image.

`background: "auto"` and `stream: false` ask for nothing. Any offer can win them.

### Seed

`seed` makes a picture repeatable on a best-effort basis. An upstream can change its model.

```json
{ "model": "<model>", "prompt": "a cat on a bicycle", "seed": 42 }
```

### Background

`background` is `transparent`, `opaque` or `auto`. `transparent` needs `output_format` `png` or `webp`, or no `output_format`. With `jpeg` the answer is `400 conflicting_parameters`.

```json
{ "model": "<model>", "prompt": "a logo", "output_format": "png", "background": "transparent" }
```

### Compression

`output_compression` is a whole number from 0 to 100. It applies to `jpeg` and `webp`. With `png` the answer is `400 conflicting_parameters`.

```json
{ "model": "<model>", "prompt": "a cat", "output_format": "webp", "output_compression": 80 }
```

### Quality

An offer can list the quality names that it takes. If the offers of a model list any, the card has a `quality` key in `parameters`, and an offer that omits your name cannot win. An offer that lists none states nothing, so it can win and its provider decides. A provider that does not take the name fails the job before it renders, so send `quality` only when the model lists it.

### Reference images

Send images in `input_references` to start from a picture. See [Reference inputs](https://inference.ai.exchange/docs/reference-inputs.md). `POST /v1/images/edits` is the same purchase for the OpenAI SDK. It needs at least one reference image, takes the files in `image`, and refuses a `mask`.

## Stream

Send `stream: true` to get the answer as server-sent events. The content type is `text/event-stream`. The request must ask for one image: `stream` with `n` above 1 gets `400 conflicting_parameters`.

```bash
curl -N https://router.inference.ai.exchange/v1/images/generations \
  -H "Authorization: Bearer sk-liquid-..." \
  -H "Content-Type: application/json" \
  -d '{"model": "<model>", "prompt": "a cat on a bicycle", "stream": true}'
```

```text
event: image_generation.partial_image
data: {"type":"image_generation.partial_image","partial_image_index":0,"b64_json":"iVBOR…","media_type":"image/png"}

event: image_generation.completed
data: {"type":"image_generation.completed","b64_json":"iVBOR…","media_type":"image/png","exchange":{"...":"..."}}
```

| Event                            | Fields                                                                 | When                                                 |
| -------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------- |
| `image_generation.partial_image` | `partial_image_index`, `b64_json`, `media_type`                        | Once for each draft. A provider sends at most three. |
| `image_generation.completed`     | `b64_json` or `url`, `media_type`, `revised_prompt` if any, `exchange` | Once, for the final image.                           |

- The exchange sends the events when the job ends, because the charge is known only then. It does not send the first draft early.
- A draft is not an image that you pay for. The charge counts the final image.
- If the winning offer does not stream, the answer is the normal JSON body.

## Response

```json
{
  "created": 1760000000,
  "data": [{ "b64_json": "iVBOR…", "media_type": "image/png" }],
  "exchange": { "...": "..." }
}
```

- Each image has `b64_json` or `url`, as the upstream of the provider returned it. `media_type` states the MIME type. `revised_prompt` appears when the upstream rewrote your prompt.
- The exchange does not download or host your images. A `url` is the link of the provider, and it can expire.
- The `exchange` block is the receipt. It reports `images`, so you can compare the images that you paid for with the images that you got. See [Receipts](https://inference.ai.exchange/docs/receipts.md).
- The answer and each event also have `mime_type`, with the same value as `media_type`, for clients that read the older name.

## Billing

The price is for one image, so the cap that the exchange fixes is exactly the charge. The price of the size and the quality that the request names sets the charge. An image offer priced in tokens is held to the charge of one image at its output token bound.

`exchange.limits.price.unit_usd` sets the highest price for one image. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md) and [Modalities](https://inference.ai.exchange/docs/modalities.md).

## Images in a chat answer

Some chat models draw in their answer. Send `modalities` with `image` to `POST /v1/chat/completions`.

```bash
curl https://router.inference.ai.exchange/v1/chat/completions \
  -H "Authorization: Bearer sk-liquid-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<a model that draws in chat>",
    "modalities": ["image", "text"],
    "messages": [{ "role": "user", "content": "a cat on a bicycle" }]
  }'
```

Each image is in `choices[0].message.images`. The `url` of an image is a `data:` URL when the provider returned bytes. It is a link when the provider returned a link. The exchange does not change the form.

Only an offer that declared image output (`output.image`) can win the job. If no offer did, the exchange refuses the request with `unsupported_parameter` before the auction. It does not send you a text answer.

### Image config

Send `image_config` to ask for a size. It needs `image` in `modalities`.

```json
{
  "model": "<a model that draws in chat>",
  "modalities": ["image", "text"],
  "image_config": { "resolution": "2K", "aspect_ratio": "16:9" },
  "messages": [{ "role": "user", "content": "a cat on a bicycle" }]
}
```

| Key                          | Value                                                       |
| ---------------------------- | ----------------------------------------------------------- |
| `size`                       | `<width>x<height>`, or `auto`.                              |
| `resolution` or `image_size` | A tier: `512`, `768`, `1K`, `1.5K`, `2K` or `4K`.           |
| `aspect_ratio`               | `<width>:<height>`, or `auto`.                              |
| `quality`                    | A quality name. It goes to the upstream and is not matched. |

- All values are strings. Send `resolution` or `image_size`, not both with different values.
- Any other key gets `400 invalid_request`, and the error names the key, such as `image_config.seed`.
- An offer wins only when it declared a pixel size that has the `size`, the tier and the shape. An offer that declared `output.image` and no size cannot win a request with one of them.

### Cost and limits in a chat answer

- An image costs output tokens. The charge is the output tokens that the provider reports, at the rate of the offer.
- If you send no `max_tokens`, the exchange allows 8,192 output tokens. Your price cap still limits the charge.
- If you set `max_tokens` below the size of one image, the model can stop before it draws. You pay for the tokens that it used.
- Send `stream` as `false`. A request with `stream: true` and `image` in `modalities` gets `400`.
- `/v1/messages` and `/v1/responses` do not return images.
- If you hang up before the answer arrives, the job does not settle, and you are not charged.

## Request parameters

| Field                | Type    | Required | Description                                                                               |
| -------------------- | ------- | -------- | ----------------------------------------------------------------------------------------- |
| `model`              | string  | Yes      | The model to buy, or a preset of the image kind.                                          |
| `prompt`             | string  | Yes      | The text that describes the image. At most 32,000 characters.                             |
| `input_references`   | array   | No       | Reference inputs of any kind, in one list. See [Reference inputs](https://inference.ai.exchange/docs/reference-inputs.md). |
| `n`                  | integer | No       | How many images, from 1 to 10. The default is 1.                                          |
| `size`               | string  | No       | A pixel size such as `1024x1024`, or `auto`.                                              |
| `resolution`         | string  | No       | A size tier: `512`, `768`, `1K`, `1.5K`, `2K` or `4K`.                                    |
| `aspect_ratio`       | string  | No       | `auto`, or a shape such as `16:9`.                                                        |
| `output_format`      | string  | No       | `png`, `jpeg` or `webp`.                                                                  |
| `quality`            | string  | No       | A quality name that the model lists in `parameters`.                                      |
| `background`         | string  | No       | `transparent`, `opaque` or `auto`.                                                        |
| `output_compression` | integer | No       | The compression of a `jpeg` or `webp` image, from 0 to 100.                               |
| `seed`               | integer | No       | A number that makes the picture repeatable.                                               |
| `stream`             | boolean | No       | `true` sends the answer as events.                                                        |
| `response_format`    | string  | No       | `url` or `b64_json`. If you omit it, any offer can answer.                                |
| `exchange`           | object  | No       | Purchasing rules, such as `cap_usd`. See [Purchasing rules](https://inference.ai.exchange/docs/rules.md).                  |

A request that sends `size`, `output_format` or `quality` goes only to offers that serve that value, so it does not fail after the choice. If you omit `size`, the provider uses its default, and every offer for the model can answer.

## Errors

| Error                        | Cause                                                                                                                                         | Fix                                                                |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `400 unsupported_model`      | You named a router model that ranks by price.                                                                                                 | Name an image model, or an Auto router model.                      |
| `400 unsupported_rule`       | A rule that limits a price or a speed in tokens.                                                                                              | Use `exchange.limits.price.unit_usd` and `cap_usd`.                |
| `400 unsupported_parameter`  | No offer serves your `size`, `resolution`, `aspect_ratio` or `output_format`, or none declared a control. `param` names the field or feature. | Omit it, or read `image_sizes` and `parameters` on the model card. |
| `400 conflicting_parameters` | `size` has another tier or shape, `background` `transparent` with `jpeg`, `output_compression` with `png`, or `stream` with `n` above 1.      | Send one of the two fields, or change the other.                   |
| `400`                        | No offer takes all of your references.                                                                                                        | Read `eligibility.excluded`.                                       |

See [Errors](https://inference.ai.exchange/docs/errors.md) for each code and exclusion reason.

## Next steps

- [Reference inputs](https://inference.ai.exchange/docs/reference-inputs.md): start from images, audio or video.
- [Video generation](https://inference.ai.exchange/docs/video-generation.md): the same job for video.
- [Modalities](https://inference.ai.exchange/docs/modalities.md): the map of all modalities.
