# Create images

Source: https://platform.lyrouter.com/ai_gateway/docs/images-generations/en
Last updated: 2026-10-19

Generates images from a text prompt, optionally with reference images for image-to-image. It is compatible with `images.generate` in the OpenAI Images API: the official OpenAI SDKs work once you set the Base URL to `https://api.lyrouter.com/v1`, and the image comes back in the same response, with no polling. Three families are available: Seedream 5.0, GPT-Image and Gemini Image. Parameters and the returned format differ by family; see the per-model sections below.

`POST /v1/images/generations`

## Authentication

Send your API key in a request header:

```text
Authorization: Bearer <API Key>
```

`Bearer` is case-sensitive. You can use `x-api-key: <API Key>` instead; when both are present, only `Authorization` is read. Create API keys on the workspace's **API Keys** page. Your API key is used only to authenticate with LYRouter and is never forwarded to the upstream provider. See [Base URLs and authentication](https://platform.lyrouter.com/ai_gateway/docs/call-gateway/en).

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `Content-Type` | Yes | `application/json` |
| `X-Gateway-Strict-Parameters` | No | When set to `true` (lowercase only), a request containing any parameter the gateway would drop returns 400 `unsupported_parameters` immediately, without calling the upstream and without charge. For which parameters are dropped, see "How each model is handled" below |

## Request body

