Kolbo.AIKolbo.AI Docs
Developer API

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_id body field that routes the output into a specific project. See Projects.

Endpoint

POST /api/v1/generate/elements

Rate 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.

FieldTypeRequiredDescription
promptstringYes*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.
modelstringNoModel 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_imagesarray of stringsNoPublic 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_videosarray of stringsNoPublic 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_urlsarray of stringsNoPublic reference audio URLs — multiple tracks. Merged (deduplicated) with audio_url and counted against elements_max_audio together with uploaded audio files.
audio_urlstringNoOne 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.
filesmultipartNoReference 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.
durationinteger | stringNoOutput 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_ratiostringNoDefault "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.
motionstringNoFree-text motion style / intensity hint. Stored on the generation record only — no elements provider currently reads it.
preset_idstringNoVideo 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_promptbooleanNoRewrite 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_idsarray of stringsNoVisual 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.
keyframesarray of objectsNoTimeline-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_shotsbooleanNoMulti-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_countintegerNoExplicit shot count for multi_shots. Omit to let the enhancer pick.
resolutionstringNoVideo 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.
draftbooleanNoBoolean 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_enabledbooleanNoRequest 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_idstringNoMorphious Styles only (kolbo-morphious-* models): a style id from GET /api/v1/morphious-styles (see Morphious Styles).
project_idstringNoTarget 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_idstringNoContinue 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_namestringNoShort 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.

FieldTypeDescription
character_idsarray of strings | JSON stringProvider character asset ids.
audio_idsarray of strings | JSON stringProvider audio asset ids.
video_listarray of objects | JSON stringSource 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:

FieldTypeNotes
modestringRequired. dialogue or music_video.
scenePromptstringUp to 5,000 characters. Required before preparing.
resolutionstring480p (default), 720p or 1080p.
aspectRatiostring16:9 (default), 9:16, 1:1, 4:3, 3:4 or 21:9.
charactersarrayUp to 3 { id, name, visualDnaId? }. id is your own short id (letters, digits, _, -, up to 80); name is 1–100 characters.
stillAssetIdsarrayImage asset ids used as still references. Character Visual DNAs and stills together are capped at 30.
referenceVideoAssetIdsarrayUp to 2 distinct video asset ids (TOO_MANY_REFERENCE_VIDEOS). Each must be 4–30 seconds and at most 100 MB.
durationMsintegerdialogue only. Whole seconds from 4,000 to 30,000.
clipsarraydialogue 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).
syncPostProcessbooleandialogue only. Opt-in, default false.
conversationAssetId, conversationTrimInMs, conversationTrimOutMsstring, integersdialogue only. Optional imported conversation recording and its range.
musicobjectmusic_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.

HTTPcodeWhen
400INVALID_SESSION_ID / INVALID_PLAN_IDMalformed path id
403INSUFFICIENT_PERMISSIONNo access to the Elements session
404SESSION_NOT_FOUND / PLAN_NOT_FOUND / MEDIA_ASSET_NOT_FOUND / VISUAL_DNA_NOT_FOUNDMissing, or not shared with this session
409PLAN_REVISION_CONFLICTrevision is not the plan's current revision. Read the plan again
409PLAN_PREPARING / PLAN_NOT_PREPAREDPreparation is running, or quote was called before preparing
409PLAN_QUOTE_ALREADY_USED / PLAN_QUOTE_EXPIREDAt generation: the quote was used or expired. Request a new quote
503LIPSYNC_PRICING_UNAVAILABLEPricing 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 list

With 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"
}
FieldTypeNotes
generation_idstringMongo ObjectId. This is what you poll and cancel with.
typestringAlways "elements"
modelstringEchoes "auto" for Smart Select requests, otherwise the identifier you sent
credits_chargednullAlways null on this endpoint — the elements pipeline reports no 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 here
session_id / project_idstringWhere 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.

