Images
Zabio generates images through POST /v1/images/generations and edits them through POST /v1/images/edits, in the shapes OpenAI’s SDKs already send. Change base_url and your key, keep the rest. A long generation can also run in the background and be fetched when it is done.
For the exact shape of the request and the response, with curl, Python and Node examples, see the API reference.
Generate an image
Section titled “Generate an image”Send a prompt and get the image back as base64. The key needs the chat scope (keys have it by default).
curl https://api.zabio.ai/v1/images/generations \ -H "Authorization: Bearer insert your zabio.ai key here" \ -H "Content-Type: application/json" \ -d '{"model": "auto", "prompt": "A red lighthouse on a rocky shore at dusk, flat illustration", "size": "1024x1024"}'{ "created": 1793655000, "data": [{"b64_json": "/9j/4AAQ..."}], "usage": {"input_tokens": 14, "input_tokens_details": {"text_tokens": 14, "image_tokens": 0}, "output_tokens": 1132, "output_tokens_details": {"image_tokens": 1120, "text_tokens": 12}, "total_tokens": 1146}, "size": "1024x1024", "output_format": "jpeg", "zabio": {"call_id": "call_...", "vendor": "google", "model_served": "gemini-3.1-flash-lite-image", "cost_micros": 35389, "units": {"kind": "image", "images": 1, "size": "1024x1024"}}}Decode data[0].b64_json and write it to a file with the extension of output_format.
Models
Section titled “Models”| Model | Maker | Price | Sizes | Qualities | Formats | Notes |
|---|---|---|---|---|---|---|
gpt-image-2.5-flare |
OpenAI | 5.00 per million text input tokens, 8.00 per million image input tokens, 30.00 per million image output tokens (about 0.21 per 1024x1024 image at high quality, OpenAI’s example for GPT Image 2) | 1024x1024, 1536x1024, 1024x1536 and any WIDTHxHEIGHT within OpenAI’s limits | low, medium, high, xhigh, max | png, jpeg, webp | OpenAI’s choice for fast everyday generation; streams partial images; transparent backgrounds |
gpt-image-2.5-sunburst |
OpenAI | the same | the same | the same | the same | OpenAI’s choice where editing precision matters most |
gpt-image-2 |
OpenAI | the same | the same | low, medium, high | the same | the previous generation |
gpt-image-1-mini |
OpenAI | 2.00 text in, 2.50 image in, 8.00 image out per million tokens (about 0.036 per 1024x1024 image at high quality, OpenAI’s example) | 1024x1024, 1536x1024, 1024x1536 | low, medium, high | the same | by name only; retires on 2026-12-01 (OpenAI’s date) |
gemini-nano-banana-2.1 |
1.50 per million input tokens, 7.50 per million text and thinking output tokens, 30.00 per million image output tokens (0.0336 per 1K image, 0.0504 per 2K, 0.113 per 4K, Google’s own figures) | 42 sizes: 14 aspect ratios at 1K, 2K and 4K | takes auto only |
jpeg | Google’s everyday model; up to 4K | |
gemini-3.1-flash-lite-image |
0.25 input, 1.50 text and thinking output, 30.00 image output (0.0336 per 1K image) | 14 sizes: 14 aspect ratios at 1K | takes auto only |
jpeg | the fastest and cheapest Google model | |
gemini-3-pro-image |
2.00 input, 12.00 text and thinking output, 120.00 image output (0.134 per 1K or 2K image, 0.24 per 4K) | 30 sizes: 10 aspect ratios at 1K, 2K and 4K | takes auto only |
jpeg | Google’s premium model; thinks before it draws | |
flux-2-dev@deepinfra |
Black Forest Labs | 0.017858 per 1024x1024 image, 0.026786 per 1536x1024 or 1024x1536 image | 1024x1024, 1536x1024, 1024x1536 | takes auto only |
jpeg | FLUX.2 [dev] served by DeepInfra; generations only |
grok-imagine-image |
xAI | 0.02 per image at every size; 0.002 per input image on an edit | 1024x1024, 1248x832, 832x1248 | takes auto only |
jpeg | Grok Imagine; processing location not guaranteed (below) |
grok-imagine-image-2.0 |
xAI | 0.04 per image at low, 0.06 at medium, at every size; 0.01 per input image on an edit |
the same | low, medium (auto is served as low) |
jpeg | Grok Imagine 2.0; a medium image takes up to about two minutes; processing location not guaranteed (below) |
Edits: the OpenAI models take up to 16 input images and a mask; gemini-nano-banana-2.1 and gemini-3-pro-image take up to 14 input images and no mask; gemini-3.1-flash-lite-image takes one input image; grok-imagine-image takes one input image and grok-imagine-image-2.0 up to five, with no mask; flux-2-dev@deepinfra does not edit. GET /v1/models gives the number as zabio.media.max_input_images.
Prices are the providers’ list prices in USD, per million tokens or per image; the plan fee applies on top (see Billing). On the OpenAI and Google models the per-image figures in brackets are the vendors’ own examples and never what a call is billed: those calls are billed on the tokens the vendor reports, image output tokens included, so a prompt the model thinks longer about costs what the vendor charges for it. flux-2-dev@deepinfra is billed per image at the price of the size made, which is what DeepInfra charges. The xAI models are billed per image, plus the model’s price per input image on an edit (0.002 on grok-imagine-image, 0.01 on grok-imagine-image-2.0), which is what xAI charges. GET /v1/models lists every image model with zabio.kind: "image", its sizes (zabio.media.sizes, plus custom_sizes where any WIDTHxHEIGHT is taken), its qualities and its formats, and the models page shows where each one is processed.
OpenAI’s custom sizes: both sides multiples of 16, an aspect ratio between 1:3 and 3:1, no side above 3840 pixels, between 655,360 and 8,294,400 pixels in total (OpenAI’s limits, read 2026-11-02). Google’s sizes are the exact pixels its models produce for each aspect ratio and tier, for example 1376x768 for 16:9 at 1K and 2752x1536 at 2K; 1536x1024 is an OpenAI size, not a Google one. The xAI sizes are the pixels xAI makes at 1K: 1024x1024 for 1:1, 1248x832 for 3:2 and 832x1248 for 2:3. 1536x1024 and 1024x1536 are not xAI sizes, so a request for them never goes to an xAI model, by name (400 on size) or under auto.
The xAI image models run on xAI’s global endpoint, because xAI serves images on no other: xAI may process those calls in any of its regions, so their processing location is not guaranteed. xAI’s chat models stay on its United States endpoint.
model: auto picks the cheapest image model that produces the size, the quality and the format you ask for, by the vendors’ own price figures for an image of that size and quality. There is no quality bar yet, so auto, auto/cheap, auto/balanced and auto/best all pick the same model on this endpoint. Without a quality, flux-2-dev@deepinfra wins at 1024x1024 and grok-imagine-image at its sizes 1248x832 and 832x1248; a named quality narrows the choice to the models that take it (low and medium to the OpenAI models and grok-imagine-image-2.0, the others to the OpenAI models); a png or webp format, a transparent background, or a custom size do the same. On an edit, auto considers only the models that take the number of images sent, counting a model’s fee per input image, and the OpenAI models when a mask is sent; a one-image edit without a mask goes to grok-imagine-image. If the chosen model fails, Zabio tries the next one at the same vendor, and at other vendors when the key’s fallback setting allows it. An auto call pays the price of automatic model choice (at most 0.00025 USD) like any other auto call.
Request
Section titled “Request”application/json, as OpenAI.
| Field | Notes |
|---|---|
prompt |
required; 32,000 characters at most |
model |
an image model or auto (the default when absent) |
n |
1 to 4 images (default 1) |
size |
auto (default) or WIDTHxHEIGHT; auto is the model’s default, 1024x1024 at 1K on the Google models |
quality |
auto (default), low, medium, high, xhigh, max; the OpenAI models, xhigh and max on the 2.5 models, and low and medium on grok-imagine-image-2.0 |
output_format |
png, jpeg or webp; the model’s default when absent (png on OpenAI, jpeg on Google) |
output_compression |
0 to 100, for jpeg and webp on the OpenAI models |
background |
auto, transparent or opaque; transparent needs png or webp and an OpenAI model |
moderation |
auto or low, the OpenAI models’ content filter level |
stream, partial_images |
stream: true with 0 to 3 partial images, the OpenAI models only, one image per request (see Streaming) |
safety_identifier, user |
your end user’s identifier, passed to the vendor |
zabio.background |
true to run the generation in the background (see Background generation); not with stream |
zabio.expires_after |
on a background generation, {"anchor": "created_at", "seconds": N} with N at least 3600: when its images are deleted, if sooner than your organization’s retention |
background, moderation and output_compression are OpenAI settings; the other models ignore them and the answer lists them under zabio.ignored. response_format is b64_json (the default) or url (see Images as URLs).
Response
Section titled “Response”OpenAI’s shape: created, data[].b64_json, usage (input tokens with their text and image details, output tokens with their image and text details), the size, quality, output_format and background produced, plus the zabio object: call_id, vendor, model_served, cost_micros (what the call cost you, fee included), units (kind: "image", the number of images, their size and quality), ignored where any, and retires_at on a model with a date.
Images as URLs
Section titled “Images as URLs”With "response_format": "url" on a generation or an edit, each image comes back as a link instead of base64, the way OpenAI’s DALL-E models answered:
{"created": 1793655000, "data": [{"url": "https://api.zabio.ai/v1/images/content/file_...?exp=1793658600&sig=..."}], "usage": {...}, "zabio": {"url_expires_at": 1793658600, ...}}The link works for 60 minutes after the image is made, without your API key, so it can go straight into an <img> tag. zabio.url_expires_at is when it stops. After that, or after the image is deleted, the link answers 404, as it does for any link that is not exactly one Zabio made. Download the image within the hour if you want to keep it.
The link is a bearer link: anyone who has it can open the image until it expires. Share it only where you would share the image. Zabio stores each image for those 60 minutes, encrypted with your organization’s key, as a file of purpose image_output that your key can also read at GET /v1/files/{id}/content or delete at once with DELETE /v1/files/{id}, which ends the link too.
On a background generation the 60 minutes start when the images are made, not when you fetch the generation: poll it within the hour. Deleting the generation deletes its images and ends their links. A streamed request sends base64 only, so url with stream is a 400. If Zabio cannot store the images after the call was made, the answer carries them as b64_json as usual and says zabio.url_unavailable: true, so an image you paid for is never lost. The links are served with Cache-Control: private, no-store: no browser cache, proxy or edge keeps them.
Long calls
Section titled “Long calls”A high-quality or large image can take longer than two minutes. Zabio keeps the connection alive: after 90 seconds without an answer it commits the 200 and writes a space every 15 seconds until the body arrives, so neither Zabio’s edge nor a proxy in front of your client closes it. The OpenAI SDKs parse the answer as usual. If the generation fails after that point, the body is {"created": ..., "data": [], "error": {"message", "type", "code"}} with status 200: check error before reading data. If your client cannot hold a connection that long, use background generation.
Background generation
Section titled “Background generation”Add "zabio": {"background": true} to a generation and the answer comes at once, with status 202 and an id:
{"id": "img_...", "object": "image.generation", "status": "queued", "created_at": 1793655000, "model": "auto", "expires_at": 1796247000, "zabio": {"background": true}}Zabio runs the generation on its side. Fetch it with GET /v1/images/generations/{id}: while it runs the same object comes back with status queued or in_progress; once it is completed the answer is the images themselves, as a direct call returns them (data[0].b64_json, usage, zabio), with the id and the status beside them and zabio.file_id, the file that keeps them (also readable at GET /v1/files/{id}/content). A generation that failed comes back with status failed and an error with its code and message. If the images were made but could not be delivered, the charge is returned to your balance and the error says so. Poll every few seconds; most images are ready within a minute or two.
DELETE /v1/images/generations/{id} cancels a generation that is still queued, and nothing is billed; one already running answers 409 generation_in_progress, since it is at the vendor and will be billed. Once it has ended, DELETE removes it and its images.
GET /v1/images/generations lists your generations, newest first, as the status objects above (never the images: fetch one by its id for those). limit (1 to 100, default 20) sets the page size, after takes the last_id of the previous page, and status keeps one status (queued, in_progress, completed, failed or cancelled). The answer is {"object": "list", "data": [...], "first_id", "last_id", "has_more"}. A key in a project sees its project’s generations.
The images are kept until your organization’s retention runs out. "zabio": {"background": true, "expires_after": {"anchor": "created_at", "seconds": 7200}} deletes them sooner, here two hours after the request; a value longer than the retention is shortened to it, and expires_at in the answer is the time that applies.
These endpoints and fields are Zabio extensions, not part of OpenAI’s API. The rules of a direct call apply: the same models, the same auto, the same bill, charged when the images are made. Background generation needs credit on the account, checked when the request is queued and again when it runs: a generation whose balance reached zero while it waited ends failed with insufficient_credits and bills nothing. The OpenAI SDKs pass the zabio object through extra_body.
An edit cannot run in the background: Zabio never stores the images you send to edit, and a queued edit would have to.
Edit an image
Section titled “Edit an image”POST /v1/images/edits takes OpenAI’s multipart form: one or more image[] files, an optional mask, and the fields of a generation as form values.
curl https://api.zabio.ai/v1/images/edits \ -H "Authorization: Bearer insert your zabio.ai key here" \ -F model=auto \ -F "image[]=@room.png" \ -F "image[]=@lamp.png" \ -F prompt="Put the lamp on the table in the room"The same edit can be sent as JSON, the shape of OpenAI’s API reference: images is a list of {"image_url": "data:image/png;base64,..."} or {"file_id": "file_..."}, mask is one of the same, and the other fields are a generation’s.
curl https://api.zabio.ai/v1/images/edits \ -H "Authorization: Bearer insert your zabio.ai key here" \ -H "Content-Type: application/json" \ -d '{"model": "auto", "prompt": "Put the lamp on the table in the room", "images": [{"image_url": "data:image/png;base64,iVBORw0..."}, {"image_url": "data:image/png;base64,iVBORw0..."}]}'An image_url must be a base64 data URL: Zabio does not fetch images from web addresses on this endpoint (400 unsupported_value). A file_id must be a file of purpose user_data that your key can read; Zabio reads it and does not copy it. POST /v1/files takes purpose batch only today, so send data URLs or the multipart form. In JSON the base64 counts toward the size of the request, so the request holds about 75 MB of images.
The images are png, jpeg or webp, 50 MB each at most and 100 MB for the whole request. The mask is a png with an alpha channel, the size of the first image, which it applies to; only the OpenAI models take one. The Google models take about 14 MB of input images in all, the limit Google sets for images sent inline. The answer, the long call and the bill are a generation’s; a streamed edit sends image_edit.partial_image and image_edit.completed events. input_fidelity and markup are not supported.
Streaming
Section titled “Streaming”With stream: true on an OpenAI image model, partial images arrive as they render and the result at the end:
event: image_generation.partial_imagedata: {"type": "image_generation.partial_image", "b64_json": "...", "partial_image_index": 0, "output_format": "png", "created_at": 1793655000}
event: image_generation.completeddata: {"type": "image_generation.completed", "b64_json": "...", "usage": {...}, "size": "1024x1024", "quality": "high", "output_format": "png", "background": "opaque", "zabio": {...}}partial_images (0 to 3) is how many partial images you want before the final one; each adds 100 image output tokens at OpenAI. A comment line keeps the stream alive while the model is silent. A failure after the stream opened is an error event. Only the OpenAI models stream; stream: true on one of them, or under auto when no streaming model can take the request, is a 400 unsupported_value on stream.
What is billed
Section titled “What is billed”On the OpenAI and Google models, the tokens the vendor reports, at the model’s prices: text input tokens, image input tokens (the images you send to edit), image output tokens, and text or thinking output tokens on the Google models. On flux-2-dev@deepinfra, the price of each image of the size made. On the xAI models, the price of each image at the quality made, plus the model’s price per input image on an edit. The plan fee applies on top, and the auto fee when auto picked the model. A failed call bills nothing; a call that failed after its 200 was committed bills nothing either. A moderation block bills nothing, except where the vendor charges for it: xAI charges for an image its moderation refuses (0.02 USD on grok-imagine-image when Zabio tested it on 2026-11-04), so a refused request on an xAI model is billed what xAI charged plus the plan fee, and its error message says how much.
What is stored
Section titled “What is stored”On a direct generation or edit, nothing. The prompt and the images you send go to the vendor that answers and the images come back to you; Zabio keeps none of them, and none appears in any log. With response_format: url, the images made are stored for 60 minutes, encrypted with your organization’s key, and anyone holding a link can open its image until it expires (see Images as URLs); the images you send to edit are still never stored. With per-key body logging on, the prompt and a reference to the images (count, type, bytes, size) are stored under your organization’s retention, never the image bytes.
A background generation is stored until it is fetched and beyond: the request (the prompt included) until the generation ends, and the images until you delete them or your organization’s retention runs out, both encrypted with your organization’s key. The usage views count the images (units.images) and the Usage tab shows “N images generated”.
Google’s images carry an invisible SynthID watermark. Tell your users that the images are AI-generated where you show them, as the vendors’ terms require.
Limits and errors
Section titled “Limits and errors”| Status | code | When |
|---|---|---|
| 400 | invalid_value |
a field is out of range (n above 4, a prompt above 32,000 characters, a size that is not WIDTHxHEIGHT, a quality or output_format outside the list), or a named model does not produce the size or take the quality; param says which |
| 400 | invalid_value |
zabio.expires_after without zabio.background, with another anchor than created_at, or under 3600 seconds; on the list, a limit, after or status outside its values; on a JSON edit, an image_url that is not a base64 data URL, or a file_id your key cannot read or that is not of purpose user_data |
| 400 | unsupported_value |
no model, or the named model, produces the output_format, a transparent background or a stream for this request; or response_format: url on a server where image URLs are not enabled |
| 400 | invalid_value |
response_format other than b64_json or url, or url with stream |
| 400 | unsupported_value |
on an edit: a mask on a model that takes none, zabio.background, or an image_url that is a web address; on a generation, zabio.background on the free plan |
| 400 | unsupported_parameter |
style (a legacy field); on an edit, input_fidelity or markup |
| 400 | missing_file, missing_field, invalid_multipart, invalid_json |
an edit without an image[] file or an images list, or a body that is neither multipart nor JSON |
| 400 | moderation_blocked |
the vendor’s safety system refused the prompt or withheld the image; the message carries the vendor’s categories where it names any and the stage (input or output), never its text; on the xAI models it also says what the refused request was billed, since xAI charges for it |
| 400 | unsupported_for_model |
the named model is not an image model |
| 402 | insufficient_credits |
no credit left; on a background generation, also the error of one whose balance reached zero before it ran |
| 404 | model_not_found |
not a model, not allowed for the key, or retired |
| 404 | not_found |
an image link that expired, was changed, or whose image was deleted |
| 409 | generation_in_progress |
DELETE on a background generation that is running |
| 410 | expired |
a completed background generation whose images were deleted or expired |
| 413 | payload_too_large |
the JSON body is above 256 KiB |
| 413 | media_too_large |
an edit’s image above 50 MB, its body above 100 MB, or its images above a named model’s limit |
| 503 | no_provider_available |
the provider for this model is not configured, or has not cleared Zabio’s account for it yet |
| 503 | background_unavailable |
background generation is not available on this server |
The key’s requests per minute limit applies; the token limit does not count image tokens.
Python
Section titled “Python”import base64from openai import OpenAI
client = OpenAI(base_url="https://api.zabio.ai/v1", api_key="insert your zabio.ai key here")res = client.images.generate(model="auto", prompt="A red lighthouse on a rocky shore at dusk, flat illustration", size="1024x1024")with open("lighthouse.jpg", "wb") as f: f.write(base64.b64decode(res.data[0].b64_json))print(res.model_extra["zabio"]["model_served"], res.model_extra["zabio"]["cost_micros"])
# An editres = client.images.edit(model="auto", image=[open("room.png", "rb"), open("lamp.png", "rb")], prompt="Put the lamp on the table in the room")
# Your background generations, newest firstpage = client.get("/images/generations", options={"params": {"limit": 10, "status": "completed"}}, cast_to=dict)
# In the backgroundimport timejob = client.post("/images/generations", body={"model": "auto", "prompt": "A city at night, detailed, 4K", "size": "4096x4096", "zabio": {"background": True}}, cast_to=dict)while (got := client.get(f"/images/generations/{job['id']}", cast_to=dict))["status"] in ("queued", "in_progress"): time.sleep(5)image = base64.b64decode(got["data"][0]["b64_json"])zabio image -o lighthouse.jpg "A red lighthouse on a rocky shore at dusk"zabio image -o poster.png --model gpt-image-2.5-flare --size 1024x1536 --quality high --format png "A poster for a jazz night"zabio image edit -o room-with-lamp.png "Put the lamp on the table in the room" room.png lamp.pngzabio image edit -o hat.png --mask mask.png "Add a red hat" portrait.pngzabio image --background --size 4096x4096 "A city at night, detailed, 4K" # prints img_...zabio image status img_...zabio image get -o city.png img_...