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
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"
}'
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.
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:
kindisimage_generationandmodalityisimage.image_sizeslists the pixel sizes that you can send assize. The tier and the shape come from these sizes. See Size, tier and shape.parameterslists each other control that at least one offer supports:output_format,quality,seed,background,output_compressionandstream. A name that is not a key is not supported, so omit it. Modalities describes each descriptor.pricinggives 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
- Send the request. The exchange picks the offers that can serve it.
- The exchange fixes the price cap. The price is for one image, so the cap is exactly the charge.
- The provider makes the images. The exchange dispatches the size that it resolved.
- 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.
{ "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 to1K. - 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
sizeas well and it has another tier or shape, the answer is400 conflicting_parametersand 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.
{ "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.
{ "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.
{ "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. 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.
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}'
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
{
"created": 1760000000,
"data": [{ "b64_json": "iVBOR…", "media_type": "image/png" }],
"exchange": { "...": "..." }
}
- Each image has
b64_jsonorurl, as the upstream of the provider returned it.media_typestates the MIME type.revised_promptappears when the upstream rewrote your prompt. - The exchange does not download or host your images. A
urlis the link of the provider, and it can expire. - The
exchangeblock is the receipt. It reportsimages, so you can compare the images that you paid for with the images that you got. See Receipts. - The answer and each event also have
mime_type, with the same value asmedia_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 and Modalities.
Images in a chat answer
Some chat models draw in their answer. Send modalities with image to POST /v1/chat/completions.
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.
{
"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
resolutionorimage_size, not both with different values. - Any other key gets
400 invalid_request, and the error names the key, such asimage_config.seed. - An offer wins only when it declared a pixel size that has the
size, the tier and the shape. An offer that declaredoutput.imageand 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_tokensbelow the size of one image, the model can stop before it draws. You pay for the tokens that it used. - Send
streamasfalse. A request withstream: trueandimageinmodalitiesgets400. /v1/messagesand/v1/responsesdo 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. |
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. |
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 for each code and exclusion reason.
Next steps
- Reference inputs: start from images, audio or video.
- Video generation: the same job for video.
- Modalities: the map of all modalities.