Image generation API: call /v1/images/generations and save the PNG
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
}'
| Parameter | What it does |
|---|---|
model | The image model id. The model catalog lists them |
prompt | A description of the image you want |
size | For example 1024x1024. Supported sizes differ by model, and some models ignore it |
n | How 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.
nis the whole bill.n: 4is four images. Keep it atn: 1while you iterate on a prompt.- Any cost tracking you built by summing
usagewill 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_imagetool 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.