Self-hosting

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.png

OpenAI-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.