An OpenAI-compatible passthrough proxy for Chutes image-generation endpoints.
It exposes a minimal /v1 surface (just GET /v1/models and
POST /v1/images/generations) and forwards each image request to a
configured Chutes /generate endpoint, reshaping the binary image response
as an OpenAI-style image-generation completion.
Chutes image endpoints speak their own small JSON dialect and return raw image bytes. This proxy lets you point any OpenAI-compatible client at one or more Chutes backends by model name, without the client knowing anything about Chutes.
| Method | Path | Description |
|---|---|---|
| GET | /v1/models |
List configured models |
| POST | /v1/images/generations |
Generate one or more images |
That is the entire surface. No other endpoints are exposed (intentionally — this is a thin passthrough, not a featureful gateway).
Models live in config/models.json (the path can be overridden using the
CONFIG_PATH environment variable). Each entry maps a model id to a
Chutes URL plus a set of default upstream parameters:
{
"models": [
{
"id": "z-image-turbo",
"chutes_url": "https://vonkaiser-z-image-turbo.chutes.ai/generate",
"owned_by": "chutes",
"defaults": {
"seed": 42,
"shift": 3,
"guidance_scale": 0,
"max_sequence_length": 512,
"num_inference_steps": 9
}
}
]
}The defaults object is where all Chutes-specific parameters (seed,
shift, guidance_scale, num_inference_steps, max_sequence_length,
...) are set — clients only need to send the OpenAI-spec fields (model,
prompt, size, n).
Add as many entries as you like — one per Chutes endpoint you want to expose. The file is re-read on every request, so you can edit it on the host without restarting the container.
The proxy stores no credentials. Each request must carry the Chutes API
key in its Authorization header (e.g. Authorization: Bearer ct_...),
which is forwarded verbatim to the upstream Chutes endpoint.
Use any OpenAI-compatible client. With the official OpenAI Python client:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://your-public-host.example.com/v1",
api_key=os.environ["CHUTES_API_KEY"],
)
img = client.images.generate(
model="z-image-turbo",
prompt="a serene mountain lake at sunset",
size="1024x1024",
)
print(img.data[0].b64_json[:64], "...")Equivalent curl:
curl -X POST https://your-public-host.example.com/v1/images/generations \
-H "Authorization: Bearer $CHUTES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-image-turbo",
"prompt": "a serene mountain lake at sunset",
"size": "1024x1024"
}'The upstream Chutes payload is built from:
- The model's
defaultsinconfig/models.json(the canonical source for Chutes-specific knobs likeseed,shift,guidance_scale,num_inference_steps,max_sequence_length), then - The OpenAI-spec fields from the request:
| OpenAI request field | Mapped to Chutes |
|---|---|
model |
selects upstream URL |
prompt |
prompt |
size (WxH) |
width, height |
n |
N sequential upstream calls |
Images are always returned as b64_json.
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8080docker-compose.yml is included.
The config/ directory is bind-mounted read-only
so models.json can be edited on the host without rebuilding the image.
docker compose up -dYou can grab my builds from GitHub Packages or Docker Hub (docker.io/qbsoon/chutes-passtrough).
| Variable | Default | Purpose |
|---|---|---|
CONFIG_PATH |
config/models.json |
Path to model registry |
UPSTREAM_TIMEOUT |
120 |
Per-request upstream timeout (s) |
LOG_LEVEL |
INFO |
Python log level |
- Only
b64_jsonresponses are returned (see above). n > 1is implemented as N sequential upstream calls (no parallelism).- The proxy is stateless: no caching, no storage, no extra endpoints.
- Errors are returned in the
{"error": {"message", "type"}}shape.