---
url: https://docs.freetheai.org/api/images.md
description: >-
  POST /v1/images/generations - generate images as a task, then fetch them.
  Provider image models are priced per image.
---

# Images

Generates images from a prompt. Image models run as tasks, so a slow model never runs into a request time limit: create the task, poll it, and read the images when it completes. Paid image models from providers are priced per image; see [freetheai.org/models](https://freetheai.org/models?kind=images).

`POST https://api.freetheai.org/v1/images/generations`, then `GET https://api.freetheai.org/v1/images/generations/{id}`

## Create the task

::: code-group

```bash [curl]
curl https://api.freetheai.org/v1/images/generations \
  -H "Authorization: Bearer $FTAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "MODEL_ID", "prompt": "A lighthouse at dawn, watercolor", "n": 1}'
```

```python [Python]
import base64, os, time
import requests

base = "https://api.freetheai.org/v1"
headers = {"Authorization": f"Bearer {os.environ['FTAI_KEY']}"}

task = requests.post(f"{base}/images/generations", headers=headers,
                     json={"model": "MODEL_ID", "prompt": "A lighthouse at dawn, watercolor", "n": 1}).json()
while task["status"] in ("queued", "in_progress"):
    time.sleep(3)
    task = requests.get(f"{base}/images/generations/{task['id']}", headers=headers).json()

for index, image in enumerate(task.get("data", [])):
    with open(f"image-{index}.png", "wb") as out:
        out.write(base64.b64decode(image["b64_json"]))
```

```ts [TypeScript]
import { writeFile } from 'node:fs/promises'

const base = 'https://api.freetheai.org/v1'
const headers = { 'Authorization': `Bearer ${process.env.FTAI_KEY}`, 'Content-Type': 'application/json' }

let task = await (await fetch(`${base}/images/generations`, {
  method: 'POST', headers, body: JSON.stringify({ model: 'MODEL_ID', prompt: 'A lighthouse at dawn, watercolor', n: 1 }),
})).json()
while (task.status === 'queued' || task.status === 'in_progress') {
  await new Promise(resolve => setTimeout(resolve, 3000))
  task = await (await fetch(`${base}/images/generations/${task.id}`, { headers })).json()
}
for (const [index, image] of (task.data ?? []).entries()) {
  await writeFile(`image-${index}.png`, Buffer.from(image.b64_json, 'base64'))
}
```

:::

| Field | Type | Description |
| --- | --- | --- |
| `model` | string | An image model ID from [`GET /v1/models`](/api/models) (`"type": "image"`). |
| `prompt` | string | What to draw. |
| `n` | integer | Optional. How many images (default 1). |
| `size` | string | Optional. For example `1024x1024`, when the model supports it. |
| `response_format` | string | Optional. `b64_json` (default) or `url` (links on the FreeTheAI API). |

The create call answers at once with the task:

```json
{
  "id": "task_...",
  "object": "image.generation",
  "status": "queued",
  "progress": 0,
  "poll_url": "/v1/images/generations/task_..."
}
```

## Get the images

Poll `GET /v1/images/generations/{id}` every few seconds. `status` moves from `queued` to `in_progress` to `completed` or `failed`. A completed task carries `data`: `[{ "b64_json": "..." }]`, or `[{ "url": "..." }]` with `"response_format": "url"`. A failed one carries `error` with a `code` and a `message`.

Images from provider models are kept for one read, or one hour if never read: save them the first time. A second read answers `410`.

You are charged for the images you receive, never more than `n`. The request first holds the price of `n` images on your wallet; a failed or expired task costs nothing.

## Errors

| Status | Meaning |
| --- | --- |
| `400` | A field has a value the endpoint does not take (the message names it). |
| `401` | Missing or invalid API key. |
| `402` | Not enough wallet balance, or the key's budget is used ([paid usage errors](/errors#billing)). |
| `404` | No model has that ID, none of the providers you follow serve it, or the task ID is not yours. |
| `410` | `these images were already fetched or have expired`. Images are handed out once. |
| `503` | The provider is busy. Retry; nothing is charged. |