The parameters shared by all models are listed below; whether each one takes effect for a given family is described in the model sections that follow.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | string | Yes | Image model ID, such as `bytedance/doubao-seedream-5.0-lite`. Only concrete model IDs work: a virtual model `@vmodel/<name>` or `lyrouter/auto` returns 403 on image endpoints. Find available models in the model marketplace, or call [List models](https://platform.lyrouter.com/ai_gateway/docs/models/en) |
| `prompt` | string | Yes | The prompt. May be omitted for layer decomposition on Seedream pro |
| `size` | string | No | Output size, written as width × height (such as `2048x2048`) or as a tier name (such as `2K`). Accepted forms and ranges differ by model; see the model sections |
| `n` | integer | No | Number of images. Forwarded unchanged to GPT-Image, up to 10. Seedream currently cannot use it to generate several images at once: `n` is forwarded to the upstream, but only 1 image comes back, with no error and no entry in `X-Gateway-Unsupported-Parameters`; send several requests if you need several images. Dropped for Gemini |
| `quality` | string | See description | Generation quality, used by the GPT-Image family; values differ by model, and it is required for `gpt-image-2`; dropped for Gemini |
| `response_format` | string | No | `url` or `b64_json`; takes effect only on Seedream (default `url`). GPT-Image and Gemini always return `b64_json`, and the gateway drops this parameter |
| `image` | string \| array | No | Reference images for image-to-image. Seedream accepts a public URL or `data:image/<format>;base64,<data>`; Gemini accepts them too, written as described in [Edit images](https://platform.lyrouter.com/ai_gateway/docs/images-edits/en) |
| `user` | string | No | End-user identifier. Forwarded unchanged to Seedream and GPT-Image; dropped for Gemini |

Other parameters not in the table are forwarded unchanged to Seedream and GPT-Image, and whether they take effect depends on the model; Gemini drops all of them.

### How each model is handled

The gateway decides per model how to handle the request body, which determines which parameters take effect:

| Model family | Model IDs | Handling | Parameters dropped by the gateway | Returned image |
| --- | --- | --- | --- | --- |
| Seedream 5.0 | `bytedance/doubao-seedream-5.0-pro`<br>`bytedance/doubao-seedream-5.0-lite` | Pass-through | None | `url` (default) or `b64_json` |
| GPT-Image | `openai/gpt-image-2`<br>`openai/gpt-image-2.5-flare`<br>`openai/gpt-image-2.5-sunburst` | Pass-through | `response_format`; the two 2.5 models also drop `stream` and `stream_options` | `b64_json` |
| Gemini Image | `google/gemini-3-pro-image`<br>`google/gemini-3.1-flash-image` | Contract translation | Every parameter except `model`, `prompt`, `size` and `image`; `size` is also dropped when it cannot be matched | `b64_json` |

- **Pass-through**: the body is forwarded unchanged with only the model name rewritten, so other parameters the provider supports can be sent as well. If the upstream does not accept a parameter, you get the upstream's own error.
- **Contract translation**: the gateway translates the parameters into the provider's format and drops those it cannot translate.
- Dropped parameters are listed in the `X-Gateway-Unsupported-Parameters` response header and the request still runs; with `X-Gateway-Strict-Parameters: true` it returns 400 instead. Parameters passed through to the upstream are not affected by this header.

For a comparison of model capabilities and use cases, see [Multimodal capabilities](https://platform.lyrouter.com/ai_gateway/docs/multimodal/en). The model marketplace is authoritative for which models exist; the sections below list each family's parameters and limits.

### Seedream 5.0 (pro / lite)

Seedream models on Volcengine Ark. Pass-through; returns an image URL by default; one generation takes about 20 s.

| Parameter | Type | Description |
| --- | --- | --- |
| `size` | string | Defaults to `2K`. Tier names are recommended, with the aspect ratio described in words in the `prompt`: `1K`, `1.5K` and `2K` on pro; `2K`, `3K` and `4K` on lite. You can also write `widthxheight`: pro requires total pixels within [921600, 4624220], lite requires at least 3686400 (the smallest square is `1920x1920`, so the common `1024x1024` is rejected), and the aspect ratio must be within [1/16, 16] on both. Do not mix the two forms |
| `response_format` | string | `url` (default) or `b64_json` |
| `image` | string \| array | Reference images: a public URL or `data:image/<format>;base64,<data>` (`<format>` must be lowercase), no more than 10 recommended; see the limits below |
| `output_format` | string | `png` or `jpeg`. With layer decomposition it controls only the base image; layers are always PNG |
| `background` | string | `opaque` (default) or `transparent`. Image-to-image only, with exactly 1 input image that has an alpha channel |
| `watermark` | boolean | `true` adds an "AI生成" (AI-generated) watermark in the bottom-right corner. The watermark is added by default when the parameter is omitted; send `false` if you do not want it |
| `layer_decomposition` | boolean | Pro only, default `false`. `true` splits 1 input image into 1 base image and up to 16 PNG layers with alpha channels; `image` becomes required and must be exactly 1 image, and `size` accepts tier names only (default `auto`) |
| `optimize_prompt_options.mode` | string | Pro only. `standard` (default) or `fast` (faster, slightly lower quality) |

Reference image limits:

| Limit | Image generation | Layer decomposition |
| --- | --- | --- |
| Format | jpeg, png, webp, bmp, tiff, gif, heic, heif | png, jpeg |
| Total pixels (width × height) | [196, 36000000] | [262144, 36000000] |
| Width and height | At least 14 px | — |
| Aspect ratio (width/height) | [1/16, 16] | [1/16, 16] |
| Size per image | ≤ 30 MB | ≤ 30 MB |
| Number of images | No more than 10 recommended; the provider currently does not reject more, but the output may be abnormal | Exactly 1 |

- Pro supports interactive editing: mark the position to change in the `prompt` with `<point>x y</point>` or `<bbox>x1 y1 x2 y2</bbox>` (coordinates normalized to 0–999).
- A layer decomposition response returns the base image and the layers together in `data[]`, with fields such as `z_index`, `bounding_box`, `name` and `description`; each request reserves 17 images of rate-limit quota and returns the unused part when it completes. For the pixel sizes of each tier, coordinate conversion and reassembly, see [Multimodal capabilities](https://platform.lyrouter.com/ai_gateway/docs/multimodal/en#seedream-5-0-pro-lite-volcengine-ark).
- Other Volcengine Ark parameters can be written directly in the body. With the OpenAI Python SDK, put them in `extra_body`, for example `extra_body={"watermark": False}`.

**Billing**: per image. Pro is tiered by resolution: 1K and 1.5K (≤ 2.61 megapixels) are one tier and 2K is another; layer decomposition has two more tiers (also split into 1K/1.5K and 2K) and is billed by the number of images actually produced (base image + each layer); pro reference images are billed as a separate item, with the first reference image in each request free and each additional one billed per image (this counts input reference images, not output images). Lite has a single tier. `1.5K` costs the same as `1K` and looks better, so prefer `1.5K` unless you have a reason not to.

```bash
curl https://api.lyrouter.com/v1/images/generations \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "bytedance/doubao-seedream-5.0-lite",
    "prompt": "A shiba inu in sunglasses on a surfboard, sunset sea, cinematic, wide landscape composition",
    "size": "2K",
    "watermark": false
  }'
```

### GPT-Image (2 / 2.5-flare / 2.5-sunburst)

Pass-through. Parameters match the OpenAI image API, and the result is always `b64_json`.

| Parameter | Type | Description |
| --- | --- | --- |
| `quality` | string | Values differ by model; see the table below. Pass it explicitly for every GPT-Image model |
| `size` | string | `widthxheight` only; tier names are not accepted (`4K` returns `Invalid size '4K'. Expected WIDTHxHEIGHT, for example '1824x1024'.`). Must meet the four hard constraints below, or the request returns 400 |
| `n` | integer | Number of images, up to 10 |
| `background` | string | `transparent`, `opaque` or `auto` |
| `output_format` | string | Output format. `gpt-image-2` accepts only `png` and `jpeg`, and `webp` returns 400 `Invalid value: 'webp'. Supported values are: 'png' and 'jpeg'.`; the 2.5 family accepts `png`, `jpeg` and `webp` |
| `moderation` | string | `auto` or `low` |
| `user` | string | End-user identifier |
| `response_format` | — | Dropped by the gateway and listed in the response header: this family always returns `b64_json`, and the upstream sometimes accepts the parameter and sometimes rejects it |
| `stream`, `stream_options` | — | Dropped by the gateway for the 2.5 family (an upstream stream makes the request fail); do not send `stream` to `gpt-image-2` either |

`quality` values:

| Model | Values | Default |
| --- | --- | --- |
| `gpt-image-2` | `low`, `medium`, `high`, `auto` | None; required: omitting it returns 400 intermittently (`INVALID_REQUEST_BODY`); `auto` behaves as `low` in measurements |
| `gpt-image-2.5-flare`, `gpt-image-2.5-sunburst` | `low`, `medium`, `high`, `xhigh`, `max`, `auto` | `low` (`auto` behaves like `low` in measurements) |

Whether `xhigh` and `max` are available depends on the upstream account, so they may be rejected; if you get `Invalid value: 'xhigh'`, use `high` instead.

The four hard constraints on `size`, checked by the provider; violating any one returns 400:

| Constraint | Provider error text | Examples |
| --- | --- | --- |
| Width and height both divisible by 16 | `Invalid size '<width>x<height>'. Width and height must both be divisible by 16.` | `1008x1008` works; `1254x1254` and `1312x1199` are rejected |
| Longest edge no more than 3840 | `Invalid size '<width>x<height>'. The longest edge must be less than or equal to 3840.` | `3840x2160` works; `4096x4096` and `1024x4096` are rejected |
| At least about 1 M pixels in total | `Invalid size '<width>x<height>'. Requested resolution is below the current minimum pixel budget.` | `1008x1008` (about 1.02 M) works; `512x1024` (about 0.52 M) is rejected |
| At most about 8.3 M pixels in total | `Invalid size '<width>x<height>'. Requested resolution exceeds the current pixel budget.` | `3840x2160` (about 8.29 M) works; `3840x2176` (about 8.36 M) is rejected |

- **Latency**: one generation took about 20–60 s in measurements, occasionally longer (about 83 s at most); an upstream 400 caused by a bad parameter came back within 2 s. Set the client timeout in minutes, at least 300 s.

**Billing**: per token, with input text, input image and output priced separately; the upstream reports text and image tokens separately in `usage.input_tokens_details`, so a prompt of the same length costs a different amount with and without an image. Cache hits have their own price; `usage` from the 2.5 family has no cache fields, so that item does not occur for it.

Output tokens depend mainly on `quality`: at `1024x1024`, the 2.5 family produces 196 output tokens at `low` and 7024 at `max`, so the top level is about 36 times the bottom one. A level with the same name is not the same amount on the two generations: `high` on the 2.5 family produces as many output tokens as `medium` on `gpt-image-2`, so compare models by tokens, not by level name. Also, for GPT-Image more pixels do not mean a higher cost (this applies to GPT-Image only): output tokens do not grow with pixel count; for example, `1024x1536` (about 1.57 million pixels) produces fewer output tokens than `1024x1024` (about 1.05 million pixels).

### Gemini Image (3 Pro / 3.1 Flash)

Contract translation: the gateway translates the request into Gemini's native `generateContent` format, uses only the parameters marked as applied below, and drops the rest, listing them in the response header. The result is always `b64_json` and cannot be a URL. One generation took 20–250 s in measurements (`gemini-3-pro-image`), and occasionally had not returned after 300 s.

| Parameter | Applied | Description |
| --- | --- | --- |
| `prompt` | Applied | Required; omitting it or sending it empty returns 400 `prompt is required for this model` |
| `size` | Applied | See the table below |
| `image` | Applied | Reference images, written as described in [Edit images](https://platform.lyrouter.com/ai_gateway/docs/images-edits/en) |
| `n` | Dropped | Send several requests if you need several images |
| `quality` | Dropped | Gemini has no equivalent parameter |
| `response_format` | Dropped | Only `b64_json` can be returned |
| `user` and other parameters | Dropped | — |

How you write `size` determines the aspect ratio and the resolution:

| Form | What the gateway does | Effect |
| --- | --- | --- |
| Tier name: `1K`, `2K`, `4K` | Passes it as the resolution tier | Controls resolution; the aspect ratio is the upstream default |
| `widthxheight`, such as `1376x768` | Matches the closest supported ratio (within 2%), then picks the resolution tier by rounding the long edge up | Controls both ratio and resolution |
| Ratio more than 2% off, such as `1000x700` | Sends none of `size` (neither ratio nor tier), and counts `size` as dropped | Returns 200, produces an image and is billed as usual, but with the upstream's default ratio and resolution; the only signal is the response header `X-Gateway-Unsupported-Parameters: size`. Returns 400 in strict mode |
| Ratio string, such as `16:9` | Passes it unchanged as a tier name | The upstream does not recognize it and the request returns 400 (`upstream_rejected`). For 16:9, write a size with that ratio, such as `1376x768` |

The provider rejects the `512` tier in production, on both models; do not use it.

Supported aspect ratios: `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:4`, `4:1`, `1:8`, `8:1`. When you write a width and height, the tier is picked by rounding the long edge up, capped at 4K; a long edge of 704 or less is mapped to `512` by the gateway and may be rejected as well, so keep the long edge above 704. The same tier produces different output tokens on the two models:

| Long edge | Tier | `gemini-3-pro-image` output tokens | `gemini-3.1-flash-image` output tokens |
| --- | --- | --- | --- |
| ≤ 1376 | `1K` | 1120 | 1120 |
| ≤ 2816 | `2K` | 1120 (the 2K tier has no effect; same as 1K) | 1680 |
| Larger (capped) | `4K` | 2000 | 2520 |

The output size is chosen by the provider from the ratio and resolution tiers and does not equal the requested width and height; for example, `gemini-3-pro-image` returns a `5504x3072` image for a `4096x2304` request. Rely on the actual width and height of the returned image.

For example, `1024x1024` maps to 1:1 + `1K`, and `2048x2048` maps to 1:1 + `2K`. For 16:9 at 4K, write `4096x2304`; do not copy `5632x3072`: its ratio is 1.833, more than 2% off 16:9, so the whole `size` is dropped. In production, send `X-Gateway-Strict-Parameters: true` so that such a request returns 400 instead.

**Billing**: per token, with input, output and thinking priced separately; reference images count toward "input". Thinking tokens appear only when the upstream returns them (`gemini-3-pro-image`), in `usage.output_tokens_details.reasoning_tokens`, and `usage.total_tokens` includes them. Thinking tokens vary from call to call and are decided by the model: production measurements range from 115 to 316, with a median of about 220, about 1.5% of a call's cost. Budget about 220 per call.

```json
{
  "data": [{ "b64_json": "iVBORw0KGgoAAAANSUhEUgAA…" }],
  "usage": {
    "input_tokens": 7,
    "output_tokens": 1120,
    "total_tokens": 1339,
    "output_tokens_details": { "reasoning_tokens": 212 }
  }
}
```

## Request examples

The examples below use `openai/gpt-image-2` and save the returned Base64 image to a file. With Seedream, the image comes back as a URL by default: read `data[0].url` and download it promptly.

**Request examples**

cURL:
```bash
curl https://api.lyrouter.com/v1/images/generations \
  --max-time 300 \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-image-2",
    "prompt": "A shiba inu in sunglasses on a surfboard, sunset sea, cinematic",
    "size": "1024x1024",
    "quality": "medium"
  }'
```

Python:
```python
import base64
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["LYROUTER_API_KEY"],
    base_url="https://api.lyrouter.com/v1",
    timeout=300,
)
result = client.images.generate(
    model="openai/gpt-image-2",
    prompt="A shiba inu in sunglasses on a surfboard, sunset sea, cinematic",
    size="1024x1024",
    quality="medium",
)
with open("shiba.png", "wb") as f:
    f.write(base64.b64decode(result.data[0].b64_json))
print(result.usage)
```

Node.js:
```js
import { writeFileSync } from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.LYROUTER_API_KEY,
  baseURL: "https://api.lyrouter.com/v1",
  timeout: 300_000,
});
const result = await client.images.generate({
  model: "openai/gpt-image-2",
  prompt: "A shiba inu in sunglasses on a surfboard, sunset sea, cinematic",
  size: "1024x1024",
  quality: "medium",
});
writeFileSync("shiba.png", Buffer.from(result.data[0].b64_json, "base64"));
console.log(result.usage);
```

## Response

A successful request returns an object in the OpenAI Images format, with the images in the top-level `data` array; an error returns an `error` object. The two 200 examples below are Seedream (returning a URL) and GPT-Image (returning Base64); the 400 is Gemini receiving parameters it does not support with strict mode on; the 403 means the model is not enabled, or a virtual model or Auto was used.

**Response examples**

200 (url):
```json
{
  "model": "doubao-seedream-5-0-260128",
  "created": 1789454321,
  "data": [
    {
      "url": "https://ark-acg-cn-beijing.tos-cn-beijing.volces.com/…",
      "size": "1920x1920"
    }
  ],
  "usage": {
    "generated_images": 1,
    "output_tokens": 14400,
    "total_tokens": 14400
  }
}
```

200 (b64_json):
```json
{
  "created": 1791950400,
  "data": [
    { "b64_json": "iVBORw0KGgoAAAANSUhEUgAA…" }
  ],
  "size": "1024x1024",
  "quality": "medium",
  "output_format": "png",
  "background": "opaque",
  "usage": {
    "input_tokens": 29,
    "input_tokens_details": { "text_tokens": 29, "image_tokens": 0 },
    "output_tokens": 1756,
    "total_tokens": 1785
  }
}
```

400:
```json
{
  "error": {
    "message": "unsupported parameters for this model: n,quality",
    "type": "invalid_request_error",
    "code": "unsupported_parameters"
  }
}
```

403:
```json
{
  "error": {
    "message": "Model is not available in the model plaza",
    "type": "access_denied",
    "code": "model_not_available"
  }
}
```

| Field | Description |
| --- | --- |
| `created` | Unix timestamp (seconds). Not returned by Gemini |
| `model` | The provider-side model name (returned by Seedream), such as `doubao-seedream-5-0-260128`; it differs from the model ID in the request |
| `data` | Array of results; it is an array even for a single image |
| `data[].url` | Image download URL, returned by Seedream by default. It is a temporary URL signed by the provider and expires after about 24 hours; download and store the image promptly |
| `data[].b64_json` | The image encoded in Base64. Always returned by GPT-Image and Gemini; returned by Seedream when `response_format` is `b64_json` |
| `data[].size` | The size actually generated (returned by Seedream) |
| `data[].output_format` | The image format (returned by Seedream pro) |
| `size`, `quality`, `output_format`, `background` | The generation settings actually used (returned by GPT-Image) |
| `usage` | Metering information; the fields vary by model, see the table below |

### Fields in usage

| Model family | Fields in `usage` | Metered by |
| --- | --- | --- |
| Seedream | `generated_images` (number of images), `output_tokens`, `total_tokens`; pro also returns `input_images` (number of input reference images) when reference images are sent | Image |
| GPT-Image | `input_tokens`, `input_tokens_details.text_tokens` / `image_tokens`, `output_tokens`, `total_tokens` | Token |
| Gemini | `input_tokens`, `output_tokens`, `total_tokens`, plus `output_tokens_details.reasoning_tokens` when there are thinking tokens | Token |

Do not assume a field is always present; check before reading it. For how each model is billed, see the model sections above; prices are those shown in the model marketplace in the console, and the actual charge for each call is in the [request logs](https://platform.lyrouter.com/ai_gateway/docs/logs/en).

### Response headers

| Header | Description |
| --- | --- |
| `X-Gateway-Unsupported-Parameters` | Present when the gateway dropped parameters; the value is a comma-separated list of parameter names, such as `n,quality`. The request still ran, and the listed parameters did not take effect |

When quotas are configured, the response also carries the `x-aigw-quota-*` headers; see [Gateway headers and extensions](https://platform.lyrouter.com/ai_gateway/docs/gateway-headers/en).

To read the dropped parameters (the Python and Node.js examples reuse `client` from the examples above):

**Reading dropped parameters**

cURL:
```bash
curl -sS -D - -o result.json https://api.lyrouter.com/v1/images/generations \
  --max-time 300 \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"google/gemini-3.1-flash-image","prompt":"A single green leaf, flat design","size":"1376x768","n":2}'
# The response headers include: X-Gateway-Unsupported-Parameters: n
```

Python:
```python
raw = client.images.with_raw_response.generate(
    model="google/gemini-3.1-flash-image",
    prompt="A single green leaf, flat design",
    size="1376x768",
    n=2,
)
print(raw.headers.get("X-Gateway-Unsupported-Parameters"))  # n
result = raw.parse()
```

Node.js:
```js
const { data: result, response } = await client.images
  .generate({
    model: "google/gemini-3.1-flash-image",
    prompt: "A single green leaf, flat design",
    size: "1376x768",
    n: 2,
  })
  .withResponse();
console.log(response.headers.get("X-Gateway-Unsupported-Parameters")); // n
```

## Gateway behavior

Compared with calling the upstream directly, calling through LYRouter differs in these ways:

- **Only the requested model is used**: image endpoints never switch to another model. When the same model has several channels, the gateway tries the other channels in turn after a failure; virtual models, Auto smart routing and fallback to the workspace's Default Model do not apply to image endpoints. See [Retries and fallback](https://platform.lyrouter.com/ai_gateway/docs/fallback/en).
- **Dropped parameters and strict mode**: the gateway drops only the parameters listed in "How each model is handled" above and lists them in the `X-Gateway-Unsupported-Parameters` response header. In production, check this header, or send `X-Gateway-Strict-Parameters: true` so that a request containing a parameter that would be dropped returns 400 immediately, without calling the upstream and without charge. With the SDKs, pass it as `extra_headers={"X-Gateway-Strict-Parameters": "true"}` (Python) or the request option `{ headers: { "X-Gateway-Strict-Parameters": "true" } }` (Node.js). Parameters passed through to the upstream are not affected by this header; the upstream decides whether to accept them.
- **Errors declared in a successful upstream response**: when Gemini's upstream rejects a request, it may return HTTP 200 with no image. The gateway rewrites this into an error response, taking the status code from the error the upstream declared: when the upstream rejected the request, `error.code` is `upstream_rejected` and `error.message` is the upstream's original text; when no image was produced, the code is `upstream_no_artifact` with `upstream_finish_reason` (such as `NO_IMAGE` or `SAFETY`). `upstream_status` keeps the upstream's original 200. These errors are not retried on another channel; change the prompt or parameters as indicated:

  ```json
  {
    "error": {
      "message": "upstream produced no image (finishReason: NO_IMAGE)",
      "type": "invalid_request_error",
      "code": "upstream_no_artifact",
      "upstream_status": 200,
      "upstream_finish_reason": "NO_IMAGE",
      "generated_by": "ai-gateway"
    }
  }
  ```

- **Upstream errors are returned unchanged**: parameter errors for pass-through models come from the upstream, with the upstream's format and error codes; Seedream, for example, returns `{"error":{"code":"InvalidParameter","message":"..."}}`. When the upstream returns an error status with no body, the gateway fills in an error body with `upstream_status` and `generated_by: "ai-gateway"`, plus `upstream_request_id` when the upstream supplied a request ID; give that ID to the provider when reporting an issue.
- **Images are not stored in logs**: Base64 images in requests and responses are replaced in the logs with a `[base64 <bytes>B #<CRC32>]` summary. Not seeing the image in the log details does not mean the request failed; the image is in the response body. For models that return a URL, the URL is kept in the logs.
- **Timeouts**: image endpoints are synchronous. Seedream takes about 20 s, the GPT-Image family about 20–60 s in measurements, and Gemini 20–250 s in measurements, occasionally more than 300 s. Set the client timeout in minutes, at least 300 s, rather than the 30 s you use for text; after a client timeout, do not resubmit right away; first check the result of the previous request in the [request logs](https://platform.lyrouter.com/ai_gateway/docs/logs/en).

## Common errors

Errors returned by the gateway take the form `{"error":{"message":"...","type":"...","code":"..."}}`; parameter errors for pass-through models come from the upstream in the upstream's format.

| Status code | `error.message` | `error.code` | Cause and fix |
| --- | --- | --- | --- |
| 400 | `you must provide a model parameter` | `missing_model` | No `model` was given and the workspace has no Default Model. Always specify the model ID on image endpoints |
| 400 | `unsupported parameters for this model: <names>` | `unsupported_parameters` | `X-Gateway-Strict-Parameters: true` was sent and the request contains parameters that would be dropped. Remove the listed parameters, or use a model that supports them |
| 400 | `prompt is required for this model` | `invalid_parameter` | No `prompt`, or an empty one, was sent to Gemini |
| 400 | The upstream's text, such as `Request contains an invalid argument.` | `upstream_rejected` | Gemini's upstream rejected the request, for example because `size` was written as `16:9`. Fix the parameters as the text says |
| 400 | `upstream produced no image (finishReason: <reason>)` | `upstream_no_artifact` | Gemini produced no image. Change the prompt or content according to `upstream_finish_reason` and retry |
| 400 | ``The parameter `size` specified in the request is not valid: image size must be at least 3686400 pixels`` | `InvalidParameter` | The size is below Seedream lite's minimum. Use the `2K` tier or a larger size, such as `1920x1920` |
| 400 | `Invalid size '<width>x<height>'. Width and height must both be divisible by 16.` | — | The GPT-Image width or height is not a multiple of 16; returned by the upstream |
| 400 | `Invalid size '<width>x<height>'. The longest edge must be less than or equal to 3840.`, `Invalid size '<width>x<height>'. Requested resolution is below the current minimum pixel budget.`, `Invalid size '<width>x<height>'. Requested resolution exceeds the current pixel budget.` | — | The GPT-Image `size` has a longest edge above 3840, or its total pixel count is not between about 1 M and about 8.3 M; returned by the upstream. Adjust it against the four hard constraints on `size` above |
| 400 | `Invalid size '4K'. Expected WIDTHxHEIGHT, for example '1824x1024'.` | — | A tier name was sent to GPT-Image. Write it as `widthxheight` |
| 400 | `Invalid value: 'xhigh'` | — | The upstream account does not support the `xhigh` / `max` levels of `quality` (availability depends on the upstream account). Use `high` |
| 400 | The upstream's text | `INVALID_REQUEST_BODY` | The GPT-Image request body is invalid; first check that `quality` is set: omitting it on `gpt-image-2` returns 400 intermittently |
| 400 | `This model does not support the images API` | `unsupported_model_api` | The interfaces the model declares (`api_types` in [List models](https://platform.lyrouter.com/ai_gateway/docs/models/en)) do not include the image endpoints, for example a chat model used on this endpoint. A chat model that declares no `api_types` does not trigger this error: the request is forwarded upstream and may return 404 `upstream_not_found`, or a 200 with no image (such as `{"data":{"content":null}}`). Use only image models on image endpoints |
| 403 | `Model is not available in the model plaza` | `model_not_available` | The workspace has not enabled this model, or `@vmodel/<name>` or `lyrouter/auto` was used. Ask an administrator to enable it in the model marketplace, or use a concrete model ID that is enabled |

- For 401 / 403 authentication issues, see [401 / 403: authentication and access](https://platform.lyrouter.com/ai_gateway/docs/auth-errors/en); for 429, see [429: rate limits and quotas](https://platform.lyrouter.com/ai_gateway/docs/rate-errors/en).
- 500 / 504 mean the connection to the upstream failed or the wait for the upstream timed out; the response body is an HTML page rather than JSON. See [Timeouts and interrupted streams](https://platform.lyrouter.com/ai_gateway/docs/stream-errors/en).
- For the full list, see [Errors and request identifiers](https://platform.lyrouter.com/ai_gateway/docs/errors/en).