StatusCodeCause
400INVALID_PROMPTprompt missing, blank, not a string, or longer than the model's max_prompt_length
400INVALID_MODELmodel sent as an array/object
400MODEL_NOT_FOUNDmodel is a string the catalog does not know; the message suggests near matches
400NO_IMAGES_PROVIDEDNo image, video, audio, keyframe or Visual DNA input at all (see the callout above)
400INVALID_IMAGE_URLSreference_images holds more than 64 entries
400INVALID_REFERENCE_MEDIA_URLSreference_videos / reference_audio_urls entries are not strings
400INVALID_REFERENCE_MEDIA_URLA reference URL is not a public http(s) URL (private / internal hosts are refused)
400REFERENCE_MEDIA_LIMIT_EXCEEDEDMore images, videos or audio than the model's elements_max_* caps (or more than 64 URLs in one array)
400MISSING_REFERENCE_MEDIAThe prompt tags @Image N / @Video N / @Audio N with no matching attachment
400INVALID_DURATIONduration not a whole number, or outside the model's output range; the range is in the error message
400INVALID_ASPECT_RATIOaspect_ratio is neither "auto" nor a positive W:H ratio
400UNSUPPORTED_ASPECT_RATIOThe chosen model does not support aspect_ratio
400UNSUPPORTED_KLING_RESOLUTIONresolution is not one of the tiers the chosen Kling model family publishes
400UNSUPPORTED_VIDEO_RESOLUTIONA named tier such as 480p-draft on a model (or Smart Select) without that variant
400VIDEO_TOO_SHORT / VIDEO_TOO_LONGSeedance-family models measure reference videos up front: a clip below min_video_duration, or more than 1 s over the per-clip cap
400REFERENCE_VIDEO_PROBE_FAILEDA Seedance-family reference video could not be read
400FILE_TOO_LARGEAn uploaded file exceeds the model's max_file_size (default 50 MB)
400PLAN_SESSION_REQUIRED / PLAN_ADMISSION_FIELDS_REQUIRED / PLAN_MODEL_MISMATCHseedance-2-5-lipsync plan rules (see above). PLAN_SESSION_REQUIRED arrives as { "status": false, "code": "…", "message": "…" }
400SDK_PROJECT_INVALID_ID / SDK_SESSION_INVALID_IDMalformed project_id / session_id
400GENERATION_ERRORpreset_id is unknown or inactive
400GENERATION_ERRORThe chosen model does not support video presets
400GENERATION_ERRORA visual_dna_ids entry does not exist or you do not have access to it
400GENERATION_ERRORA keyframes entry is invalid (missing URL / timestamp, more than max_keyframes, or a timestamp more than 1 s past duration)
400INVALID_DRAFT_MODE / UNSUPPORTED_DRAFT_MODEdraft is not a boolean, or draft: true on a model without a draft variant
403INSUFFICIENT_CREDITSBalance too low — checked before the generation starts; body carries balance, required and pricing_url
403STORAGE_LIMIT_EXCEEDEDYour media storage is full
403SDK_PROJECT_ACCESS_DENIEDYou only have view access to project_id (or the session's project)
404SDK_PROJECT_NOT_FOUND / SDK_SESSION_NOT_FOUNDproject_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
500GENERATION_ERRORpreset_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:

CodeCause
AUDIO_TOO_SHORTAn uploaded audio file below min_audio_duration (short audio URLs are padded with silence instead)
AUDIO_TOO_LONGAn uploaded audio file more than 1 s above the model's audio input cap (audio passed by URL is trimmed instead)
AUDIO_PROCESSING_FAILEDAn uploaded audio file could not be decoded or converted to MP3
VIDEO_CONVERSION_FAILEDAn uploaded non-MP4 video file could not be converted
VIDEO_TOO_SHORT / VIDEO_TOO_LONGA reference_videos clip below min_video_duration, or more than 1 s over the per-clip cap
FILE_PROCESSING_FAILEDAn uploaded image file could not be processed
IMAGE_URL_ERRORA 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 multipart files, or as a combination of both — they are merged and counted together.
  • URLs are re-routed by extension: a .mp4/.mov/.webm/.avi/.mkv/.m4v URL sent in reference_images is moved to the video slot, and a .mp3/.wav/.aac/.m4a/.flac/.ogg URL 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's max_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_credit also bill reference-video seconds — see Models and Pricing.
  • Use poll_interval_hint from 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, and supported_resolutions off the chosen model via GET /api/v1/models?type=elements.