First & Last Frame
Generate videos by interpolating between a first and last frame image using the Kolbo API.
Generate videos by providing a first and last frame image. The AI interpolates between the two frames, creating smooth animated transitions.
Smart Select (recommended): Omit the model field and Kolbo automatically picks the best model for your input. This is the default and recommended approach for most use cases.
Model identifiers are Kolbo-specific. Never hardcode model identifiers — always fetch the current list from GET /api/v1/models?type=first_last_frame first. Models may be added, renamed, or retired at any time.
Endpoint
POST /api/v1/generate/first-last-frameRequest Body
Accepts application/json or multipart/form-data (when uploading files).
You must provide frames in one of two ways — do not mix them:
- URL mode: Provide
first_frame_urlandlast_frame_urlas JSON fields. - File mode: Upload exactly 2 image files via multipart
filesfield (first frame, then last frame). Max 10 MB total.
| Field | Type | Required | Description |
|---|---|---|---|
first_frame_url | string | Conditional | URL of the first frame image (required if not uploading files) |
last_frame_url | string | Conditional | URL of the last frame image (required if not uploading files) |
files | multipart | Conditional | Exactly 2 image files: first frame then last frame (required if not using URLs) |
prompt | string | No | Describe the transition between frames |
model | string | No | Model identifier from GET /api/v1/models?type=first_last_frame (default: auto-select) |
duration | number | No | Duration in seconds (default: 5) |
aspect_ratio | string | No | "16:9", "9:16", "1:1" (default: "16:9") |
enhance_prompt | boolean | No | Enhance prompt (default: true) |
visual_dna_ids | array | No | Visual DNA IDs for consistency (max 3) |
Do not mix URLs and files. Provide either both first_frame_url and last_frame_url, OR exactly two multipart files. Mixing will return a 400 error.
Examples
URL Mode
cURL (Smart Select — recommended):
curl -X POST https://api.kolbo.ai/api/v1/generate/first-last-frame \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_frame_url": "https://example.com/sunrise.jpg",
"last_frame_url": "https://example.com/sunset.jpg",
"prompt": "Smooth time-lapse transition from sunrise to sunset over a cityscape",
"duration": 5,
"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=first_last_frame", {
// 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/first-last-frame", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
first_frame_url: "https://example.com/sunrise.jpg",
last_frame_url: "https://example.com/sunset.jpg",
prompt: "Smooth time-lapse transition from sunrise to sunset over a cityscape",
duration: 5,
aspect_ratio: "16:9"
})
});
const data = await response.json();
console.log("Generation ID:", data.generation_id);
console.log("Poll URL:", data.poll_url);
// Poll for completion
const pollForResult = async (generationId) => {
while (true) {
await new Promise((r) => setTimeout(r, data.poll_interval_hint * 1000));
const status = await fetch(
`https://api.kolbo.ai/api/v1/generate/${generationId}/status`,
{ headers: { "X-API-Key": API_KEY } }
).then((r) => r.json());
console.log("State:", status.state, "Progress:", status.progress);
if (status.state === "completed") {
console.log("Video URL:", status.result.urls[0]);
return status;
}
if (status.state === "failed") {
console.error("Generation failed:", status.error);
return status;
}
}
};
await pollForResult(data.generation_id);
}
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=first_last_frame",
# headers={"X-API-Key": API_KEY},
# ).json()
# print("Available models:", models)
response = requests.post(
"https://api.kolbo.ai/api/v1/generate/first-last-frame",
headers=HEADERS,
json={
"first_frame_url": "https://example.com/sunrise.jpg",
"last_frame_url": "https://example.com/sunset.jpg",
"prompt": "Smooth time-lapse transition from sunrise to sunset over a cityscape",
"duration": 5,
"aspect_ratio": "16:9",
},
)
data = response.json()
print("Generation ID:", data["generation_id"])
print("Poll URL:", data["poll_url"])
# Poll for completion
generation_id = data["generation_id"]
poll_interval = data.get("poll_interval_hint", 8)
while True:
time.sleep(poll_interval)
status = requests.get(
f"https://api.kolbo.ai/api/v1/generate/{generation_id}/status",
headers={"X-API-Key": API_KEY},
).json()
print(f"State: {status['state']} Progress: {status.get('progress', 0)}%")
if status["state"] == "completed":
print("Video URL:", status["result"]["urls"][0])
break
if status["state"] == "failed":
print("Error:", status.get("error"))
breakFile Mode
cURL with file uploads:
curl -X POST https://api.kolbo.ai/api/v1/generate/first-last-frame \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-F "[email protected]" \
-F "[email protected]" \
-F "prompt=Smooth transition between the two frames" \
-F "duration=5" \
-F "aspect_ratio=16:9"Python with file uploads:
import requests
import time
API_KEY = "kolbo_live_YOUR_API_KEY"
files = [
("files", ("first-frame.jpg", open("first-frame.jpg", "rb"), "image/jpeg")),
("files", ("last-frame.jpg", open("last-frame.jpg", "rb"), "image/jpeg")),
]
response = requests.post(
"https://api.kolbo.ai/api/v1/generate/first-last-frame",
headers={"X-API-Key": API_KEY},
files=files,
data={
"prompt": "Smooth transition between the two frames",
"duration": "5",
"aspect_ratio": "16:9",
},
)
data = response.json()
print("Generation ID:", data["generation_id"])
# Poll for completion
generation_id = data["generation_id"]
poll_interval = data.get("poll_interval_hint", 8)
while True:
time.sleep(poll_interval)
status = requests.get(
f"https://api.kolbo.ai/api/v1/generate/{generation_id}/status",
headers={"X-API-Key": API_KEY},
).json()
print(f"State: {status['state']} Progress: {status.get('progress', 0)}%")
if status["state"] == "completed":
print("Video URL:", status["result"]["urls"][0])
break
if status["state"] == "failed":
print("Error:", status.get("error"))
breakWith Specific Model
First, fetch available models:
curl https://api.kolbo.ai/api/v1/models?type=first_last_frame \
-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/first-last-frame \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"first_frame_url": "https://example.com/sunrise.jpg",
"last_frame_url": "https://example.com/sunset.jpg",
"prompt": "Smooth time-lapse transition from sunrise to sunset",
"model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
"duration": 5
}'Model identifiers come from GET /api/v1/models?type=first_last_frame. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.
Response
Initial Response
{
"success": true,
"generation_id": "flf123",
"type": "first_last_frame",
"model": "auto",
"credits_charged": 50,
"poll_url": "/api/v1/generate/flf123/status",
"poll_interval_hint": 8
}Completed Status
{
"success": true,
"generation_id": "flf123",
"type": "first_last_frame",
"state": "completed",
"progress": 100,
"result": {
"urls": ["https://cdn.kolbo.ai/videos/..."],
"thumbnail_url": "https://cdn.kolbo.ai/thumbs/...",
"duration": 5,
"aspect_ratio": "16:9",
"prompt_used": "Smooth time-lapse transition from sunrise to sunset over a cityscape",
"model": "auto",
"created_at": "2026-04-12T10:30:00.000Z"
}
}Tips
- Use Smart Select (the default). Omit the
modelfield and Kolbo picks the best model for your input. This is the simplest and most future-proof approach. - First & Last Frame generation typically takes 1-5 minutes depending on the model and duration.
- Both frames should have the same aspect ratio for best results. The API may crop or pad mismatched frames.
- The
promptfield is optional but recommended — it guides the interpolation style (e.g., "smooth camera pan", "dramatic zoom", "time-lapse"). - File uploads are limited to 10 MB total (both files combined).
- Credits are charged per second:
model.credit x duration. - 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.