Realtime TTS-2 is live. Built for realtime conversation that feels human. Read the Realtime TTS-2 announcement

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; ["image"] alone also works. If you omit modalities, a model that generates images still returns images, and image_config still applies. If you set modalities, keep "image" in it: a request whose modalities leaves out "image" returns no images and ignores image_config. 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, audio and PDF inputs.

Choose a model

Browse 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. To list image models from code, call the List models API with ?output_modalities=image. Check spec.inputModalities for image if you plan to send reference images, and pricing for the model's rates.

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 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. 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 for those clients.

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

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. When you request several images with image_config.n, a non-streaming response returns them all in the same choice's images array, not as separate choices. The scripts decode inline image bytes or download the file, and choose a file extension from its media type.

The image format is the one the model returns, usually PNG; a data URL names it in its media type, such as image/png or image/jpeg. No request field chooses the output format, so convert the file in your application if you need a different one.

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 wantHow to request it
A different shape on Google image generationSet image_config.aspect_ratio to a ratio the model supports. When the request includes a reference image, Inworld ignores this setting without an error.
A different size on GoogleSet 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 OpenAISet image_config.image_size to supported pixel dimensions, such as 1024x1024. Inworld ignores aspect_ratio for OpenAI models without an error.
Several images from one requestSet image_config.n. OpenAI models accept 1 to 10. Google image models return one image; Inworld passes a larger value to Google, which can reject it. The top-level n field does not set the image count.

The provider is the first part of the model ID, such as google-ai-studio/ or openai/. For a model from another provider, start with its defaults and check its catalog entry before you add settings. For every image option, including streaming previews, see image_config in the API reference. 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. Encode the file itself instead of sending a remote image URL: a Google image model rejects a remote URL with 400, and an OpenAI image model ignores it and generates from the text alone.

Only user messages reach the image model. Inworld sends their text as the prompt and every inline image in them as a reference; system and assistant messages are not used. Inworld sets no limit on the number of references, but the selected model has its own limit and can reject a request that exceeds it.

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. 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. To receive progressive previews from an OpenAI model, set image_config.partial_images to a value from 1 to 3. When it is 0 or unset, OpenAI sends no previews. A preview is not an additional final image. See the API reference 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 happenedWhat to check
401 or 403Check the credential and permissions; see API key authentication.
400Check the model ID, output modalities, image settings and reference format. For Vertex image output, turn streaming off. Image output needs a billing method on your workspace. A safety block, or a prompt the model returns no image for, also returns 400.
429Wait before resubmitting; see Rate limits.
A response contains no imagesInspect 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 resultSend its bytes as a base64 data URL in a user message and describe the intended change.

Image models are priced per token, not per image. Inworld bills each request from its usage:

  • Generated images count as output tokens. They are included in usage.completion_tokens, which is billed at the model's output rate (pricing.completionToken in the List models API). usage.completion_tokens_details.image_tokens shows the image part when the provider reports it.
  • The prompt and reference images count as input tokens. They are included in usage.prompt_tokens, which is billed at the input rate (pricing.promptToken). usage.prompt_tokens_details.image_tokens shows the reference-image part when the provider reports it.

The token count of an image depends on the model and its output settings, such as size and image count, so compare models with the same prompt and settings. Use the models catalog for current rates and Inference billing for account billing.

Next steps