Elements Generation
Generate animated videos from reference images and prompts using the Kolbo API.
Generate a video from reference elements — images, videos, and/or an audio track — plus a text prompt. Use it to animate specific assets you already have ("animate this product", "put these three characters into one scene"). For text-only video use Video Generation.
Smart Select: Omit model (or send "auto" / "smart-select") and Kolbo routes the request to a model itself. Passing a specific identifier from GET /api/v1/models?type=elements gives you deterministic behaviour and lets you pre-validate its input caps.
Model identifiers are Kolbo-specific. Never hardcode model identifiers — always fetch the current list from GET /api/v1/models?type=elements first. Models may be added, renamed, or retired at any time.
All generation endpoints accept an optional
project_idbody field that routes the output into a specific project. See Projects.
Endpoint
POST /api/v1/generate/elementsRate limit: 30 accepted requests per minute per user, counted in a bucket shared with the other standard-limit generation endpoints (/v1/generate/video, /v1/generate/music, /v1/generate/speech, /v1/chat, and so on). Image generation (30/min) and transcription (100/min) have their own buckets. Rejected requests (any 4xx/5xx answer) do not consume the budget.
Request Body
Accepts application/json or multipart/form-data (when uploading files). File uploads and URLs may be combined in the same request.
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes* | Text description of the desired video. Must be a non-empty string — otherwise 400 INVALID_PROMPT. Also capped at the chosen model's max_prompt_length (400 INVALID_PROMPT, limit in the message). *Optional only for the Morphious (kolbo-morphious-*) and Genjutsu models, which build their own instruction, and for seedance-2-5-lipsync. |
model | string | No | Model identifier from GET /api/v1/models?type=elements. Must be a string, never an array (400 INVALID_MODEL). Omitted / "auto" / "smart-select" → Smart Select. An identifier that does not resolve to a known model returns 400 MODEL_NOT_FOUND. |
reference_images | array of strings | No | Public http(s) image URLs used as reference elements. Objects of the form { "url": "…" } / { "image_url": "…" } are also accepted; duplicates and non-http(s) entries are dropped. At most 64 entries (400 INVALID_IMAGE_URLS). Counts against the model's elements_max_images together with image files and Visual DNA images. |
reference_videos | array of strings | No | Public reference video URLs. Counts against elements_max_videos together with uploaded video files. Each clip must be at least the model's min_video_duration; a clip up to 1 s over max_video_duration (the per-clip input cap) is auto-trimmed, a longer one fails with VIDEO_TOO_LONG. |
reference_audio_urls | array of strings | No | Public reference audio URLs — multiple tracks. Merged (deduplicated) with audio_url and counted against elements_max_audio together with uploaded audio files. |
audio_url | string | No | One reference audio URL — the legacy single-track form of reference_audio_urls; both may be sent and are merged. Every referenced track is downloaded and normalised: shorter than min_audio_duration (default 1 s) is padded with trailing silence; longer than the output duration — or than the model's audio input cap, whichever is smaller — is auto-trimmed to that bound minus one frame rather than rejected. Non-MP3 audio is converted server-side. A URL that cannot be downloaded is non-fatal: the original URL is forwarded untouched. |
files | multipart | No | Reference media files — repeat the files field once per file. Images, videos and audio are told apart by each part's declared MIME type (curl derives it from the file extension), so send audio as audio/* and video as video/*. There is no file-count cap, but every file counts against the matching elements_max_* cap, and 200 MB is only the transport ceiling: each file must also fit the chosen model's max_file_size (default 52428800 = 50 MB when the model does not set one), otherwise 400 FILE_TOO_LARGE. Image files are merged with reference_images. Video files are converted to MP4 and trimmed to the per-clip video cap; audio files are duration-checked (AUDIO_TOO_SHORT / AUDIO_TOO_LONG, with a 1 s grace over the max) and converted to MP3. |
duration | integer | string | No | Output duration in seconds. Default 5. Must be a whole number from the model's min_video_duration (default 1) up to the largest value in supported_durations (falling back to max_video_duration, then 15); otherwise 400 INVALID_DURATION, with the allowed range spelled out in the error message. |
aspect_ratio | string | No | Default "16:9" (no default for kolbo-morphious-reframe, which needs an explicit target). "auto" or any positive W:H ratio parses (400 INVALID_ASPECT_RATIO otherwise); the chosen model must also support it, otherwise 400 UNSUPPORTED_ASPECT_RATIO. See supported_aspect_ratios / supported_aspect_ratios_by_type. |
motion | string | No | Free-text motion style / intensity hint. Stored on the generation record only — no elements provider currently reads it. |
preset_id | string | No | Video preset id (a 24-character ObjectId) from GET /api/v1/presets?type=video. An unknown or inactive id returns 400; a value that is not a valid ObjectId fails the preset lookup and returns 500. The chosen model must also advertise preset support, otherwise 400. |
enhance_prompt | boolean | No | Rewrite the prompt with the prompt enhancer. Default false on the API. true turns it on — in a multipart request send the string "true" (or "1"). |
visual_dna_ids | array of strings | No | Visual DNA ids. Send an array (a bare string id is ignored). 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. On models that do not take native elements, DNA stills fill the image slots left after your own images — at least one slot per DNA — and count against elements_max_images. When no ids are sent and the model supports Visual DNA, @Name mentions in the prompt are resolved to your DNAs automatically. |
keyframes | array of objects | No | Timeline-pinned keyframes for multi-keyframe models (supports_keyframes: true on the model). Each item is { "image_url": "…", "timestamp_seconds": 2.5 } (camelCase imageUrl / timestampSeconds also accepted); items missing either value are dropped. At most max_keyframes items (400). Timestamps are seconds on the output timeline: up to 1 s past duration is clamped, more is rejected (400). Ignored on models that do not support keyframes. Keyframes alone satisfy the visual-input requirement. |
multi_shots | boolean | No | Multi-shot toggle. On models with a native multi-shot switch it is forwarded to the provider. On other models (Seedance included) it only rewrites the prompt into shots when enhance_prompt is true, multi_shot_count is omitted or 2+, and your prompt has no shot structure of its own — otherwise the prompt is sent as written. |
multi_shot_count | integer | No | Explicit shot count for multi_shots. Omit to let the enhancer pick. |
resolution | string | No | Video resolution tier. Model-dependent — check supported_resolutions. Named tiers such as "480p-draft" (see draft_resolutions) select the draft variant and need an explicit model that has one. On Kling-family identifiers the value also selects the matching model variant, and a tier that family does not publish is rejected with 400 UNSUPPORTED_KLING_RESOLUTION. A named tier the chosen model does not offer returns 400 UNSUPPORTED_VIDEO_RESOLUTION. Higher tiers may multiply the credit cost — see Credit Multipliers. |
draft | boolean | No | Boolean alias for Draft mode. true forces the model's 480p-draft tier (whatever resolution says) and returns 400 when the chosen model has no draft variant; false maps a draft tier back to the regular resolution. The strings "true" / "false" are also accepted; any other value is 400. |
sound_enabled | boolean | No | Request synced audio on models that can emit it. Only meaningful when sound_generation_type is not "none"; sound_enabled_by_default is the model default and sound_credit_multiplier is the cost penalty when on. |
style_id | string | No | Morphious Styles only (kolbo-morphious-* models): a style id from GET /api/v1/morphious-styles (see Morphious Styles). |
project_id | string | No | Target project. Omit to use your auto-created "API Generations" project. Malformed id → 400 SDK_PROJECT_INVALID_ID; not found or no access → 404 SDK_PROJECT_NOT_FOUND; view-only access → 403 SDK_PROJECT_ACCESS_DENIED. |
session_id | string | No | Continue in an existing Elements session — pass the session_id from an earlier elements response. Wins over project_id (the generation lands in that session's project). Not an elements session → 404 SDK_SESSION_NOT_FOUND; malformed → 400 SDK_SESSION_INVALID_ID. |
session_name | string | No | Short sidebar title for the session (trimmed to 60 characters; sessionName also accepted). Skips automatic naming. |
At least one visual or reference input is required. The check is satisfied by reference_images, uploaded image or video files, reference_videos, reference_audio_urls / audio_url, keyframes, or visual_dna_ids whose DNA carries images. A request carrying only uploaded audio files, or no media at all, returns 400 NO_IMAGES_PROVIDED. (seedance-2-5-lipsync takes its media from the performance plan.) For a pure text-to-video prompt use POST /v1/generate/video.
Array fields in a multipart request. Repeat the field once per value for reference_images (a JSON-encoded string is not decoded and the list is silently lost; a single bare URL works). For reference_videos, reference_audio_urls and visual_dna_ids, send either a JSON-encoded array string or a repeated field — a single bare value that is not a JSON array is silently dropped. keyframes must be one JSON-encoded array string (repeated object fields are dropped). Numbers and booleans may be sent as strings: "5", "true" / "false" for multi_shots, enhance_prompt and sound_enabled ("false" or "0" turns sound off).
The per-model reference caps are enforced. Before anything is charged, the request is counted against the chosen model's elements_max_images, elements_max_videos and elements_max_audio (URLs + uploaded files + Visual DNA image slots). Over any cap returns 400 REFERENCE_MEDIA_LIMIT_EXCEEDED; the error message states the model's three caps. A model that publishes null for a cap accepts none of that kind. Every reference URL must be a public http(s) URL (400 INVALID_REFERENCE_MEDIA_URL), and each URL array is limited to 64 entries.
Numbered reference tags. You can point at an attachment from the prompt by its 1-based position — @Image 1, @Image 2 (images in submission order), @Video 1, @Audio 1. A tag with no matching attachment (for example @Image 3 with two images) fails up front with 400 MISSING_REFERENCE_MEDIA, whose error message names the missing tags.
Provider-asset fields (model-specific)
Some elements models (Gemini Omni family) run against assets that already live with the provider. These optional fields are merged on top of the ids Kolbo derives from visual_dna_ids and passed straight through — leave them unset unless you already hold provider asset ids.
| Field | Type | Description |
|---|---|---|
character_ids | array of strings | JSON string | Provider character asset ids. |
audio_ids | array of strings | JSON string | Provider audio asset ids. |
video_list | array of objects | JSON string | Source clips as { url, start, ends } — a clip list, not a flat URL list. |
Seedance 2.5 Lipsync (seedance-2-5-lipsync)
This model runs from a prepared performance plan rather than free references. It requires session_id (the Elements session that owns the plan — otherwise 400 PLAN_SESSION_REQUIRED) plus all of performance_plan_id, performance_plan_revision and quote_id (otherwise 400 PLAN_ADMISSION_FIELDS_REQUIRED). Plans are built with the /api/v1/lipsync-plans/{sessionId} routes (below). Sending plan fields with any other model returns 400 PLAN_MODEL_MISMATCH.
Performance plans
A performance plan describes who speaks or sings, which recorded audio drives each character, and the scene. The flow is create → prepare → quote → generate: each step takes the plan's current integer revision, and the quote you receive is what POST /generate/elements admits.
POST /api/v1/lipsync-plans/:sessionId/assets (multipart, field "file")
POST /api/v1/lipsync-plans/:sessionId create a plan
GET /api/v1/lipsync-plans/:sessionId/:planId read a plan
PATCH /api/v1/lipsync-plans/:sessionId/:planId replace the plan's fields
POST /api/v1/lipsync-plans/:sessionId/:planId/prepare { "revision": 1 }
POST /api/v1/lipsync-plans/:sessionId/:planId/quote { "revision": 1 }:sessionId is an Elements session you can use (the session_id returned by an earlier POST /generate/elements). Reading needs view access to that session; uploading and creating need create access; update, prepare and quote need edit access. These routes answer with { "status": true, "data": { ... } } on success and { "status": false, "code": "...", "message": "..." } on failure. Writes share the 30/min generation limiter; the read uses the 120/min media-library limiter.
Upload an asset. POST .../assets stores an audio, image or video file in your media library for this session and returns 201 with data: { assetId, filename, mediaType, size, url }. Audio and video files may be up to 100 MB, images up to 25 MB (413 MEDIA_FILE_TOO_LARGE); other file types return 415 UNSUPPORTED_MEDIA_TYPE. Any uploaded or generated media-library item you can access in the session's project can also be referenced by its id.
Create or update a plan (manual). The body fields are camelCase:
| Field | Type | Notes |
|---|---|---|
mode | string | Required. dialogue or music_video. |
scenePrompt | string | Up to 5,000 characters. Required before preparing. |
resolution | string | 480p (default), 720p or 1080p. |
aspectRatio | string | 16:9 (default), 9:16, 1:1, 4:3, 3:4 or 21:9. |
characters | array | Up to 3 { id, name, visualDnaId? }. id is your own short id (letters, digits, _, -, up to 80); name is 1–100 characters. |
stillAssetIds | array | Image asset ids used as still references. Character Visual DNAs and stills together are capped at 30. |
referenceVideoAssetIds | array | Up to 2 distinct video asset ids (TOO_MANY_REFERENCE_VIDEOS). Each must be 4–30 seconds and at most 100 MB. |
durationMs | integer | dialogue only. Whole seconds from 4,000 to 30,000. |
clips | array | dialogue only. Up to 12 { id, characterId, assetId, startMs, trimInMs, trimOutMs }: an audio asset, the source range to use (trimInMs–trimOutMs) and where it starts in the scene (startMs). Clips may not overlap (DIALOGUE_CLIP_OVERLAP) or run past durationMs (DIALOGUE_EXCEEDS_DURATION). |
syncPostProcess | boolean | dialogue only. Opt-in, default false. |
conversationAssetId, conversationTrimInMs, conversationTrimOutMs | string, integers | dialogue only. Optional imported conversation recording and its range. |
music | object | music_video only. { assetId, trimInMs, trimOutMs, characterId? }. The song section must be 4–30 seconds (INVALID_MUSIC_DURATION); characterId, if set, must be one of characters. |
A dialogue plan needs at least one character and one clip before it can be prepared. Invalid input returns 400 INVALID_LIPSYNC_PLAN with a message naming the problem. PATCH takes the same fields plus the current revision; it replaces the plan, resets it to draft and clears any preparation or quote.
Create a plan (automatic). Send "automatic": true with scenePrompt, attachments (media-library items as [{ "assetId": "...", "name": "optional label" }]: up to 12 audio files, 30 images and 2 videos; at least one audio file is required), optional visualDnaIds (up to 3 character DNAs), performanceMode (dialogue or music_video), durationMs, resolution, aspectRatio and syncPostProcess. Kolbo analyzes the audio and drafts the characters and clips for you. Each audio file must be 30 seconds or shorter, and together they must fit the 30-second scene (AUDIO_EXCEEDS_DURATION).
Prepare and quote. prepare analyzes the selected audio and returns data: { plan, analysis }; it is idempotent for an already prepared revision. quote returns data: { plan, quote } where quote is { quoteId, credits, currency, expiresAt, providerDurationSec, durationMs }. A quote expires after 10 minutes and admits exactly one generation. Only the user who prepared the plan can quote it (403 PLAN_PREPARER_MISMATCH).
Then call POST /api/v1/generate/elements with model: "seedance-2-5-lipsync", the same session_id, performance_plan_id, performance_plan_revision and quote_id.
| HTTP | code | When |
|---|---|---|
| 400 | INVALID_SESSION_ID / INVALID_PLAN_ID | Malformed path id |
| 403 | INSUFFICIENT_PERMISSION | No access to the Elements session |
| 404 | SESSION_NOT_FOUND / PLAN_NOT_FOUND / MEDIA_ASSET_NOT_FOUND / VISUAL_DNA_NOT_FOUND | Missing, or not shared with this session |
| 409 | PLAN_REVISION_CONFLICT | revision is not the plan's current revision. Read the plan again |
| 409 | PLAN_PREPARING / PLAN_NOT_PREPARED | Preparation is running, or quote was called before preparing |
| 409 | PLAN_QUOTE_ALREADY_USED / PLAN_QUOTE_EXPIRED | At generation: the quote was used or expired. Request a new quote |
| 503 | LIPSYNC_PRICING_UNAVAILABLE | Pricing is temporarily unavailable |
Examples
cURL with reference images (Smart Select):
curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Animate the product with a dynamic reveal and floating particles",
"reference_images": ["https://example.com/product-photo.jpg"],
"duration": 5,
"aspect_ratio": "16:9"
}'cURL with multiple reference types:
curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "@Image 1 is the character and @Image 2 is her jacket. She walks through the street from @Video 1, matching its camera move, while @Audio 1 plays as ambience and @Audio 2 as her footsteps.",
"model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
"reference_images": ["https://example.com/character.jpg", "https://example.com/jacket.jpg"],
"reference_videos": ["https://example.com/street-plate.mp4"],
"reference_audio_urls": ["https://example.com/street-ambience.mp3", "https://example.com/footsteps.mp3"],
"duration": 5,
"resolution": "1080p"
}'Check the chosen model's elements_max_images, elements_max_videos, elements_max_audio and supported_resolutions first — a request over any cap is rejected with 400 REFERENCE_MEDIA_LIMIT_EXCEEDED.
cURL with keyframes (only on models with supports_keyframes: true):
curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A flower opens from bud to full bloom",
"model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
"keyframes": [
{ "image_url": "https://example.com/bud.jpg", "timestamp_seconds": 0 },
{ "image_url": "https://example.com/bloom.jpg", "timestamp_seconds": 5 }
],
"duration": 5
}'cURL with file upload:
curl -X POST https://api.kolbo.ai/api/v1/generate/elements \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-F "prompt=Animate the product with a dynamic reveal" \
-F "[email protected]" \
-F "[email protected]" \
-F "reference_images=https://example.com/logo.png" \
-F 'reference_videos=["https://example.com/motion-ref.mp4"]' \
-F "duration=5" \
-F "aspect_ratio=16:9"In multipart, repeat files and reference_images once per item; reference_videos, reference_audio_urls, visual_dna_ids and keyframes take a JSON-encoded array string.
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=elements", {
// 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/elements", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: "Animate the product with a dynamic reveal and floating particles",
reference_images: ["https://example.com/product-photo.jpg"],
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"}
# Optional: fetch available models
# models = requests.get(
# "https://api.kolbo.ai/api/v1/models?type=elements",
# headers={"X-API-Key": API_KEY},
# ).json()
# print("Available models:", models)
response = requests.post(
"https://api.kolbo.ai/api/v1/generate/elements",
headers=HEADERS,
json={
"prompt": "Animate the product with a dynamic reveal and floating particles",
"reference_images": ["https://example.com/product-photo.jpg"],
"duration": 5,
"aspect_ratio": "16:9",
},
)
started = response.json()
if not started.get("success"):
raise Exception(started.get("error"))
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(started["poll_interval_hint"])
status = requests.get(
f"https://api.kolbo.ai/api/v1/generate/{started['generation_id']}/status",
headers={"X-API-Key": API_KEY},
).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 listWith Specific Model:
First, fetch available models:
curl https://api.kolbo.ai/api/v1/models?type=elements \
-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/elements \
-H "X-API-Key: kolbo_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Animate the product with a dynamic reveal and floating particles",
"reference_images": ["https://example.com/product-photo.jpg"],
"model": "MODEL_IDENTIFIER_FROM_MODELS_ENDPOINT",
"duration": 5,
"aspect_ratio": "16:9"
}'Model identifiers come from GET /api/v1/models?type=elements. Always fetch the latest list rather than hardcoding identifiers, as models may change over time.
Response
This endpoint is asynchronous and fire-and-forget: the POST answers 202 Accepted 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": "6820f1c2a1b2c3d4e5f60718",
"type": "elements",
"model": "auto",
"credits_charged": null,
"poll_url": "/v1/generate/6820f1c2a1b2c3d4e5f60718/status",
"poll_interval_hint": 8,
"session_id": "6820f1c2a1b2c3d4e5f60700",
"project_id": "6820f1c2a1b2c3d4e5f606ff"
}| Field | Type | Notes |
|---|---|---|
generation_id | string | Mongo ObjectId. This is what you poll and cancel with. |
type | string | Always "elements" |
model | string | Echoes "auto" for Smart Select requests, otherwise the identifier you sent |
credits_charged | null | Always null on this endpoint — the elements 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": "6820f1c2a1b2c3d4e5f60718",
"type": "elements",
"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": "Animate the product with a dynamic reveal and floating particles",
"model": "MODEL_IDENTIFIER",
"created_at": "2026-04-12T10:30:00.000Z"
},
"credits_used": 90,
"credits_breakdown": [
{ "model": "MODEL_IDENTIFIER", "amount": 90, "base": 60, "final": 90, "duration_multiplier": 1.5, "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. Every reference asset you sent lands in one finished clip — there is no partial read.
result.thumbnail_url falls back to the first reference image when the pipeline produced no poster frame. result.duration is a number when the pipeline measured it and a string when it falls back to the requested duration — coerce it. result.model is never "auto": it resolves to the identifier that actually ran. The visual_dna, preset and cinematic_presets keys are added only when you sent those inputs. result.reference_images lists the reference URLs the pipeline recorded, when it recorded any, result.model_name carries the display name of the model that ran when the record stores one (typically after Smart Select), and result.playback_urls (one entry per clip, null when the master already plays in every browser) appears when a browser-friendly preview copy exists — urls always stays the master file. credits_used is the authoritative, multiplier-adjusted cost.
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.
Errors
These are returned synchronously by the POST, as { "success": false, "error": "…", "code": "…" } — read limits and offending values from the error message; only INSUFFICIENT_CREDITS and 429 add extra fields. An error without its own code arrives as GENERATION_ERROR.
| Status | Code | Cause |
|---|---|---|
| 400 | INVALID_PROMPT | prompt missing, blank, not a string, or longer than the model's max_prompt_length |
| 400 | INVALID_MODEL | model sent as an array/object |
| 400 | MODEL_NOT_FOUND | model is a string the catalog does not know; the message suggests near matches |
| 400 | NO_IMAGES_PROVIDED | No image, video, audio, keyframe or Visual DNA input at all (see the callout above) |
| 400 | INVALID_IMAGE_URLS | reference_images holds more than 64 entries |
| 400 | INVALID_REFERENCE_MEDIA_URLS | reference_videos / reference_audio_urls entries are not strings |
| 400 | INVALID_REFERENCE_MEDIA_URL | A reference URL is not a public http(s) URL (private / internal hosts are refused) |
| 400 | REFERENCE_MEDIA_LIMIT_EXCEEDED | More images, videos or audio than the model's elements_max_* caps (or more than 64 URLs in one array) |
| 400 | MISSING_REFERENCE_MEDIA | The prompt tags @Image N / @Video N / @Audio N with no matching attachment |
| 400 | INVALID_DURATION | duration not a whole number, or outside the model's output range; the range is in the error message |
| 400 | INVALID_ASPECT_RATIO | aspect_ratio is neither "auto" nor a positive W:H ratio |
| 400 | UNSUPPORTED_ASPECT_RATIO | The chosen model does not support aspect_ratio |
| 400 | UNSUPPORTED_KLING_RESOLUTION | resolution is not one of the tiers the chosen Kling model family publishes |
| 400 | UNSUPPORTED_VIDEO_RESOLUTION | A named tier such as 480p-draft on a model (or Smart Select) without that variant |
| 400 | VIDEO_TOO_SHORT / VIDEO_TOO_LONG | Seedance-family models measure reference videos up front: a clip below min_video_duration, or more than 1 s over the per-clip cap |
| 400 | REFERENCE_VIDEO_PROBE_FAILED | A Seedance-family reference video could not be read |
| 400 | FILE_TOO_LARGE | An uploaded file exceeds the model's max_file_size (default 50 MB) |
| 400 | PLAN_SESSION_REQUIRED / PLAN_ADMISSION_FIELDS_REQUIRED / PLAN_MODEL_MISMATCH | seedance-2-5-lipsync plan rules (see above). PLAN_SESSION_REQUIRED arrives as { "status": false, "code": "…", "message": "…" } |
| 400 | SDK_PROJECT_INVALID_ID / SDK_SESSION_INVALID_ID | Malformed project_id / session_id |
| 400 | GENERATION_ERROR | preset_id is unknown or inactive |
| 400 | GENERATION_ERROR | The chosen model does not support video presets |
| 400 | GENERATION_ERROR | A visual_dna_ids entry does not exist or you do not have access to it |
| 400 | GENERATION_ERROR | A keyframes entry is invalid (missing URL / timestamp, more than max_keyframes, or a timestamp more than 1 s past duration) |
| 400 | INVALID_DRAFT_MODE / UNSUPPORTED_DRAFT_MODE | draft is not a boolean, or draft: true on a model without a draft variant |
| 403 | INSUFFICIENT_CREDITS | Balance too low — checked before the generation starts; body carries balance, required and pricing_url |
| 403 | STORAGE_LIMIT_EXCEEDED | Your media storage is full |
| 403 | SDK_PROJECT_ACCESS_DENIED | You only have view access to project_id (or the session's project) |
| 404 | SDK_PROJECT_NOT_FOUND / SDK_SESSION_NOT_FOUND | project_id / session_id not found, not accessible, or not an elements session |
| 429 | — | More than 30 accepted requests in one minute in the shared generation bucket; body carries limit, window_seconds and retryAfter |
| 500 | GENERATION_ERROR | preset_id is not a valid 24-character ObjectId — the preset lookup throws |
Errors after the job is accepted
Uploaded files and reference URLs are downloaded and processed after the 202. A problem there does not change the POST answer — the generation ends in state: "failed" with the message in error, and the credit hold is released. Typical causes:
| Code | Cause |
|---|---|
AUDIO_TOO_SHORT | An uploaded audio file below min_audio_duration (short audio URLs are padded with silence instead) |
AUDIO_TOO_LONG | An uploaded audio file more than 1 s above the model's audio input cap (audio passed by URL is trimmed instead) |
AUDIO_PROCESSING_FAILED | An uploaded audio file could not be decoded or converted to MP3 |
VIDEO_CONVERSION_FAILED | An uploaded non-MP4 video file could not be converted |
VIDEO_TOO_SHORT / VIDEO_TOO_LONG | A reference_videos clip below min_video_duration, or more than 1 s over the per-clip cap |
FILE_PROCESSING_FAILED | An uploaded image file could not be processed |
IMAGE_URL_ERROR | A reference_images URL could not be downloaded or processed |
Tips
- Elements generation typically takes 1-5 minutes depending on the model and duration.
- Reference images may be supplied as URLs (
reference_images), as multipartfiles, or as a combination of both — they are merged and counted together. - URLs are re-routed by extension: a
.mp4/.mov/.webm/.avi/.mkv/.m4vURL sent inreference_imagesis moved to the video slot, and a.mp3/.wav/.aac/.m4a/.flac/.oggURL is moved to the audio slot. Re-routed URLs then count against that slot's cap. - There is no cap on the number of uploaded files, but every file counts against its
elements_max_*cap and must fit the chosen model'smax_file_size(default 50 MB). 200 MB is only the hard transport ceiling. - Credit cost depends on the model, duration, resolution tier, and whether sound is enabled; models that publish
video_input_creditalso bill reference-video seconds — see Models and Pricing. - Use
poll_interval_hintfrom the initial response to set your polling interval. - Before generating, read
elements_max_images,elements_max_videos,elements_max_audio,supported_durations,min_video_duration,max_video_duration,min_audio_duration,max_audio_duration,max_file_size,max_prompt_length,supports_keyframes/max_keyframes,supported_aspect_ratios, andsupported_resolutionsoff the chosen model viaGET /api/v1/models?type=elements.
Related
Video Generation
Text-to-video and image-to-video, when you are not animating reference assets
First & Last Frame
Interpolate a video between two fixed frames
Visual DNA
Character, style, and product consistency across generations
Models and Pricing
Model catalog, input caps, and credit multipliers
Polling and Cancellation
The state machine, the reference poll loop, failures and cancel