Image API — generation & editing
Call gpt-image-2 to generate or edit images with ZetaAPI. OpenAI Images-compatible, with curl / Python / Node examples, multipart edits, output dimensions, billing and timeout notes.
Updated 2026-07-26
ZetaAPI’s image endpoints are fully OpenAI Images API-compatible — use your existing API key to generate or edit images with gpt-image-2, no SDK changes needed. To try it in the browser first, open the Image Playground in the console; this page covers calling it from code.
Base URL & auth
Image requests go to the dedicated image domain:
https://image.zetaapi.ai
Every request carries Authorization: Bearer sk-xxxxx (the same key you use for chat).
Why images get their own domain: image generation is a long, non-streaming request — waiting for the upstream render can take up to 240 seconds. The main domain
api.zetaapi.aisits behind a CDN that cuts the connection at roughly 120 seconds. You get a failure back, but the image was still generated upstream, and you are still billed for it. The image domain connects straight to origin and has no such limit — so do not auto-retry image requests against the main domain.This domain serves only
/v1/images/generationsand/v1/images/edits. Everything else — including the Gemini high-resolution path below, which goes through/v1/chat/completions— still useshttps://api.staging.cosmos-router.com.If you received our earlier migration notice, it referred to
direct.zetaapi.ai: that domain remains valid and is equivalent.
Checking that the domain is reachable
Any request to this domain outside the two endpoints above — including the root path / — is dropped without a response. curl reports Empty reply from server or HTTP/2 stream 1 was not closed cleanly: PROTOCOL_ERROR, not a 404. This is a deliberate security policy and does not mean the domain is down.
To confirm the domain is reachable, POST the endpoint itself (no key required, no quota consumed):
curl -i -X POST https://image.zetaapi.ai/v1/images/generations \
-H "Content-Type: application/json" -d '{}'
A 401 Authorization Required response means DNS, TLS and the gateway are all fine — it is simply asking for your API key.
Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/images/generations | Text-to-image |
POST /v1/images/edits | Edit (upload source image + optional mask, multipart) |
Generate images
Parameters
| Param | Required | Notes |
|---|---|---|
model | yes | gpt-image-2 |
prompt | yes | Image description |
n | no | 1–4 images, default 1 |
size | no | Accepted for OpenAI SDK compatibility but has no effect — see Output dimensions |
response_format | no | b64_json (default) or url |
curl
curl https://image.zetaapi.ai/v1/images/generations \
-H "Authorization: Bearer sk-xxxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "a red panda coding at night, neon rim light, 16:9 cinematic banner",
"n": 1
}'
Python (openai SDK)
import base64
from openai import OpenAI
client = OpenAI(api_key="sk-xxxxx", base_url="https://image.zetaapi.ai/v1")
resp = client.images.generate(
model="gpt-image-2",
prompt="a red panda coding at night, neon rim light, 16:9 cinematic banner",
n=1,
)
with open("out.png", "wb") as f:
f.write(base64.b64decode(resp.data[0].b64_json))
Node.js (openai SDK)
import { writeFileSync } from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-xxxxx",
baseURL: "https://image.zetaapi.ai/v1",
});
const resp = await client.images.generate({
model: "gpt-image-2",
prompt: "a red panda coding at night, neon rim light, 16:9 cinematic banner",
n: 1,
});
writeFileSync("out.png", Buffer.from(resp.data[0].b64_json, "base64"));
Response
{
"created": 1730000000,
"data": [{ "b64_json": "iVBORw0KGgo..." }],
"usage": { "input_tokens": 12, "output_tokens": 1024, "total_tokens": 1036 }
}
Returns b64_json (a base64-encoded PNG) by default. Pass "response_format": "url" to get an image URL instead.
Output dimensions
Control the aspect ratio from the prompt, not the size parameter.
gpt-image-2 renders every image at a fixed pixel budget of roughly 1.57 megapixels. You cannot raise it — asking for “4K” or “3840x2160” in the prompt does not produce more pixels. What you can control is the shape: describe the aspect ratio in the prompt and it is honoured precisely.
| Ask for in the prompt | You get | Ratio |
|---|---|---|
| (nothing) or “square” | 1254 × 1254 | 1:1 |
| ”16:9 cinematic banner” | 1672 × 941 | 16:9 |
| ”9:16 vertical poster” | 941 × 1672 | 9:16 |
| ”21:9 ultra-wide” | 1915 × 821 | 21:9 |
| ”3:1 panoramic banner” | 2172 × 724 | 3:1 |
| ”1:3 tall vertical strip” | 736 × 2135 | ~1:2.9 |
Supported range is 1:3 to 3:1. Common ratios are hit exactly (16:9 → 1672 × 941); near the limits you get the closest available size — asking for 1:3 measured ~1:2.9. Every ratio costs the same, since the pixel count is constant.
The size parameter is accepted so that the OpenAI SDKs keep working, but it is ignored — size: "1024x1536" with a neutral prompt still returns a square image. If you need an exact pixel size, generate at the closest ratio and resize locally.
Need genuinely higher resolution? The pixel cap is an upstream limit on
gpt-image-2that we cannot change — use Gemini 3 Pro Image instead, which renders up to 17 MP.
High resolution: Gemini 3 Pro Image
For work that needs real pixels — print, large banners, wallpapers — use gemini-3-pro-image-preview. Resolution is selectable and honoured exactly. The sizes below are measured without aspect_ratio — setting one changes the width/height while keeping the same pixel tier:
image_size | Output | Pixels |
|---|---|---|
1K (default) | 1408 × 768 | 1.1 MP |
2K | 2816 × 1536 | 4.3 MP |
4K | 5632 × 3072 | 17.3 MP |
This model is not available on /v1/images/* — the upstream provider only exposes it through chat completions. Two things differ from the Images API:
- Call
POST /v1/chat/completionsand pass the resolution inextra_body. - The image comes back as a Markdown data-URI inside
choices[0].message.content, not asdata[0].b64_json.
curl https://api.staging.cosmos-router.com/v1/chat/completions \
-H "Authorization: Bearer sk-xxxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3-pro-image-preview",
"messages": [{"role": "user", "content": "Generate an image: a red panda coding at night, neon rim light"}],
"extra_body": {"google": {"image_config": {"image_size": "4K"}}}
}'
import base64, re
from openai import OpenAI
client = OpenAI(api_key="sk-xxxxx", base_url="https://api.staging.cosmos-router.com/v1")
resp = client.chat.completions.create(
model="gemini-3-pro-image-preview",
messages=[{"role": "user", "content": "Generate an image: a red panda coding at night"}],
# The SDK merges extra_body into the top level, but the gateway reads a
# top-level `extra_body` field — so it must be nested once more here.
# Omitting the extra level silently falls back to 1K.
extra_body={"extra_body": {"google": {"image_config": {"image_size": "4K"}}}},
)
# content looks like: "Image generated successfully.\n"
content = resp.choices[0].message.content
m = re.search(r"base64,([A-Za-z0-9+/=]+)", content or "")
if not m:
raise RuntimeError(f"no image in response (refused or plain text): {content!r}")
with open("out.png", "wb") as f:
f.write(base64.b64decode(m.group(1)))
Notes:
- Billing is per token, like chat: a 2K image is ~1,120 image tokens (≈ $0.06) and a 4K image ~2,000 (≈ $0.09). Rates are on the pricing page.
- Also supports
aspect_ratioin the sameimage_configblock (e.g."16:9"). - Non-streaming and slower than
gpt-image-2at low resolutions: typical 20–60s, occasionally longer at 4K. Note: this goes over the chat path, where the gateway’s first-byte budget is 90s — a 4K request with a complex prompt can hit a504. Retry, or drop to2K. - A 4K PNG is ~19MB before base64, so the JSON response is ~25MB — make sure your client and any proxy in front of it accept a response that large.
4K output in the console
No code required: open the console → image Playground and set Resolution to “4K (long edge >= 3840)”. Both the generate and edit tabs support it.
- The result comes back at 4K — click “Download” in the top-right corner to save it
- This tier generates one image per run
- Requires a recent desktop Chrome or Edge; the option is greyed out on browsers that do not support it
This is a console feature. Calling
gpt-image-2through the API still returns the ~1.57 MP output described above.
Edit images
Edits use multipart/form-data: upload the source image (required) plus an optional mask (transparent areas are repainted):
curl https://image.zetaapi.ai/v1/images/edits \
-H "Authorization: Bearer sk-xxxxx" \
-F model=gpt-image-2 \
-F 'prompt=make the center square blue' \
-F image=@source.png \
-F mask=@mask.png
- Source image + mask total ≤ 14MB (larger returns
413) - Formats: png / jpeg
sizebehaves the same as on generations — accepted, ignored; see Output dimensions
Generating images in Codex
Codex ships with a built-in image tool, image_gen, but it does not work on this
platform — that is not a configuration problem, so there is no switch to look for.
image_gen is a hosted tool that Codex hard-codes to the ChatGPT login. In any session
authenticated with an API key plus a custom base_url — which is what cc-switch and
similar tools set up — the tool never appears in the model’s tool list at all, and
[features] image_generation = true has no effect on such a session. OpenAI is tracking
this in issue #24465; there is no
workaround today.
We ship a Codex plugin that fills the gap:
codex plugin marketplace add Hermerz/zetaapi-codex-plugins
codex plugin add zeta-imagegen@zetaapi
export ZETAAPI_KEY=sk-your-key
Once installed, just ask Codex in plain language — “draw a cyberpunk city at night, 16:9”. Editing works too (“make this one snowy” plus a local file path).
The plugin only accepts
ZETAAPI_KEY, or reuses the key of the activemodel_providerwhen itsbase_urlpoints at this platform. It never reads credentials belonging to your other providers — a gateway key is indistinguishable in shape from an OpenAI or Anthropic key, so guessing would send your real third-party credentials to the wrong place. Ownership is therefore decided bybase_url, not by the key itself. Source is public: Hermerz/zetaapi-codex-plugins
Prefer not to install a plugin? Codex can run commands: wrap the curl above in a
script and let Codex call it — same result.
Billing, latency & timeout
- Per-image billing: every generation / edit deducts real quota from the key; multipliers are on the pricing page. Edits feed the whole source image as input, so input tokens are noticeably higher.
- Latency: generation typically takes 20–60s, but complex prompts (multi-view, ultra-detailed renders) or peak load can exceed 3 minutes. Set your client timeout to ≥ 300s so requests aren’t cut off early — the gateway only returns
504after 240s without a first byte from upstream, so disconnecting sooner just wastes the wait. Edits (/v1/images/edits) need more: your client timeout usually starts at upload, while the gateway’s 240s budget only starts once the source image has finished uploading — set it to ≥ upload time + 300s (≥ 400s to be safe). - Image responses are non-streaming — the full result comes back at once.
Error codes
Consistent with the other endpoints. Common ones:
| HTTP | Meaning |
|---|---|
400 | Bad request (missing prompt, or the prompt was refused by content policy) |
401 | Invalid or revoked key |
402 | Insufficient balance |
413 | Edit upload over 14MB |
429 | Rate-limited — retry with exponential backoff |
Full list in the API Reference.
More
- Try it live → Image Playground in the console
- General API → API Reference
- Billing → pricing page