Image editing
Upload an image, describe the change in words, and receive a new image. The original is never modified.
POST
/v1/images/editsThis endpoint takes multipart/form-data because it carries a file. Everything else — the job lifecycle, polling, statuses and billing — is identical to Image generation.
curl
curl https://api.xkiro.com/v1/images/edits \
-H "Authorization: Bearer $XKIRO_API_KEY" \
-F image=@./photo.jpg \
-F prompt="Make the sky a dramatic sunset, keep everything else unchanged" \
-F size=1024x1024202 Accepted
{
"id": "9c4e1b70-2a83-4f16-8d55-7be0c1a94d22",
"object": "image.generation.job",
"status": "processing",
"created": 1785734400,
"prompt": "Make the sky a dramatic sunset, keep everything else unchanged"
}Form fields
| Field | Type | Description |
|---|---|---|
imagerequired | file | The source image. JPEG, PNG, GIF or WebP. The type is detected from the bytes, so a wrong file extension is not a problem — but a non-image file is rejected with 400. |
promptrequired | string | What to change. Describe the end state, not the editing steps. |
model | string | Image model ID. Omit for your account default. |
size | string | Output size. Defaults to the source image's aspect ratio. |
n | integer | How many variations to produce. Each is billed as one unit. |
TypeScript
import { readFile } from "node:fs/promises";
const form = new FormData();
form.append(
"image",
new Blob([await readFile("./photo.jpg")], { type: "image/jpeg" }),
"photo.jpg",
);
form.append("prompt", "Make the sky a dramatic sunset, keep everything else unchanged");
const job = await fetch("https://api.xkiro.com/v1/images/edits", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.XKIRO_API_KEY}` },
// No Content-Type header: fetch sets it, including the multipart boundary.
// Setting it by hand omits the boundary and the request fails to parse.
body: form,
}).then((r) => r.json());
console.log(job.id); // poll GET /v1/images/generations/{id}Python
import requests
with open("photo.jpg", "rb") as f:
job = requests.post(
"https://api.xkiro.com/v1/images/edits",
headers={"Authorization": f"Bearer {API_KEY}"},
files={"image": ("photo.jpg", f, "image/jpeg")},
data={"prompt": "Make the sky a dramatic sunset, keep everything else unchanged"},
).json()
print(job["id"]) # poll GET /v1/images/generations/{id}Getting the result#
Poll GET /v1/images/generations/{id} exactly as you would for a generated image. The polling loop on the Image generation page works unchanged.
A returned image is always a new one
If the model cannot produce a genuinely different image for your instruction, the job fails rather than handing back your original. Silently returning the input would look like success and waste a billable unit.
Writing edit instructions#
- Describe the destination, not the route."A dramatic sunset sky" works; "select the sky and apply a gradient" does not — the model is not an image editor.
- Say what must stay.Adding "keep the subject and composition unchanged" measurably reduces unwanted rewrites of the rest of the frame.
- One change at a time. Three instructions in one prompt usually produce one good change and two approximations. Chain jobs instead, feeding each result into the next.
Limits#
- Uploads are capped in size; oversized files are rejected before any work is queued.
- Edits share the same daily image allowance, wallet charging and in-flight job cap as generation. See Pricing & billing.
