Kolbo.AIKolbo.AI Docs
Developer API

Elements Generation

Generate animated videos from reference images and prompts using the Kolbo API.

Generate a video from reference elements — images, videos, and/or an audio track — plus a text prompt. Use it to animate specific assets you already have ("animate this product", "put these three characters into one scene"). For text-only video use Video Generation.

Smart Select: Omit model (or send "auto" / "smart-select") and Kolbo routes the request to a model itself. Passing a specific identifier from GET /api/v1/models?type=elements gives you deterministic behaviour and lets you pre-validate its input caps.

Model identifiers are Kolbo-specific. Never hardcode model identifiers — always fetch the current list from GET /api/v1/models?type=elements first. Models may be added, renamed, or retired at any time.

All generation endpoints accept an optional project_id body field that routes the output into a specific project. See Projects.

Endpoint

POST /api/v1/generate/elements

Rate limit: 10 requests per minute per user, counted in a bucket shared with the other standard-limit /v1/generate/* endpoints. Image generation and transcription sit in their own, higher-limit buckets.

Request Body

Accepts application/json or multipart/form-data (when uploading files). File uploads and URLs may be combined in the same request.

FieldTypeRequiredDescription
promptstringYesText description of the desired video. Must be a non-empty string — otherwise 400 INVALID_PROMPT.
modelstringNoModel identifier from GET /api/v1/models?type=elements. Must be a string, never an array (400 INVALID_MODEL). Omitted / "auto" / "smart-select" → Smart Select.
reference_imagesarray of stringsNoPublic image URLs used as reference elements. The chosen model's declared cap is elements_max_images on GET /api/v1/models.
reference_videosarray of stringsNoReference video URLs. The chosen model's declared cap is elements_max_videos on GET /api/v1/models.
audio_urlstringNoURL of one reference audio track. Audio shorter than the model's min_audio_duration (default 1s when the model does not set one) returns 400 AUDIO_TOO_SHORT. Audio longer than the output duration — or than max_audio_duration (default 15s), whichever is smaller — is auto-trimmed to that bound minus one frame rather than rejected. Non-MP3 audio is converted server-side. A URL that cannot be downloaded is non-fatal: the original URL is forwarded untouched.
filesmultipartNoReference media files — repeat the files field once per file. There is no file-count cap, and 200 MB is only the transport ceiling: each file must also fit the chosen model's max_file_size (default 52428800 = 50 MB when the model does not set one), otherwise 400 FILE_TOO_LARGE. Image files are merged with reference_images and counted together. Video files are converted to MP4 and trimmed to max_video_duration; audio files are duration-checked (400 AUDIO_TOO_SHORT / 400 AUDIO_TOO_LONG) and converted to MP3. Uploaded video/audio files do not satisfy the "at least one visual input" check below — only image files and reference_images do.
durationnumber | stringNoOutput duration in seconds. Default 5. Range-checked against the chosen model's min_video_durationmax_video_duration (defaults 115 when the model does not set them); out of range returns 400 INVALID_DURATION, with the allowed range spelled out in the error message.
aspect_ratiostringNoDefault "16:9". Must be one of supported_aspect_ratios (or supported_aspect_ratios_by_type) on the chosen model.
motionstringNoFree-text motion style / intensity hint. Stored on the generation record only — no elements provider currently reads it.
preset_idstringNoVideo preset id (a 24-character ObjectId) from GET /api/v1/presets?type=video. An unknown or inactive id returns 400; a value that is not a valid ObjectId fails the preset lookup and returns 500. The chosen model must also advertise preset support, otherwise 400.
enhance_promptbooleanNoRewrite the prompt with the prompt enhancer. Default true (send false to disable).
visual_dna_idsarray of stringsNoVisual DNA ids. Every id is access-checked before the generation starts — one you do not own or have shared access to fails the request with 400 Visual DNA not found or inaccessible. Cap: max_visual_dna; ignored when supports_visual_dna is false.
resolutionstringNoVideo resolution tier. Model-dependent — check supported_resolutions. On Kling-family identifiers the value also selects the matching model variant, and a tier that family does not publish is rejected with 400 UNSUPPORTED_KLING_RESOLUTION. Higher tiers may multiply the credit cost — see Credit Multipliers.
sound_enabledbooleanNoRequest synced audio on models that can emit it. Only meaningful when sound_generation_type is not "none"; sound_enabled_by_default is the model default and sound_credit_multiplier is the cost penalty when on.
project_idstringNoTarget project. Omit to use your auto-created "API Generations" project.

At least one visual input is required. The check is satisfied by reference_images, uploaded image files, reference_videos, or visual_dna_ids. A request carrying only an audio_url, or only uploaded audio/video files, returns 400 NO_IMAGES_PROVIDED. Elements always animates something — for a pure text-to-video prompt use POST /v1/generate/video.

In a multipart request, send reference images as files. reference_images is only decoded into a URL list on application/json requests. Sent as a multipart/form-data field it is not parsed back into a list and reaches the generator as one malformed reference. reference_videos, audio_url and visual_dna_ids are handled correctly in both modes.

The per-model input caps are advisory here. elements_max_images, elements_max_videos and elements_max_audio are published in the model catalog for you to pre-validate against, but this endpoint does not count your references against them — an over-cap request is not rejected up front and reaches the provider. Check the caps yourself before submitting.

Provider-asset fields (model-specific)

Some elements models (Gemini Omni family) run against assets that already live with the provider. These optional fields are merged on top of the ids Kolbo derives from visual_dna_ids and passed straight through — leave them unset unless you already hold provider asset ids.

FieldTypeDescription
character_idsarray of strings | JSON stringProvider character asset ids.
audio_idsarray of strings | JSON stringProvider audio asset ids.
video_listarray of objects | JSON stringSource clips as { url, start, ends } — a clip list, not a flat URL list.

Examples

cURL with reference images (Smart Select):

curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
  -H "X-API-Key: kolbo_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Animate the product with a dynamic reveal and floating particles",
    "reference_images": ["https://example.com/product-photo.jpg"],
    "duration": 5,
    "aspect_ratio": "16:9"
  }'

cURL with multiple reference types:

curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
  -H "X-API-Key: kolbo_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "The character from the photo walks through the scene in the clip",
    "model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
    "reference_images": ["https://example.com/character.jpg"],
    "reference_videos": ["https://example.com/street-plate.mp4"],
    "audio_url": "https://example.com/footsteps.mp3",
    "duration": 5,
    "resolution": "1080p"
  }'

cURL with file upload:

curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
  -H "X-API-Key: kolbo_live_YOUR_API_KEY" \
  -F "prompt=Animate the product with a dynamic reveal" \
  -F "[email protected]" \
  -F "duration=5" \
  -F "aspect_ratio=16:9"

JavaScript:

const API_KEY = "kolbo_live_YOUR_API_KEY";

async function main() {
  // Optional: fetch available models
  // const models = await fetch("https://api.kolbo.ai/api/v1/models?type=elements", {
  //   headers: { "X-API-Key": API_KEY }
  // }).then(r => r.json());
  // console.log("Available models:", models);

  const response = await fetch("https://api.kolbo.ai/api/v1/generate/elements", {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      prompt: "Animate the product with a dynamic reveal and floating particles",
      reference_images: ["https://example.com/product-photo.jpg"],
      duration: 5,
      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("Video URL:", result.result.urls[0]); // urls is 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(
      `https://api.kolbo.ai/api/v1/generate/${generationId}/status`,
      { headers: { "X-API-Key": API_KEY } }
    ).then((r) => r.json());
    if (TERMINAL.has(status.state)) return status;
  }
}

main();

Python:

import requests
import time

API_KEY = "kolbo_live_YOUR_API_KEY"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}

# Optional: fetch available models
# models = requests.get(
#     "https://api.kolbo.ai/api/v1/models?type=elements",
#     headers={"X-API-Key": API_KEY},
# ).json()
# print("Available models:", models)

response = requests.post(
    "https://api.kolbo.ai/api/v1/generate/elements",
    headers=HEADERS,
    json={
        "prompt": "Animate the product with a dynamic reveal and floating particles",
        "reference_images": ["https://example.com/product-photo.jpg"],
        "duration": 5,
        "aspect_ratio": "16:9",
    },
)

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"https://api.kolbo.ai/api/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("Video URL:", status["result"]["urls"][0])  # urls is always a list

With Specific Model:

First, fetch available models:

curl https://api.kolbo.ai/api/v1/models?type=elements \
  -H "X-API-Key: kolbo_live_YOUR_API_KEY"

Then use a model identifier from the response:

curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
  -H "X-API-Key: kolbo_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Animate the product with a dynamic reveal and floating particles",
    "reference_images": ["https://example.com/product-photo.jpg"],
    "model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
    "duration": 5,
    "aspect_ratio": "16:9"
  }'

Model identifiers come from GET /api/v1/models?type=elements. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.

Response

This endpoint is asynchronous and fire-and-forget: the POST answers 202 Accepted as soon as the job is queued and never contains a video.

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": "6820f1c2a1b2c3d4e5f60718",
  "type": "elements",
  "model": "auto",
  "credits_charged": null,
  "poll_url": "/v1/generate/6820f1c2a1b2c3d4e5f60718/status",
  "poll_interval_hint": 8,
  "session_id": "6820f1c2a1b2c3d4e5f60700",
  "project_id": "6820f1c2a1b2c3d4e5f606ff"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll and cancel with.
typestringAlways "elements"
modelstringEchoes "auto" for Smart Select requests, otherwise the identifier you sent
credits_chargednullAlways null on this endpoint — the elements 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 — 8 here
session_id / project_idstringWhere the generation lives in the Kolbo app

Completed Status

GET /api/v1/generate/{generation_id}/status

{
  "success": true,
  "generation_id": "6820f1c2a1b2c3d4e5f60718",
  "type": "elements",
  "state": "completed",
  "progress": 100,
  "result": {
    "urls": ["https://media.kolbo.ai/videos/.../output.mp4"],
    "thumbnail_url": "https://media.kolbo.ai/videos/.../thumb.jpg",
    "duration": 5,
    "aspect_ratio": "16:9",
    "prompt_used": "Animate the product with a dynamic reveal and floating particles",
    "model": "MODEL_IDENTIFIER",
    "created_at": "2026-04-12T10:30:00.000Z"
  },
  "credits_used": 90,
  "credits_breakdown": [
    { "model": "MODEL_IDENTIFIER", "amount": 90, "base": 60, "final": 90, "duration_multiplier": 1.5, "pricing": null }
  ]
}

The output lives in result.urls — always an array of strings, even though this endpoint produces exactly one video. Read urls[0].

result (and credits_used / credits_breakdown) appear only under state: "completed"; a processing body has exactly five keys and no result at all. Every reference asset you sent lands in one finished clip — there is no partial read.

result.thumbnail_url falls back to the first reference image when the pipeline produced no poster frame. result.duration is a number when the pipeline measured it and a string when it falls back to the requested duration — coerce it. result.model is never "auto": it resolves to the identifier that actually ran. The visual_dna, preset and cinematic_presets keys are added only when you sent those inputs. credits_used is the authoritative, multiplier-adjusted cost.

Failure and cancellation

A failed generation is still an HTTP 200 with success: true — the failure is in state, with an error string and a best-effort failure object. cancelled carries neither result nor error, so treat all three terminal states explicitly. Failed generations are not charged. See Polling & Cancellation.

Errors

StatusCodeCause
400INVALID_PROMPTprompt missing, blank, or not a string
400INVALID_MODELmodel sent as an array/object, or the identifier does not resolve to a visible elements model
400NO_IMAGES_PROVIDEDNo reference image (reference_images or an uploaded image file), no reference_videos, and no visual_dna_ids
400INVALID_DURATIONduration outside the model's min_video_durationmax_video_duration; the allowed range is spelled out in the error message
400AUDIO_TOO_SHORTReference audio below the model's minimum audio duration
400AUDIO_TOO_LONGAn uploaded audio file above the model's maximum (audio passed by audio_url is trimmed instead)
400AUDIO_PROCESSING_FAILEDAn uploaded audio file could not be decoded or converted to MP3
400VIDEO_CONVERSION_FAILEDAn uploaded non-MP4 video file could not be converted
400FILE_TOO_LARGEAn uploaded file exceeds the model's max_file_size (default 50 MB)
400FILE_PROCESSING_FAILEDAn uploaded image file could not be processed
400IMAGE_URL_ERRORA reference_images URL could not be downloaded or processed
400UNSUPPORTED_KLING_RESOLUTIONresolution is not one of the tiers the chosen Kling model family publishes
400GENERATION_ERRORpreset_id is unknown or inactive
400GENERATION_ERRORThe chosen model does not support video presets
400GENERATION_ERRORA visual_dna_ids entry does not exist or you do not have access to it
403INSUFFICIENT_CREDITSBalance too low — checked before the generation starts
429More than 10 generation requests in one minute
500GENERATION_ERRORpreset_id is not a valid 24-character ObjectId — the preset lookup throws

Tips

  • Elements generation typically takes 1-5 minutes depending on the model and duration.
  • Reference images may be supplied as URLs (reference_images), as multipart files, or as a combination of both — they are merged and counted together.
  • URLs are re-routed by extension: a .mp4/.mov/.webm/.avi/.mkv/.m4v URL sent in reference_images is moved to the video slot, and a .mp3/.wav/.aac/.m4a/.flac/.ogg URL is moved to the audio slot.
  • There is no cap on the number of uploaded files, but every file must fit the chosen model's max_file_size (default 50 MB). 200 MB is only the hard transport ceiling.
  • Credit cost depends on the model, duration, resolution tier, and whether sound is enabled — see Models and Pricing.
  • Use poll_interval_hint from the initial response to set your polling interval.
  • Before generating, read elements_max_images, elements_max_videos, elements_max_audio, min_video_duration, max_video_duration, min_audio_duration, max_audio_duration, max_file_size, supported_aspect_ratios, and supported_resolutions off the chosen model via GET /api/v1/models?type=elements.