← Back to Blog
Tutorial

Image generation API: call /v1/images/generations and save the PNG

FastMetal

The short version

Send a prompt to /v1/images/generations, read the image from data[0].b64_json, and decode it to a file. The request and response follow the OpenAI Images API, so the OpenAI SDK works with only the base URL changed.

The thing that catches people out is billing. Images are charged per image, with no token count involved, so the usage block in every response reads zero. Your cost is the number of images you generate.

A minimal request

curl https://api.fastmetal.ai/v1/images/generations \
  -H "Authorization: Bearer $FASTMETAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "z-image-turbo",
    "prompt": "a red maple leaf on a white background, minimal",
    "size": "1024x1024",
    "n": 1
  }'
ParameterWhat it does
modelThe image model id. The model catalog lists them
promptA description of the image you want
sizeFor example 1024x1024. Supported sizes differ by model, and some models ignore it
nHow many images to generate. Each one is billed

Reading the response

This is the shape of a real response (measured August 2026, trimmed):

{
  "created": 1756...,
  "size": "1024x1024",
  "output_format": "png",
  "data": [
    {
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA...",
      "url": null,
      "revised_prompt": null
    }
  ],
  "usage": {
    "total_tokens": 0,
    "input_tokens": 0,
    "output_tokens": 0
  }
}

The image is base64 in b64_json, and url is null. Code written to download from a URL will fail here, so decode the string and write it to disk:

import base64
from openai import OpenAI

client = OpenAI(
    api_key="<FASTMETAL_API_KEY>",
    base_url="https://api.fastmetal.ai/v1",
)

resp = client.images.generate(
    model="z-image-turbo",
    prompt="a red maple leaf on a white background, minimal",
    size="1024x1024",
    n=1,
)

with open("out.png", "wb") as f:
    f.write(base64.b64decode(resp.data[0].b64_json))
print("saved: out.png")

Why usage is always zero

A zero usage block is the expected result on this endpoint. Each model has a fixed price per image, and the length of your prompt does not change it. Token counts play no part in the bill, so the gateway reports none.

Three practical consequences:

  • Cost is predictable: images generated times the model's per-image price. There is no output length to vary it the way text generation has.
  • n is the whole bill. n: 4 is four images. Keep it at n: 1 while you iterate on a prompt.
  • Any cost tracking you built by summing usage will miss image spend entirely. Count images instead.

The per-image price for each model is on the pricing page and on the model's own page. If you rewrite a prompt many times, iterate on a cheaper model and switch to a stronger one for the final render.

Choosing a model

FastMetal serves several image models. Split them by job:

  • Iterating on composition and prompt wording: a model at the lower end of the price range.
  • Final output, where following the instructions closely matters: a higher-quality model.

The image generation face-off compares the models on the same prompts. The model catalog has the current list.

Leave text out of the image

Current image models draw text badly. Japanese lettering in particular comes out as broken characters or shapes that only look like writing. The reliable fix is to generate the image with no text and add the words afterwards in a design tool. Ask for that in the prompt ("no text, leave empty space at the top") and you get an image that takes an overlay cleanly.

The chat endpoint bills differently

Some image models can also be called through /v1/chat/completions, and there the billing changes. The images endpoint charges per image; the chat path charges per token. The same model can therefore produce two different-looking bills depending on which endpoint you call. If the job is "make one image", use /v1/images/generations.

Common mistakes

Reading url

It is null. The image is in b64_json.

Calculating cost from usage

It is zero. Count images.

Testing with a large n

Every image is billed. Validate prompts with n: 1.

Expecting size to always apply

Supported sizes vary by model. z-image-turbo always returns 1024x1024, whatever size you send, and a provider-specific resolution field in the request gets a 400. Check the model page before you design around an aspect ratio.

Prompting in a language the model follows poorly

Some models follow English prompts better than others. If a prompt in another language misses, rewrite it in English.

FAQ

The usage fields are all zero. Is that a bug?

No. Image generation is billed per image, so no token counts are recorded. Work out cost from the number of images.

Does the API return an image URL?

No. The image is base64 in data[0].b64_json. Decode it and save it to a file.

Does a larger size cost more?

No. The price is per image. Supported sizes differ by model, and z-image-turbo returns 1024x1024 for every request.

Can I put text in the image?

Not reliably. Generate without text and add it in a design tool.

Related

  • Sending an image to a model to read is a different endpoint with different parameters: see vision and image input.
  • Video generation is an async job API: see the video generation tutorial.
  • For agents, the generate_image tool on the MCP server does the same job and returns a short-lived download link.

Try it

Create an account, add credit (the minimum top-up is $5), and generate one image with n: 1. Models are in the catalog and prices on the pricing page.

Try the latest AI models right now

the latest AI models is available on FastMetal through one API key. Start in the browser, or call it from the OpenAI SDK.