Image-to-image editing
Hand the model an image plus an instruction and get the edited image back — full usage and examples for gpt-image-2 / Seedream / Nano-Banana on /v1/images/generations and /v1/images/edits
Image-to-image means handing the model one or more reference images plus an edit instruction and getting a new image back (recolour, swap elements, style transfer, multi-image composition). The platform exposes two endpoints, both on the same pipeline underneath:
| Endpoint | Wire shape | When to use it |
|---|---|---|
POST /v1/images/generations | JSON, references in the image array | simplest for hand-written HTTP. Omit image and it's text-to-image — one endpoint, both modes |
POST /v1/images/edits | JSON or multipart/form-data | when you want OpenAI's SDK client.images.edit(image=...), or want to upload raw local file bytes without base64 |
Both endpoints share the same models, billing, channel ladder, Idempotency-Key handling and error shape — /v1/images/edits simply normalises multipart into the same JSON envelope and continues down the same path. The one behavioural difference: on /v1/images/edits a request without a reference is rejected with 400 (a missing reference is a caller bug there, and it will never silently degrade into text-to-image), while /v1/images/generations without image is just an ordinary text-to-image call.
Correction to earlier docs (2026-08-02): this page used to state that
gpt-image-2was text-to-image only and that the platform had no/v1/images/edits. That conclusion was wrong — we had only probed the upstream's/images/generations(references are parsed only on its edits surface), so a gap in our own configuration got recorded as a limit of the model.gpt-image-2image-to-image is now live and production-verified, on both endpoints.
Route 1: gpt-image-2 (recommended)#
gpt-image-2 accepts both public URLs and base64 data URIs, works on both endpoints, costs $0.059 per image, and does not charge for reference images (one reference or ten, same price).
The request (/v1/images/generations)#
{
"model": "gpt-image-2",
"prompt": "Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
"image": ["https://your-host.com/original.png"]
}
| Field | Required | Notes |
|---|---|---|
model | ✓ | gpt-image-2 |
prompt | ✓ | the edit instruction. To control the aspect ratio, put it here (see "Size and aspect ratio" below) |
image | ✓ (for editing) | reference image(s): a string or an array; each item is a public https URL or data:image/png;base64,.... Omit it for text-to-image |
image_urls | equivalent spelling (array); both are merged, wire order preserved | |
n | how many images, default 1, max 10. unit price × n is held on submit and settled against the number actually returned | |
size | forwarded verbatim, but the upstream does not honour it for gpt-image-2 (see below) |
curl#
curl https://zhonkezhonkeapi.dflop.top/v1/images/generations \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
--max-time 300 \
-d '{
"model": "gpt-image-2",
"prompt": "Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
"image": ["https://your-host.com/original.png"]
}'
The response is the OpenAI Images shape, with data[].url holding the edited image (verbatim from a live call):
{
"created": 1765432100,
"expires_at": 1765518500,
"data": [
{
"url": "https://r2.dflop.top/gateway/images/ephemeral/ab2f1e08-....png",
"revised_prompt": "..."
}
],
"usage": {
"input_tokens": 1103,
"input_tokens_details": { "image_tokens": 1024, "text_tokens": 79 },
"output_tokens": 229,
"total_tokens": 1332
}
}
usage.input_tokens_details.image_tokens > 0means the reference was actually consumed. It's the most reliable machine-checkable signal that editing really took effect — if it's0and the image still changed, the model redrew from your prompt rather than editing your picture. Measured: one 768×768 reference = 1024, one 1254×1254 = 1521. (With 8 or more references the upstream's usage accounting collapses to0; that's a bug in its statistics, not a dropped reference.)⚠️
data[]carries nosizefield here (unlike Seedream) — decode the image if you need its dimensions.
With the official OpenAI SDK (/v1/images/edits, upload the file directly)#
No need to base64 anything yourself — the SDK's images.edit() sends multipart, which this platform accepts natively:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["PLATFORM_API_KEY"],
base_url="https://zhonkezhonkeapi.dflop.top/v1",
timeout=300.0, # gpt-image-2 measurably takes 30–215s; don't use the default timeout
)
result = client.images.edit(
model="gpt-image-2",
image=open("original.png", "rb"), # several references: [open(a, "rb"), open(b, "rb")]
prompt="Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
)
print(result.data[0].url)
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.PLATFORM_API_KEY,
baseURL: "https://zhonkezhonkeapi.dflop.top/v1",
timeout: 300_000,
});
const result = await client.images.edit({
model: "gpt-image-2",
image: fs.createReadStream("original.png"),
prompt: "Make the sofa blue, leave everything else unchanged. Aspect ratio: 3:2 (landscape)",
});
console.log(result.data[0].url);
Plain curl over multipart works the same way (field name image, or image[] for several):
curl https://zhonkezhonkeapi.dflop.top/v1/images/edits \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
--max-time 300 \
-F "model=gpt-image-2" \
-F "prompt=Make the sofa blue, leave everything else unchanged" \
-F "image[][email protected]" \
-F "image[][email protected]"
Reference not publicly reachable? Send a data URI#
When you pass a reference as a URL, the server fetching that URL is the upstream's, not our gateway's — the fact that you can open it, or that we can, says nothing about whether the upstream can. Object storage inside China, intranet addresses, image hosts behind auth or bot protection, short-lived signed links: the typical symptom is an upstream 400, Unable to download content from the provided URL.
When that happens, send the bytes (data URI, or the multipart form above) rather than fighting the URL:
B64=$(base64 -i original.png | tr -d '\n')
curl https://zhonkezhonkeapi.dflop.top/v1/images/edits \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
--max-time 300 \
-d "{
\"model\": \"gpt-image-2\",
\"prompt\": \"Make the sofa blue, leave everything else unchanged\",
\"image\": [\"data:image/png;base64,$B64\"]
}"
Size and aspect ratio: size does nothing for gpt-image-2 — put it in the prompt#
The upstream does not honour the size field for gpt-image-2 (ask for 1536x1024 and you may get a 902×1744 portrait). What measurably does work is writing the ratio into the prompt, and it lands reliably:
Make the sofa blue, leave everything else unchanged. Aspect ratio: 16:9 (landscape)
- A ratio in the prompt overrides the
sizefield when the two disagree - Common forms:
1:1 (square),3:4 (portrait),16:9 (landscape),9:16 (portrait) - Don't put
8K/4Kin the prompt — it doesn't raise the actual pixel count and only disturbs composition - Other models (Seedream / Nano) honour
sizenormally; this only applies togpt-image-2
Multiple reference images#
The image array is forwarded in written order, and that order is part of the contract (composition instructions like "the character from the first, the background from the second" depend on it). Forty references still read back accurately down to the last one, so the real ceiling isn't the count but the request body size:
- The gateway's body limit is 95 MB, and base64 inflates bytes by roughly 33%
- Real phone photos run ~3 MB each, which puts the practical ceiling around 20 images
- Billing is independent of the reference count: one or forty, it's the same $0.059 per generated image
Cost and latency#
| Price | $0.059 per image (references are not charged) |
| Measured latency | roughly 30–215 seconds (three live edits on 2026-08-04 took 33s / 48s / 70s; the historical peak is 215s) — set your client timeout to ≥ 300 seconds |
| Gateway budget | 240s per attempt, 280s across the whole channel ladder |
| Returned image | data[].url. For gpt-image-2 this is measurably a platform-hosted temporary link (https://r2.dflop.top/gateway/images/ephemeral/<uuid>.png, swept after 24 hours; expires_at in the body is the exact expiry). Don't treat it as permanent hosting — copy it into your own storage; until it expires the receipt on logs.dflop.top also shows the thumbnail and link |
⚠️ A short client timeout (the usual 60s / 120s defaults) will cut off a request the gateway is still legitimately waiting on: you are billed, and you never receive the image.
Verification status of the examples on this page (2026-08-04): all three shapes — generations + data URI, edits + multipart file, generations + a public URL (using the
image_urlsspelling) — returned HTTP 200 withimage_tokens > 0, and the output preserved elements of the reference that the prompt never mentioned (only the object named in the prompt changed colour). That is the reference genuinely being read and edited, not redrawn from the prompt. Feeding a previous result's URL back in as the reference (chained editing) works too.
Route 2: Seedream / Nano-Banana#
Same /v1/images/generations endpoint, references in the same image array. Best when you want batches (n > 1) or a lower unit price.
Models that can edit#
| Model ID | Display name | Price (per image) | How to pass the reference |
|---|---|---|---|
doubao-seedream-4-0-250828 | Seedream 4.0 | $0.029 | URL or data-URI, size ≥ 960×960 |
doubao-seedream-4-5-251128 | Seedream 4.5 | $0.037 | URL or data-URI, size must be ≥ 1920×1920 |
doubao-seedream-5-0-260128 | Seedream 5.0 | $0.032 | URL or data-URI, size must be ≥ 1920×1920 |
doubao-seedream-5-0-pro-260628 | Seedream 5.0 Pro | $0.044 for output ≤ 2.36 MP, $0.088 above, plus $0.003 per input reference image | URL or data-URI, size ≥ 960×960 is enough |
nano-banana | Nano Banana | $0.039 | public URL only (no data-URI) |
nano-banana-pro | Nano Banana Pro | $0.134 | public URL only (no data-URI) |
nano-banana-2 | Nano Banana 2 | $0.04 | URL or data-URI |
Reference-image input formats differ slightly per model, so a publicly reachable https URL is the safest bet (every model accepts one). data-URIs are accepted by the whole Seedream line,
nano-banana-2andgpt-image-2.
The request#
{
"model": "doubao-seedream-4-5-251128",
"prompt": "Make the sofa blue, leave everything else unchanged",
"image": ["https://your-host.com/original.png"],
"size": "2048x2048"
}
| Field | Required | Notes |
|---|---|---|
model | ✓ | any editing-capable model ID from the table above |
prompt | ✓ | the edit instruction (describe what you want changed) |
image | ✓ (for editing) | array of reference images, 1–10; omit it and this degrades to text-to-image |
size | "WxH", passed upstream; Seedream 4.5/5.0 require ≥ 1920×1920 | |
n | how many images, default 1, max 10 |
image can also be written as image_urls (equivalent). With several references, the upstream treats the first as the primary edit target and the rest as style or element references.
curl#
curl https://zhonkezhonkeapi.dflop.top/v1/images/generations \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "doubao-seedream-4-5-251128",
"prompt": "Make the sofa blue, leave everything else unchanged",
"image": ["https://your-host.com/original.png"],
"size": "2048x2048"
}'
The response#
The same OpenAI Images shape as text-to-image, with data[].url holding the edited image:
{
"model": "doubao-seedream-4-5-251128",
"created": 1765432100,
"data": [{ "url": "https://...", "size": "2048x2048" }],
"usage": { "generated_images": 1 }
}
Route 3: GPT-5.x chat with the image_generation tool#
When what you actually need is for the model to understand the picture before deciding how to change it, use the built-in image_generation tool on a gpt-5.x chat model and pass the reference as a multimodal message. This route bills per token and suits edits that need semantic understanding.
The request#
curl https://zhonkezhonkeapi.dflop.top/v1/chat/completions \
-H "Authorization: Bearer $PLATFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.5",
"stream": true,
"tools": [{ "type": "image_generation" }],
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "Make the sofa in this image blue, leave the rest alone" },
{ "type": "image_url", "image_url": { "url": "https://your-host.com/original.png" } }
]
}]
}'
| Point | Notes |
|---|---|
| Model | gpt-5.5 / gpt-5.6-* (chat models that support the image_generation tool) |
stream | must be true — turns carrying image_generation are forced to stream |
tools | must explicitly include [{ "type": "image_generation" }] |
| Reference image | goes in an image_url block inside the content array; url accepts an https URL or a data:image/...;base64, data-URI |
| Size | optional, written into the tool spec: {"type":"image_generation","size":"1024x1536"}, supporting 1024x1024 / 1024x1536 / 1536x1024 / auto |
The response#
The edited image arrives inline in the streamed body, as markdown image syntax:
data: {"choices":[{"delta":{"content":""}}]}
Regex  out of the accumulated delta.content to get the link.
This route bills by token, not per image: an
image_generationturn carries a fixed overhead of roughly 2,300 input tokens, plus the vision tokens for any reference image (roughly doubling it), with very few output tokens.
Which route to pick#
| Route 1 (gpt-image-2) | Route 2 (Seedream / Nano) | Route 3 (GPT-5.x + tool) | |
|---|---|---|---|
| Endpoint | /v1/images/generations or /v1/images/edits | /v1/images/generations | /v1/chat/completions |
| Call shape | one synchronous response | one synchronous response | streaming (SSE) |
| Billing | $0.059/image, references free | per image ($0.029–$0.134) | per token |
| Images per call | up to n (≤10) | up to n (≤10) | always 1 |
| References | URL / data-URI / file upload, dozens (size-bound) | URL (data-URI on some), 1–10 | multimodal array, several |
| Size control | prompt-only aspect ratio | size works normally | size in the tool spec |
| Latency | 30–215 seconds | typically 5–20 seconds | depends on the turn |
| Best for | GPT-style edits, multi-image composition, uploading local files | fast batch edits with exact dimensions | fine edits that need image understanding |
For ordinary edits — recolouring, swapping elements, style transfer — pick route 1 when you want GPT's look and multi-image composition, and route 2 when you want exact dimensions, speed, or batches.
Limitations#
- Returned image URLs are always temporary:
gpt-image-2measurably serves platform-hostedr2.dflop.top/gateway/images/ephemeral/…links (swept after 24 hours;expires_atis the exact moment), while Seedream / Nano usually return upstream pre-signed links (about 24 hours). Neither is permanent hosting — download and store them promptly. - When a reference is a URL, that URL must be reachable from the upstream's network; if it can't be fetched you get the upstream's 400, and the fix is a data URI or a multipart upload.
- The request body limit is 95 MB (base64 inflates size by roughly 33%).
gpt-image-2ignores thesizefield upstream — put the aspect ratio in the prompt./v1/images/editswithout a reference returns 400 with`image` is required on /v1/images/edits(not billed); use/v1/images/generationsfor text-to-image as the message says.- Generation is slow (30–215 seconds for
gpt-image-2in particular), so set your client timeout to ≥ 300 seconds. - To retry without being charged twice, send an
Idempotency-Key(supported on both endpoints; the same key replays the first response and is not billed again) — see Image / video / music APIs. - If your key has an
allowed_modelsallowlist, add the editing model to it first or you'll get a 403. - Full image / video / music endpoint documentation: Image / video / music APIs.