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 does the same thing. The initial response then returns "model": "auto"; the status response reports the model that actually ran.
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. See Projects. Both endpoints on this page are rate limited to 10 requests per minute per API key, and return403with codeINSUFFICIENT_CREDITSbefore 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. |
aspect_ratio | string | No | Output aspect ratio, e.g. "16:9", "9:16", "1:1". Must be one of the model's supported_aspect_ratios. 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: true — only an explicit false disables it. |
resolution | string | No | Video resolution tier, e.g. "720p" / "1080p"; some models use labels such as "512P" / "1024P". 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. |
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. On text-to-video, omitting it means off — the provider's sound parameter is set from your value coerced to a boolean, so the model's sound_enabled_by_default is never applied here. Pass true explicitly to get audio. Enabling sound may apply the model's sound_credit_multiplier. |
preset_id | string | No | Video preset id from GET /api/v1/presets?type=video. An unknown or inactive id is rejected with 400 and code INVALID_PRESET_ID. |
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. |
reference_images now 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 so on for 31 of the 40 text-to-video models. Models already typed for both (e.g. veo3) take the image directly. The generation comes back as type video_from_image, priced as the image-to-video model.
Two cases now 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_ids still does nothing on plain text-to-video. It is accepted and recorded but never read by that pipeline. It is honoured on POST /api/v1/generate/video/from-image — so it now takes effect on a text-to-video call that carries a reference_images entry, because that request is delegated there. For a DNA-locked result without a reference image, generate the still first and animate it, or use Elements.
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 | URL of the source image. 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. |
aspect_ratio | string | No | Output aspect ratio, e.g. "16:9", "9:16", "1:1". Must be one of the model's supported_aspect_ratios. 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: true — only an explicit false disables 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"). 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. |
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. On image-to-video the credit reservation counts sound as enabled whenever the model's sound_enabled_by_default is true, whatever you send — so the multiplier can still apply on a sound_enabled: false request. |
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. See Projects. |
This endpoint takes exactly one source image, via image_url; reference_images is not part of the request. preset_id is accepted here (validated against the video presets, same as text-to-video) — earlier releases dropped it at the API layer. The source image is also stamped as the session thumbnail so the generation is identifiable while it processes.
@Name mentions. 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 | Echoes "auto" for Smart Select requests, otherwise the identifier you sent |
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 |
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) |
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 }, on text-to-video when you passedpreset_id.nameisnullfor video presets: the resolver looks the id up in the image-preset collection, so only the id is reliable.visual_dna—[{ id, name, thumbnail_url }], on image-to-video 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 are rate limited to 10 requests per minute per API key.
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