Image editing

Upload an image, describe the change in words, and receive a new image. The original is never modified.

POST/v1/images/edits

This 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=1024x1024
202 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
FieldTypeDescription
imagerequiredfileThe 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.
promptrequiredstringWhat to change. Describe the end state, not the editing steps.
modelstringImage model ID. Omit for your account default.
sizestringOutput size. Defaults to the source image's aspect ratio.
nintegerHow 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.