Kolbo.AIKolbo.AI Docs
Developer API

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_id body 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 return 403 with code INSUFFICIENT_CREDITS before any work starts when the account cannot cover the generation.

Text to Video

Endpoint

POST /api/v1/generate/video

Request Body

FieldTypeRequiredDescription
promptstringYesText description of the video. Must be a non-empty string — otherwise 400 with code INVALID_PROMPT.
modelstringNoModel identifier from GET /api/v1/models?type=video. Omit (or send "auto" / "auto-select" / "smart-select" / "") for Smart Select.
aspect_ratiostringNoOutput aspect ratio, e.g. "16:9", "9:16", "1:1". Must be one of the model's supported_aspect_ratios. Default: "16:9".
durationnumber | stringNoDuration in seconds. Must be in the model's supported_durations, or within min_output_durationmax_output_duration when the model exposes a range instead. Default: 5.
enhance_promptbooleanNoRun the prompt through Kolbo's enhancer before generating. Default: true — only an explicit false disables it.
resolutionstringNoVideo 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_enabledbooleanNoTurn 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_idstringNoVideo 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_palettebooleanNoOpt this single request out of the account's active Color DNA palette. Default: the active palette is applied.
project_idstringNoProject 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-videowan/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 list

With 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-image

Request Body

FieldTypeRequiredDescription
image_urlstringYesURL of the source image. Missing → 400 image_url is required.
promptstringNoDescription 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.
modelstringNoModel identifier from GET /api/v1/models?type=video_from_image. Omit (or "auto" / "auto-select" / "smart-select" / "") for Smart Select.
aspect_ratiostringNoOutput aspect ratio, e.g. "16:9", "9:16", "1:1". Must be one of the model's supported_aspect_ratios. Default: "16:9".
durationnumber | stringNoDuration in seconds. Must be in supported_durations, or within min_output_durationmax_output_duration. Default: 5.
enhance_promptbooleanNoRun the motion prompt through Kolbo's enhancer. Default: true — only an explicit false disables it.
visual_dna_idsarray of stringsNoVisual 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.
resolutionstringNoVideo 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_enabledbooleanNoTurn 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_palettebooleanNoOpt this single request out of the account's active Color DNA palette. Default: the active palette is applied.
project_idstringNoProject 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": "…"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll and cancel with.
typestring"video" or "video_from_image"
modelstringEchoes "auto" for Smart Select requests, otherwise the identifier you sent
credits_chargednullAlways null on both endpoints — neither video pipeline reports an 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 for video
session_id / project_idstringWhere 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 fieldTypeNotes
urlsstring[]The finished video
thumbnail_urlstring | nullGenerated poster frame, else the source image on image-to-video
durationnumber | string | nullUnion 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_ratiostring | null
prompt_usedstringThe post-enhancement prompt when enhance_prompt was on
modelstring | nullNever "auto" or a router id — resolved to the identifier that actually produced the video
model_namestringPresent only when the pipeline recorded a display name
created_atstring (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 passed preset_id. name is null for 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 when visual_dna_ids was supplied or an @Name mention 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 model hands the request to Smart Select. Passing a specific identifier gives you deterministic behaviour and lets you read that model's constraints from GET /api/v1/models up 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_used on the completed status response rather than credits_charged on the submit response — see Credits and Billing.
  • Use poll_interval_hint from the initial response to set your polling interval.
  • Both endpoints are rate limited to 10 requests per minute per API key.