Kolbo.AIKolbo.AI Docs
Developer API

Image Editing

Edit and transform images using AI models via the Kolbo API.

Kolbo exposes two different image-editing endpoints:

EndpointUse it for
POST /api/v1/generate/image-editPrompt-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/imageTargeted 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_id and session_id body 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-edit

Request Body

FieldTypeRequiredDescription
promptstringYesDescription 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).
modelstringNoModel 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_imagesarray of stringsNoURLs 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_imagesarray of stringsNoURLs 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_ratiostringNo"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_imagesintegerNoNumber 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_promptbooleanNoLet Kolbo rewrite the prompt before sending it to the model. Default: false — true (or the string "true" / "1") enables it.
visual_dna_idsarray of stringsNoVisual 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_idstringNoMoodboard ObjectId whose master prompt and style guide are applied. Must be a valid ObjectId — otherwise 400 Invalid moodboard_id format.
enable_web_searchbooleanNoGround the prompt with web search. Default: false.
cinematicobjectNoCinematic 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_palettebooleanNoOpt 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.
resolutionstringNoResolution 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.
qualitystringNoQuality 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.
backgroundstringNo"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_formatstringNo"png" | "jpeg" | "webp", for models that expose a format (the GPT Image 2.5 family). Transparent output requires PNG or WebP.
output_compressionintegerNo0–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.
moderationstringNo"auto" | "low", for models that expose it (the GPT Image 2.5 family).
mask_image_urlstringNoMask 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_idsarray of stringsNoUp 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_idstringNoSaved 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_idstringNoProject ObjectId to drop the edit into. Call GET /v1/projects to discover IDs. Omit to use the auto-created "API Generations" project.
session_idstringNoAn 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 list

Response

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"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll and cancel with.
typestringAlways "image_edit" for this endpoint
modelstring"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_chargednullAlways null on this endpoint — the edit pipeline reports no estimate at submit time. Read credits_used from the completed status.
poll_urlstringThe status path without the /api prefix. Prepend https://api.kolbo.ai/api.
poll_interval_hintnumberSuggested seconds between polls — 3 for image edits
session_id / project_idstringWhere 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/image

Applies 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.

