Video Generation
Generate videos from text prompts or animate images using the Kolbo API.
Generate videos from text or animate images using models like Kling, Seedance, Hailuo, Veo, and Sora. Browse the full model catalog for everything currently available, or fetch the live list at any time with GET /api/v1/models?type=text_to_video.
Smart Select: omit model and Kolbo routes the request to an auto-selected model. Passing "auto", "auto-select", "smart-select", "smart_select", "smartselect", or an empty string (case-insensitive) does the same thing. The initial response then returns "model": "auto"; the status response reports the model that actually ran.
An unrecognised identifier is rejected with 400 MODEL_NOT_FOUND — the message suggests near matches. Only an omitted, blank or auto-alias model uses Smart Select.
Model identifiers are Kolbo-specific. Never hardcode model identifiers — always fetch the current list from GET /api/v1/models?type=video first. Models may be added, renamed, or retired at any time. model must be a string; sending an array or object returns 400 with code INVALID_MODEL.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project, and an optionalsession_idthat appends to an existing session. See Projects. Both endpoints on this page share the account-wide generation rate limit of 30 accepted requests per minute (the same bucket as other generation routes such as music, speech, chat and cancel); rejected requests do not count against it, and an exceeded limit returns429withlimit,window_secondsandretryAfterin the body. Both return403with codeINSUFFICIENT_CREDITS(plusbalance,requiredandpricing_url) before any work starts when the account cannot cover the generation.
Text to Video
Endpoint
POST /api/v1/generate/videoRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Text description of the video. Must be a non-empty string — otherwise 400 with code INVALID_PROMPT. |
model | string | No | Model identifier from GET /api/v1/models?type=video. Omit (or send "auto" / "auto-select" / "smart-select" / "") for Smart Select. An unknown identifier returns 400 MODEL_NOT_FOUND. |
aspect_ratio | string | No | Output aspect ratio, e.g. "16:9", "9:16", "1:1". Use one of the model's supported_aspect_ratios (or supported_aspect_ratios_by_type.text_to_video when present). Default: "16:9". |
duration | number | string | No | Duration in seconds. Must be in the model's supported_durations, or within min_output_duration–max_output_duration when the model exposes a range instead. Default: 5. |
enhance_prompt | boolean | No | Run the prompt through Kolbo's enhancer before generating. Default: false — true (or the string "true" / "1") enables it. |
resolution | string | No | Video resolution tier, e.g. "720p" / "1080p"; some models use labels such as "512P" / "1024P", and draft-capable models also list a named draft tier such as "480p-draft". Model-specific — check supported_resolutions on GET /api/v1/models?type=video, and resolution_multipliers on the same record to predict the credit cost. See Credit Multipliers. Omit to use the model default. A draft tier the selected model does not have returns 400 with code UNSUPPORTED_VIDEO_RESOLUTION. |
draft | boolean | No | Boolean alias for the model's draft tier. true forces the draft resolution (e.g. "480p-draft") regardless of resolution, and requires an explicit model that has a draft tier (check draft_resolutions on the model record) — otherwise 400 "Draft is not supported by the selected model." false maps a draft tier in resolution back to the regular resolution ("480p-draft" → "480p"). The strings "true" / "false" are also accepted; any other value returns 400 "draft must be a boolean." These two draft errors carry code UNSUPPORTED_DRAFT_MODE / INVALID_DRAFT_MODE, and the body has both status: false / message and success: false / error. |
sound_enabled | boolean | No | Turn AI-generated synced audio on or off. Only honored by models whose sound_generation_type is "native"; on other models the flag has no effect (models flagged sound_baked_in always carry audio). On text-to-video, omitting it means off for the provider — pass true explicitly to get audio. The credit reservation, however, falls back to the model's sound_enabled_by_default when you omit it, so send an explicit false if you do not want the sound multiplier reserved. Enabling sound may apply the model's sound_credit_multiplier. |
visual_dna_ids | array of strings | No | Visual DNA profile ids. Every id is access-checked first — an id you cannot use fails the request with 400 ("Visual DNA not found or inaccessible: …"). How the DNA is applied depends on the model — see the callout below. |
reference_images | array of strings | No | At most one image URL. Delegates the request to image-to-video — see the callout below. |
preset_id | string | No | Video preset id from GET /api/v1/presets?type=video. An unknown or inactive id, or a model that does not support presets, is rejected with 400 and code GENERATION_ERROR (message "Invalid preset ID: …" or "Selected model(s) do not support video presets: …"). |
skip_color_palette | boolean | No | Opt this single request out of the account's active Color DNA palette. Default: the active palette is applied. |
project_id | string | No | Project to file the generation into. Defaults to the auto-created API project — see Projects. |
session_id | string | No | Append to an existing video session (one returned by an earlier video or video_from_image generation). Takes precedence over project_id — the session's own project is used, and you need edit access to it. Malformed → 400 SDK_SESSION_INVALID_ID; unknown or wrong session type → 404 SDK_SESSION_NOT_FOUND. |
reference_images routes to image-to-video. Send exactly one reference image and the request is delegated to that model's image-to-video sibling — wan/v2.6/text-to-video → wan/v2.6/image-to-video, and likewise for any …/text-to-video identifier with an …/image-to-video pair. A model that is already an image-to-video model takes the image directly, and Smart Select stays on Smart Select. The generation comes back as type video_from_image, priced as the image-to-video model, and the Image to Video rules apply — including that the Color DNA palette is not applied.
Two cases fail loudly instead of ignoring the input: more than one reference image returns 400 TOO_MANY_REFERENCE_IMAGES (use First-Last Frame for two keyframes or Elements for several references), and a text-to-video-only model with no image-to-video variant returns 400 REFERENCE_IMAGES_NOT_SUPPORTED naming the alternative.
Visual DNA on text-to-video. visual_dna_ids is honoured here. On models with reference-image slots (elements_max_images greater than 0 on the model record), the DNA's stills are attached as reference images — up to that cap — and each @Name in the prompt is rewritten to the matching @ImageN token. Kling and WAN models receive the DNA as native elements instead. Models with no reference-image slots get the DNA as description only (the @Name becomes the plain name). For identity-critical work with several characters or products, Elements gives explicit per-reference control.
Examples
cURL (Smart Select):
curl -X POST https://api.kolbo.ai/api/v1/generate/video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A drone flying over a snowy mountain range at golden hour",
"duration": 5,
"aspect_ratio": "16:9"
}'JavaScript:
const API_KEY = "kolbo_live_YOUR_API_KEY";
async function main() {
const response = await fetch("https://api.kolbo.ai/api/v1/generate/video", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: "A drone flying over a snowy mountain range at golden hour",
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"}
TERMINAL = {"completed", "failed", "cancelled"}
def poll_until_done(generation_id, interval_seconds):
"""Minimal loop. Add transient-error retries and an overall timeout for
production — see /docs/developer-api/polling-and-cancellation."""
while True:
time.sleep(interval_seconds)
status = requests.get(
f"https://api.kolbo.ai/api/v1/generate/{generation_id}/status",
headers={"X-API-Key": API_KEY},
).json()
if status["state"] in TERMINAL:
return status
started = requests.post(
"https://api.kolbo.ai/api/v1/generate/video",
headers=HEADERS,
json={
"prompt": "A drone flying over a snowy mountain range at golden hour",
"duration": 5,
"aspect_ratio": "16:9",
},
).json()
result = poll_until_done(started["generation_id"], started["poll_interval_hint"])
if result["state"] != "completed":
raise Exception(result.get("error", result["state"]))
print("Video URL:", result["result"]["urls"][0]) # urls is always a listWith a specific model, resolution, sound, and a preset:
Fetch identifiers from GET /api/v1/models?type=video first, then pass the identifier value:
curl -X POST https://api.kolbo.ai/api/v1/generate/video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A drone flying over a snowy mountain range at golden hour",
"model": "your-model-identifier",
"duration": 5,
"aspect_ratio": "16:9",
"resolution": "1080p",
"sound_enabled": false,
"preset_id": "your-video-preset-id"
}'Model identifiers come from GET /api/v1/models?type=video. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.
Image to Video
Animate a still image into a video.
Endpoint
POST /api/v1/generate/video/from-imageRequest Body
| Field | Type | Required | Description |
|---|---|---|---|
image_url | string | Yes | http(s) URL of the source image. An array or a { "url": … } object is also accepted — only the first http(s) URL is used. Missing → 400 image_url is required. |
prompt | string | No | Description of the desired motion (the subject is already in the image). Optional here; when present it must be a string. Defaults to an empty prompt. |
model | string | No | Model identifier from GET /api/v1/models?type=video_from_image. Omit (or "auto" / "auto-select" / "smart-select" / "") for Smart Select. An unknown identifier returns 400 MODEL_NOT_FOUND. |
aspect_ratio | string | No | Output aspect ratio, e.g. "16:9", "9:16", "1:1". Use one of the model's supported_aspect_ratios (or supported_aspect_ratios_by_type.img_to_video when present). Default: "16:9". |
duration | number | string | No | Duration in seconds. Must be in supported_durations, or within min_output_duration–max_output_duration. Default: 5. |
enhance_prompt | boolean | No | Run the motion prompt through Kolbo's enhancer. Default: false — true (or the string "true" / "1") enables it. |
visual_dna_ids | array of strings | No | Visual DNA profile ids for character / style consistency. 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: …"). Whether the model actually consumes them is model-dependent: check supports_visual_dna and max_visual_dna on the model record and pass no more than max_visual_dna ids. |
resolution | string | No | Video resolution tier (e.g. "720p", "1080p", or a draft tier such as "480p-draft"). Model-dependent — check supported_resolutions on GET /api/v1/models?type=video_from_image. Higher tiers can multiply the credit cost — see Credit Multipliers. Omit to use the model default. |
draft | boolean | No | Draft-tier alias — same behaviour as on text-to-video. |
sound_enabled | boolean | No | Turn AI-generated synced audio on or off. Only honored by models whose sound_generation_type is "native". May apply the model's sound_credit_multiplier. When omitted, the credit reservation uses the model's sound_enabled_by_default; an explicit true / false is respected. |
preset_id | string | No | Video preset id from GET /api/v1/presets?type=video — validated the same way as on text-to-video. |
skip_color_palette | boolean | No | Accepted, but has no effect here: the Color DNA palette is applied only to text-to-video (and Elements), never to image-to-video, because the source image already fixes the colour. |
project_id | string | No | Project to file the generation into. See Projects. |
session_id | string | No | Append to an existing video session — same rules as on text-to-video. |
This endpoint takes exactly one source image, via image_url; reference_images is not part of the request. The source image is also stamped as the session thumbnail so the generation is identifiable while it processes.
@Name mentions (both endpoints). When you omit visual_dna_ids and the chosen model supports Visual DNA, any @ProfileName written in the prompt is resolved to that profile automatically (names come from GET /api/v1/visual-dna). A mention that resolves to nothing is stripped from the prompt rather than failing the request. Passing visual_dna_ids explicitly disables this parsing.
Examples
cURL (Smart Select):
curl -X POST https://api.kolbo.ai/api/v1/generate/video/from-image \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/photo.jpg",
"prompt": "Slow zoom in with gentle camera pan",
"duration": 5
}'JavaScript:
const API_KEY = "kolbo_live_YOUR_API_KEY";
async function main() {
const response = await fetch("https://api.kolbo.ai/api/v1/generate/video/from-image", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
image_url: "https://example.com/photo.jpg",
prompt: "Slow zoom in with gentle camera pan",
duration: 5
})
});
const started = await response.json();
// pollUntilDone() from the text-to-video example above — the status route,
// state machine and result shape are identical for both endpoints.
const result = await pollUntilDone(started.generation_id, started.poll_interval_hint);
console.log("Video URL:", result.result.urls[0]);
}
main();Python:
import requests
API_KEY = "kolbo_live_YOUR_API_KEY"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
started = requests.post(
"https://api.kolbo.ai/api/v1/generate/video/from-image",
headers=HEADERS,
json={
"image_url": "https://example.com/photo.jpg",
"prompt": "Slow zoom in with gentle camera pan",
"duration": 5,
},
).json()
# poll_until_done() from the text-to-video example above — the status route,
# state machine and result shape are identical for both endpoints.
result = poll_until_done(started["generation_id"], started["poll_interval_hint"])
print("Video URL:", result["result"]["urls"][0])With a specific model and a Visual DNA:
curl -X POST https://api.kolbo.ai/api/v1/generate/video/from-image \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"image_url": "https://example.com/photo.jpg",
"prompt": "Slow zoom in with gentle camera pan",
"model": "your-model-identifier",
"visual_dna_ids": ["your-visual-dna-id"],
"resolution": "1080p",
"duration": 5
}'Model identifiers come from GET /api/v1/models?type=video_from_image. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.
Response
Both endpoints are asynchronous and fire-and-forget: the POST returns 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 either 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
Both endpoints return the same envelope. type is "video" for text-to-video and "video_from_image" for image-to-video:
{
"success": true,
"generation_id": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "video",
"model": "auto",
"credits_charged": null,
"poll_url": "/v1/generate/68f2c1a9b4e5d6f7a8b9c0d1/status",
"poll_interval_hint": 8,
"session_id": "…",
"project_id": "…"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | "video" or "video_from_image" |
model | string | "auto" for Smart Select requests, otherwise the canonical identifier your model resolved to |
credits_charged | null | Always null on both endpoints — neither video pipeline reports an 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 for video |
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": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "video",
"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": "A drone flying over a snowy mountain range at golden hour",
"model": "MODEL_IDENTIFIER",
"created_at": "2026-04-12T10:00:00.000Z"
},
"credits_used": 35,
"credits_breakdown": [
{ "model": "MODEL_IDENTIFIER", "amount": 35, "base": 35, "final": 35, "duration_multiplier": null, "pricing": null }
]
}The output lives in result.urls — always an array of strings, even though these endpoints produce exactly one video. Read urls[0]; do not assume a bare string.
result (and credits_used / credits_breakdown) appear only under state: "completed". A processing body has exactly five keys and no result at all — that includes the provider-side upload phase, which still reports processing. There is no partial or streaming read.
| Result field | Type | Notes |
|---|---|---|
urls | string[] | The finished video (the full-quality master) |
playback_urls | (string | null)[] | Present only when at least one clip has a browser-friendly preview copy (e.g. the master is HEVC). One entry per urls item, null where the master already plays everywhere. Download urls; preview playback_urls. |
thumbnail_url | string | null | Generated poster frame, else the source image on image-to-video |
duration | number | string | null | Union type. A number when the pipeline measured it; a string when it falls back to the requested duration — /generate/video commonly returns "5", not 5. Coerce it. |
aspect_ratio | string | null | |
prompt_used | string | The post-enhancement prompt when enhance_prompt was on |
model | string | null | Never "auto" or a router id — resolved to the identifier that actually produced the video |
model_name | string | Present only when the pipeline recorded a display name |
created_at | string (ISO 8601) | |
reference_images | string[] | Present only when the generation recorded reference images — the inputs it actually conditioned on, which can include Visual DNA stills injected server-side |
credits_used is the authoritative, multiplier-adjusted cost, not credits_charged from the submit response.
The completed result also echoes back the creative inputs that shaped the generation, each field present only when that input was actually used:
preset—{ id, name }, when you passedpreset_id.nameis the video preset's display name (nullonly if the preset no longer exists).visual_dna—[{ id, name, thumbnail_url }], whenvisual_dna_idswas supplied or an@Namemention resolved.
Neither endpoint accepts a moodboard id or cinematic-dimension selections, so the moodboard and cinematic_presets keys the shared status shaper can emit never appear on these two types.
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 whose sub-fields are frequently null on this family. cancelled carries neither result nor error, so a loop that only tests for completed and failed will spin forever. Treat all three terminal states explicitly. Failed generations are not charged. See Polling & Cancellation.
Tips
- Omitting
modelhands the request to Smart Select. Passing a specific identifier gives you deterministic behaviour and lets you read that model's constraints fromGET /api/v1/modelsup front. - Video generation is slow — plan for several minutes. Long durations and high resolutions on heavy models routinely run past 10 minutes under provider queue load.
- Check
supported_durations,supported_aspect_ratios,supported_resolutions,supports_visual_dna,max_visual_dna,sound_generation_type, and the multiplier fields on each model via the Models endpoint before requesting specific values. - Read the settled cost from
credits_usedon the completed status response rather thancredits_chargedon the submit response — see Credits and Billing. - Use
poll_interval_hintfrom the initial response to set your polling interval. - Both endpoints share the account-wide limit of 30 accepted generation requests per minute.
Related
Video-to-Video
Restyle, motion-transfer, or subtitle an existing video
Video Editing and Trim
Upscale, reframe, extend, lipsync, and frame-accurate trimming
Visual DNA
Character, style, and product consistency across generations
Models and Pricing
Model catalog, resolution and sound credit multipliers
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel