# Image generation

> Generate and edit images with /v1/images/generations and /v1/images/edits — runnable scripts that save PNGs to disk, plus the two request shapes, parameters, per-image billing, and error handling.

**Deliverable:** a `generate.py` and an `edit.py` that write a PNG to disk.

**Prerequisites:** `pip install requests`, a `MIRAPI_API_KEY`, and an image-capable model enabled for your key.

MirAPI exposes two OpenAI-compatible endpoints for images, both billed **per generated image**:

- `POST /v1/images/generations` — text-to-image.
- `POST /v1/images/edits` — edit an image from a source image plus an instruction.

There is no separate Anthropic or Gemini image-generation endpoint — those protocols only read images back into chat models (see [Vision inputs](/docs/guides/vision)).

:::tip
**Pick an image model.** Replace `deepseek-chat` in the examples below with an image model from your catalogue — filter by output type in the console, or check the model card's capability flags. Image endpoints reject models without image-generation capability.
:::

## Text-to-image

`/v1/images/generations` takes a chat-style `input.messages` payload with a `parameters` object:

```python
import base64
import os
import requests

BASE = "https://api.mirapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}

resp = requests.post(
    f"{BASE}/images/generations",
    headers=HEADERS,
    json={
        "model": "deepseek-chat",
        "input": {
            "messages": [
                {"role": "user", "content": [{"text": "A neon-lit street in the rain, cinematic"}]}
            ]
        },
        "parameters": {"size": "1024*1024", "prompt_extend": True, "watermark": False},
    },
    timeout=120,
)
resp.raise_for_status()

image = resp.json()["data"][0]
if image["b64_json"]:
    with open("output.png", "wb") as f:
        f.write(base64.b64decode(image["b64_json"]))
else:
    open("output.png", "wb").write(requests.get(image["url"], timeout=60).content)
print("Saved output.png")
```

## Edit an image

`/v1/images/edits` sends the source image and the instruction in the same message-style shape. The `image` field accepts a public HTTP(S) URL or a base64 data URI:

```python
import base64
import os
import requests

BASE = "https://api.mirapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}

# A base64 data URI of the source image:
with open("input.png", "rb") as f:
    data_uri = "data:image/png;base64," + base64.b64encode(f.read()).decode()

resp = requests.post(
    f"{BASE}/images/edits",
    headers=HEADERS,
    json={
        "model": "deepseek-chat",
        "input": {
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {"image": data_uri},
                        {"text": "Turn the sky into a starry night"},
                    ],
                }
            ]
        },
        "parameters": {"n": 1, "watermark": False},
    },
    timeout=120,
)
resp.raise_for_status()

image = resp.json()["data"][0]
if image["b64_json"]:
    with open("edited.png", "wb") as f:
        f.write(base64.b64decode(image["b64_json"]))
else:
    open("edited.png", "wb").write(requests.get(image["url"], timeout=60).content)
print("Saved edited.png")
```

## The two request shapes

The image endpoints accept two payload formats:

- **Message-style** — `/v1/images/generations` and `/v1/images/edits` (no trailing slash). Uses `input.messages` plus `parameters`, as in the scripts above.
- **OpenAI classic** — the trailing-slash variants `/v1/images/generations/` and `/v1/images/edits/`. Uses a top-level `prompt` for OpenAI SDK compatibility; edits send `image` + `prompt` as multipart form data.

```bash
curl https://api.mirapi.ai/v1/images/generations/ \
  -H "Authorization: Bearer $MIRAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-chat",
    "prompt": "A neon-lit street in the rain, cinematic",
    "n": 1,
    "size": "1024x1024"
  }'
```

The classic edits variant uses `multipart/form-data` with an `image` file, a `prompt`, and `response_format` (`url` or `b64_json`).

## Parameters

Message-style requests put everything under `parameters`:

| Parameter | Purpose |
|---|---|
| `model` | Model name exactly as shown in the catalogue |
| `input.messages[].content[].text` | The generation or edit instruction |
| `input.messages[].content[].image` | Source image for edits (URL or base64 data URI) |
| `parameters.size` | Output dimensions as a string, e.g. `1024*1024` |
| `parameters.negative_prompt` | Things to avoid in the output |
| `parameters.prompt_extend` | Let the model expand the prompt (`true` / `false`) |
| `parameters.watermark` | Request a visible watermark (`true` / `false`) |
| `parameters.n` | Number of images to return (edits; where supported) |

Not every model accepts every parameter, and many models only support `n: 1` — check the model card in the catalogue for supported values.

## Retrieving the image

The response contains an array of images:

```json
{
  "created": 1780000000,
  "data": [
    {
      "url": "https://…",
      "b64_json": null,
      "revised_prompt": "…"
    }
  ]
}
```

- **`url`** — a temporary URL to download the image.
- **`b64_json`** — the image as a base64 string.
- **`revised_prompt`** — the prompt actually used when the model rewrites yours.

To save the image to disk, prefer `b64_json` (no second request) or download `url` immediately — it is temporary.

## Errors and billing

**Billing.** Generation and editing are charged per image, deducted from your prepaid balance; `parameters.n` multiplies the cost, so start with `n: 1`. When the balance is exhausted, requests return `403`; topping up restores the same key immediately. Reconcile spend in the console billing log, or via `GET /api/usage/token` (account summary) and `GET /api/log/token` (per-request detail).

**Errors.** The gateway returns standard status codes: `401` (fix your key), `403` (balance, model whitelist, quota, or IP allowlist), `413` (request too large), `429` (rate limited — retry with jittered exponential backoff, 1s → 2s → 4s capped at ~30s; there is no `Retry-After` header), and `500` (transient upstream failure; idempotent requests are safe to retry). Error responses use the OpenAI envelope `{"error":{"message","type","param","code"}}`, and every message ends with a request ID — include it in any support ticket.

## Troubleshooting

- **Wrong model.** Image endpoints reject models without image-generation capability. Filter the catalogue by output type and use the model's exact `id`.
- **`403` on a valid key.** Check your balance first, then the model whitelist, quota, and IP allowlist on the key.
- **`url` no longer works.** Download it immediately after the request, or use `b64_json`.
- **`413` with base64.** Large images exceed the size limit — downscale before encoding.
- **`n` ignored or rejected.** Some models return a single image regardless; check the model card.

## Related links

- [Image generation guide](/docs/guides/images) — the endpoints in full
- [Vision inputs](/docs/guides/vision) — read images back into chat models
- [Text-to-video](/docs/tutorials/text-to-video) — the asynchronous video flow
- [Image-to-video](/docs/tutorials/image-to-video) — first-frame control
- [Models & pricing](/docs/models) — find image-capable models
- [Billing & top-ups](/docs/billing) — per-image and per-token pricing