OperationWhat it doesRequired fieldsOther fields it reads
upscaleIncrease 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_upscaleAlias of upscale — same handler, same image_upscale model pool. Routing is decided only by model.—Same as upscale
enhanceTopaz 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
reframeRe-generates the whole picture at a new aspect ratio. The subject is re-imagined, not preserved.aspect_ratiomodel
zoom_outOutpaint / 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
removebgRemove the background (transparent output).—model
background_replaceRemove the background and generate a replacement from prompt.promptmodel, background_reference_image, negative_prompt
enhance_skinPortrait skin retouching.—skin_strength, freckle_intensity, fix_lighting, model
inpaintRepaint 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)
eraseErase 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_swapSwap the face in image_url with the face reference.mask_image_url or a non-empty additional_imagesmodel
camera_angleRe-render the shot from a different camera angle.—horizontal_angle, vertical_angle, camera_zoom, prompt, generate_all_angles, model
splitSplit an existing grid image into its tiles. No generation model is called; sending model is a 400 MODEL_NOT_CONFIGURABLE.—split_layout
split_upscaleTopaz 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_shotGenerate 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_editDeprecated. 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.promptmodel (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

FieldTypeRequiredDescription
image_urlstringYesURL of the primary source image. Missing → 400 image_url is required.
operationstringYesOne 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.
modelstringNoModel 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.
promptstringNoText 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_ratiostringNoTarget aspect ratio, e.g. "16:9". Required for reframe. Ignored by zoom_out.
scalenumberNoUpscale 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_modelstringNoTopaz 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_formatstringNo"jpeg" | "png" for Topaz upscale / enhance (default jpeg; the transparent engine always returns PNG). Also forwarded by zoom_out.
resolutionstringNoOnly 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.
qualitystringNoRead 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_strengthstringNo"subtle" | "realistic" | "pimple" | "freckle". Only used by enhance_skin. Default: "realistic".
freckle_intensitynumberNoFor enhance_skin with skin_strength: "freckle" only. Default: 50.
fix_lightingbooleanNoFor enhance_skin with skin_strength: "realistic" only: also even out the face lighting. Default: false.
horizontal_anglenumberNoFor camera_angle: rotation around the subject in degrees, clamped to 0–360 (0 = front, 90 = right side, 180 = behind). Default: 0.
vertical_anglenumberNoFor camera_angle: tilt in degrees, clamped to -30–90 (-30 = from below, 0 = eye level, 90 = overhead). Default: 0.
camera_zoomnumberNoFor 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_anglesbooleanNoFor 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_enhancementbooleanNoTopaz face recovery on upscale (precision/generative engines) and split_upscale. Default: true.
face_enhancement_strengthnumberNoTopaz upscale: how strongly faces are recovered, 0–1. Default: 0.8.
face_enhancement_creativitynumberNoTopaz upscale: how much new facial detail may be invented, 0–1. Default: 0.
subject_detectionstringNoTopaz upscale: "All" | "Foreground" | "Background". Default: "All".
crop_to_fillbooleanNoTopaz upscale: crop to fill the output size instead of keeping the source framing. Default: false.
creativitynumberNoClarity 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).
resemblancenumberNoClarity upscaler: how closely the result matches the source, 0–1. Default: 0.6.
negative_promptstringNoWhat must not appear. Used by the Clarity upscaler and background_replace.
background_reference_imagestringNoFor background_replace: URL of an image to use as the new background reference (prompt is still required).
split_layoutstringNoFor split / split_upscale: "auto" | "2x2" | "3x3" | "4x4". Default: "auto" (detected from the source).
mask_expansionnumberNoFor erase: pixels to grow the mask so edges blend. Default: 15.
mask_image_urlstringNoBlack & 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_imagesarray of stringsNoExtra 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_percentagenumberNoFor zoom_out: uniform expansion on all sides as a percentage, clamped to 0–90. Default: 20.
expand_leftnumberNoFor zoom_out: pixels of new content on the left, clamped to 0–700. Default: 0.
expand_rightnumberNoFor zoom_out: pixels of new content on the right, clamped to 0–700. Default: 0.
expand_topnumberNoFor zoom_out: pixels of new content on top, clamped to 0–700. Default: 0.
expand_bottomnumberNoFor zoom_out: pixels of new content on the bottom, clamped to 0–700. Default: 0.
ai_optimizebooleanNoLet Kolbo enhance your prompt before sending it to the model. Default: false — only the exact value true enables it.
font_idsarray of stringsNoUp 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_idstringNoProject ObjectId to drop the edit into.
session_idstringNoAn 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>:

OperationModel type
upscale, clarity_upscale, split_upscaleimage_upscale
enhancegraphics_enhance
reframeimage_reframe
zoom_outimage_zoom_out
removebgbackground_remove
background_replacebackground_replace
enhance_skinskin_enhancer
inpaintinpaint
eraseerase
face_swapface_swap
camera_angle, magic_editimage_editing
split, multi_shotnone — 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, omit model to let Smart Select pick an editing model, or send an image_editing identifier for a deterministic choice. On /edit/image there is no Smart Select — omit model to get the operation's default; an alias like "auto" is a 400.
  • source_images and reference_images accept URLs — no file uploads needed. The API downloads and processes them. Upload local files first via the Media Library.
  • Send source_images (or visual_dna_ids) for a real edit; without either the request runs as a plain text-to-image generation.
  • Use moodboard_id to layer a style preset on top of any edit. See Moodboards. moodboard_id, visual_dna_ids, reference_images, cinematic, background, and skip_color_palette exist only on /generate/image-edit — /v1/edit/image reads none of them.
  • On /generate/image-edit, read the settled cost from credits_used on the completed status rather than credits_charged on the submit response. /v1/edit/image reports neither — diff your balance from GET /v1/account/credits instead. See Credits and Billing.