Image Editing
Edit and transform images using AI models via the Kolbo API.
Kolbo exposes two different image-editing endpoints:
| Endpoint | Use it for |
|---|---|
POST /api/v1/generate/image-edit | Prompt-driven content edits — change the scene, add/remove/replace objects, restyle, recolor, composite multiple source images. Runs on dedicated editing models. |
POST /api/v1/edit/image | Targeted operations — upscale, Topaz photo enhance, outpaint/expand, reframe, background removal or replacement, skin retouch, inpaint, erase, face swap, camera angle, grid split, multi-shot. Selected by an operation enum, not a prompt. |
Both endpoints accept optional
project_idandsession_idbody fields that route the output into a specific project or session. See Projects.
Rate limit: the three image endpoints (/v1/generate/image, /v1/generate/image-edit, /v1/edit/image) share one bucket of 30 accepted requests per minute per user, separate from the bucket used by the other generation endpoints. Rejected requests (4xx/5xx) do not count against it.
Endpoint
POST /api/v1/generate/image-editRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Description of the edit to apply. Must be a non-empty string — anything else is a 400 INVALID_PROMPT. A #Name moodboard mention in the prompt is resolved to that moodboard (an explicit moodboard_id wins). |
model | string | No | Model identifier from GET /api/v1/models?type=image_edit (alias of type=image_editing). Must be a string — arrays/objects are a 400 INVALID_MODEL. Omit, or send any Smart Select alias ("", "auto", "auto-select", "smart-select", "smart_select", "smartselect", "kolbo_smart_select_router"), for Smart Select. An identifier that matches no catalog model is rejected with 400 MODEL_NOT_FOUND, so check spelling against GET /api/v1/models. You may also pass a text-to-image identifier — it is auto-routed to that model's editing variant (see below). |
source_images | array of strings | No | URLs of the images to edit or composite pixel-accurately. Multiple images are accepted (composite several sources into one result). Refer to them in the prompt by ordinal ("FIRST source image") or @image1 / @image2 tags. Send at most max_reference_images for the chosen model (GET /api/v1/models), counting source_images and reference_images together. The server does not require this field: with no source_images, reference_images or visual_dna_ids the call runs as a plain text-to-image generation with no edit source. See the reference limits callout below. |
reference_images | array of strings | No | URLs of style/composition references sent alongside source_images on the same call. They steer the look; their pixels are not composited. Sources are placed first, then references, in one combined reference list. |
aspect_ratio | string | No | "1:1", "16:9", "9:16", etc. Send a value from the model's supported_aspect_ratios on GET /api/v1/models. The value is not validated server-side — an unsupported ratio is forwarded to the provider and falls back to a supported one. Default: "1:1". |
num_images | integer | No | Number of output images, 1–8. Parsed as an integer; 0, null or omitted fall back to 1; a value outside 1–8 is rejected with 400 ("Invalid quantity: … Must be 1-8"). Models with a fixed images_per_request (GET /api/v1/models) return their own count regardless (Midjourney always returns 4). |
enhance_prompt | boolean | No | Let Kolbo rewrite the prompt before sending it to the model. Default: false — true (or the string "true" / "1") enables it. |
visual_dna_ids | array of strings | No | Visual DNA IDs for character/style/product consistency. Cap: at most max_visual_dna IDs for the chosen model (GET /api/v1/models). The DNA's description is injected into the prompt as plaintext regardless of enhance_prompt, and its stills share the model's reference-image budget with source_images. Do not pass a person's DNA when source_images already contains that person's face — the two references average together. |
moodboard_id | string | No | Moodboard ObjectId whose master prompt and style guide are applied. Must be a valid ObjectId — otherwise 400 Invalid moodboard_id format. |
enable_web_search | boolean | No | Ground the prompt with web search. Default: false. |
cinematic | object | No | Cinematic Presets — an optional "Cinema mode" object mapping dimensions (camera, lens, focal_length, aperture, angle, shot_type, color_palette, lighting) to preset ids from GET /api/v1/cinematic-presets. Include only the dimensions you want; omitted or null ones are Auto. Must be a plain object — arrays are ignored. An unknown preset id is a 400. The selection is silently dropped when the chosen model does not support Cinema mode. |
skip_color_palette | boolean | No | Opt this single call out of the account's active Color DNA palette. By default an active palette grades every edit. Only the exact value true skips it. Default: palette applied. |
resolution | string | No | Resolution tier, e.g. "1K", "2K", "3K", "4K". Model-dependent — send a value from the chosen model's supported_resolutions on GET /api/v1/models?type=image_edit. Higher tiers multiply the credit cost via resolution_multipliers — see Credit Multipliers. Omit to use the model default. |
quality | string | No | Quality tier for models that have one. Read supported_qualities on GET /api/v1/models?type=image_edit; models with an empty list have no tier, so omit the field. On the gpt-image-2 family "auto" is rewritten server-side to "medium" and bills at that tier; the GPT Image 2.5 models accept "auto", "low", "medium", "high", "xhigh", "max" and keep "auto" as a real tier. Multiplies credit cost via quality_multipliers — see Credit Multipliers. |
background | string | No | "auto" | "opaque" | "transparent". Any other value is ignored. "transparent" requires an explicit model whose supports_transparent_background is true on GET /api/v1/models — Smart Select or an unsupported model is a 422 TRANSPARENT_BACKGROUND_UNSUPPORTED. With "transparent" and no output_format, the output is PNG. |
output_format | string | No | "png" | "jpeg" | "webp", for models that expose a format (the GPT Image 2.5 family). Transparent output requires PNG or WebP. |
output_compression | integer | No | 0–100. JPEG or WebP only; on GPT Image 2.5 a PNG format or an out-of-range value is a 422 INVALID_IMAGE_SETTINGS. |
moderation | string | No | "auto" | "low", for models that expose it (the GPT Image 2.5 family). |
mask_image_url | string | No | Mask for a localized edit on models that take one (the GPT Image 2.5 family): a PNG under 4 MB with an alpha channel, the same size as the first source image; transparent pixels mark the area to edit. Requires at least one source image. |
font_ids | array of strings | No | Up to 3 ready personal font family IDs (24-char hex). More than 3, or a malformed id, is INVALID_FONT_IDS. An explicit model must have supports_custom_fonts: true, otherwise CUSTOM_FONTS_UNSUPPORTED; with Smart Select only font-capable models are considered. |
preset_id | string | No | Saved preset to apply. Both image-edit presets (GET /api/v1/presets?type=image_edit) and image-generation presets are accepted — the id is looked up in the generation collection first, then the editing one. An id found in neither, or an inactive one, is a 400 ("Invalid preset ID: …"). |
project_id | string | No | Project ObjectId to drop the edit into. Call GET /v1/projects to discover IDs. Omit to use the auto-created "API Generations" project. |
session_id | string | No | An image session id returned by an earlier /v1/generate/image or /v1/generate/image-edit call (both use the same session type). When sent it wins over project_id, so related edits land in one session. A malformed id is 400 SDK_SESSION_INVALID_ID; an id from another generation type, or one you cannot write to, is 404 SDK_SESSION_NOT_FOUND. |
You do not have to know the editing identifier. If you send a text-to-image model (e.g. gpt-image-2, nano-banana, flux-2) together with 1–8 reference images in total (source_images + reference_images), Kolbo automatically runs its editing variant (gpt-image-2/edit, nano-banana/edit, flux-2/edit). The same swap happens when visual_dna_ids is set. This happens before pricing, so the credits charged and the result.model returned on GET /v1/generate/{id}/status are those of the editing model.
Models with no editing variant (e.g. midjourney) and identifiers that are already editors are passed through unchanged; on a model with no editor the images act as style references only. GET /api/v1/models exposes the link as editing_model_identifier — use it if you want to name the editor explicitly.
Reference limits are mostly not checked at submit. Keep source_images + reference_images (plus any Visual DNA stills) within the chosen model's max_reference_images. Only Midjourney is rejected up front: more than 3 references is a 400. The GPT Image 2.5 models cap references at 16, but that is checked when the job runs, so an oversized request is accepted and then finishes with state: "failed". Above 8 references in total the editing auto-routing above is skipped. Anything else past a model's cap is not validated when you submit.
Insufficient credits fail synchronously. The credit gate runs before the edit is queued, so the initial POST returns 403 with "code": "INSUFFICIENT_CREDITS" (plus balance, required and pricing_url) rather than failing later during polling. The same gate protects POST /v1/edit/image.
Examples
Basic Edit (Smart Select)
curl -X POST https://api.kolbo.ai/api/v1/generate/image-edit \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Remove the background and place on a clean white surface",
"source_images": ["https://example.com/product-photo.jpg"]
}'With a Specific Model
To choose a specific model, first fetch identifiers from GET /api/v1/models?type=image_edit, then pass the identifier value (e.g., nano-banana-2-image-editing, nano-banana-pro/edit):
curl -X POST https://api.kolbo.ai/api/v1/generate/image-edit \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Transform into a watercolor painting style",
"source_images": ["https://example.com/photo.jpg"],
"model": "nano-banana-2-image-editing",
"enhance_prompt": false
}'Composite Several Sources
source_images takes several images; reference_images adds style guidance on the same call. Keep the combined count within the model's max_reference_images.
curl -X POST https://api.kolbo.ai/api/v1/generate/image-edit \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Place the product from @image2 on the table in @image1, keep both exactly as they are, match the lighting of the style reference",
"model": "nano-banana-2-image-editing",
"source_images": [
"https://example.com/kitchen.jpg",
"https://example.com/product.png"
],
"reference_images": ["https://example.com/lighting-ref.jpg"]
}'JavaScript
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.kolbo.ai/api"; // poll_url is relative to this
async function editImage() {
const response = await fetch(`${BASE_URL}/v1/generate/image-edit`, {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: "Change the car color to red",
source_images: ["https://example.com/car.jpg"],
aspect_ratio: "16:9"
})
});
const started = await response.json();
if (!started.success) throw new Error(started.error);
const result = await pollUntilDone(started.generation_id, started.poll_interval_hint);
if (result.state !== "completed") throw new Error(result.error || result.state);
console.log("Edited image URLs:", result.result.urls); // always an array
}
const TERMINAL = new Set(["completed", "failed", "cancelled"]);
// Minimal loop. A production client should also handle transient HTTP errors and
// an overall timeout — see /docs/developer-api/polling-and-cancellation.
async function pollUntilDone(generationId, intervalSeconds) {
while (true) {
await new Promise((r) => setTimeout(r, intervalSeconds * 1000));
const status = await fetch(`${BASE_URL}/v1/generate/${generationId}/status`, {
headers: { "X-API-Key": API_KEY }
}).then((r) => r.json());
if (TERMINAL.has(status.state)) return status;
}
}
editImage();Python
import requests
import time
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.kolbo.ai/api" # poll_url is relative to this
# Start edit (Smart Select picks the best model automatically)
response = requests.post(
f"{BASE_URL}/v1/generate/image-edit",
headers={"X-API-Key": API_KEY},
json={
"prompt": "Remove background and add soft shadow",
"source_images": ["https://example.com/product.jpg"]
}
)
started = response.json()
TERMINAL = {"completed", "failed", "cancelled"}
# Minimal loop. Add transient-error retries and an overall timeout for production
# — see /docs/developer-api/polling-and-cancellation.
while True:
time.sleep(started["poll_interval_hint"])
status = requests.get(
f"{BASE_URL}/v1/generate/{started['generation_id']}/status",
headers={"X-API-Key": API_KEY}
).json()
if status["state"] in TERMINAL:
break
if status["state"] != "completed":
raise Exception(status.get("error", status["state"]))
print("URLs:", status["result"]["urls"]) # always a listResponse
POST /v1/generate/image-edit is asynchronous and fire-and-forget. It answers 200 OK with a job id as soon as the edit is queued — the response never contains an image.
Polling is the only completion mechanism. Kolbo never calls you back: there is no webhook, no callback_url field on this request body, and no route delivers outbound notifications. Socket.IO events are the web app's internal transport and are not part of the API contract — API-key generations are registered so the shared progress emitter drops their events, and the few emit sites that bypass that check are undocumented, unversioned and unsafe to build on. After the POST, loop against GET /api/v1/generate/{generation_id}/status until state is completed, failed or cancelled. Full contract: Polling & Cancellation.
Generation Started
{
"success": true,
"generation_id": "6601a1b2c3d4e5f6a7b8c9e2",
"type": "image_edit",
"model": "auto",
"credits_charged": null,
"poll_url": "/v1/generate/6601a1b2c3d4e5f6a7b8c9e2/status",
"poll_interval_hint": 3,
"session_id": "6601a1b2c3d4e5f6a7b8c9d0",
"project_id": "6601a1b2c3d4e5f6a7b8c9d1"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "image_edit" for this endpoint |
model | string | "auto" when Smart Select was used, otherwise the canonical identifier of the model you sent. The text-to-image → editor swap happens later, so read result.model on the completed status for the model that ran. |
credits_charged | null | Always null on this endpoint — the edit pipeline reports no estimate at submit time. Read credits_used from the completed status. |
poll_url | string | The status path without the /api prefix. Prepend https://api.kolbo.ai/api. |
poll_interval_hint | number | Suggested seconds between polls — 3 for image edits |
session_id / project_id | string | Where the edit lives in the Kolbo app |
Completed Status
GET /api/v1/generate/{generation_id}/status
{
"success": true,
"generation_id": "6601a1b2c3d4e5f6a7b8c9e2",
"type": "image_edit",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://media.kolbo.ai/kolbo/images/6601a1b2c3d4e5f6a7b8c9e2-0.png"],
"thumbnail_url": "https://media.kolbo.ai/kolbo/images/6601a1b2c3d4e5f6a7b8c9e2-0.png",
"prompt_used": "Enhanced version of your prompt",
"model": "nano-banana-pro/edit",
"created_at": "2026-03-05T10:30:00Z"
},
"credits_used": 18,
"credits_breakdown": [
{ "model": "nano-banana-pro/edit", "amount": 18 }
]
}The output lives in result.urls — always an array of strings, even for a single edited image. result.thumbnail_url is literally urls[0], not a separate smaller asset.
result (and credits_used / credits_breakdown) appear only under state: "completed". A processing body has exactly five keys and no result at all. credits_used is the authoritative, multiplier-adjusted charge — not credits_charged. See Credits and Billing.
Each credits_breakdown entry is { model, amount } — one per credit deduction.
The completed result also carries these keys, each present only when it applies: reference_images (every reference URL the edit actually conditioned on — can include Visual DNA stills added server-side), selected_fonts (when font_ids was used), and the creative inputs visual_dna, moodboard, preset, cinematic_presets. model_name appears only when Smart Select recorded a display name.
result.model is the model that actually ran — never "auto". When Smart Select routed the edit, the status response resolves and returns the chosen identifier. The model on the initial POST response still reads "auto" because routing has not happened at that point.
Failure and cancellation
A failed edit is still an HTTP 200 with success: true — the failure is in state, with an error string and a best-effort failure object whose category / code are usually null on this pipeline. cancelled carries neither result nor error, so treat all three terminal states explicitly. Failed edits are not charged. See Polling & Cancellation.
Targeted Edits
POST /api/v1/edit/imageApplies one named operation to an existing image. Unlike /generate/image-edit, the behaviour is chosen by the operation enum rather than inferred from a prompt. Like the other generation endpoints, it returns immediately with a generation_id and is polled at /v1/generate/:generation_id/status.
Operations
operation is required. Sending anything outside this list returns a 400 listing the valid values.
| Operation | What it does | Required fields | Other fields it reads |
|---|---|---|---|
upscale | Increase resolution. Routed to Topaz (model = topaz/upscale/image), Replicate (model = prunaai/p-image-upscale), or Clarity (any other image_upscale model). Omitting model runs the catalog's default image_upscale model, and the same routing applies to it. The factor comes from scale — see the upscale callout below. | — | model, scale; Topaz: enhancement_model, output_format, face_enhancement, face_enhancement_strength, face_enhancement_creativity, subject_detection, crop_to_fill, creativity; Clarity: prompt, negative_prompt, creativity, resemblance |
clarity_upscale | Alias of upscale — same handler, same image_upscale model pool. Routing is decided only by model. | — | Same as upscale |
enhance | Topaz photo correction at the source resolution (no resizing). The tool is chosen by model: topaz/adjust/image, topaz/sharpen/image, topaz/denoise/image, topaz/restore/image (model type graphics_enhance). Billed on the measured image area and the engine. | — | model, enhancement_model (the correction engine for that tool), output_format |
reframe | Re-generates the whole picture at a new aspect ratio. The subject is re-imagined, not preserved. | aspect_ratio | model |
zoom_out | Outpaint / expand / uncrop. Original pixels are preserved; only the new area is generated. | — | zoom_out_percentage, expand_left, expand_right, expand_top, expand_bottom, prompt, output_format, model |
removebg | Remove the background (transparent output). | — | model |
background_replace | Remove the background and generate a replacement from prompt. | prompt | model, background_reference_image, negative_prompt |
enhance_skin | Portrait skin retouching. | — | skin_strength, freckle_intensity, fix_lighting, model |
inpaint | Repaint a masked area. Needs a mask, but it is not checked at request time — see the callout below. | — | mask_image_url, prompt, additional_images, model, resolution and quality (credit multipliers only) |
erase | Erase the object inside the masked area. Needs a mask, but it is not checked at request time. | — | mask_image_url, additional_images, mask_expansion, model, quality (forwarded as the object-removal tier; default best_quality) |
face_swap | Swap the face in image_url with the face reference. | mask_image_url or a non-empty additional_images | model |
camera_angle | Re-render the shot from a different camera angle. | — | horizontal_angle, vertical_angle, camera_zoom, prompt, generate_all_angles, model |
split | Split an existing grid image into its tiles. No generation model is called; sending model is a 400 MODEL_NOT_CONFIGURABLE. | — | split_layout |
split_upscale | Topaz upscale (default 4× with face enhancement), then split into tiles. Billed at a flat 40 credits whatever you send. | — | split_layout, scale (upscale factor), face_enhancement |
multi_shot | Generate a 3×3 camera-coverage grid from image_url and auto-split it. Returns 9 shots plus the full coverage grid as the last URL. The model, prompt, and framing are all pinned server-side; sending model is a 400 MODEL_NOT_CONFIGURABLE. | — | resolution (output size and credit multiplier), quality (credit multiplier only) |
magic_edit | Deprecated. Prompt-driven content edit. The request is internally rewritten and forwarded to POST /v1/generate/image-edit with source_images = [image_url, ...additional_images] — so it is tracked as "type": "image_edit", not global_image_edit, and every other field on this endpoint is dropped. | prompt | model (defaults to nano-banana-pro/edit), additional_images, project_id, font_ids (see below) |
zoom_out vs reframe. If the user wants to expand, extend, widen, uncrop, or add space while keeping the existing artwork, use zoom_out. reframe re-generates the entire image and produces a different-looking picture. aspect_ratio is ignored by zoom_out — size that expansion with zoom_out_percentage or the expand_* pixel fields.
Prefer /v1/generate/image-edit for open-ended content edits ("make it night", restyling, adding or removing objects). It runs on stronger dedicated editing models. magic_edit here exists only for backward compatibility and forwards to that endpoint anyway.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
image_url | string | Yes | URL of the primary source image. Missing → 400 image_url is required. |
operation | string | Yes | One of upscale, clarity_upscale, enhance, reframe, zoom_out, removebg, background_replace, enhance_skin, inpaint, erase, face_swap, camera_angle, split, split_upscale, multi_shot, magic_edit. Missing → 400 operation is required. |
model | string | No | Model identifier override. Must be a string — arrays/objects are a 400 INVALID_MODEL. Omit to use the platform default for the operation. When sent it must be a model of the operation's type (table below); anything else — including Smart Select aliases such as "auto", which are not recognised here — is a 400 INVALID_EDIT_MODEL. split and multi_shot take no model: sending one is a 400 MODEL_NOT_CONFIGURABLE. |
prompt | string | No | Text instruction. Required for background_replace and magic_edit. Used by inpaint, zoom_out (describes the new area only), camera_angle, and the Clarity upscaler (as guidance). Must be a string when present. |
aspect_ratio | string | No | Target aspect ratio, e.g. "16:9". Required for reframe. Ignored by zoom_out. |
scale | number | No | Upscale factor for upscale / clarity_upscale / split_upscale. Send a number. Topaz and Clarity accept 1–4 (or "auto", which targets 8K from the source size); a value outside that range fails the generation. Both default to 2. The Replicate upscaler ignores it and always runs at 2×. split_upscale defaults to 4. |
enhancement_model | string | No | Topaz engine. With upscale on topaz/upscale/image it selects the engine family and the rate: precision ("Standard V2", "High Fidelity V3", "High Fidelity V2", "Low Resolution V2", "CGI", "Text Refine"), generative ("Wonder 3.5", "Wonder 3", "Wonder 2", "Standard MAX", "Redefine", "Recover 3", …), creative ("Bloom 2", "Bloom", "Bloom Realism" — the most expensive), or "Transparent" (keeps alpha, PNG only). With enhance it selects the correction engine of the chosen tool. Omit for the default ("Standard V2"); an unknown name runs the default. |
output_format | string | No | "jpeg" | "png" for Topaz upscale / enhance (default jpeg; the transparent engine always returns PNG). Also forwarded by zoom_out. |
resolution | string | No | Only multi_shot uses it to size the output: "1K" | "2K" | "4K", and any other value is coerced to "4K" (also the default). On multi_shot and inpaint it also multiplies the credit cost via the model's resolution_multipliers (GET /api/v1/models). Upscale size is set by scale, not by resolution. |
quality | string | No | Read by three operations, and only for billing on two of them. On inpaint and multi_shot it multiplies the credit cost on top of resolution via the model's quality_multipliers (GET /api/v1/models) without changing the output — send a value from that model's supported_qualities. On erase it is forwarded as the object-removal model tier: "low_quality" | "medium_quality" | "high_quality" | "best_quality", default "best_quality". All other operations ignore it. |
skin_strength | string | No | "subtle" | "realistic" | "pimple" | "freckle". Only used by enhance_skin. Default: "realistic". |
freckle_intensity | number | No | For enhance_skin with skin_strength: "freckle" only. Default: 50. |
fix_lighting | boolean | No | For enhance_skin with skin_strength: "realistic" only: also even out the face lighting. Default: false. |
horizontal_angle | number | No | For camera_angle: rotation around the subject in degrees, clamped to 0–360 (0 = front, 90 = right side, 180 = behind). Default: 0. |
vertical_angle | number | No | For camera_angle: tilt in degrees, clamped to -30–90 (-30 = from below, 0 = eye level, 90 = overhead). Default: 0. |
camera_zoom | number | No | For camera_angle: camera distance, clamped to 0–10 (5 = unchanged, higher = closer). Default: 5. When none of the three angle fields is sent, the angles are read from keywords in prompt ("left side", "back", "low angle", "bird's eye", "close up", "wide"…); with neither, the view barely changes. |
generate_all_angles | boolean | No | For camera_angle: generate the full set of 12 angles (4 rotations × 3 tilts) instead of one; the angle fields are then ignored. Billed at 12× the per-image model cost. Default: false. |
face_enhancement | boolean | No | Topaz face recovery on upscale (precision/generative engines) and split_upscale. Default: true. |
face_enhancement_strength | number | No | Topaz upscale: how strongly faces are recovered, 0–1. Default: 0.8. |
face_enhancement_creativity | number | No | Topaz upscale: how much new facial detail may be invented, 0–1. Default: 0. |
subject_detection | string | No | Topaz upscale: "All" | "Foreground" | "Background". Default: "All". |
crop_to_fill | boolean | No | Topaz upscale: crop to fill the output size instead of keeping the source framing. Default: false. |
creativity | number | No | Clarity upscaler: 0–1, default 0.35. On Topaz upscale it is also forwarded, unvalidated, to the generative engines and to the creative engines (where Bloom 2 uses a 1–9 scale). |
resemblance | number | No | Clarity upscaler: how closely the result matches the source, 0–1. Default: 0.6. |
negative_prompt | string | No | What must not appear. Used by the Clarity upscaler and background_replace. |
background_reference_image | string | No | For background_replace: URL of an image to use as the new background reference (prompt is still required). |
split_layout | string | No | For split / split_upscale: "auto" | "2x2" | "3x3" | "4x4". Default: "auto" (detected from the source). |
mask_expansion | number | No | For erase: pixels to grow the mask so edges blend. Default: 15. |
mask_image_url | string | No | Black & white mask URL (white = affected area) for inpaint and erase. For face_swap this is the face reference image. Placed first in the additional-images slot. |
additional_images | array of strings | No | Extra reference image URLs — several are accepted. Truncated to 8 entries total, counting mask_image_url when present (it is placed at index 0). Used by inpaint and erase (index 0 is read as the mask when mask_image_url is absent), face_swap (index 0 accepted as the face reference), and magic_edit (extra composite sources). multi_shot ignores it. |
zoom_out_percentage | number | No | For zoom_out: uniform expansion on all sides as a percentage, clamped to 0–90. Default: 20. |
expand_left | number | No | For zoom_out: pixels of new content on the left, clamped to 0–700. Default: 0. |
expand_right | number | No | For zoom_out: pixels of new content on the right, clamped to 0–700. Default: 0. |
expand_top | number | No | For zoom_out: pixels of new content on top, clamped to 0–700. Default: 0. |
expand_bottom | number | No | For zoom_out: pixels of new content on the bottom, clamped to 0–700. Default: 0. |
ai_optimize | boolean | No | Let Kolbo enhance your prompt before sending it to the model. Default: false — only the exact value true enables it. |
font_ids | array of strings | No | Up to 3 personal font family IDs. Accepted only with operation: "magic_edit" — any other operation is a 400 CUSTOM_FONTS_UNSUPPORTED. With fonts the request is forwarded to /v1/generate/image-edit with source_images: [image_url] only, so additional_images and the nano-banana-pro/edit default are not applied — send a font-capable model. |
project_id | string | No | Project ObjectId to drop the edit into. |
session_id | string | No | An image-edit session id from an earlier /v1/edit/image call; wins over project_id. Malformed → 400 SDK_SESSION_INVALID_ID; not yours or another type → 404 SDK_SESSION_NOT_FOUND. |
Model type per operation — list candidates with GET /api/v1/models?type=<type>:
| Operation | Model type |
|---|---|
upscale, clarity_upscale, split_upscale | image_upscale |
enhance | graphics_enhance |
reframe | image_reframe |
zoom_out | image_zoom_out |
removebg | background_remove |
background_replace | background_replace |
enhance_skin | skin_enhancer |
inpaint | inpaint |
erase | erase |
face_swap | face_swap |
camera_angle, magic_edit | image_editing |
split, multi_shot | none — omit model |
The four enhance models are hidden, so they do not appear in GET /api/v1/models; pass one of topaz/adjust/image, topaz/sharpen/image, topaz/denoise/image or topaz/restore/image directly.
Upscale size is set by scale. The value reaches the upscaler as its factor and is also what the factor-based price tier is computed from. Topaz rejects anything outside 1–4 (an 8 fails the generation); Topaz is priced on the measured output area, so a large source at 4× is billed more, not refused. The Replicate upscaler (prunaai/p-image-upscale) does not read it and always runs at 2×. split_upscale uses scale too but is always billed at a flat 40 credits.
inpaint and erase do not validate the mask up front. Unlike reframe, background_replace, and face_swap, a missing mask_image_url (and empty additional_images) is not rejected with a 400. The request is accepted, credits may be reserved, and the generation later finishes with state: "failed". Always send a mask.
multi_shot is fully pinned server-side. The model is fixed to an editing model chosen by Kolbo — sending model is a 400 MODEL_NOT_CONFIGURABLE. resolution is clamped to "1K" / "2K" / "4K" (anything else becomes "4K") so the billing multiplier always matches what is generated. The coverage prompt, the 3×3 split, and the output aspect ratio (detected from the source image) are all decided server-side, so prompt, aspect_ratio, and additional_images have no effect. One call returns 10 URLs: the 9 split shots, then the full coverage grid as the last entry.
Duplicate requests inside 30 seconds are rejected. The guard keys on user + edit session + operation + image_url, so the same image_url and operation re-sent from the same API key and project_id within 30 seconds — before the first edit finishes — returns 409 with "code": "DUPLICATE_REQUEST" and an existingGenerationId you can poll instead. The URL is compared case-insensitively with the query string stripped, so cache-busting a signed URL does not get you past it.
Example — expand an image to a wider frame
curl -X POST https://api.kolbo.ai/api/v1/edit/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://media.kolbo.ai/images/source.jpg",
"operation": "zoom_out",
"expand_left": 320,
"expand_right": 320,
"prompt": "continue the beach and horizon into the new space"
}'Example — 4× Topaz upscale
curl -X POST https://api.kolbo.ai/api/v1/edit/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://media.kolbo.ai/images/portrait.jpg",
"operation": "upscale",
"model": "topaz/upscale/image",
"scale": 4,
"enhancement_model": "High Fidelity V3",
"output_format": "png"
}'Example — remove the background
curl -X POST https://api.kolbo.ai/api/v1/edit/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://media.kolbo.ai/images/product.jpg",
"operation": "removebg"
}'Example — inpaint with a mask
curl -X POST https://api.kolbo.ai/api/v1/edit/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://media.kolbo.ai/images/room.jpg",
"operation": "inpaint",
"mask_image_url": "https://media.kolbo.ai/images/room-mask.png",
"prompt": "a green velvet armchair"
}'Example — nine camera shots from one still
multi_shot needs nothing but the source image: the coverage prompt, framing, and 3×3 split are all decided server-side.
curl -X POST https://api.kolbo.ai/api/v1/edit/image \
-H "X-API-Key: kolbo_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://media.kolbo.ai/images/scene.jpg",
"operation": "multi_shot",
"resolution": "4K"
}'The completed status returns the nine shots in result.urls, followed by the full coverage grid as a tenth URL.
Response
Also fire-and-forget: this endpoint answers 202 Accepted with a job id and nothing else. Polling is the only way to get the image — see Polling & Cancellation.
Edit started:
{
"success": true,
"generation_id": "gen_1772100000000_k3f9q1x7d",
"type": "global_image_edit",
"model": "MODEL_IDENTIFIER",
"credits_charged": null,
"poll_url": "/v1/generate/gen_1772100000000_k3f9q1x7d/status",
"poll_interval_hint": 8,
"session_id": "6601a1b2c3d4e5f6a7b8c9d0",
"project_id": "6601a1b2c3d4e5f6a7b8c9d1"
}Unlike the other generation endpoints, generation_id here is a gen_<timestamp>_<random> string rather than an ObjectId — the status route accepts both formats. model is the concrete engine the operation resolved to (the operation's default when you omit model); it is null only when no engine applies. credits_charged is always null on this endpoint — the underlying edit pipeline does not report a cost at submit time. poll_url is relative to https://api.kolbo.ai/api, and poll_interval_hint is 8 seconds here rather than the 3 used by the two /generate/* image endpoints.
Completed status — GET /api/v1/generate/{generation_id}/status:
{
"success": true,
"generation_id": "gen_1772100000000_k3f9q1x7d",
"type": "global_image_edit",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://media.kolbo.ai/kolbo/images/edited-0.png"],
"thumbnail_url": "https://media.kolbo.ai/kolbo/images/edited-0.png",
"prompt_used": "remove the background",
"model": "MODEL_IDENTIFIER",
"created_at": "2026-03-05T10:30:00Z"
}
}The output lives in result.urls — an array, and split, split_upscale and multi_shot return several entries in it. result.thumbnail_url is the first URL. The status body echoes "type": "global_image_edit", but the result object itself uses the same shape as /generate/image-edit. There is no edit_type field on the result — the operation you requested is not echoed back.
Do not rely on a settled cost for /v1/edit/image. credits_charged is null on submit, and the completed status usually does not carry credits_used / credits_breakdown: the status route looks up ledger rows by the gen_... id you poll with, while this pipeline normally files them against its internal edit document. To measure the spend, read your balance from GET /api/v1/account/credits before and after. See Credits and Billing.
Each generation_id resolves to its own edit. The status route matches the edit document that carries your gen_... id, so concurrent /v1/edit/image calls can be polled independently. For up to 10 minutes after submit, before that document is written, the status answers state: "processing" with progress: 0 instead of a 404.
Finding Models
Model identifiers are Kolbo-specific — always fetch them dynamically rather than hardcoding. type=image_edit is an alias for the underlying model type image_editing; both work.
curl "https://api.kolbo.ai/api/v1/models?type=image_edit" \
-H "X-API-Key: YOUR_API_KEY"Read supported_aspect_ratios, supported_resolutions, supported_qualities, max_reference_images, max_visual_dna, supports_transparent_background, and supports_custom_fonts off each model to know exactly which fields it accepts. For /v1/edit/image, query the operation's model type from the table above (e.g. type=image_upscale).
Tips
- On
/generate/image-edit, omitmodelto let Smart Select pick an editing model, or send animage_editingidentifier for a deterministic choice. On/edit/imagethere is no Smart Select — omitmodelto get the operation's default; an alias like"auto"is a400. source_imagesandreference_imagesaccept URLs — no file uploads needed. The API downloads and processes them. Upload local files first via the Media Library.- Send
source_images(orvisual_dna_ids) for a real edit; without either the request runs as a plain text-to-image generation. - Use
moodboard_idto layer a style preset on top of any edit. See Moodboards.moodboard_id,visual_dna_ids,reference_images,cinematic,background, andskip_color_paletteexist only on/generate/image-edit—/v1/edit/imagereads none of them. - On
/generate/image-edit, read the settled cost fromcredits_usedon the completed status rather thancredits_chargedon the submit response./v1/edit/imagereports neither — diff your balance fromGET /v1/account/creditsinstead. See Credits and Billing.
Related
Image Generation
Create a new image from a text prompt
Visual DNA
Character, style, and product consistency across edits
Moodboards
Style templates applied via moodboard_id
Models and Pricing
Model catalog, resolution and quality credit multipliers
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel