OpenAI & Google Gemini Compatible Image Generation API
A free, self-hosted proxy that wraps Z.AI image generation and exposes it as OpenAI and Gemini compatible API endpoints.
Most AI image generation APIs cost money per image. Z.AI offers high quality image generation, but with a non-standard API. This proxy bridges that gap, giving you a free, self-hosted image generation API that works with any tool or SDK that speaks OpenAI or Gemini.
| Feature | Description |
|---|---|
| 🔌 OpenAI Compatible | Drop-in replacement for POST /v1/images/generations |
| 💬 Chat Completions | Works with POST /v1/chat/completions — compatible with Open WebUI, LibreChat, LiteLLM, etc. |
| 🔷 Gemini Compatible | Supports POST /v1/models/:model:generateContent with inlineData response |
| 🖼️ Two Quality Tiers | z-image (1K, ~30s) and z-image-hd (2K HD, ~2min) |
| 🔄 Smart Model Aliases | Send dall-e-3, gpt-image-1, gemini-2.0-flash, or imagen-3 and they all just work |
| 🛡️ Production Hardened | Rate limiting, structured logging, graceful shutdown, error boundaries, secret masking |
| 🔑 Auto Session Refresh | OAuth-based token renewal via chat.z.ai |
| 🚀 Deploy Anywhere | Render, Vercel, Railway, Docker, or any Node.js host |
git clone https://github.com/TheOwlKun/zai-image-proxy.git
cd zai-image-proxy
npm installcp .env.example .envEdit .env and fill in your tokens:
Z_IMAGE_SESSION=your_session_token_here
Z_CHAT_TOKEN=your_chat_token_here
API_KEY=sk-your-secret-key
PORT=3000🔑 How to get your tokens (click to expand)
- Open image.z.ai in your browser
- Open DevTools → F12 (or right-click → Inspect)
- Go to Application tab → Storage → Cookies → Select
https://image.z.ai - Find the cookie named
session - Copy its Value — that's your
Z_IMAGE_SESSION
- Open chat.z.ai in your browser
- Open DevTools → F12
- Go to Application tab → Storage → Cookies → Select
https://chat.z.ai - Find the cookie named
token - Copy its Value — that's your
Z_CHAT_TOKEN
💡 Tip: The
Z_CHAT_TOKENallows the proxy to automatically refresh theZ_IMAGE_SESSIONwhen it expires, so you don't have to manually update it.
npm startYou'll see a startup banner confirming everything is working:
╔══════════════════════════════════════════════════════════════╗
║ zai-image-proxy v1.0.0 ║
║ OpenAI + Gemini Compatible Image API ║
╠══════════════════════════════════════════════════════════════╣
║ Port: 3000 ║
║ Session: ✓ Valid (29 days left) ║
║ API Key: ✓ Configured ║
╚══════════════════════════════════════════════════════════════╝
POST /v1/images/generations
The standard OpenAI image generation endpoint. Compatible with the official OpenAI SDK.
curl -X POST http://localhost:3000/v1/images/generations \
-H "Authorization: Bearer sk-your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a serene mountain landscape at sunset, digital art",
"model": "z-image-hd",
"size": "1024x1024",
"quality": "hd",
"response_format": "b64_json",
"n": 1
}'Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
prompt |
string | required | Text description of the image |
model |
string | z-image |
Model to use (see Model Mapping) |
n |
integer | 1 |
Number of images (1–4, generated sequentially) |
size |
string | 1024x1024 |
Image dimensions (see Supported Sizes) |
quality |
string | standard |
standard (1K) or hd (2K) |
response_format |
string | b64_json |
b64_json or url |
style |
string | — | Accepted for compatibility but has no effect |
POST /v1/chat/completions
Send a chat message and receive a generated image — makes the proxy work with tools like Open WebUI, LibreChat, LiteLLM, Cursor, and any OpenAI SDK.
curl -X POST http://localhost:3000/v1/chat/completions \
-H "Authorization: Bearer sk-your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"model": "dall-e-3",
"messages": [
{ "role": "user", "content": "Generate a cyberpunk city at night with neon lights" }
]
}'The response wraps the generated image in standard multimodal chat completion format with image_url content parts.
POST /v1/models/:model:generateContent
Compatible with Google's GenAI SDK. Supports x-goog-api-key header and ?key= query parameter.
curl -X POST "http://localhost:3000/v1/models/gemini-2.0-flash:generateContent" \
-H "x-goog-api-key: sk-your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"parts": [{ "text": "A futuristic cityscape with flying cars" }]
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"]
}
}'Returns image as inlineData with mimeType and base64 data in the standard Gemini candidates response.
POST /generate — Direct access to Z.AI parameters.
curl -X POST http://localhost:3000/generate \
-H "Authorization: Bearer sk-your-secret-key" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a cute cat with blue eyes",
"ratio": "16:9",
"resolution": "2K",
"noWatermark": true
}'GET /images — List your past generations (paginated).
GET /options — List all supported ratios, resolutions, and size mappings.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/v1/models |
Yes | List all available models |
GET |
/v1/models/:id |
Yes | Get a specific model |
GET |
/health |
No | Health check (for platform monitoring) |
GET |
/session |
Yes | View session status |
POST |
/session |
Yes | Set session or chat token via API |
POST |
/session/refresh |
Yes | Force session token refresh |
Python — OpenAI SDK
from openai import OpenAI
client = OpenAI(
api_key="sk-your-secret-key",
base_url="http://localhost:3000/v1"
)
response = client.images.generate(
model="z-image-hd",
prompt="a serene mountain landscape at sunset",
size="1024x1024",
response_format="b64_json",
n=1
)
import base64
image_data = base64.b64decode(response.data[0].b64_json)
with open("generated.png", "wb") as f:
f.write(image_data)
print("✓ Image saved to generated.png")Node.js — OpenAI SDK
import OpenAI from 'openai';
import fs from 'fs';
const client = new OpenAI({
apiKey: 'sk-your-secret-key',
baseURL: 'http://localhost:3000/v1',
});
const response = await client.images.generate({
model: 'z-image-hd',
prompt: 'a serene mountain landscape at sunset',
size: '1024x1024',
response_format: 'b64_json',
n: 1,
});
const imageBuffer = Buffer.from(response.data[0].b64_json, 'base64');
fs.writeFileSync('generated.png', imageBuffer);
console.log('✓ Image saved to generated.png');Python — Chat Completions
from openai import OpenAI
client = OpenAI(
api_key="sk-your-secret-key",
base_url="http://localhost:3000/v1"
)
response = client.chat.completions.create(
model="dall-e-3",
messages=[
{"role": "user", "content": "Generate an image of a magical forest"}
]
)
# Image is embedded as base64 in the response content
print(response.choices[0].message.content)Any client SDK can use familiar model names — they're automatically mapped to Z.AI's engine.
| Client sends | Resolves to | Resolution | Speed |
|---|---|---|---|
z-image |
z-image | 1K | ~30s |
z-image-hd |
z-image-hd | 2K (HD) | ~2min |
dall-e-2 |
z-image | 1K | ~30s |
dall-e-3 |
z-image-hd | 2K (HD) | ~2min |
gpt-image-1 |
z-image-hd | 2K (HD) | ~2min |
gpt-image-2 |
z-image-hd | 2K (HD) | ~2min |
gemini-2.0-flash |
z-image-hd | 2K (HD) | ~2min |
imagen-3 |
z-image-hd | 2K (HD) | ~2min |
Note: Any unrecognized model name defaults to
z-image(1K).
OpenAI size |
Z.AI Ratio | Orientation |
|---|---|---|
1024x1024 |
1:1 | Square |
1792x1024 |
16:9 | Landscape |
1024x1792 |
9:16 | Portrait |
1920x1080 |
16:9 | Landscape |
1080x1920 |
9:16 | Portrait |
1280x720 |
16:9 | Landscape |
720x1280 |
9:16 | Portrait |
Additional ratios via the native /generate endpoint: 3:4, 4:3, 21:9, 9:21.
| Variable | Required | Default | Description |
|---|---|---|---|
Z_IMAGE_SESSION |
Yes | — | Session token from image.z.ai cookies |
Z_CHAT_TOKEN |
Yes | — | Chat token from chat.z.ai for auto-refresh |
API_KEY |
Yes | — | API key for all authenticated endpoints |
PORT |
Yes | 3000 |
Server port |
Z_USER_AGENT |
No | random | Fixed UA string (if not set, rotates randomly per request) |
RATE_LIMIT_MAX |
No | 30 |
Max requests per window per IP |
RATE_LIMIT_WINDOW_MS |
No | 60000 |
Rate limit window duration (ms) |
GENERATION_TIMEOUT |
No | 180000 |
Image generation timeout (ms) |
DOWNLOAD_TIMEOUT |
No | 30000 |
Image download timeout (ms) |
LOG_LEVEL |
No | info |
debug · info · warn · error |
CORS_ORIGINS |
No | * |
Comma-separated allowed origins |
NODE_ENV |
No | development |
development or production |
See .env.example for the complete template.
Render runs a real server with no timeout limits, perfect for HD generation.
- Fork this repo
- Click the button above or go to render.com > New > Web Service
- Connect your forked repo
- Set environment variables:
Z_IMAGE_SESSION,Z_CHAT_TOKEN,API_KEY - Deploy!
Or use the included Render Blueprint for one-click setup.
npm i -g vercel
vercel
⚠️ Vercel Limitation: Free tier has a 60 second function timeout. 2K (HD) generation can take around 2 minutes, so usez-image(1K) for reliability on Vercel.
- Fork this repo
- Click the button above or go to railway.com > New Project > Deploy from GitHub
- Connect your fork, set environment variables
- Done!
docker build -t zai-image-proxy .
docker run -p 3000:3000 --env-file .env zai-image-proxyThe Dockerfile uses a multi-stage Alpine build (~50MB), runs as non-root, and includes a health check.
curl http://localhost:3000/healthReturns session validity, expiry info, and server status. No authentication required — use this for platform monitoring and uptime checks.
| Feature | Details |
|---|---|
| Rate Limiting | In-memory sliding window per IP (configurable) |
| Request Timeouts | Prevents hung connections |
| Graceful Shutdown | SIGTERM/SIGINT with 10s connection draining |
| Input Validation | All parameters validated before hitting Z.AI |
| Error Boundaries | Global handler + unhandledRejection catch |
| Structured Logging | Request IDs, timestamps, levels, masked secrets |
| Secret Masking | Tokens and API keys are never logged in full |
| Auto Session Refresh | Proactive refresh when token nears expiry |
| CORS | Configurable via CORS_ORIGINS env |
| Serverless Safe | No filesystem dependency by default |
| Fail-Fast Startup | Missing config = clear warning at boot |
This proxy wraps Z.AI's image generation service. The following features are not supported because Z.AI doesn't offer them:
| Feature | Status |
|---|---|
| Image editing / inpainting | ❌ Not supported |
| Image variations | ❌ Not supported |
| Streaming responses | ❌ Not supported |
| Negative prompts | ❌ Not supported |
revised_prompt field |
❌ Omitted (not faked) |
Batch generation (n > 1) |
|
style parameter |
zai-image-proxy/
├── api/
│ └── index.js ← Vercel serverless entry
├── src/
│ ├── app.js ← Express app factory
│ ├── config.js ← Centralized configuration
│ ├── errors.js ← Custom error hierarchy
│ ├── logger.js ← Structured logger + secret masking
│ ├── models.js ← Model definitions & alias mapping
│ ├── z-image-client.js ← Core Z.AI reverse-engineered client
│ ├── middleware/
│ │ ├── auth.js ← Bearer + Gemini API key auth
│ │ ├── rate-limiter.js ← In-memory sliding window
│ │ └── error-handler.js ← Global error handler
│ └── routes/
│ ├── openai-images.js ← POST /v1/images/generations
│ ├── openai-chat.js ← POST /v1/chat/completions
│ ├── openai-models.js ← GET /v1/models
│ ├── gemini.js ← POST /v1/models/:model:generateContent
│ ├── native.js ← POST /generate, GET /images
│ └── health.js ← GET /health, session management
├── index.js ← Standalone server entry point
├── vercel.json ← Vercel routing config
├── render.yaml ← Render Blueprint
├── Dockerfile ← Multi-stage Alpine build
└── package.json ← 4 dependencies only
Contributions are welcome! Here's how:
- Fork the repo
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
This project is licensed under the MIT License.
Made with ❤️ by TheOwlKun
⭐ Star this repo if you find it useful!