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. Required — there is no 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.

Was this page helpful?