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:
- JSON body +
image_key(Tokensmart extension): upload the image first via File Upload API to get akey, then passimage_key - multipart/form-data +
imagefile (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)
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | ✅ | Model name, e.g. gpt-image-2 |
prompt | string | ✅ | Edit instruction, up to 32,000 characters (e.g. "change background to blue sky") |
image_key | string | ⚠️ | The key returned by the upload endpoint (e.g. files/USER_ID/UUID_xxx.jpg) — mutually exclusive with generation_id |
generation_id | string | ⚠️ | UUID of a previously generated image (from /v1/images/generations or /v1/images/edits) — mutually exclusive with image_key |
n | integer | ❌ | Number of images, default 1, max depends on model config |
size | string | ❌ | Image dimensions, see supported list below |
quality | string | ❌ | Quality level: auto, high, medium, low |
background | string | ❌ | Background: auto, transparent, opaque |
output_format | string | ❌ | Output format: png, jpeg, webp |
response_format | string | ❌ | Return format, always b64_json (no external URLs exposed) |
Method 2: multipart/form-data (direct upload, OpenAI standard)
| Field | Type | Required | Description |
|---|---|---|---|
image | File | ✅ | Source image file (JPEG/PNG/GIF/WebP, max 25 MB) |
model | string | ✅ | Model name, e.g. gpt-image-2 |
prompt | string | ✅ | Edit instruction |
n | string | ❌ | Number of images (string form, e.g. "1") |
size | string | ❌ | Image dimensions |
quality | string | ❌ | Quality 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
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_request | Both image_key and generation_id supplied, or neither, or empty prompt |
| 400 | invalid_image_key | Malformed image_key, contains traversal characters, or does not belong to the caller |
| 400 | invalid_generation_id | generation_id is not a valid UUID |
| 400 | IMAGE_TYPE_UNSUPPORTED | Source image MIME type is not supported |
| 400 | IMAGE_TOO_LARGE | Source image exceeds 25 MB |
| 400 | content_policy_violation | Prompt hit content moderation |
| 401 | invalid_api_key | Invalid API key |
| 402 | insufficient_balance | Insufficient balance |
| 402 | OUTSTANDING_DEBT | Account has outstanding unpaid charges |
| 404 | generation_not_found | generation_id does not exist or does not belong to the caller |
| 404 | IMAGE_NOT_FOUND | File pointed to by image_key no longer exists |
| 409 | generation_not_ready | generation_id's source image is still being stored — retry shortly |
| 429 | rate_limit_exceeded | Rate limit exceeded |
| 502 | upstream_error | Service 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/completionsinstead of this endpoint