Image Generation
Serve the OpenAI Images API from your own instance — generations and edits, streaming, per-image billing, and what the gateway forwards.
The self-hosted gateway serves the OpenAI Images API beside the chat
endpoints: POST /v1/images/generations for text-to-image and
POST /v1/images/edits for instruction-based editing. Which models answer
them is your configuration: a model must declare image output
(output_modalities), and each of its candidates bills either per token or
per delivered image. GET /v1/models lists every model's
output_modalities, so clients can discover the image models an instance
serves.
Quick start
curl http://localhost:8080/v1/images/generations \
-H "Authorization: Bearer sk-yr-..." \
-H "Content-Type: application/json" \
-d '{"model":"wan2.2-image","prompt":"a cat","n":1,"size":"1024x1024"}'The response follows the OpenAI Images shape (data[].url or data[].b64_json,
depending on the upstream).
What is forwarded
The request body is forwarded with only the model field rewritten to the
provider's own model id. The gateway reads model, prompt, n, size,
quality, response_format and stream; fields it does not read (style,
background, provider-private extensions, …) reach the upstream exactly as you
wrote them — and so does size: an upstream with its own size vocabulary
answers with its own refusal, e.g. Ark Seedream takes 2K/4K or pixel
sizes of at least 1280 and rejects 1024x1024.
DashScope providers — dashscope.aliyuncs.com, dashscope-intl.aliyuncs.com,
and Model Studio workspace domains ({workspaceId}.{region}.maas.aliyuncs.com)
— are served through the native multimodal-generation endpoint: the request is
re-encoded into that dialect and the response decoded back into the OpenAI
shape. Those upstreams answer with image URLs only, so a
response_format: "b64_json" request is refused for those candidates.
Kling providers (*.klingai.com) — the image line is a task family
(the model_name form; v1/v1-5/v2/v2-new retire 2026-09-15): kling-v3
(Kling Image 3.0) rides /v1/images/generations, while the
multi-reference Omni pair (kling-v3-omni, kling-image-o1) rides
/v1/images/omni-image — the gateway routes by model. The request is
re-encoded into that dialect and submitted, and the gateway drives the
task to its terminal state inside the request's own budget before
answering synchronously in the OpenAI shape — indistinguishable from
any other image endpoint to the caller. Size mapping: omitted → the
endpoint's own defaults (1k, 16:9); 1k/2k pass through (4k is the
Omni family's own tier — kling-v3 asks of it are refused by the
upstream itself); a WIDTHxHEIGHT spelling maps to the nearest of the
eight documented aspect ratios with 1k/2k picked by long side; any other
spelling is refused for the candidate. Omni-native fields pass
through: image_list[] (multi-reference, URL or bare base64),
element_list[] (subject-library element ids — subjects are created in
Kling's console), result_type (single/series), and series_amount
(2-9/auto) ride verbatim to the upstream; a series delivery's
series_images join data[] and bill per delivered image; the
<<<image_1>>> prompt-template references ride with the prompt. These
upstreams answer with URLs too (cleaned by the upstream after 30 days),
b64_json and edits asks are refused for those candidates, and a
per-image price table should be sized from Kling's own pricing page (a
task's final_unit_deduction is the reconciliation reference). Key
verification goes through the image probe — one real generation, billed
to the account.
Edits
POST /v1/images/edits takes the OpenAI multipart upload — one or more
reference images, a prompt, and the usual fields:
curl http://localhost:8080/v1/images/edits \
-H "Authorization: Bearer sk-yr-..." \
-F model=qwen-image-edit \
-F prompt="make the background pure white" \
-F size=1024x1024 \
-F image=@input.pngOpenAI-compatible providers receive the upload with only the model field
rewritten; mask and other fields you send travel with it as written.
DashScope providers are served through the native multimodal-generation
dialect with the reference images carried as base64 data URIs — including
multi-image edits, which that dialect accepts natively. The dialect has no
mask field, so a request that carries one is refused for those candidates
rather than silently dropping it; on an all-DashScope chain the request
fails with the aggregated reason. The audit row records the upload's shape
— field names, file names, sizes — never the pixel bytes.
Edit-shaped models (whose names say edit, e.g. qwen-image-edit) are
probed with a reference image attached, so the provider test measures the
mapping rather than the edit family's own input rule.
Streaming
With stream=true, gpt-image-* models answer in named-event SSE —
image_generation.partial_image and image_generation.completed frames on
the generation route, image_edit.* on the edits route — forwarded verbatim
as they arrive. Any other model family rejects a streaming ask with a 400,
including the DashScope native dialect, which has no streaming half.
Usage is read from the completed events, and the per-image settlement counts
the images that actually completed. An incomplete delivery bills nothing:
the upstream's own error event, a stream that ends before completing an
image, or a broken read all settle with no charge.
Billing
Per the candidate's declared billing mode:
- Per image — a quality×size price table (with an optional default price) resolves the unit price, multiplied by the number of images actually delivered. No match and no default price leaves the request unpriced — recorded as unknown, which is not the same as free.
- Per token — the upstream-reported token usage at the candidate's per-million prices.
Failed requests, and HTTP 200 responses that delivered no images, are never billed. Requests settle against your API key's budget like any other call.
Limitations
- Returned URLs belong to the upstream: they expire on the upstream's schedule, and the gateway does not re-host image files.
- Streaming is a
gpt-image-*family capability: it is absent for other families as a phased capability of this gateway, not a permanent contract.