Skip to content

Image generation

Deliverable: generate.py, edit.py, and nano_banana.py, each of which writes a PNG to disk.

Prerequisites: pip install requests, a MIRAPI_API_KEY, and an image-capable model enabled for your key (for the Gemini script, a Nano Banana model such as gemini-3.1-flash-image-preview).

MirAPI exposes two OpenAI-compatible endpoints for images, both billed per generated image:

  • POST /v1/images/generations: text-to-image.
  • POST /v1/images/edits: edit a source image according to an instruction.

Google’s Gemini image models (the Nano Banana family) are multimodal chat models and use the Gemini-native POST /v1beta/models/{model}:generateContent instead, as the third script below does. Anthropic has no image-generation endpoint; its models only take images as input (see Vision inputs).

/v1/images/generations takes a chat-style input.messages payload with a parameters object:

import base64
import os
import requests
BASE = "https://api.mirapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}
resp = requests.post(
f"{BASE}/images/generations",
headers=HEADERS,
json={
"model": "deepseek-chat",
"input": {
"messages": [
{"role": "user", "content": [{"text": "A neon-lit street in the rain, cinematic"}]}
]
},
"parameters": {"size": "1024*1024", "prompt_extend": True, "watermark": False},
},
timeout=120,
)
resp.raise_for_status()
image = resp.json()["data"][0]
if image["b64_json"]:
with open("output.png", "wb") as f:
f.write(base64.b64decode(image["b64_json"]))
else:
open("output.png", "wb").write(requests.get(image["url"], timeout=60).content)
print("Saved output.png")

/v1/images/edits sends the source image and the instruction in the same message-style shape. The image field accepts a public HTTP(S) URL or a base64 data URI:

import base64
import os
import requests
BASE = "https://api.mirapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['MIRAPI_API_KEY']}"}
# A base64 data URI of the source image:
with open("input.png", "rb") as f:
data_uri = "data:image/png;base64," + base64.b64encode(f.read()).decode()
resp = requests.post(
f"{BASE}/images/edits",
headers=HEADERS,
json={
"model": "deepseek-chat",
"input": {
"messages": [
{
"role": "user",
"content": [
{"image": data_uri},
{"text": "Turn the sky into a starry night"},
],
}
]
},
"parameters": {"n": 1, "watermark": False},
},
timeout=120,
)
resp.raise_for_status()
image = resp.json()["data"][0]
if image["b64_json"]:
with open("edited.png", "wb") as f:
f.write(base64.b64decode(image["b64_json"]))
else:
open("edited.png", "wb").write(requests.get(image["url"], timeout=60).content)
print("Saved edited.png")

gemini-2.5-flash-image, gemini-3-pro-image-preview, gemini-3.1-flash-image-preview and the other Nano Banana names in the catalog are multimodal chat models, so they don’t use the two endpoints above. Call POST /v1beta/models/{model}:generateContent with the x-goog-api-key header, ask for a picture with generationConfig.responseModalities: ["TEXT", "IMAGE"], and pick the base64 inlineData part out of the response:

import base64
import os
import requests
MODEL = "gemini-3.1-flash-image-preview"
resp = requests.post(
f"https://api.mirapi.ai/v1beta/models/{MODEL}:generateContent",
headers={"x-goog-api-key": os.environ["MIRAPI_API_KEY"]},
json={
"contents": [{"role": "user", "parts": [{"text": "A neon-lit street in the rain, cinematic"}]}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": {"aspectRatio": "16:9"},
},
},
timeout=120,
)
resp.raise_for_status()
for part in resp.json()["candidates"][0]["content"]["parts"]:
if "inlineData" in part:
with open("nano_banana.png", "wb") as f:
f.write(base64.b64decode(part["inlineData"]["data"]))
print("Saved nano_banana.png")
elif "text" in part:
print(part["text"])

imageConfig.imageSize (1K / 2K / 4K) is optional and applies only to Gemini 3 image models. To edit or reference an image, add an inlineData part next to the text, in the same shape as Vision inputs. The same models also answer POST /v1/chat/completions (OpenAI format), with the image options under extra_body.google.image_config (aspect_ratio, image_size).

The image endpoints accept two payload formats:

  • Message-style: /v1/images/generations and /v1/images/edits (no trailing slash). These use input.messages plus parameters, as in the scripts above.
  • OpenAI classic: the trailing-slash variants /v1/images/generations/ and /v1/images/edits/. These use a top-level prompt for OpenAI SDK compatibility; edits send image + prompt as multipart form data.
Terminal window
curl https://api.mirapi.ai/v1/images/generations/ \
-H "Authorization: Bearer $MIRAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-chat",
"prompt": "A neon-lit street in the rain, cinematic",
"n": 1,
"size": "1024x1024"
}'

The classic edits variant uses multipart/form-data with an image file, a prompt, and response_format (url or b64_json).

Message-style requests put everything under parameters:

Parameter Purpose
model Model name exactly as shown in the catalog
input.messages[].content[].text The generation or edit instruction
input.messages[].content[].image Source image for edits (URL or base64 data URI)
parameters.size Output dimensions as a string, e.g. 1024*1024
parameters.negative_prompt Things to avoid in the output
parameters.prompt_extend Let the model expand the prompt (true / false)
parameters.watermark Request a visible watermark (true / false)
parameters.n Number of images to return (edits; where supported)

Not every model accepts every parameter, and many models only support n: 1. Check the model card in the catalog for supported values.

The response contains an array of images:

{
"created": 1780000000,
"data": [
{
"url": "https://…",
"b64_json": null,
"revised_prompt": "…"
}
]
}
  • url: a temporary URL to download the image.
  • b64_json: the image as a base64 string.
  • revised_prompt: the prompt actually used when the model rewrites yours.

To save the image to disk, prefer b64_json (no second request) or download url immediately, because it is temporary.

Billing. Generation and editing are charged per image, deducted from your prepaid balance; parameters.n multiplies the cost, so start with n: 1. When the balance is exhausted, requests return 403; topping up restores the same key immediately. Reconcile spend in the Platform billing log, or via GET /api/usage/token (account-level summary) and GET /api/log/token (per-request detail). Gemini image models called through generateContent are billed per token like any other Gemini call: the generated image counts as output tokens (see usageMetadata in the response), at the rate on the model card.

Errors. The gateway returns standard status codes: 401 (fix your key), 403 (balance, model whitelist, quota, or IP allowlist), 413 (request too large), 429 (rate limited: retry with jittered exponential backoff, 1s → 2s → 4s capped at ~30s; there is no Retry-After header), and 500 (transient upstream failure; idempotent requests are safe to retry). Error responses use the OpenAI envelope {"error":{"message","type","param","code"}}, and every message ends with a request ID; include it in any support ticket.

  • Wrong model. Image endpoints reject models without image-generation capability. Filter the catalog by output type and use the model’s exact id.
  • Gemini image models (Nano Banana). Call POST /v1beta/models/{model}:generateContent with responseModalities: ["TEXT", "IMAGE"], not /v1/images/generations; the image is in inlineData.data.
  • 403 on a valid key. Check your balance first, then the model whitelist, quota, and IP allowlist on the key.
  • url no longer works. Download it immediately after the request, or use b64_json.
  • 413 with base64. Large images exceed the size limit, so downscale them before encoding.
  • n ignored or rejected. Some models return a single image regardless; check the model card.