Video-to-Video
Restyle and transform existing videos using AI with the Kolbo API.
Restyle and transform existing videos using AI. Provide a source video and a text prompt describing the desired transformation, and Kolbo generates a new video with the applied style or changes.
model is required here. Unlike the other generation endpoints, video-to-video does not support Smart Select — the model families are too heterogeneous (restyle vs. subtitles vs. motion control) for an auto-pick. Omitting model (or sending an auto-select alias such as "auto" / "smart-select" / "") returns 400 with code MODEL_REQUIRED. List the options with GET /api/v1/models?type=video_from_video and pass an identifier.
Model identifiers are Kolbo-specific. Never hardcode model identifiers — always fetch the current list from GET /api/v1/models?type=video_from_video first. Models may be added, renamed, or retired at any time. model must be a string; 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. This endpoint is rate limited to 10 requests per minute per API key and returns403/INSUFFICIENT_CREDITSbefore any work starts when the account cannot cover the generation.
Endpoint
POST /api/v1/generate/video-from-videoRequest Body
Accepts multipart/form-data (for file uploads) or application/json (for URL-based inputs).
| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Model identifier from GET /api/v1/models?type=video_from_video. The identifier's types array must contain video_to_video — anything else (an image-to-video model, for example) is rejected up front with 400 and code WRONG_MODEL_TYPE, listing each rejected identifier and its real types in rejectedModels. No auto-select — see the callout above. |
video_url | string | Conditional | URL of the primary input video. Required unless you upload a files part. For motion-control models this slot carries the driving video — see Motion Control. |
files | file(s) | Conditional | Video file upload (multipart field name files, max 100 MB per file). Required unless video_url is set. |
prompt | string | No | Describe the desired transformation or style. Optional at the API layer, but effectively required by most models: everything except the prompt-less family fails with 400 Missing required field: prompt in metadata. That family is VEED Subtitles, Act Two, Wan Animate, Kling Motion Control, Bytedance Dreamactor, and every ltx-2.3-quality/… variant other than reference-video-to-video and inpaint. When present it must be a string, otherwise 400 / INVALID_PROMPT. |
aspect_ratio | string | No | Output aspect ratio. Must be one of the model's supported_aspect_ratios. Default: "16:9". |
duration | number | string | No | Output duration in seconds. Must be in supported_durations, or within min_output_duration–max_output_duration. Omit and the fallback is model-dependent — some models match the source length, others apply their own default. |
enhance_prompt | boolean | No | Run the prompt through Kolbo's enhancer. Default: true — only an explicit false disables it. |
resolution | string | No | Video resolution tier (e.g. "720p", "1080p"). Model-dependent — check supported_resolutions on GET /api/v1/models?type=video_from_video. Higher tiers may multiply the credit cost — see Credit Multipliers. |
sound_enabled | boolean | No | Billing only on this endpoint. It is recorded on the generation and fed to the credit calculation (the model's sound_credit_multiplier), but video-to-video never translates it into a provider sound parameter — it cannot turn audio on or off in the output. Leave it unset unless you know why you are sending it. |
reference_images | array of strings | No | Reference image URLs for models that take image inputs. Pass at most max_images from the model record — max_images: 0 means the model takes none. The cap is advisory: it is not enforced server-side, so over-sending fails at the provider instead of returning a clean 400. Required for motion-control models — see Motion Control. |
reference_videos | array of strings | No | Additional reference video URLs for models that take multiple video inputs. Pass at most max_videos from the model record; max_videos <= 1 means only the primary video is accepted. Advisory, same as above. |
elements | array of strings | No | Element image URLs (style / character reference assets alongside the main video). Pass at most max_elements from the model record; max_elements: 0 means the model takes none. Advisory, same as above. |
keyframes | array of objects | No | Timestamp-anchored keyframes, each { "image_url": string, "timestamp_seconds": number }. Only accepted by models that declare keyframe support — on any other model they are silently dropped and a plain video-to-video runs instead. Any entry whose image_url is missing or whose timestamp_seconds is not a finite number is dropped without an error before validation, so malformed entries shrink the array rather than failing the request. Surviving entries: images must be Kolbo-hosted (upload via Media Library first) — an external URL returns 400 Keyframe images must be uploaded to Kolbo first.; a negative timestamp returns 400 Each keyframe requires a valid timestamp (seconds ≥ 0).; a timestamp more than 1 s past source_duration_seconds returns 400 Keyframe timestamp is beyond the video duration., a smaller overshoot is clamped to the duration, and collisions inside the same 0.1 s bucket are nudged down in 0.1 s steps. More surviving entries than the model's ceiling returns 400 This model accepts at most N keyframes. Accepted keyframes are reduced to image_url + timestamp_seconds and re-sorted ascending by time, so the order you send them in does not matter. |
source_fps | number | No | Frame rate of the source video. Only read alongside keyframes, to place them accurately. |
source_duration_seconds | number | No | Duration of the source video in seconds. Only read alongside keyframes — it is the ceiling that keyframe timestamps are validated against. Omit it (or send a non-positive value) and no ceiling check runs at all. |
preset | string | No | VEED Subtitles only. Caption style preset (e.g. "glass", "whisper", "fusion", "simple", "vegas"). Ignored by other models. |
source_language | string | No | VEED Subtitles only. BCP-47 code of the spoken language to improve transcription accuracy (e.g. "en-US", "es-ES", "he-IL"). Omit to auto-detect. |
translation_language | string | No | VEED Subtitles only. BCP-47 code to translate the subtitles into (e.g. "en-US", "fr-FR"). Omit to keep the original spoken language. |
srt_content | string | No | VEED Subtitles only. Raw .srt subtitle text to burn in. When set, auto-transcription is skipped. |
srt_file_url | string | No | VEED Subtitles only. URL to a .srt file. Alternative to srt_content. When set, auto-transcription is skipped. |
vocabulary | array | No | VEED Subtitles only. Brand names / jargon to help transcription, e.g. [{"word":"Kolbo","replaces":["colbo"]}]. Ignored when srt_content / srt_file_url is set. |
customization | object | No | VEED Subtitles only. Style overrides — position (top/center/bottom), shadow (none/min/mid/max), and text_customizations.baseline / .highlighted each with font (Google font name), weight (100-900), color (hex). Any omitted field keeps the preset default; best supported by Basic presets. |
project_id | string | No | Project to file the generation into. See Projects. |
You must provide video_url or a files upload. With neither, the request fails with 400 Provide video_url or upload a video file — this gate is unconditional, including for the model families that could otherwise run from reference inputs alone. Sending both is not rejected: the uploaded file wins and video_url is ignored.
Array and object fields (reference_images, reference_videos, elements, keyframes, vocabulary, customization) may be sent as real JSON in a JSON body, or as JSON-encoded strings in a multipart/form-data request. A value that does not parse to a non-empty array/object is dropped silently.
visual_dna_ids is not supported on video-to-video: the field is accepted by the request but never reaches the generation pipeline. Use reference_images / elements on models that support them instead.
The LTX inpaint variant cannot be driven from this endpoint. ltx-2.3-quality/inpaint requires a tracked mask video, and there is no request field to supply one — every call fails with 400 Inpaint requires a mask. Draw a box over the object first. Use one of the other video_to_video models, or the inpaint operation on POST /api/v1/edit/video, which does take a mask_video_url.
Motion Control (character image + driving video)
Motion-control / animate-move models (identifiers containing motion-control, animate-move, or wan-animate) transfer the motion of a reference video onto a character image. For these models the inputs are:
reference_images[0]— the character image (the subject that gets animated)video_url— the driving / reference video whose motion is transferred (in the MCP tool this is thesource_videoargument)
Omitting the character image returns 400 with code MOTION_CONTROL_INPUTS.
curl -X POST https://api.kolbo.ai/api/v1/generate/video-from-video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-motion-control-model-identifier",
"reference_images": ["https://example.com/character.jpg"],
"video_url": "https://example.com/dance-reference.mp4"
}'VEED Subtitles (prompt-less)
The VEED Subtitles model (veed/subtitles) burns styled, auto-transcribed subtitles into a video. It takes no prompt — pass a style preset and optional language settings instead. Billed per minute of input video with a 1-minute minimum.
curl -X POST https://api.kolbo.ai/api/v1/generate/video-from-video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "veed/subtitles",
"video_url": "https://example.com/talking-clip.mp4",
"preset": "glass",
"source_language": "en-US",
"translation_language": "es-ES"
}'Examples
cURL with URL
First fetch identifiers from GET /api/v1/models?type=video_from_video, then pass the identifier value as model:
curl -X POST https://api.kolbo.ai/api/v1/generate/video-from-video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-identifier",
"prompt": "Transform into anime style with vibrant colors",
"video_url": "https://example.com/original-clip.mp4",
"aspect_ratio": "16:9"
}'cURL with File Upload
curl -X POST https://api.kolbo.ai/api/v1/generate/video-from-video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-F "model=your-model-identifier" \
-F "prompt=Transform into anime style with vibrant colors" \
-F "[email protected]"Timestamp-Anchored Keyframes
Models that support keyframes let you pin specific images to specific moments in the output. The keyframe images must already live on Kolbo storage — upload them through the Media Library first.
Keyframe capability is not exposed by GET /api/v1/models. The gate is a pair of model fields (supportsKeyframes / maxKeyframes) that the public models endpoint does not return, so you cannot discover from the API which models accept keyframes or how many. Send them on a model that does not support them and they are silently dropped along with source_fps / source_duration_seconds — verify with a small test generation before relying on them.
curl -X POST https://api.kolbo.ai/api/v1/generate/video-from-video \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "your-keyframe-capable-model-identifier",
"video_url": "https://media.kolbo.ai/videos/source.mp4",
"prompt": "Match the reference frames",
"source_fps": 24,
"source_duration_seconds": 8,
"keyframes": [
{ "image_url": "https://media.kolbo.ai/images/frame-a.png", "timestamp_seconds": 0 },
{ "image_url": "https://media.kolbo.ai/images/frame-b.png", "timestamp_seconds": 4.5 }
]
}'Model identifiers come from GET /api/v1/models?type=video_from_video. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.
JavaScript
const API_KEY = "kolbo_live_YOUR_API_KEY";
// Fetch available video-to-video models
async function initModels() {
const res = await fetch("https://api.kolbo.ai/api/v1/models?type=video_from_video", {
headers: { "X-API-Key": API_KEY },
});
const data = await res.json();
console.log("Available models:", data.models.map((m) => m.identifier));
}
async function main() {
await initModels();
const response = await fetch("https://api.kolbo.ai/api/v1/generate/video-from-video", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "your-model-identifier", // required — no auto-select on this endpoint
prompt: "Transform into anime style with vibrant colors",
video_url: "https://example.com/original-clip.mp4",
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"
BASE_URL = "https://api.kolbo.ai/api"
HEADERS = {"X-API-Key": API_KEY}
# Fetch available video-to-video models
models_res = requests.get(
f"{BASE_URL}/v1/models",
headers=HEADERS,
params={"type": "video_from_video"},
)
print("Available models:", [m["identifier"] for m in models_res.json()["models"]])
# --- Option A: Using URL ---
response = requests.post(
f"{BASE_URL}/v1/generate/video-from-video",
headers={**HEADERS, "Content-Type": "application/json"},
json={
"model": "your-model-identifier", # required — no auto-select on this endpoint
"prompt": "Transform into anime style with vibrant colors",
"video_url": "https://example.com/original-clip.mp4",
"aspect_ratio": "16:9",
},
)
data = response.json()
print("Generation ID:", data["generation_id"])
# --- Option B: Using file upload ---
# with open("original-clip.mp4", "rb") as vid:
# response = requests.post(
# f"{BASE_URL}/v1/generate/video-from-video",
# headers=HEADERS,
# files={"files": vid},
# data={
# "model": "your-model-identifier",
# "prompt": "Transform into anime style with vibrant colors",
# },
# )
# data = 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(data["poll_interval_hint"])
status = requests.get(
f"{BASE_URL}/v1/generate/{data['generation_id']}/status",
headers=HEADERS,
).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 listResponse
This endpoint is 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 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": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "video_from_video",
"model": "your-model-identifier",
"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 | Always "video_from_video" |
model | string | Echoes the identifier you sent — this endpoint has no Smart Select, so model is required |
credits_charged | null | Always null on this endpoint — the video-to-video 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": "68f2c1a9b4e5d6f7a8b9c0d1",
"type": "video_from_video",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://media.kolbo.ai/videos/.../output.mp4"],
"thumbnail_url": null,
"duration": 5,
"aspect_ratio": "16:9",
"prompt_used": "Transform into anime style with vibrant colors",
"model": "your-model-identifier",
"created_at": "2026-04-12T10:00:00.000Z"
},
"credits_used": 50,
"credits_breakdown": [
{ "model": "your-model-identifier", "amount": 50, "base": 50, "final": 50, "duration_multiplier": null, "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. There is no partial or streaming read.
result.thumbnail_url is usually null here — video-to-video has no source still to fall back on, so it is populated only when the pipeline generated a poster frame. result.duration is a number on this endpoint. Unlike the other video endpoints, video-to-video returns no creative-input keys (visual_dna, preset, cinematic_presets, moodboard) — the underlying record does not store them.
credits_used is the authoritative, multiplier-adjusted cost, not credits_charged from the submit response.
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.
Tips
- Pick the model first.
max_images,max_videos,max_elements,min_video_duration, andmax_video_durationon the model record describe which extra inputs the model takes and what source length it is built for. This endpoint does not enforce them — over-sending fails at the provider mid-generation instead of returning a clean400, so validate against the model record yourself before submitting. - Video-to-video generation typically takes several minutes depending on the model and duration.
- Be descriptive in your prompt — specify the target style, mood, and any visual changes you want applied.
- When using file uploads, the limit is 100 MB per file.
- Read the settled cost from
credits_usedon the completed status response — see Credits and Billing. - Use
poll_interval_hintfrom the initial response to set your polling interval. - Check
supported_durationsandsupported_aspect_ratioson each model via the Models endpoint before requesting specific values.
Related
Video Generation
Text-to-video and image-to-video
Video Editing and Trim
Upscale, reframe, extend, lipsync, inpaint, and frame-accurate trimming
Media Library
Upload source videos and keyframe images to Kolbo storage
Models and Pricing
Model catalog and the per-model input caps referenced above
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel