Back to Home

Image Edits API (Image-to-Image)

OpenAI-compatible image editing endpoint. Generates a new image from a reference source image plus a text prompt. Supports common image-to-image workflows: anchor-image variants, character outfit changes, background swaps, etc.

Two calling methods are supported:

  1. JSON body + image_key (Tokensmart extension): upload the image first via File Upload API to get a key, then pass image_key
  2. multipart/form-data + image file (OpenAI standard): upload the image directly in the request — compatible with the OpenAI SDK and third-party clients (LobeChat, Cherry Studio, etc.)

Need multi-image compose (blend several reference images)? Use the Image Compose API — pass up to 3 reference images in a single request, with the same response shape as /v1/images/edits.

Request

POST /v1/images/edits

Headers

Method 1 (JSON):

Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx
Content-Type: application/json

Method 2 (multipart):

Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx
Content-Type: multipart/form-data

x-api-key (Anthropic SDK style) is also supported.

Body

Method 1: JSON body (requires prior upload)

ParameterTypeRequiredDescription
modelstringModel name, e.g. gpt-image-2
promptstringEdit instruction, up to 32,000 characters (e.g. "change background to blue sky")
image_keystring⚠️The key returned by the upload endpoint (e.g. files/USER_ID/UUID_xxx.jpg) — mutually exclusive with generation_id
generation_idstring⚠️UUID of a previously generated image (from /v1/images/generations or /v1/images/edits) — mutually exclusive with image_key
nintegerNumber of images, default 1, max depends on model config
sizestringImage dimensions, see supported list below
qualitystringQuality level: auto, high, medium, low
backgroundstringBackground: auto, transparent, opaque
output_formatstringOutput format: png, jpeg, webp
response_formatstringReturn format, always b64_json (no external URLs exposed)

Method 2: multipart/form-data (direct upload, OpenAI standard)

FieldTypeRequiredDescription
imageFileSource image file (JPEG/PNG/GIF/WebP, max 25 MB)
modelstringModel name, e.g. gpt-image-2
promptstringEdit instruction
nstringNumber of images (string form, e.g. "1")
sizestringImage dimensions
qualitystringQuality level

In multipart mode, the system automatically uploads the image to storage and then performs the edit. Max request body: 26 MB.

Supported Sizes

Same policy as Image Generation API — any auto or WIDTHxHEIGHT string (≤ 16 chars) is accepted. See the Supported Sizes section of the Image Generation page for the verified-working list and the auto-refund behaviour when a size is not supported.

Request Examples

Using image_key from an upload:

{
  "model": "gpt-image-2",
  "prompt": "replace background with a city skyline at sunset, keep pose unchanged",
  "image_key": "files/USER_ID/UUID_anchor.jpg",
  "n": 1,
  "size": "1024x1024"
}

Using generation_id from a previous generation (chained edits):

{
  "model": "gpt-image-2",
  "prompt": "change the shirt color to red",
  "generation_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "n": 1
}

Response

{
  "created": 1776864985,
  "data": [
    {
      "b64_json": "iVBORw0KGgo..."
    }
  ],
  "picklyone_generation_ids": [
    "a1b2c3d4-5678-90ab-cdef-1234567890ab"
  ]
}

picklyone_generation_ids lists the UUIDs of images produced by this call — you can use any of them as the generation_id for the next /v1/images/edits call (chained edits).

Billing

Identical to Image Generation API: per-call billing, actualCount × price_per_image. The pre-deduction freezes balance for the requested n, settlement charges for the actually returned count, the difference is refunded automatically.

Python Example (full two-step flow)

import requests, base64

API_KEY = "pk_live_xxxxxxxxxxxxxxxx"
BASE = "https://api.tokensmart.ai"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Step 1: upload source image
with open("anchor.jpg", "rb") as f:
    up = requests.post(
        f"{BASE}/api/v1/upload",
        headers=HEADERS,
        files={"files": f},
    ).json()
image_key = up["uploaded"][0]["key"]

# Step 2: call edits
resp = requests.post(
    f"{BASE}/v1/images/edits",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "model": "gpt-image-2",
        "prompt": "replace background with a city skyline at sunset",
        "image_key": image_key,
        "n": 1,
        "size": "1024x1024",
    },
).json()

# Save the edited image
image_data = base64.b64decode(resp["data"][0]["b64_json"])
with open("edited.png", "wb") as f:
    f.write(image_data)

cURL Example

# Step 1: upload source image
UPLOAD=$(curl -sX POST https://api.tokensmart.ai/api/v1/upload \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx" \
  -F "files=@/path/to/anchor.jpg")
IMAGE_KEY=$(echo "$UPLOAD" | jq -r '.uploaded[0].key')

# Step 2: call edits
curl https://api.tokensmart.ai/v1/images/edits \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"gpt-image-2\",
    \"prompt\": \"change background to city skyline at sunset\",
    \"image_key\": \"$IMAGE_KEY\",
    \"n\": 1
  }"

Method 2: multipart/form-data (direct upload, OpenAI standard)

curl https://api.tokensmart.ai/v1/images/edits \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxx" \
  -F "image=@/path/to/anchor.jpg" \
  -F "model=gpt-image-2" \
  -F "prompt=change background to city skyline at sunset" \
  -F "n=1"

One step — compatible with the OpenAI SDK and third-party clients like LobeChat and Cherry Studio.

Node.js Example

import fs from "fs";

const API_KEY = "pk_live_xxxxxxxxxxxxxxxx";
const BASE = "https://api.tokensmart.ai";

// Step 1: upload
const form = new FormData();
form.append("files", new Blob([fs.readFileSync("anchor.jpg")], { type: "image/jpeg" }), "anchor.jpg");
const up = await fetch(`${BASE}/api/v1/upload`, {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}` },
  body: form,
}).then(r => r.json());
const imageKey = up.uploaded[0].key;

// Step 2: edits
const res = await fetch(`${BASE}/v1/images/edits`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-image-2",
    prompt: "change background to city skyline at sunset",
    image_key: imageKey,
    n: 1,
  }),
}).then(r => r.json());

fs.writeFileSync("edited.png", Buffer.from(res.data[0].b64_json, "base64"));

Common Errors

HTTPCodeDescription
400invalid_requestBoth image_key and generation_id supplied, or neither, or empty prompt
400invalid_image_keyMalformed image_key, contains traversal characters, or does not belong to the caller
400invalid_generation_idgeneration_id is not a valid UUID
400IMAGE_TYPE_UNSUPPORTEDSource image MIME type is not supported
400IMAGE_TOO_LARGESource image exceeds 25 MB
400content_policy_violationPrompt hit content moderation
401invalid_api_keyInvalid API key
402insufficient_balanceInsufficient balance
402OUTSTANDING_DEBTAccount has outstanding unpaid charges
404generation_not_foundgeneration_id does not exist or does not belong to the caller
404IMAGE_NOT_FOUNDFile pointed to by image_key no longer exists
409generation_not_readygeneration_id's source image is still being stored — retry shortly
429rate_limit_exceededRate limit exceeded
502upstream_errorService error

Notes

  • Edits and generations are priced identically — per-call cost is the same
  • Source images max 25 MB. Tokensmart does not re-compress — original quality is fully preserved to avoid accumulated quality loss across chained edits. Larger source images mean longer latency, so practically aim for 5-10 MB
  • Source images are not auto-deleted — use the file management endpoint or console to clean up
  • If you upload and immediately call edits, you may occasionally hit 409 generation_not_ready — retry after 1-2 seconds
  • For non-image models use /v1/chat/completions instead of this endpoint