> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inworld.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Image generation

> Generate an image, save it to a file, and use a reference image to guide the next result

Create images from a text prompt through Inworld Inference. Choose an image model, describe the result you want, and save the returned image in your application. To change an existing image, send it with instructions for what to preserve or change.

Image generation uses `POST https://api.inworld.ai/v1/chat/completions`. Set `modalities` to `["text", "image"]` to request image output. The examples below wait for the result in the same request and save every returned image to a file.

To ask a model to describe an image instead, see [Image and audio inputs](https://docs.inworld.ai/router/capabilities/vision.md).

## Choose a model

Browse [inworld.ai/models](https://inworld.ai/models) and select **Image**. Copy the API model ID and check the model's supported inputs, output settings and pricing. A model that can read images does not necessarily generate them.

The examples use `google-ai-studio/gemini-nano-banana-2.1`. You can replace it with another image-generation model. Start with the model's default output settings, then adjust them after your first image works.

### Try a prompt in the Playground

Open [Inworld Portal](https://platform.inworld.ai/) and choose **Image generation** in your workspace. Select a model, enter a prompt, and generate an image. Download the result you want to keep.

To compare models, use the same prompt and reference images for each. The Playground offers settings shared by the models you select. A setting that works for one model may need a different value when you switch models in your API request.

## Generate and save your first image

Create a **Standard** API key in [Inworld Portal](https://platform.inworld.ai/api-keys). Copy the complete Base64 credential and set it as-is; do not encode it again:

```bash
export INWORLD_API_KEY="<your Base64 API key>"
```

Run the examples on your server. Keep a Standard API key out of browser and mobile code; use a server proxy or [backend-minted token](https://docs.inworld.ai/portal/ephemeral-tokens.md#connect-a-client-through-your-backend) for those clients.

<Tabs>
<Tab title="Python">

Save this as `generate-image.py`. It uses Python 3.10 or later and the standard library, with no packages to install.

```python
import base64
import json
import os
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

payload = {
    "model": "google-ai-studio/gemini-nano-banana-2.1",
    "messages": [{
        "role": "user",
        "content": "A ceramic teapot on a wooden table by a window, soft morning light, product photograph",
    }],
    "modalities": ["text", "image"],
    "stream": False,
}

request = Request(
    "https://api.inworld.ai/v1/chat/completions",
    data=json.dumps(payload).encode(),
    headers={
        "Authorization": f"Basic {os.environ['INWORLD_API_KEY']}",
        "Content-Type": "application/json",
    },
)
try:
    with urlopen(request, timeout=300) as response:
        result = json.load(response)
except HTTPError as error:
    raise RuntimeError(
        f"HTTP {error.code}: {error.read().decode(errors='replace')}"
    ) from error

images = [
    image
    for choice in result.get("choices", [])
    for image in choice.get("message", {}).get("images", [])
]
if not images:
    raise RuntimeError(f"No image returned. Inspect the response: {result}")

extensions = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
for index, image in enumerate(images, start=1):
    url = image["image_url"]["url"]
    if url.startswith("data:"):
        header, encoded = url.split(",", 1)
        media_type = header[5:].split(";", 1)[0]
        data = base64.b64decode(encoded, validate=True)
    elif url.startswith("https://"):
        with urlopen(url, timeout=60) as download:
            media_type = download.headers.get_content_type()
            data = download.read()
    else:
        raise RuntimeError("Expected an image data URL or HTTPS URL")
    if media_type not in extensions:
        raise RuntimeError(f"Handle this image format before saving: {media_type}")
    path = Path(f"image-{index}{extensions[media_type]}")
    path.write_bytes(data)
    print(f"Saved {path}")
```

Run it:

```bash
python3 generate-image.py
```

</Tab>
<Tab title="Node.js">

Save this as `generate-image.mjs`. It uses Node.js 20 or later, with no packages to install.

```javascript
import { writeFile } from 'node:fs/promises';

const apiKey = process.env.INWORLD_API_KEY;
if (!apiKey) throw new Error('Set INWORLD_API_KEY first');

const payload = {
  model: 'google-ai-studio/gemini-nano-banana-2.1',
  messages: [{
    role: 'user',
    content: 'A ceramic teapot on a wooden table by a window, soft morning light, product photograph',
  }],
  modalities: ['text', 'image'],
  stream: false,
};

const response = await fetch('https://api.inworld.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Basic ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload),
  signal: AbortSignal.timeout(300_000),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const result = await response.json();
const images = (result.choices ?? []).flatMap(choice => choice.message?.images ?? []);
if (!images.length) throw new Error(`No image returned: ${JSON.stringify(result)}`);

const extensions = { 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp' };
for (const [index, image] of images.entries()) {
  const url = image.image_url.url;
  let mediaType;
  let data;
  if (url.startsWith('data:')) {
    const comma = url.indexOf(',');
    mediaType = url.slice(5, comma).split(';')[0];
    data = Buffer.from(url.slice(comma + 1), 'base64');
  } else if (url.startsWith('https://')) {
    const download = await fetch(url, { signal: AbortSignal.timeout(60_000) });
    if (!download.ok) throw new Error(`Image download failed: ${download.status}`);
    mediaType = download.headers.get('content-type')?.split(';')[0];
    data = Buffer.from(await download.arrayBuffer());
  } else {
    throw new Error('Expected an image data URL or HTTPS URL');
  }
  if (!extensions[mediaType]) throw new Error(`Handle this image format before saving: ${mediaType}`);
  const path = `image-${index + 1}${extensions[mediaType]}`;
  await writeFile(path, data);
  console.log(`Saved ${path}`);
}
```

Run it:

```bash
node generate-image.mjs
```

</Tab>
</Tabs>

Open the file printed by the script, such as `image-1.png`. Each run sends a new generation request and can incur a charge. A timeout does not prove that generation stopped, so consider the cost before resubmitting.

## Read the response

Read generated images from `choices[].message.images[].image_url.url`. The value can be a base64 data URL or a download URL. The scripts decode inline image bytes or download the file, and choose a file extension from its media type.

This abbreviated example shows the shape, not a complete image or a captured response:

```json
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "",
      "images": [{
        "type": "image_url",
        "image_url": { "url": "data:image/png;base64,<image bytes>" }
      }]
    },
    "finish_reason": "stop"
  }]
}
```

An empty `message.content` does not mean the request failed: an image can arrive without accompanying text. Check for returned images before treating a response as text-only. Store image files you need in your own storage instead of relying on a returned download URL remaining available.

## Change the output settings

Add `image_config` to the request body. For example, to request a landscape image from a Google image model that supports this ratio:

```json
"image_config": { "aspect_ratio": "16:9" }
```

Choose settings for the selected model:

| What you want | How to request it |
|:--|:--|
| A different shape on Google image generation | Set `image_config.aspect_ratio` to a ratio the model supports. This setting is not forwarded when you include a reference image. |
| A different size on Google | Set `image_config.image_size` to a supported tier, such as `1K` or `2K`. The available tiers depend on the model. |
| A different shape or size on OpenAI | Set `image_config.image_size` to supported pixel dimensions, such as `1024x1024`. `aspect_ratio` does not control OpenAI output. |

For every image option, including image count and streaming previews, see [`image_config` in the API reference](https://docs.inworld.ai/api-reference/routerAPI/chat-completions.md). Use the model's defaults when you do not need a specific size. Unsupported values can cause a request to fail, and settings are not interchangeable across models.

## Guide the result with an existing image

Use an image-capable generation model and send a reference as a base64 data URL in a **user** message. The prompt should say what to preserve and what to change. Remote image URLs are not used as reference inputs by the image-generation path, so encode the file itself.

For example, in the Python script above, insert this before `request = Request(...)`. Replace `image-1.png` with the file you want to change:

```python
import mimetypes

reference = Path("image-1.png")
media_type, _ = mimetypes.guess_type(reference.name)
if media_type not in {"image/png", "image/jpeg", "image/webp"}:
    raise ValueError("Use a PNG, JPEG or WebP reference with the matching file extension")
encoded = base64.b64encode(reference.read_bytes()).decode("ascii")
payload["messages"] = [{
    "role": "user",
    "content": [
        {"type": "text", "text": "Keep the teapot and composition. Change the background to a pale blue studio backdrop."},
        {"type": "image_url", "image_url": {"url": f"data:{media_type};base64,{encoded}"}},
    ],
}]
```

Use a PNG, JPEG or WebP that the selected model accepts. Keep the file extension consistent with its actual format: the example uses the extension to choose the data URL's media type. Send the reference file again on each request that should use it. Describe a subtle or strong variation in the prompt; the Playground's variation actions also use reference images and prompt instructions.

## Stream image output

Start with `stream: false` while you build and verify image saving. For a model that supports image streaming, set `stream: true` and read images from `choices[].delta.images` in the [server-sent events](https://docs.inworld.ai/router/capabilities/streaming.md). Process text and usage events separately from image events.

Google AI Studio and OpenAI image requests can stream. Google Vertex image output currently requires `stream: false`. OpenAI's `image_config.partial_images` requests progressive previews; a preview is not an additional final image. See the [API reference](https://docs.inworld.ai/api-reference/routerAPI/chat-completions.md) for the option.

## Handle failures and costs

Check the HTTP status before reading the JSON response. Authentication and other errors can return a plain-text body. The scripts include the error body when a request fails.

| What happened | What to check |
|:--|:--|
| `401` or `403` | Check the credential and permissions; see [API key authentication](https://docs.inworld.ai/portal/api-key-auth.md). |
| `400` | Check the model ID, output modalities, image settings and reference format. For Vertex image output, turn streaming off. |
| `429` | Wait before resubmitting; see [Rate limits](https://docs.inworld.ai/resources/rate-limits.md). |
| A response contains no images | Inspect `choices`, `finish_reason`, any text and any error before treating it as a generated image. The model may have refused the request. |
| A reference does not affect the result | Send its bytes as a base64 data URL in a user message and describe the intended change. |

Rates depend on the selected model and the input and output it processes. Use the [models catalog](https://inworld.ai/models) for current pricing and [Inference billing](https://docs.inworld.ai/portal/billing.md#llm-router) for account billing. Compare models with the same prompt and output settings, and include reference-image costs when the model charges for them.

## Next steps

- [Image-to-video](https://docs.inworld.ai/router/image-to-video.md): use a generated image as a video's first frame.
- [Chat Completions reference](https://docs.inworld.ai/api-reference/routerAPI/chat-completions.md): request fields, image options and response shape.
- [Image and audio inputs](https://docs.inworld.ai/router/capabilities/vision.md): ask a model to analyze an image.
