# Image generation

> Generate and edit images with POST /v1/images and OpenAI's /v1/images/generations and /edits: models, parameters, pricing, and images in chat.

```text
POST https://api.clonvo.com/v1/images
POST https://api.clonvo.com/v1/images/generations
POST https://api.clonvo.com/v1/images/edits
```

Generate and edit images. `POST /v1/images` is OpenRouter's Image API. `POST /v1/images/generations` and `/v1/images/edits` are OpenAI's, so the OpenAI SDKs' `images.generate()` and `images.edit()` work by changing the base URL. All three use the same API key, credits, your own provider keys and spending limits as chat completions, and return images as base64. Image models only serve these endpoints, except Gemini's, which also answer chat completions (see [Images in chat](#images-in-chat)); sending any other image model to chat completions returns `400` with the code `model_not_chat`.

## Models

| Model id | Provider | Sizes | Images | References | Price |
| --- | --- | --- | --- | --- | --- |
| `openai/gpt-image-2.6-sunburst` | OpenAI | 1K, 2K, 4K or pixels | Up to 10 | Up to 16 | $5.00 input, $8.00 image input, $30.00 image output per 1M tokens |
| `openai/gpt-image-2.6-flare` | OpenAI | 1K, 2K, 4K or pixels | Up to 10 | Up to 16 | $5.00 input, $8.00 image input, $30.00 image output per 1M tokens |
| `openai/gpt-image-2.5-sunburst` | OpenAI | 1K, 2K, 4K or pixels | Up to 10 | Up to 16 | $5.00 input, $8.00 image input, $30.00 image output per 1M tokens |
| `openai/gpt-image-2.5-flare` | OpenAI | 1K, 2K, 4K or pixels | Up to 10 | Up to 16 | $5.00 input, $8.00 image input, $30.00 image output per 1M tokens |
| `google/gemini-nano-banana-2.1` | Google AI Studio | 1K, 2K, 4K | Up to 4 | Up to 14 | $1.50 input, $7.50 text and thinking output, $30.00 image output per 1M tokens |
| `google/gemini-3.1-flash-lite-image` | Google AI Studio | 1K | Up to 4 | Up to 14 | $0.25 input, $1.50 text and thinking output, $30.00 image output per 1M tokens |
| `google/gemini-3-pro-image` | Google AI Studio | 1K, 2K, 4K | Up to 4 | Up to 14 | $2.00 input, $12.00 text and thinking output, $120.00 image output per 1M tokens |
| `x-ai/grok-imagine-image-2.0` | xAI | 1K, 1.5K, 2K | Up to 10 | Up to 5 | 1K low $0.04, 1.5K low $0.05, 2K low $0.06, 1K medium $0.06, 1.5K medium $0.07, 2K medium $0.08 per image, plus $0.01 per reference image |
| `meta/muse-image-1.0` | Meta | aspect ratio only | Up to 10 | Up to 10 | $0.01 per image |
| `qwen/qwen-image-3-pro` | Alibaba Cloud (Qwen) | 1K, 2K or pixels | Up to 6 | Up to 3 | 1K $0.04, 2K $0.075 per image, plus $0.003 per reference image |
| `qwen/qwen-image-3` | Alibaba Cloud (Qwen) | 1K, 2K or pixels | Up to 6 | Up to 3 | $0.03 per image, plus $0.003 per reference image |
| `qwen/qwen-image-2.1-pro` | Alibaba Cloud (Qwen) | 1K, 2K or pixels | Up to 6 | Up to 10 | $0.04 per image |
| `qwen/qwen-image-2.1-turbo` | Alibaba Cloud (Qwen) | 1K, 2K or pixels | Up to 6 | Up to 10 | $0.016 per image |

## Generate an image

```ts
import fs from "node:fs";
import OpenAI from "openai";

const client = new OpenAI({ baseURL: "https://api.clonvo.com/v1", apiKey: process.env.CLONVO_API_KEY });

const result = await client.images.generate({
  model: "openai/gpt-image-2.6-flare",
  prompt: "A watercolor fox in a snowy forest",
  size: "1536x1024",
  quality: "low",
});
fs.writeFileSync(`fox.${result.output_format ?? "png"}`, Buffer.from(result.data[0].b64_json, "base64"));
```

Or with OpenRouter's request, which takes a resolution tier and an aspect ratio for any model:

```bash
curl https://api.clonvo.com/v1/images \
  -H "Authorization: Bearer $CLONVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "google/gemini-nano-banana-2.1", "prompt": "A watercolor fox in a snowy forest", "resolution": "2K", "aspect_ratio": "16:9"}'
```

| Parameter | Type | Description |
| --- | --- | --- |
| `model` | string | **Required.** A model id from the table above. |
| `prompt` | string | **Required.** What to draw. |
| `n` | integer | How many images, 1 to the model's maximum (default 1). Providers may return fewer; only the images returned are billed. Gemini makes one image per call, so `n` images are `n` calls. |
| `resolution` | string | A tier: `1K`, `1.5K`, `2K` or `4K`, as the model takes. |
| `aspect_ratio` | string | `1:1`, `16:9`, `9:16`, `3:2` and so on, or `auto`. `GET /v1/images/models` lists each model's ratios. |
| `size` | string | `auto`, a tier (`2K`), or pixels (`1024x1024`). OpenAI and Qwen render the pixels asked for, within their limits; Meta takes the ratio only; Gemini and xAI get the nearest tier and ratio. Pixels can't be combined with `resolution` or `aspect_ratio`. |
| `quality` | string | OpenAI: `auto`, `low`, `medium`, `high`, `xhigh` or `max`. xAI: `auto`, `low` or `medium`. |
| `output_format` | string | OpenAI: `png`, `jpeg` or `webp`. Meta: `webp`, `png` or `jpeg`. The other models choose their format; `media_type` says which. GIF and SVG aren't offered. |
| `background` | string | OpenAI: `auto`, `transparent` or `opaque`. Transparent needs `png` or `webp`. |
| `output_compression` | integer | OpenAI, 0 to 100, for `jpeg` and `webp`. |
| `seed` | integer | Gemini and Qwen. |
| `input_references` | array | Reference images to edit or draw from: `[{"type": "image_url", "image_url": {"url": "https://… or data:image/png;base64,…"}}]`, up to the model's limit. |
| `response_format` | string | `/v1/images/generations` only: `b64_json`. `url` returns `400`; Clonvo always returns base64. |
| `provider` | object | **Extension.** `order` and `allow_fallbacks` choose between your own key and credits (see [Fallbacks and routing](https://clonvo.com/docs/fallbacks.md)). |

A parameter the model doesn't have (`quality` on Meta, `seed` on OpenAI) is ignored, as on OpenRouter. A value the model doesn't take returns `400` listing the ones it does. `stream` returns `400`: images come back in one response. A request carries at most 30 MB.

## Response

`POST /v1/images` answers in OpenRouter's shape:

```json
{
  "created": 1794482337,
  "data": [{ "b64_json": "<base64>", "media_type": "image/jpeg" }],
  "usage": { "prompt_tokens": 8, "completion_tokens": 1413, "total_tokens": 1421, "completion_tokens_details": { "image_tokens": 1120 }, "cost": 0.0340415, "is_byok": false },
  "model": "google/gemini-3.1-flash-lite-image",
  "provider": "Google AI Studio"
}
```

`/v1/images/generations` and `/v1/images/edits` answer in OpenAI's shape, with `output_format` and the Images API's token usage, plus `cost`, `is_byok`, `model` and `provider`:

```json
{
  "created": 1794482337,
  "data": [{ "b64_json": "<base64>" }],
  "output_format": "jpeg",
  "usage": { "input_tokens": 12, "input_tokens_details": { "image_tokens": 0, "text_tokens": 12 }, "output_tokens": 196, "total_tokens": 208, "cost": 0.00594, "is_byok": false },
  "model": "openai/gpt-image-2.5-flare",
  "provider": "OpenAI"
}
```

`usage.cost` is what the request cost. Models priced per image report no tokens. Every response has the `x-clonvo-generation-id` header.

## Edit an image

Send the images to edit as files, as with OpenAI:

```ts
const edited = await client.images.edit({
  model: "openai/gpt-image-2.5-flare",
  image: [fs.createReadStream("fox.png")],
  prompt: "Make it night, with a full moon",
});
```

Or as JSON: `/v1/images/edits` takes `images: [{"image_url": "https://… or data:…"}]`, and `/v1/images` takes `input_references`. Every model with a reference limit in the table can edit. Masks aren't supported; describe the area to change in the prompt.

## Images in chat

Gemini's image models also answer chat completions, as on OpenRouter. Ask for images with `modalities`, and set the size with `image_config`:

```ts
const completion = await client.chat.completions.create({
  model: "google/gemini-nano-banana-2.1",
  modalities: ["image", "text"],
  image_config: { aspect_ratio: "16:9", image_size: "2K" },
  messages: [{ role: "user", content: "Draw a lighthouse at dusk." }],
});
const url = completion.choices[0].message.images?.[0]?.image_url.url; // "data:image/jpeg;base64,…"
```

Images arrive in `choices[0].message.images` as `{"type": "image_url", "image_url": {"url": "data:image/…;base64,…"}}`, and in `delta.images` when streaming. Other models return `400` when `modalities` asks for images.

## Pricing

Each model is billed the way its provider bills it: by tokens (OpenAI and Gemini, with image tokens at their own rate), per image returned (Meta and Qwen, plus a fee per reference image on Qwen Image 3), or at the cost the provider reports for the request (xAI, by resolution and quality). Billing is all-or-nothing: a generation that fails, is blocked or is cancelled returns an error and costs nothing. While a request runs, your balance must cover its most expensive outcome: `n` images at the dearest tier, or the model's most output tokens.

## Listing image models

`GET /v1/images/models` lists every image model in OpenRouter's discovery shape: `supported_parameters` maps each request field to what it takes (`{"type": "enum", "values": [...]}`, `{"type": "range", "min", "max"}` or `{"type": "boolean"}`; an absent field isn't supported), and `pricing` lists each billable line (`input_text`, `input_image`, `output_text`, `output_image`, per `token` or per `image`, with a `variant` for tiered prices). `GET /v1/models?output_modalities=image` lists them as model objects; they aren't in the default list, which holds chat models.

## Errors

Non-image models return `400` `model_wrong_kind`; values a model doesn't take return `400` with `unsupported_parameter`, and too many reference images `too_many_images`; requests over 30 MB return `413`. An image the provider blocks returns `400` (`content_filter` on Gemini, the provider's own code otherwise). Everything else is as for chat (see [Errors](https://clonvo.com/docs/errors.md)).