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_idbody field that routes the output into a specific project. See Projects.
Endpoint
POST /api/v1/generate/elementsRate 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.
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Text description of the desired video. Must be a non-empty string — otherwise 400 INVALID_PROMPT. |
model | string | No | Model 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_images | array of strings | No | Public image URLs used as reference elements. The chosen model's declared cap is elements_max_images on GET /api/v1/models. |
reference_videos | array of strings | No | Reference video URLs. The chosen model's declared cap is elements_max_videos on GET /api/v1/models. |
audio_url | string | No | URL 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. |
files | multipart | No | Reference 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. |
duration | number | string | No | Output duration in seconds. Default 5. Range-checked against the chosen model's min_video_duration–max_video_duration (defaults 1–15 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_ratio | string | No | Default "16:9". Must be one of supported_aspect_ratios (or supported_aspect_ratios_by_type) on the chosen model. |
motion | string | No | Free-text motion style / intensity hint. Stored on the generation record only — no elements provider currently reads it. |
preset_id | string | No | Video 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_prompt | boolean | No | Rewrite the prompt with the prompt enhancer. Default true (send false to disable). |
visual_dna_ids | array of strings | No | Visual 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. |
resolution | string | No | Video 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_enabled | boolean | No | Request 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_id | string | No | Target 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.
| Field | Type | Description |
|---|---|---|
character_ids | array of strings | JSON string | Provider character asset ids. |
audio_ids | array of strings | JSON string | Provider audio asset ids. |
video_list | array of objects | JSON string | Source 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 listWith 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"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "elements" |
model | string | Echoes "auto" for Smart Select requests, otherwise the identifier you sent |
credits_charged | null | Always null on this endpoint — the elements 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 — 8 here |
session_id / project_id | string | Where 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
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PROMPT | prompt missing, blank, or not a string |
| 400 | INVALID_MODEL | model sent as an array/object, or the identifier does not resolve to a visible elements model |
| 400 | NO_IMAGES_PROVIDED | No reference image (reference_images or an uploaded image file), no reference_videos, and no visual_dna_ids |
| 400 | INVALID_DURATION | duration outside the model's min_video_duration–max_video_duration; the allowed range is spelled out in the error message |
| 400 | AUDIO_TOO_SHORT | Reference audio below the model's minimum audio duration |
| 400 | AUDIO_TOO_LONG | An uploaded audio file above the model's maximum (audio passed by audio_url is trimmed instead) |
| 400 | AUDIO_PROCESSING_FAILED | An uploaded audio file could not be decoded or converted to MP3 |
| 400 | VIDEO_CONVERSION_FAILED | An uploaded non-MP4 video file could not be converted |
| 400 | FILE_TOO_LARGE | An uploaded file exceeds the model's max_file_size (default 50 MB) |
| 400 | FILE_PROCESSING_FAILED | An uploaded image file could not be processed |
| 400 | IMAGE_URL_ERROR | A reference_images URL could not be downloaded or processed |
| 400 | UNSUPPORTED_KLING_RESOLUTION | resolution is not one of the tiers the chosen Kling model family publishes |
| 400 | GENERATION_ERROR | preset_id is unknown or inactive |
| 400 | GENERATION_ERROR | The chosen model does not support video presets |
| 400 | GENERATION_ERROR | A visual_dna_ids entry does not exist or you do not have access to it |
| 403 | INSUFFICIENT_CREDITS | Balance too low — checked before the generation starts |
| 429 | — | More than 10 generation requests in one minute |
| 500 | GENERATION_ERROR | preset_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 multipartfiles, or as a combination of both — they are merged and counted together. - URLs are re-routed by extension: a
.mp4/.mov/.webm/.avi/.mkv/.m4vURL sent inreference_imagesis moved to the video slot, and a.mp3/.wav/.aac/.m4a/.flac/.oggURL 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_hintfrom 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, andsupported_resolutionsoff the chosen model viaGET /api/v1/models?type=elements.
Related
Video Generation
Text-to-video and image-to-video, when you are not animating reference assets
First & Last Frame
Interpolate a video between two fixed frames
Visual DNA
Character, style, and product consistency across generations
Models and Pricing
Model catalog, input caps, and credit multipliers
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel