---
title: Images
description: The POST /v1/images/generations call — the prompt, how many images, the size, the response shape, and why generation is counted in images rather than tokens.
keywords: images, generation, b64_json, size, n
group: gateway
---

## Quick {#quick keywords="curl, python, node, prompt"}

One operation makes images from a text prompt. The request, response and error envelopes are OpenAI-compatible.

:::code-group
```bash title=curl
curl https://api.kumorouter.com/v1/images/generations \
  -H "Authorization: Bearer $KUMO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "<model>",
    "prompt": "A lighthouse in fog, ink on paper",
    "n": 1,
    "size": "1024x1024"
  }'
```
```python title=Python
import base64
import os

import requests

response = requests.post(
    "https://api.kumorouter.com/v1/images/generations",
    headers={"Authorization": f"Bearer {os.environ['KUMO_API_KEY']}"},
    json={
        "model": "<model>",
        "prompt": "A lighthouse in fog, ink on paper",
        "n": 1,
        "size": "1024x1024",
    },
    timeout=300,
)

for index, entry in enumerate(response.json()["data"]):
    if "b64_json" in entry:
        with open(f"image-{index}.png", "wb") as file:
            file.write(base64.b64decode(entry["b64_json"]))
    else:
        print(entry["url"])
```
```javascript title=Node
const response = await fetch("https://api.kumorouter.com/v1/images/generations", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.KUMO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "<model>",
    prompt: "A lighthouse in fog, ink on paper",
    n: 1,
    size: "1024x1024",
  }),
});

const body = await response.json();
const images = body.data.map((entry) =>
  entry.b64_json ? Buffer.from(entry.b64_json, "base64") : entry.url,
);
```
:::

## Request members {#request keywords="prompt, n, size, model"}

| Member | What it is |
| --- | --- |
| `model` | Required. The public model name to generate with. |
| `prompt` | Required. The prompt to generate an image from; up to 32,000 characters. |
| `n` | How many images to generate, from 1 to 10. Absent, it is one. |
| `size` | The image size as `WIDTHxHEIGHT`, for example `1024x1024`. |

This operation has no other members. Edits and variations are not served.

## The response {#response keywords="created, data, b64_json, url"}

| Reply member | What it is |
| --- | --- |
| `created` | When the images were generated, as a Unix timestamp in seconds. |
| `data` | The generated images, in the order the supplier answered them. |

A `data` entry carries `b64_json` — the image bytes, base64-encoded — or a `url` the image can be fetched from. Both are optional, so a client reads whichever arrived rather than the one it expected.

```json title=Response
{
  "created": 1756900000,
  "data": [{ "b64_json": "iVBORw0KGgoAAAANSUhEUg..." }]
}
```

A refusal arrives in the OpenAI-compatible envelope: `error.message`, `error.type` and `error.code`, with `param` always `null` — this surface never names a field of the request.

## How it is counted {#billing keywords="images, units, rate limits, estimate"}

Image generation is counted **in images, not tokens**, and is debited through the same admission, reservation and settlement chain as every other model surface.

One consequence matters for rate limits. An operation that reports no tokens at all keeps the estimate it was charged before it went upstream: correcting it to zero would refund the whole estimate and let this surface cost nothing against the token ceiling.

> [How the ceilings work →](/en/limits)

## Next {#next keywords="errors, rate limits, catalog"}

:::cards
- [Errors](/en/errors) — the refusal envelope and the status codes.
- [Rate limits](/en/limits) — the request and token ceilings.
- [Model catalog](/en/models) — which models can generate images.
- [API reference](/en/api-reference) — the operation member by member, straight from the API description.
:::
