AI film lab for filmmakers: generate and review images/clips with input provenance, cost preflight
Maintain this server? Claim it
Call this server's tools from your browser. Nothing to install.
Requests go through mcptry because browsers can't call most MCP servers directly. Header values are sent to the server for that request only and are never stored or logged.
Tool list from our last check. Connect live to refresh it.
Prompt check of ONE generated image or clip: does the result show what its prompt asked for? The backend first DESCRIBES the result without seeing the prompt, then splits the prompt into checkable claims (counts as digits, quoted text, identity, place, framing, style; for clips the motion) and scores each. verdict: pass | mismatch | unverified, plus `checks`, `issues` and the neutral `beschreibung`. Runs automatically after every generation (lab_job_status shows it as `abgleich`); call this to read it, or with neu=true to check again. On mismatch: offer at most ONE retry per original, lead the new prompt with the failed claim; never re-run a video on your own. Free for the account (FilmLab carries the cost).
Asset id (positive integer as string)
What kind of asset
Discard an existing result and check again (ignored within 60 s of the last check)
This tool isn't marked read-only, so it may change something on the server or in an account you connected.
Tool list last changed Oct 26, 2026.
Prompt check of ONE generated image or clip: does the result show what its prompt asked for? The backend first DESCRIBES the result without seeing the prompt, then splits the prompt into checkable claims (counts as digits, quoted text, identity, place, framing, style; for clips the motion) and scores each. verdict: pass | mismatch | unverified, plus `checks`, `issues` and the neutral `beschreibung`. Runs automatically after every generation (lab_job_status shows it as `abgleich`); call this to read it, or with neu=true to check again. On mismatch: offer at most ONE retry per original, lead the new prompt with the failed claim; never re-run a video on your own. Free for the account (FilmLab carries the cost).
| Argument | Type | Description |
|---|---|---|
| id* | string | Asset id (positive integer as string) |
| neu | boolean | Discard an existing result and check again (ignored within 60 s of the last check) |
| reference_type* | string | What kind of asset |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"reference_type",
"id"
],
"properties": {
"id": {
"type": "string",
"description": "Asset id (positive integer as string)"
},
"neu": {
"type": "boolean",
"description": "Discard an existing result and check again (ignored within 60 s of the last check)"
},
"reference_type": {
"enum": [
"image",
"video"
],
"type": "string",
"description": "What kind of asset"
}
},
"additionalProperties": false
}Append a shot to a sequence's timeline. Call it once per shot, in cutting order — `start_time` is computed from the clips already there, so appending in order is enough. The continuity pass reads exactly this timeline: a shot missing here is a shot nobody checks. Returns the new clip's index and the sequence's total duration.
| Argument | Type | Description |
|---|---|---|
| text | string | Text content for type 'title', 'credits' or 'lower_third' |
| type* | string | What this clip is. 'image' references a generated image via source_id; 'title', 'credits' and 'lower_third' carry `text` instead. 'lower_third' is an OVERLAY: appended without start_time it is laid over the clip before it (track V2), takes no time of its own and never runs past that clip's end; metadata.versatz_s delays it inside that clip. As the very first clip it is a card on black. 'video' is where it gets sharp — read the source_id note before using it |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| metadata | object | JSON stored alongside the clip. The continuity pass reads video_id (a generated video's id — it checks the start image), image_url (a frame URL), location_id and scene_num from here; for a 'lower_third', versatz_s (seconds into the clip it sits on) sets when it appears; other keys are stored as-is |
| source_id | string | For type 'image': the id of a generated image (from lab_generate). For type 'video': source_id is IGNORED by the continuity pass (a video id used to be misread as an unrelated image, see vault/followups/2026-08-04-continuity-loest-source-id-blind-auf.md). Give a video clip its frame via metadata instead: metadata.video_id = the generated video's id (the pass checks the video's start image), or metadata.image_url = a frame URL. Without either, the shot is counted as 'not checked' (summary.ohne_frame). |
Bring your own video, audio or image into a project — not generated, yours. Two ways: `media_url` (a public http(s) address; the server fetches it, max 100 MB) or `file_name` + `content_type` (you get a signed `upload_url` and the `header` that are part of its signature: PUT the file's raw bytes there within the hour with EXACTLY those headers — the answer's `anleitung` has the ready curl line; a missing header is a 403). Either way the answer is the `asset_key` — use it in lab_timeline_apply as clips[].asset_key (video) or bett.asset_key (audio). Exception: in projects that check images, an uploaded image is not in the project after the PUT — the answer then carries `abschluss_noetig` and an `abschluss_key`; after the PUT call lab_add_media again with the same project_id, art, herkunft and `abschluss_key` only. That call checks the image and returns the `asset_key`, or says why it was refused. Free. Internal addresses are refused.
| Argument | Type | Description |
|---|---|---|
| art* | string | What the file is |
| herkunft* | string | Required, no default: 'eigen' = shot, recorded or made by people (no AI generation); 'ki' = generated or altered with AI. Decides the EU AI Act Art. 50 disclosure of every export that uses this file. If the file itself carries an AI mark (C2PA or IPTC source type), it stays AI material whatever you declare, and the answer says so. |
| file_name | string | Name of a local file to upload, with extension. Give this OR media_url |
| media_url | string | Public http(s) URL of the file. Give this OR file_name |
| project_id* |
Read a photo and extract location fields from it (Gemini Vision, costs credits — exact price via get_cost: true). Returns the fields — it does NOT create a board; feed the result into lab_create_location yourself. Note the payload: the image travels as base64 inside the call, so keep it small (a downscaled JPEG, not a full-resolution capture).
| Argument | Type | Description |
|---|---|---|
| get_cost | boolean | Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run. |
| mime_type | string | Defaults to 'image/jpeg' |
| image_base64 | string | The image as base64. A data-URL prefix ('data:image/jpeg;base64,…') is stripped by the backend |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run."
},
"mime_type": {
"type": "string",
"description": "Defaults to 'image/jpeg'"
},
"image_base64": {
"type": "string",
"minLength": 1,
"description": "The image as base64. A data-URL prefix ('data:image/jpeg;base64,…') is stripped by the backend"
}
},
"additionalProperties": false
}Animate a completed image into a clip with a video model. Pick the model from lab_list_models; check the price with get_cost or let the user confirm a card via lab_propose. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| seed | integer | Seed for a repeatable run. Omit it and the project's stored seed applies (POST /api/lab/projects/{id}/einstellungen), and without that the provider picks one. The seed that actually rendered comes back in lab_job_status as `seed` with `seed_quelle` ('provider' = read from the provider's answer, 'gesetzt' = the one sent, null = the model tells no seed). Models whose endpoint has no seed field do not drop a given seed silently: the run proceeds and the row carries `seed_hinweis: seed_nicht_unterstuetzt` — some models tell their seed but take none, others take one but tell none. |
| tool | string | Video model id — copy it verbatim from lab_list_models (kind: video). Omit to let the intent router choose. Every model has its own resolutions, duration grid, audio behaviour and price; the catalog is the only place those live, this text deliberately names none. |
| shots | array | Multi-shot: a sequence of shots rendered as ONE output, holding character, place and voice across the cuts. This is NOT the same as several clips stitched together — those you pay for individually and they do not hold the cast. PRICE: the billed length is the SUM of the shot durations, not duration_seconds. Three 5s shots cost the same as one 15s clip (140 credits at the reference resolution), not the same as one 5s clip (47). The sum must fit the provider's 3-15s window — each shot may be legal on its own and the sum still too long. Only kling_3_pro. Requesting it elsewhere is rejected before quota is spent. |
| in_sec | number |
Animate MANY images into videos in ONE call (max 25 jobs). Prefer this over looping lab_animate: it validates every job BEFORE spending any quota, so an invalid job 12 of 15 does not leave 11 paid-for generations running. Quota is spent once, collectively (one ledger row); refunds stay per-clip, so a partial failure refunds only that clip. Returns a batch_id — poll lab_batch_status with it instead of polling each id separately. Every job's prompt goes through the Dreamer first: without valid dreamer_signatur on ALL jobs, nothing is generated or charged; the batch returns each dreamer_fassung and signature for human approval (at most four Dreamer calls at once). Repeat with the approved versions and signatures. `get_cost: true` preflights the whole batch without generating anything.
| Argument | Type | Description |
|---|---|---|
| jobs* | array | One entry per clip, max 25. All jobs are validated before any quota is spent |
| get_cost | boolean | Preflight: return the summed price in credits for the WHOLE batch and your balance WITHOUT generating, reading the same catalog as lab_list_models. For a card the user confirms, use lab_propose instead. |
| project_id* | string | Project id (from lab_list_projects) |
| ohne_keyframe | boolean | Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check. |
Compute the sequence's clip durations from the project's cutting preset and — when the tempo allows it — pull the cut points onto the measured beat grid. WRITES the timeline; costs nothing. The old duration of every changed clip is kept in `clip.metadata.dauer_vorher`, so one undo step exists (a second differing run overwrites it — there is no history). Clip count is never changed: no clip is added or removed. SNAPPING IS NOT GUARANTEED, AND A REFUSAL TO SNAP IS A RESULT, NOT A FAILURE — read `gerastet` and `raster.grund` before you repeat anything: 'tempo_unverlaesslich' = measured below the confidence threshold (deliberate; pass trotz_unsicherem_tempo to override), 'nicht_gemessen' = run lab_beatgrid first, 'keine_musikspur'/'musikspur_nicht_aufgeloest' = run lab_bind_audio first, 'rhythmus_ohne_raster' = the preset's rhythm is 'frei' or 'atmend', which means not snapping IS the promise ('auf den Beat', 'auf die Songstruktur' and 'gegen den Takt' are the three that snap), 'toleranz_null' = you passed toleranz_s 0. In every one of those cases the durations are still recomputed from the preset and written — that alone is the difference between 40 and 6 cuts over 30 seconds. Also read `raster.getroffen` of `raster.von`: `gerastet: true` only says snapping ran; this pair says whether it landed. And `geklemmt` lists clips the renderer's own duration clamp moved back OFF the grid.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| toleranz_s | number | How far a cut point may travel to land on a beat, in seconds (default 0.12 — three frames at 25 fps). 0 disables snapping entirely and only recomputes the durations |
| trotz_unsicherem_tempo | boolean | Snap onto a grid whose own measurement reports `verlaesslich: false`. Default false, and that default is the honest one: below the threshold the detector has not confirmed it found the pulse. Set it only as a deliberate decision — the answer then carries raster.unsicheres_tempo_uebergangen: true |
Break one scene down into a STRUCTURED shot list of 2, 4 or 6 shots — each with shot size, camera angle, purpose and its own ready prompt, plus one shared floor plan (anchor, axis, light) so the shots cut together. Two families: an 'aufloesung' shows ONE moment from several angles (coverage, dialog, orbit, pov, zwischenschnitte for cutaways …); a 'montage' lets time pass between shots (ursache_wirkung, zeitverlauf, kleiner_bogen …). An unknown art is rejected with the list of known ones. Costs no credits: it is one text-model call and renders nothing; generate the shots afterwards with lab_generate / lab_animate, one prompt each. This is NOT lab_shots_ableiten — that one groups clips that already exist on a timeline; this one plans shots that do not exist yet. By default nothing is written. uebernehmen=true appends the shots as scenes to the project's shot list (mod_shotlist), which the image and video steps read — ask before you set it.
| Argument | Type | Description |
|---|---|---|
| art | string | Which breakdown. Default coverage. Known: coverage, dialog, winkel, hoehen, orbit, pov, naehe, gesicht, haende, reaktionen, zwischenschnitte (aufloesung); ursache_wirkung, entdeckung, zeitverlauf, gefuehlswende, kleiner_bogen (montage). |
| laenge | integer | Number of shots: 2, 4 or 6. Default 4. Never odd — a breakdown runs in pairs. |
| prompt* | string | The scene or the master prompt to break down |
| scene_ref | string | Optional scene reference the shots are filed under when uebernehmen=true |
| project_id* | integer | The project the scene belongs to |
Auto run with a hard spending cap: the person approves ONE run with an upper limit in credits; inside it you may generate without asking per item, but never above the cap. aktion: anlegen (project_id + deckel_credits → status 'angelegt', nothing can be spent yet) · freigeben (ONLY after the person explicitly approved the cap) · stand (cap, reserved, free, items) · beenden. While a run is active, EVERY paid call in that project reserves its price against the cap — whichever tool makes it; reservations are never refunded (a failed item keeps its share, a retry reserves again), upscales and exports count too. The cap counts credits: runs on the person's OWN provider key (BYOK, 0 credits) are not counted. Other projects are not affected. When the cap would be crossed the backend answers budget_exceeded: stop, report what finished, what is open and the minimum budget still needed. Under budget pressure lower the resolution first, never cut runtime, never drop character or location variants.
| Argument | Type | Description |
|---|---|---|
| aktion* | string | What to do |
| lauf_id | string | freigeben / stand / beenden: the id from anlegen |
| project_id* | string | Project id (from lab_list_projects) |
| deckel_credits | integer | anlegen only: the cap in credits. Name it to the person in credits AND in the price list's terms before freigeben |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"aktion",
"project_id"
],
"properties": {
"aktion": {
"enum": [
"anlegen",
"freigeben",
"stand",
"beenden"
],
"type": "string",
"description": "What to do"
},
"lauf_id": {
"type": "string",
"description": "freigeben / stand / beenden: the id from anlegen"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"deckel_credits": {
"type": "integer",
"maximum": 100000,
"minimum": 1,
"description": "anlegen only: the cap in credits. Name it to the person in credits AND in the price list's terms before freigeben"
}
},
"additionalProperties": false
}Auto-generate a storyboard from either script OR situationen (mutually exclusive). An LLM splits script into scenes; situationen bypasses that LLM entirely and generates exactly one frame per entry, ignoring max_scenes. situationen requires figur_id of an existing character sheet with a reference image: every frame uses that character on seedream_5_pro, without creating a new sheet. format selects 16:9 (default) or 9:16 for every model. bewegung_en is saved in each frame’s metadata for later animation. COSTS CREDITS — every scene is one generated image, charged up front for the number of scenes the script is split into (at most max_scenes). Ask the human before running it. CHARACTERS ARE NOT GUESSED: every character who appears in more than one frame first gets a reference image (an existing project character with a reference is reused for free; a new one is created and its reference sheet is charged separately as character_sheet_generate), and every frame showing such a character is generated WITH those references on seedream_5_pro instead of `model`. Frames without a recurring character use `model`. The answer lists `figuren` (name, status, scenes), `figuren_kosten_geschaetzt` and `kosten_je_bild`; prices per model come from lab_list_models. If a reference cannot be made, the frames that need it fail and are refunded — they are never generated without it. The scene split is SYNCHRONOUS and can take up to ~55s and then time out (504) for long scripts; run it in the app if so. The answer lists the new image ids; the frames themselves render in the background as pending images — poll them with lab_job_status (type 'image').
| Argument | Type | Description |
|---|---|---|
| model | string | Image model for the frames. Default krea_2_medium. Prices per model: lab_list_models. |
| format | string | Frame aspect ratio for every model, default 16:9 |
| script | string | Screenplay or scene description; provide either script or situationen. The backend splits script into scenes; each scene becomes one image. |
Aggregated progress of a video batch started by lab_animate_batch. Returns {total, completed, failed, pending, done, items[]} — poll this ONCE per batch instead of calling lab_job_status per clip. `done: true` means nothing is pending any more (some items may still have failed). Each item carries retry_count (an automatic second attempt happened) and fallback_from (the provider was swapped after a content-policy rejection).
| Argument | Type | Description |
|---|---|---|
| batch_id* | string | The batch_id returned by lab_animate_batch (format: vb_<hex>) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"batch_id"
],
"properties": {
"batch_id": {
"type": "string",
"minLength": 1,
"description": "The batch_id returned by lab_animate_batch (format: vb_<hex>)"
}
},
"additionalProperties": false
}Turn the still-image clips of a sequence into video clips that exactly fill their beat-snapped slots. Free — this tool itself generates nothing. aktion plan: one job per image clip, ready for lab_animate_batch — start image, the frame's motion (bewegung_en), and the SMALLEST duration the model can deliver that still covers the clip's timeline duration (a shorter video would end early and shift every later cut off the beat). Refuses with 409 if a start image is not accepted yet. Pass the returned `jobs` unchanged to lab_animate_batch (get_cost first, then run). aktion einsetzen: once the videos are done, each finished video replaces its image clip — same duration, source in-point 0. Repeatable; reports what is still open and any video shorter than its slot (left as still). Run lab_beatgrid and lab_apply_rhythm on the sequence BEFORE plan, so the durations are the beat durations.
| Argument | Type | Description |
|---|---|---|
| tool | string | plan only: video model id from lab_list_models — default vidu_q3_turbo (1-second grid) |
| aktion* | string | plan: build the animate jobs; einsetzen: put finished videos into the timeline |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| resolution | string | plan only: resolution tier from lab_list_models — default 720p |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"aktion"
],
"properties": {
"tool": {
"type": "string",
"description": "plan only: video model id from lab_list_models — default vidu_q3_turbo (1-second grid)"
},
"aktion": {
"enum": [
"plan",
"einsetzen"
],
"type": "string",
"description": "plan: build the animate jobs; einsetzen: put finished videos into the timeline"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"resolution": {
"type": "string",
"description": "plan only: resolution tier from lab_list_models — default 720p"
}
},
"additionalProperties": false
}Measure the TEMPO of the sequence's bound music track and store the beat grid — opens the real file with ffmpeg and analyses it, no guessing and no provider, so it costs nothing but a few seconds. Writes `bpm` and `metadata.beat_grid` on the audio row; it does NOT touch the timeline (`timeline_geaendert: false`). Deterministic and cached: a second call returns the same numbers without downloading again (pass `neu_messen` to force). READ `confidence` AND `verlaesslich` BEFORE YOU CUT. Below the threshold (0.35) lab_apply_rhythm computes durations but deliberately does NOT snap them to the grid — that is a decision, not a failure. Soft material measures badly: a real swing track came out at 0.164, an ordered hard pulse (techno, 128 bpm) at 0.621. Half and double tempo are the classic misreads, so `bpm_alternativen` is part of the answer. `frame_konflikt` reports the collision between beat grid and frame grid, for BOTH renderers: `export` (24 fps, the delivered MP4) and `murnau` (25 fps, the editor timeline). Read `frames_pro_schlag` as a DEVIATION, not a count: it is how far one beat sits from a whole frame, between 0 and 0.5. 0 means beat and frame coincide (100, 125 and 150 bpm at 25 fps do); anything above means beat-accurate and frame-accurate cannot both hold, and the rounding error accumulates across clips — measured on a finished MP4 at 128 bpm: up to 64.7 ms off the audible beat although every cut point had snapped. Shown here, not fixed: that decision belongs to a human. Refusals that name their own remedy: 409 without a bound music track (run lab_bind_audio), 409 when the track is pending (poll lab_list_audio), 502/504 when the audio host is unreachable or slow, 422 when the file carries no usable audio, 503 when ffmpeg is missing.
| Argument | Type | Description |
|---|---|---|
| takt | integer | Beats per bar (default 4). INPUT, not detection — time-signature detection is explicitly out of scope. It only affects where the downbeats fall, which is what the rhythm 'auf die Songstruktur' snaps to |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
See a clip at EXACT seconds: up to 12 frames, in the order you list them, tiled into one image and shown. Use it to check what is on screen where you plan a cut, whether a motion has ended, or whether a face drifted between two moments — lab_contact_sheet spreads frames evenly and cannot answer 'what is at 3.2 s'. A second past the clip's end is rejected by name, not clamped. Works on generated clips (video_id) AND on your own uploaded footage (project_id + asset_key from lab_add_media) — look at it before you cut it. Free (no credits).
| Argument | Type | Description |
|---|---|---|
| preview | boolean | Show the image inline (default true) |
| sekunden* | array | Seconds into the clip, e.g. [0.5, 3.2, 3.24]. Order is kept: left to right, then the next row (4 per row) |
| video_id | string | Generated video id (from lab_animate/lab_job_status) — positive integer as string. Give this OR asset_key |
| asset_key | string | Storage key of uploaded footage (from lab_add_media / lab_list_assets). Needs project_id |
| project_id | string | Project the asset_key belongs to — required with asset_key |
| kachel_breite | integer | Width of one frame in px (default 480) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"sekunden"
],
"properties": {
"preview": {
"type": "boolean",
"description": "Show the image inline (default true)"
},
"sekunden": {
"type": "array",
"items": {
"type": "number",
"minimum": 0
},
"maxItems": 12,
"minItems": 1,
"description": "Seconds into the clip, e.g. [0.5, 3.2, 3.24]. Order is kept: left to right, then the next row (4 per row)"
},
"video_id": {
"type": "string",
"description": "Generated video id (from lab_animate/lab_job_status) — positive integer as string. Give this OR asset_key"
},
"asset_key": {
"type": "string",
"description": "Storage key of uploaded footage (from lab_add_media / lab_list_assets). Needs project_id"
},
"project_id": {
"type": "string",
"description": "Project the asset_key belongs to — required with asset_key"
},
"kachel_breite": {
"type": "integer",
"maximum": 960,
"minimum": 240,
"description": "Width of one frame in px (default 480)"
}
},
"additionalProperties": false
}Bind the project's generated audio to a sequence's tracks — ONE call that does both halves of the job, because doing only the first half silently produces a silent film. Half one (`POST …/soundmix`) creates the four empty tracks (dialog, music, sfx, atmo); half two (`POST …/soundmix/resolve-stems`) is what actually attaches the audio rows to them. On 2026-08-28 that split cost a complete silent export: the mix looked configured, and every track was empty. From here it is one tool, and it is idempotent — an existing mix is NOT re-initialised (that would throw away volumes, effects and the other tracks' bindings), only re-resolved. Per track it takes the audio row marked `selected`, otherwise the newest one with a url. Rows still pending have no url and stay unbound. That choice is re-made on every call: generate a second music track and run this again, and the music track follows the NEWER row — unless one row is pinned with `selected`, which is set in the web UI. With `audio_ids` the named rows are bound and PINNED, and every track you do NOT name is pinned EMPTY (so an old narrator or sfx row of the project cannot slip into the mix); later automatic resolves, e.g. before a render, keep all of that. A call WITHOUT `audio_ids` releases every pin and resolves as described above. Changes state, costs nothing. Next: lab_beatgrid.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| audio_ids | object | Bind EXACTLY these rows instead of the newest/selected one per track, as { music: '12', dialog: '13' }. Named tracks are pinned to these rows, tracks you leave out are pinned empty. Each row must belong to this project, match the track's audio type and have a file. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"audio_ids": {
"type": "object",
"description": "Bind EXACTLY these rows instead of the newest/selected one per track, as { music: '12', dialog: '13' }. Named tracks are pinned to these rows, tracks you leave out are pinned empty. Each row must belong to this project, match the track's audio type and have a file.",
"propertyNames": {
"enum": [
"dialog",
"music",
"sfx",
"atmo"
],
"type": "string"
},
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "number"
}
]
}
}
},
"additionalProperties": false
}See the CUT at exact seconds of the finished film: up to 12 frames, tiled into one image and shown. Each second is mapped like the export maps it — which clip is on screen, where in its source (src_in_s counts), the export's framing (scaled and letterboxed), title cards drawn. A video whose source is shorter than its clip duration ends early in the export, and everything after it moves forward; this tool follows that, so you see the film that will be rendered. The answer names per frame the clip (position, clip_id), clip start and offset; a clip that would render black says so ('schwarz'). NOT in the image: lens/grade, lower thirds, the AI badge — that needs lab_start_export. Use it after lab_timeline_apply to check cut points before exporting. Free (no credits).
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
| preview | boolean | Show the image inline (default true) |
| profile | string | Export profile key (default 'web'); a 9:16 profile gives portrait frames — see lab_export_profiles |
| sekunden* | array | Seconds into the finished film, e.g. [0.5, 4.9, 5.1]. Order is kept: left to right, then the next row (4 per row) |
| kachel_breite | integer | Width of one frame in px (default 480) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"sekunden"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
},
"preview": {
"type": "boolean",
"description": "Show the image inline (default true)"
},
"profile": {
"type": "string",
"description": "Export profile key (default 'web'); a 9:16 profile gives portrait frames — see lab_export_profiles"
},
"sekunden": {
"type": "array",
"items": {
"type": "number",
"minimum": 0
},
"maxItems": 12,
"minItems": 1,
"description": "Seconds into the finished film, e.g. [0.5, 4.9, 5.1]. Order is kept: left to right, then the next row (4 per row)"
},
"kachel_breite": {
"type": "integer",
"maximum": 960,
"minimum": 240,
"description": "Width of one frame in px (default 480)"
}
},
"additionalProperties": false
}Create character sheets for the people found in a project's material — the writing counterpart to lab_cast_gaps. Creates at most 6 per call and binds each to the project. Costs an LLM run per call. Run lab_cast_gaps first if you want to see what it would create. The sheets it creates have NO reference image: each one still needs lab_generate_character_reference before lab_generate accepts it as `character_refs`. Fail-open: no script or no AI key returns an EMPTY `created` list with a `note`, not an error.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}Find people who appear in a project's material (script / cast module) but have NO character sheet yet. Returns up to 6 `missing` entries, each with a name and a short hint from the text. It creates nothing — lab_cast_autopopulate does that. Costs an LLM run, so it is credit-gated. Fail-open by design: no script, no AI key, or an unparseable answer all come back as an EMPTY list (with a `note` saying which) — an empty list is the correct answer, not an error.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}Build a contact sheet for a generated clip — N frames spread evenly over its runtime, tiled into ONE image — and show it. This is how a clip gets judged at all: MCP has no video content type, so without a sheet a clip is just a URL and a promise. It is also the fastest way to SEE camera drift: measured 2026-08-04, the wander in one clip was a PSNR number until frames sat side by side. The sheet is cached on the video row — a second call returns the same URL without re-rendering (pass force to redo it). Rendering downloads the clip and runs ffmpeg, so the first call takes a few seconds.
| Argument | Type | Description |
|---|---|---|
| cols | integer | Grid columns (default 3) |
| rows | integer | Grid rows (default 3). cols x rows = number of frames |
| force | boolean | Re-render even if a sheet already exists |
| width | integer | Total sheet width in px (default 1280) |
| preview | boolean | Attach the sheet itself, downscaled to 512px, instead of only its URL. Default true — showing it is the whole point; pass false to get just the URL. |
| video_id* | string | Generated video id (from lab_animate/lab_job_status) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"video_id"
],
"properties": {
"cols": {
"type": "integer",
"maximum": 6,
"minimum": 1,
"description": "Grid columns (default 3)"
},
"rows": {
"type": "integer",
"maximum": 6,
"minimum": 1,
"description": "Grid rows (default 3). cols x rows = number of frames"
},
"force": {
"type": "boolean",
"description": "Re-render even if a sheet already exists"
},
"width": {
"type": "integer",
"maximum": 2560,
"minimum": 320,
"description": "Total sheet width in px (default 1280)"
},
"preview": {
"type": "boolean",
"description": "Attach the sheet itself, downscaled to 512px, instead of only its URL. Default true — showing it is the whole point; pass false to get just the URL."
},
"video_id": {
"type": "string",
"description": "Generated video id (from lab_animate/lab_job_status) — positive integer as string"
}
},
"additionalProperties": false
}Start the continuity pass over a sequence — every shot is compared against its PREDECESSOR and, with check_references, against the character and location reference boards. This is the pass that catches drift (the flat that changed between two shots, the cardigan that turned into a coat), which is the actual enemy in AI film, not the cut. Runs in the BACKGROUND: returns a `job_id` with HTTP 202 — poll it with lab_get_job until status is done|failed, the findings sit in the job result. Two refusals worth knowing before you spend time: an empty timeline is rejected with 400 (add shots with lab_add_clip first), and the backend answers 503 if GEMINI_API_KEY is not configured.
| Argument | Type | Description |
|---|---|---|
| model | string | Vision model for the comparison. Omit it — the backend resolves its default through the model catalogue, so it follows a model retirement on its own. A literal passed here does not, and that is how a dead model id stays wired in |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| check_references | boolean | Also check every shot against the character and location reference boards, not just against its predecessor. Backend default is true; pass false for a cheaper neighbour-only pass |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"model": {
"type": "string",
"description": "Vision model for the comparison. Omit it — the backend resolves its default through the model catalogue, so it follows a model retirement on its own. A literal passed here does not, and that is how a dead model id stays wired in"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"check_references": {
"type": "boolean",
"description": "Also check every shot against the character and location reference boards, not just against its predecessor. Backend default is true; pass false for a cheaper neighbour-only pass"
}
},
"additionalProperties": false
}Create a character sheet for a human, animal or creature in the cast register. The backend derives an English `prompt_prefix_en` from the fields you give — that prefix is what later describes this character in every image, so the richer the description, the more stable the identity. This is step 1 of 2: a fresh sheet has NO reference image and is therefore not yet usable as `character_refs`. Run lab_generate_character_reference on the returned id to finish it. Free — it writes a row, it burns no credits.
| Argument | Type | Description |
|---|---|---|
| age | integer | Age in years, as a number |
| eyes | string | Eye colour and expression |
| hair | string | Hair colour, length, cut |
| name* | string | Character name, e.g. 'Maren' — the only required field |
| skin | string | Skin tone and texture |
| build | string | Body build and posture |
| gender | string | e.g. 'female', 'male', 'non-binary' |
| gattung | string | Character kind: human, animal or creature. Omitted or empty means mensch. For tier/kreatur, begin the description with the species and its distinctive features. |
Create a location board. The backend derives an English `prompt_prefix_en` from the fields you give — that prefix is what later anchors every image and clip shot there, so the richer the description, the more consistent the place. Pass project_id to bind the board to a project (recommended: unbound boards are invisible to lab_list_project_locations).
| Argument | Type | Description |
|---|---|---|
| name* | string | Board name, e.g. 'Wohnung Hanna — Balkon' |
| props | string | Objects that must be present |
| colors | string | Dominant palette |
| atmosphere | string | Mood, weather, sound of the place |
| project_id | string | Bind the board to this project (from lab_list_projects) — positive integer as string |
| description | string | What the place is, in prose |
| time_of_day | string | e.g. 'morning', 'blue hour', 'night' |
| architecture | string | Building style, era, materials |
| project_name | string | Free-text project label shown in listings |
Create a new FilmLab project. Returns the new project id and its `datenmodus`. Optionally set a title and the data mode: 'eu' keeps the project on models whose data path is proven inside the EU (lab_list_models eu: true lists them; other models are refused with alternatives), 'frei' allows every model. Omit to get the account default.
| Argument | Type | Description |
|---|---|---|
| title | string | Project title (optional) |
| datenmodus | string | Data mode of the project: 'eu' (only EU-allowed models) or 'frei' (all models). Omit for the default |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"title": {
"type": "string",
"description": "Project title (optional)"
},
"datenmodus": {
"enum": [
"eu",
"frei"
],
"type": "string",
"description": "Data mode of the project: 'eu' (only EU-allowed models) or 'frei' (all models). Omit for the default"
}
},
"additionalProperties": false
}Create a named prop (Requisite) in this project. Free, no model call: its prompt prefix is built deterministically from the fields. Returns its @prop_ tag for shot prompts. To generate views use lab_prop_ansichten separately (costs credits).
| Argument | Type | Description |
|---|---|---|
| name* | string | |
| epoche | string | |
| farben | string | |
| groesse | string | |
| zustand | string | |
| material | string | |
| project_id* | string | |
| beschreibung | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"name"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"epoche": {
"type": "string"
},
"farben": {
"type": "string"
},
"groesse": {
"type": "string"
},
"zustand": {
"type": "string"
},
"material": {
"type": "string"
},
"project_id": {
"type": "string"
},
"beschreibung": {
"type": "string"
}
},
"additionalProperties": false
}Create a sequence (Stage 9 timeline) inside a project — START HERE to turn generated shots into something the continuity pass can read. The new sequence is EMPTY: add shots with lab_add_clip before anything else works. lab_continuity_check rejects an empty timeline with 400. Returns the sequence record including its `id`.
| Argument | Type | Description |
|---|---|---|
| title | string | Sequence title. Defaults to 'Main Sequence' on the backend |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"title": {
"type": "string",
"description": "Sequence title. Defaults to 'Main Sequence' on the backend"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}Create a style kit in this project: rules (English text), palette (#RRGGBB) and up to three reference images taken from this project's own images (image ids). Free, no model call. Set als_standard to make it the project default, which then runs in every image and clip of the project (switch it off per call with ohne_stil_kit).
| Argument | Type | Description |
|---|---|---|
| name* | string | |
| regeln | string | Style rules in English, e.g. 'cold moonlight, deep blue shadows, film grain' |
| palette | array | Up to 8 colours as #RRGGBB |
| bild_ids | array | Up to 3 image ids of THIS project |
| project_id* | string | |
| als_standard | boolean | Make this kit the project default |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"name"
],
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"regeln": {
"type": "string",
"description": "Style rules in English, e.g. 'cold moonlight, deep blue shadows, film grain'"
},
"palette": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 8,
"description": "Up to 8 colours as #RRGGBB"
},
"bild_ids": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 3,
"description": "Up to 3 image ids of THIS project"
},
"project_id": {
"type": "string"
},
"als_standard": {
"type": "boolean",
"description": "Make this kit the project default"
}
},
"additionalProperties": false
}DELETE a location board permanently. There is no undo and no soft-archive — the row is removed. Images and clips that were generated with this `location_id` keep their already-rendered prompt, but the board they point at is gone: nothing can be re-anchored to it, and lab_get_location will 404. Confirm with the user before calling this. Use lab_restyle_location if you only want the place to look different.
| Argument | Type | Description |
|---|---|---|
| loc_id* | string | Location board id to delete permanently (from lab_list_locations) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"loc_id"
],
"properties": {
"loc_id": {
"type": "string",
"description": "Location board id to delete permanently (from lab_list_locations)"
}
},
"additionalProperties": false
}Create or read a depth map for a project video in FilmLab: use it for fog, depth of field, relighting or occlusion. Near is white, like Resolve's Depth Map. Free on our own CPU, no credits; processing takes minutes. modell: v2 (default) is Depth Anything V2, sharpest edges. vda is Video Depth Anything: steadier over time (no brightness pumping across the shot, slightly less flicker), softer edges, about 2x slower than v2 (on our server ~0.5 s per frame for v2, ~1.1 s for vda), only for 16:9 landscape or 9:16 portrait videos (16:10 and 1.85:1 are fine, 4:3 and 2.39:1 are not). Each modell keeps its own map; always pass the same modell when reading again (engine in the answer says which one). Reuses the existing map or running job by default; neu: true starts a new job. Returns status and job_id; when done, tiefe_url is FFV1 gray16 and vorschau_url is a playable H.264 grayscale preview, both signed for download without a browser session. wait_s waits at most 15 seconds (default 0). If still running, call this tool again with the same project_id, video_id and modell, neu: false. Once done, use resolve_ergebnis_an_timeline with vorschau_url to put the preview as a matte above the original clip in Resolve.
| Argument | Type | Description |
|---|---|---|
| neu | boolean | Start a new depth job instead of reusing the existing map or running job (default false) |
| modell | string | Depth engine: v2 (default, sharpest) or vda (steady over time, 16:9 or 9:16 only) |
| wait_s | number | Wait up to this many seconds for completion (default 0, maximum 15) |
| video_id* | integer | Completed video id belonging to this project |
| nur_lesen |
Is everything wired? Backend reachability and latency, identity, credit check, catalog age, last error of this account, host widget support. Traffic light plus one sentence per point. Read-only, free.
| Argument | Type | Description |
|---|---|---|
| problem_report | boolean | Return the same findings as one block of text to paste into a bug report. This covers the MCP side only — server version, backend health, last failed job of this account. The Resolve bridge log lives on the user's machine and cannot be read from here: have them press ‚Problem-Report kopieren‘ in the bridge panel for bridge version, Resolve build, Python and the last 200 log lines. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"problem_report": {
"type": "boolean",
"description": "Return the same findings as one block of text to paste into a bug report. This covers the MCP side only — server version, backend health, last failed job of this account. The Resolve bridge log lives on the user's machine and cannot be read from here: have them press ‚Problem-Report kopieren‘ in the bridge panel for bridge version, Resolve build, Python and the last 200 log lines."
}
},
"additionalProperties": false
}Build a dossier of a project's takes: one ZIP with an offline index.html (no external resources) plus the media files, named Project_SceneNN_YYYYMMDD-Tnn.ext. modus 'kunde' is for a client and NEVER shows seed or costs, whatever the switches say; 'intern' shows everything that helps to rebuild a take. Both modes carry the AI disclosure (EU AI Act Art. 50) on the page and in the image files. Free (no credits). Runs as a job — poll lab_get_dossier.
| Argument | Type | Description |
|---|---|---|
| modus | string | kunde (default) or intern |
| auswahl | string | gewaehlt (default): the selected take per scene, plus every take without a scene (e.g. a series) that is not rejected; alle: every take that is not rejected. Stars/favourites do not count as a selection. |
| bild_ids | array | Exactly these image ids (overrides auswahl) |
| schalter | object | Switches: prompt, referenzen, aufloesung_dauer, datum, modell, seed, kosten, audio, mini_proxies (480p video proxies). Unknown keys are ignored. |
| video_ids | array | Exactly these video ids (overrides auswahl) |
| project_id* | string | Project id |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"modus": {
"enum": [
"kunde",
"intern"
],
"type": "string",
"description": "kunde (default) or intern"
},
"auswahl": {
"enum": [
"gewaehlt",
"alle"
],
"type": "string",
"description": "gewaehlt (default): the selected take per scene, plus every take without a scene (e.g. a series) that is not rejected; alle: every take that is not rejected. Stars/favourites do not count as a selection."
},
"bild_ids": {
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
"maxItems": 120,
"description": "Exactly these image ids (overrides auswahl)"
},
"schalter": {
"type": "object",
"description": "Switches: prompt, referenzen, aufloesung_dauer, datum, modell, seed, kosten, audio, mini_proxies (480p video proxies). Unknown keys are ignored.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "boolean"
}
},
"video_ids": {
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
"maxItems": 120,
"description": "Exactly these video ids (overrides auswahl)"
},
"project_id": {
"type": "string",
"description": "Project id"
}
},
"additionalProperties": false
}Dub a sequence into another language: either a ready audio track (en_audio_url — e.g. the one lab_dub_tts just produced) OR a cloned voice reading a line of text (voice_id + dub_text), then a controlled visual lipsync runs over the sequence's own footage (ROI-crop + composite for a long static talking-head shot, or the full-frame veed_v2 path for multi-shot sequences and shots where the mouth is partly hidden). ASYNCHRONOUS: returns a job_id immediately: poll lab_job_status until it completes. Needs a cloned voice first — lab_voice_clone makes one, lab_list_voices shows what a project already has.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| backend | string | Lipsync engine. Omit to use the backend default (replicate — ROI-crop, best for one long static talking-head shot). veed_v2 renders the full frame instead and is the pick for multi-shot sequences or a partly hidden mouth. 'inhouse' shares replicate's ROI-crop path. |
| dub_text | string | The line(s) this cloned voice should read, in the target `language`. Required together with voice_id. |
| get_cost | boolean | `true` reports the price and your balance WITHOUT starting a dub and without spending quota. The number is computed by the same function that later charges you, from THIS sequence: a sound rate per video clip, plus the chosen lipsync engine's own rate over the sequence's running time, plus a surcharge for the cloned-voice reading. `preisbasis` in the answer breaks it into those parts, so you can see which switch moves which share — turning the lipsync pass off removes its whole item. Not drawn from the lab_list_models catalog (that covers image and video models only; the lipsync engine is chosen with `backend` here, and dub has no catalog entry). |
Generate the EN-dub audio track for a sequence via ElevenLabs TTS — SYNCHRONOUS, returns a ready audio_url immediately, no job_id and nothing to poll. Optionally pads the track with silence up to the sequence's own length so a shorter reading doesn't shorten the final cut. Feed the resulting audio_url into lab_dub's en_audio_url to lipsync the sequence to this track.
| Argument | Type | Description |
|---|---|---|
| text* | string | The line(s) to speak. Kept short — very long text is rejected by the backend before anything is spent. |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| get_cost | boolean | `true` reports the price and your balance WITHOUT generating anything and without spending quota. This one is billed flat, one sound rate per run — the length of your text does not change it. Not drawn from the lab_list_models catalog (that covers image and video models only): the TTS voice is picked with `voice_id`, and the price does not follow the voice. |
| voice_id | string | An ElevenLabs voice id, from lab_dub_voices. Omit to use the backend's configured default voice — NOT a lab_cloned_voices id. |
| pad_to_video | boolean | Pad the generated track with silence up to the sequence's own duration if the reading comes out shorter. Omit to use the backend default (on). |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"text"
],
"properties": {
"text": {
"type": "string",
"minLength": 1,
"description": "The line(s) to speak. Kept short — very long text is rejected by the backend before anything is spent."
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"get_cost": {
"type": "boolean",
"description": "`true` reports the price and your balance WITHOUT generating anything and without spending quota. This one is billed flat, one sound rate per run — the length of your text does not change it. Not drawn from the lab_list_models catalog (that covers image and video models only): the TTS voice is picked with `voice_id`, and the price does not follow the voice."
},
"voice_id": {
"type": "string",
"description": "An ElevenLabs voice id, from lab_dub_voices. Omit to use the backend's configured default voice — NOT a lab_cloned_voices id."
},
"pad_to_video": {
"type": "boolean",
"description": "Pad the generated track with silence up to the sequence's own duration if the reading comes out shorter. Omit to use the backend default (on)."
}
},
"additionalProperties": false
}List the ElevenLabs TTS voices available for lab_dub_tts's `voice_id` — this is the EN-dub-track text-to-speech voice list, NOT a project's cloned voices (those are lab_list_voices). Reads only — no state changes, no credits. If ELEVENLABS_API_KEY isn't configured on the backend the list comes back empty and `configured` is false.
No arguments.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {},
"additionalProperties": false
}Append context to a board's `prompt_prefix_en` (plain string concatenation server-side — no AI call, no quota). Idempotent: context already present in the prefix is not duplicated. This does NOT rewrite or regenerate the prefix — for a board whose prefix is empty, too thin or a raw field dump, use lab_restyle_location (it rewrites prompt_prefix_en with an AI call, costs credits — exact price via get_cost: true) instead of recreating the board.
| Argument | Type | Description |
|---|---|---|
| loc_id* | string | Location board id (from lab_list_locations) |
| context* | string | What to add or sharpen, e.g. 'late 1970s West Berlin, nicotine-stained walls' |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"loc_id",
"context"
],
"properties": {
"loc_id": {
"type": "string",
"description": "Location board id (from lab_list_locations)"
},
"context": {
"type": "string",
"maxLength": 500,
"minLength": 3,
"description": "What to add or sharpen, e.g. 'late 1970s West Berlin, nicotine-stained walls'"
}
},
"additionalProperties": false
}List the available export delivery profiles (web, social_vertical, social_youtube, streaming_hdr, cinema, master, alpha_prores4444, alpha_png_sequence) and quality presets (draft, standard, high). Call this before lab_start_export to pick a valid profile/quality. The two alpha profiles keep transparency (ProRes 4444, or one RGBA PNG per frame delivered as a zip) and are REFUSED unless the sequence actually carries an alpha source — a cut-out layer element, or a Switch X transform whose alpha track was kept.
No arguments.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {},
"additionalProperties": false
}Use when the person asks about earlier states of a cut, wants to go back to a previous version, compare versions, name a version, or create a second cut (e.g. a short version next to the long one). The history and the versions of ONE cut (sequence). Every timeline write (lab_timeline_apply, the editor, the Resolve bridge) first saves the state it replaces; this tool reads those saved states and works with them. Costs nothing. aktion 'liste' (default): the saved states, newest first, with `marke` (a name a person gave a state), plus where this sequence was branched from (`ursprung`, with what changed since the branch point) and which versions were branched from it (`abgezweigt`). 'markieren': give a state a name (`marke`); a named state is never cleaned up — states without a name are capped per sequence. Empty `marke` removes the name. 'vergleichen': what changed since a state — against the current cut, or against another state (`gegen`), clip by clip. 'zurueckholen': write a state back as the current cut; the cut it replaces is saved first, so this is undoable. 'abzweigen': make a NEW sequence (a version — e.g. a broadcast cut next to the festival cut) from a state, or from the current cut without `checkpoint_id`. The new sequence keeps the clip ids and the shot layer, knows its origin, and the branch point gets a name so it stays. There is no merging back: a version is its own cut from then on. Edit it with lab_timeline_apply on the returned sequence id.
| Argument | Type | Description |
|---|---|---|
| gegen | integer | vergleichen: a second state instead of the current cut |
| marke | string | markieren: the name. Empty removes it |
| titel | string | abzweigen: the name of the new version |
| aktion |
Tell the FilmLab team about a bug or a missing feature — it reaches the people who can fix it, the chat does not. Use art 'fehler' when a tool returned something wrong, failed without a clear reason, or contradicted its own description; 'wunsch' when a step you needed does not exist. Write what you called, what you expected and what came back; put ids, arguments and the error text into `kontext` (no secrets, no personal data of third parties). One report per problem. Do not report a refused request that was correct (e.g. 'Project not found' for someone else's project). Needs an account. Free.
| Argument | Type | Description |
|---|---|---|
| art* | string | fehler = something is broken; wunsch = something is missing |
| text* | string | What you called, what you expected, what happened — in a few sentences |
| titel* | string | One line, e.g. 'lab_capture: black frame for an image clip' |
| kontext | object | Machine-readable details: ids, arguments, error text. Max ~8000 characters as JSON |
| werkzeug | string | The tool it is about, e.g. 'lab_timeline_apply' |
| project_id | string | Your project it happened in (only your own projects are accepted) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"art",
"titel",
"text"
],
"properties": {
"art": {
"enum": [
"fehler",
"wunsch"
],
"type": "string",
"description": "fehler = something is broken; wunsch = something is missing"
},
"text": {
"type": "string",
"maxLength": 4000,
"minLength": 10,
"description": "What you called, what you expected, what happened — in a few sentences"
},
"titel": {
"type": "string",
"maxLength": 200,
"minLength": 3,
"description": "One line, e.g. 'lab_capture: black frame for an image clip'"
},
"kontext": {
"type": "object",
"description": "Machine-readable details: ids, arguments, error text. Max ~8000 characters as JSON",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"werkzeug": {
"type": "string",
"maxLength": 80,
"description": "The tool it is about, e.g. 'lab_timeline_apply'"
},
"project_id": {
"type": "string",
"description": "Your project it happened in (only your own projects are accepted)"
}
},
"additionalProperties": false
}Finish an accepted DRAFT (a lab_reference_motion run with draft: true) in 1080p — same motion, same seed, same sound, sharper picture. A NEW video row comes back; the draft stays. Works only while the draft is valid and only through the account that ordered it; otherwise the backend refuses with a named reason (entwurf_abgelaufen, entwurf_anderes_konto, entwurf_fehlt, bereits_fertiggestellt) before anything is charged. Never download and re-upload a draft to get here: that is a new run, not this draft. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| get_cost | boolean | Preflight: return the price in credits and the draft's expiry WITHOUT finishing it and without spending anything. Do this first and let the human confirm. |
| video_id* | string | Id of the DRAFT video row (from lab_reference_motion's answer or lab_job_status) — positive integer as string. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"video_id"
],
"properties": {
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price in credits and the draft's expiry WITHOUT finishing it and without spending anything. Do this first and let the human confirm."
},
"video_id": {
"type": "string",
"description": "Id of the DRAFT video row (from lab_reference_motion's answer or lab_job_status) — positive integer as string."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string."
}
},
"additionalProperties": false
}Use when the person wants a saved flow, pipeline or multi-step workflow priced, costed, calculated or checked against the balance BEFORE anything runs. Price a whole Flow (Pipeline) before running it: one quote over every node, checked against the credit balance and the project's cost cap. `workflow` is a FilmOS workflow in schema v1 (vault/workflows/_schema.md): nodes of type `lab.tool` call FilmLab tools by name, `{{node_id.field}}` passes one node's result into the next, `parallel` and `condition` are supported, `foreach` and `workflow.invoke` not yet. Every node whose tool costs money is priced with that tool's own get_cost preflight; a paid tool without a preflight, or a price that cannot be determined, rejects the whole quote by name — nothing is estimated. Nothing is generated and nothing is charged. Returns `angebot_id` (valid 24 h, for exactly this workflow, these inputs and this project), the total, one line per node, the balance and what the cap still allows. A node whose price only exists once its input exists (lab_upscale fed by a clip from another node) is listed with `preis_folgt: true` and is NOT in the total: the run stops before it, names the exact price and continues only after lab_flow_fortsetzen — tell the person so when you name the total.
| Argument | Type | Description |
|---|---|---|
| inputs | object | Values for the workflow's `inputs`, referenced as {{name}} |
| workflow* | object | The Flow (Pipeline) as a FilmOS workflow object, schema_version "1" |
| project_id* | string | Project id (from lab_list_projects) — every node runs in this project |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"workflow"
],
"properties": {
"inputs": {
"type": "object",
"description": "Values for the workflow's `inputs`, referenced as {{name}}",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"workflow": {
"type": "object",
"description": "The Flow (Pipeline) as a FilmOS workflow object, schema_version \"1\"",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — every node runs in this project"
}
},
"additionalProperties": false
}Use when a flow/pipeline run is waiting for approval or a price confirmation and the person gives the go-ahead to continue. Resume a Flow (Pipeline) run that stopped and waits (status 'wartet' with a reason like 'Wartet auf Freigabe'). A run stops when a clip node needs a start image the person has not approved yet — approve the image first (lab_review_asset, verdict accepted), then resume. A run also stops before a node whose price only exists once its input exists (lab_upscale after a clip — the quote listed it as `preis_folgt`): the reason starts with 'Wartet auf Preisbestätigung' and names the exact credits. Resuming IS the confirmation of that price — ask the person first. The confirmed amount travels as max_credits; if the price rises before the run, nothing is charged and the run stops again with the new price. The waiting nodes are tried again; nothing that already ran is repeated.
| Argument | Type | Description |
|---|---|---|
| lauf_id* | string | From lab_flow_starten |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lauf_id"
],
"properties": {
"lauf_id": {
"type": "string",
"minLength": 1,
"description": "From lab_flow_starten"
}
},
"additionalProperties": false
}Use when the person has seen the quote for a flow/pipeline and says to run, start or execute it. Run a Flow (Pipeline) that was quoted with lab_flow_angebot. Pass the SAME workflow and inputs plus the angebot_id — a changed workflow, an expired quote (24 h) or a quote that was already used is refused; get a new quote then. Only after the person confirmed the price. Returns right away with `lauf_id`; the run continues on the server without anyone watching. Follow it with lab_flow_status. Each node calls its FilmLab tool under your account; every tool's own gates still apply (approved keyframe before a paid clip, cost cap of the project). If the cost cap is hit, the whole run stops and nothing after it starts. The quoted price is also the run's own hard cap (auto_lauf_id): every node reserves against it, failed nodes keep their share, and a run whose retries would cost more than the quote stops with budget_exceeded instead of spending more.
| Argument | Type | Description |
|---|---|---|
| inputs | object | Exactly the inputs that were quoted |
| workflow* | object | Exactly the workflow that was quoted |
| angebot_id* | string | From lab_flow_angebot |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"angebot_id",
"workflow"
],
"properties": {
"inputs": {
"type": "object",
"description": "Exactly the inputs that were quoted",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"workflow": {
"type": "object",
"description": "Exactly the workflow that was quoted",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"angebot_id": {
"type": "string",
"minLength": 1,
"description": "From lab_flow_angebot"
}
},
"additionalProperties": false
}Use when the person asks how far a flow/pipeline run is, whether it finished, why it stopped or hangs, or what a step returned. State of a Flow (Pipeline) run started with lab_flow_starten: the run's status (wartet, laeuft, fertig, fehlgeschlagen, abgebrochen) and, per node, its status, attempts, result and error. A node's result is exactly what its tool returned — ids from there go into the next tools. `fehler` is the run's stored message as written by the executor; when `fehler_text` is present it is the same message in the person's language — quote that one to the person. With `angebot_id` instead of `lauf_id` it finds the run started from that offer — `gefunden: false` means no run was created from it, so nothing was charged for it.
| Argument | Type | Description |
|---|---|---|
| lauf_id | string | From lab_flow_starten |
| angebot_id | string | From lab_flow_angebot — finds the run started from this offer (when lab_flow_starten's answer was lost) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"lauf_id": {
"type": "string",
"minLength": 1,
"description": "From lab_flow_starten"
},
"angebot_id": {
"type": "string",
"minLength": 1,
"description": "From lab_flow_angebot — finds the run started from this offer (when lab_flow_starten's answer was lost)"
}
},
"additionalProperties": false
}Release a paused PERSON-reference run (lab_reference_motion with use_depth: 'personen') after the human has looked at its mask preview: this starts the paid model run. Until now only the mask was charged. Show the preview (metadata.personen_referenz_url of the paused clip) first — holes in hair or hands cost a full run if released. A NEW video row comes back; the paused row stays as the record of the mask. The backend refuses with a named reason (nicht_wartend, bereits_freigegeben) before anything is charged. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| job_id | string | Alternatively the job id from lab_reference_motion's answer — resolved to its video row. Give this or video_id. |
| get_cost | boolean | Preflight: return the price of the run WITHOUT starting it and without spending anything. Do this first and let the human confirm. |
| video_id | string | Id of the PAUSED video row (from lab_reference_motion's answer or lab_job_status) — positive integer as string. Give this or job_id. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "Alternatively the job id from lab_reference_motion's answer — resolved to its video row. Give this or video_id."
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price of the run WITHOUT starting it and without spending anything. Do this first and let the human confirm."
},
"video_id": {
"type": "string",
"description": "Id of the PAUSED video row (from lab_reference_motion's answer or lab_job_status) — positive integer as string. Give this or job_id."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string."
}
},
"additionalProperties": false
}Generate an image on the production pipeline (fal.ai, quota-tracked, persisted to the project's gallery). Waits up to `wait_s` seconds (default 45) for the image and then answers with the finished rows — image_url set, inline previews attached, so the picture is in the answer like lab_job_status with preview. Pass `wait_s: 0` to return right away with rows in status 'pending' (poll lab_job_status with id + project_id then). If the budget runs out, the rows come back as they are and `fertig: false` says so. Default tool is 'flux_pro'; use 'nano_banana_pro' (Gemini 3 Pro Image) for the Papercut-style look. Pass seed_image_url to run img-to-img guided by an existing image URL. `get_cost: true` preflights credits — it returns the price without generating anything. FOR A PERSON WHO MUST LOOK THE SAME across several images, do not re-describe them per prompt: get an id from lab_list_characters (or build one with lab_create_character + lab_generate_character_reference) and pass it as `character_refs`.
| Argument | Type | Description |
|---|---|---|
| seed | integer | Seed for a repeatable run. Omit it and the project's stored seed applies (POST /api/lab/projects/{id}/einstellungen), and without that the provider picks one. The seed that actually rendered comes back in lab_job_status as `seed` with `seed_quelle` ('provider' = read from the provider's answer, 'gesetzt' = the one sent, null = the model tells no seed). Models whose endpoint has no seed field do not drop a given seed silently: the run proceeds and the row carries `seed_hinweis: seed_nicht_unterstuetzt` — some models tell their seed but take none, others take one but tell none. |
| tool | string | Image model id — copy it verbatim from lab_list_models (kind: image). Omit to use the default. Every model has its own resolutions, aspect handling and price; the catalog is the only place those live, this text deliberately names none. |
| aspect | array | Frame as [width, height], e.g. [9, 16]. Overrides the project's pose_state — pass it when the Pose-Director's blockout is set but not yet saved. Measured 2026-07-31: without it a 9:16 blockout came back as 16:9, because pose_state was still NULL. |
Generate the reference sheet for a character — six core views rendered from the sheet's `prompt_prefix_en`: detail (three-quarter close-up, the identity anchor), frontal (frontal close-up), profile, fullbody (front), fullbody_back (back view: hair and the back of the costume) and costume (the outfit on an invisible mannequin, NO person — use it where the costume is meant, never as a face reference). Back view and costume are derived from fullbody, everything else from detail. The poses pose_sitting, pose_walking, pose_action are optional extras for blocking — not part of the default run and not used as references for image or video models. A seventh view, 'detail_smile', is NOT in the default set and must be asked for: the same close-up with the mouth open and the teeth visible. Render it for every character who SPEAKS — otherwise the model invents the teeth and the jaw the first time they laugh, and the smile arrives as somebody else's mouth. It is an extra view for the mouth, never an identity anchor: a smiling reference carries its smile into every shot, funerals included. MEASURED 2026-09-15 (n=10, criterion fixed beforehand): the teeth land 10/10, but the PERSON only holds in about 4 of 10 even with the anchor active — two runs were plainly a different man. Generate it, then LOOK before you rely on it; the failure is obvious to the eye (a stranger's mouth on your character), not silent. THIS IS WHAT MAKES A SHEET USABLE: until it has run, lab_generate rejects the id as `character_refs`. Step 2 of 2 after lab_create_character. COSTS CREDITS — one provider call per view; the default run is six for humans, five for tier/kreatur (no costume). Pass `views` to render fewer, and get the exact price first with get_cost: true (free). Takes about 25 s PER VIEW (the tool requests them one by one, anchor first), so a full run is 2–3 minutes. If a single request times out, the answer says `noch_in_arbeit` instead of failing: that view is still rendering and already paid for — check it with lab_get_character_sheet, do NOT re-run it. The sheet needs a `prompt_prefix_en`; the backend regenerates one on demand but only best-effort, and answers 400 if that fails too. Views whose image could not be mirrored to R2 are deliberately NOT saved and come back in `nicht_gespiegelt` — those views are missing, not broken, and a re-run renders only what you ask for.
| Argument | Type | Description |
|---|
Generate time-of-day reference images for a location board (costs credits per variant — check the price with get_cost first). Without `variants` the backend renders all four. The images are what makes a board usable as a visual reference.
| Argument | Type | Description |
|---|---|---|
| loc_id* | string | Location board id (from lab_list_locations) |
| get_cost | boolean | Free: the exact price of this run (per variant and total) — nothing is rendered or charged. Name it to the person before the real run. |
| variants | array | Which time-of-day variants to render. Allowed: day, night, golden_hour, rain. Omit for all of them. Fewer variants = fewer credits. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"loc_id"
],
"properties": {
"loc_id": {
"type": "string",
"description": "Location board id (from lab_list_locations)"
},
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this run (per variant and total) — nothing is rendered or charged. Name it to the person before the real run."
},
"variants": {
"type": "array",
"items": {
"type": "string"
},
"description": "Which time-of-day variants to render. Allowed: day, night, golden_hour, rain. Omit for all of them. Fewer variants = fewer credits."
}
},
"additionalProperties": false
}Generate a MUSIC track for a project — the first step of the rhythm chain, and the only one that costs credits. Default engine: ElevenLabs Music v2.5 (produced, contemporary instrumental beds, 3-300 s, priced per second). Stable Audio stays selectable with tool 'stable_audio' (flat per run, up to 190 s, noticeably dated sound). ElevenLabs refuses prompts that name artists, bands or song lyrics; the failed row then carries its suggested rewording in metadata.error. ASYNCHRONOUS: the call returns immediately with a row whose `audio_url` is null and `metadata.status` is 'pending'. Nothing is playable and nothing can be bound yet. Poll lab_list_audio (audio_type 'music') until that row's status is 'completed' and audio_url is set — usually under a minute, and the backend gives up after five (status turns 'failed', metadata.error carries the reason, and the credits are refunded). PUT THE TEMPO IN THE PROMPT if you intend to cut on the beat. This is measured, not assumed: a soft swing track measured at confidence 0.164 (unusable), an ordered hard pulse ('techno, 128 bpm, four-on-the-floor') at 0.621. The tempo detector is autocorrelation over an onset envelope — it finds a hard pulse and shrugs at drone, ambient and rubato. Then: lab_bind_audio, lab_beatgrid, lab_apply_rhythm.
| Argument | Type | Description |
|---|---|---|
| tool | string | Music engine. Default 'elevenlabs_music' (ElevenLabs Music v2.5, per second, 3-300 s). 'stable_audio' is the older flat-priced engine (1-190 s) |
| prompt* | string | What the music should be. Name genre, instrumentation, mood AND the tempo in bpm if the cut is supposed to sit on the beat — e.g. 'driving techno, four-on-the-floor kick, 128 bpm, no vocals'. A prompt without a stated pulse is the usual reason lab_beatgrid comes back unreliable |
| get_cost | boolean | `true` preflights the credit price and your balance WITHOUT generating anything and without spending quota. |
Get ONE character sheet in full — every descriptive field plus the three that decide what you can do with it. `prompt_prefix_en` is the text block describing this person; it is what the backend prepends wherever the sheet is used. `reference_images` decides whether the sheet is usable at all: lab_generate REJECTS a character_refs entry without one, before any quota is spent — if it is empty, run lab_generate_character_reference first. `states` carries the state sheets against character drift (costume, light, physical condition); lab_get_character_sheet renders them agent-readable.
| Argument | Type | Description |
|---|---|---|
| preview | boolean | Attach the reference images themselves, downscaled to 512px. `reference_images` decides whether the sheet is usable at all — seeing them is the check, counting them is not: a sheet can carry a URL to a picture of the wrong person. |
| character_id* | string | Character sheet id (from lab_list_characters) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"character_id"
],
"properties": {
"preview": {
"type": "boolean",
"description": "Attach the reference images themselves, downscaled to 512px. `reference_images` decides whether the sheet is usable at all — seeing them is the check, counting them is not: a sheet can carry a URL to a picture of the wrong person."
},
"character_id": {
"type": "string",
"description": "Character sheet id (from lab_list_characters) — positive integer as string"
}
},
"additionalProperties": false
}The character sheet PREPARED FOR PROMPTING: base prefix, the state sheets by name, which reference views exist, and the ready-made sentence to paste into a lab_generate prompt. Same source as lab_get_character, different shape — use this one when you are about to generate, and lab_get_character when you want the raw record. State sheets exist against character drift: one sheet per costume, light or physical-condition change, so a person stays the same person when the situation changes. THIS IS THE ANSWER to 'same person, different light': character_refs alone routes to i2i and drags the reference's lighting along, so a drastically different light fights the reference. A state sheet puts the change into the TEXT, where the prompt can win. Free.
| Argument | Type | Description |
|---|---|---|
| state | string | Render the snippet for THIS state key (from `states[].key`) instead of the base state |
| preview | boolean | Attach the reference views themselves, downscaled to 512px. `usable_as_character_ref` only says a URL exists — the picture says whether it is the right person, in the right view. That check is the whole point of a character sheet. |
| character_id* | string | Character sheet id (from lab_list_characters) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"character_id"
],
"properties": {
"state": {
"type": "string",
"description": "Render the snippet for THIS state key (from `states[].key`) instead of the base state"
},
"preview": {
"type": "boolean",
"description": "Attach the reference views themselves, downscaled to 512px. `usable_as_character_ref` only says a URL exists — the picture says whether it is the right person, in the right view. That check is the whole point of a character sheet."
},
"character_id": {
"type": "string",
"description": "Character sheet id (from lab_list_characters) — positive integer as string"
}
},
"additionalProperties": false
}Read the accumulated context/output for one pipeline module of a project (e.g. the IDEE, STORYBOARD or SHOTS stage). Needs project_id and module name.
| Argument | Type | Description |
|---|---|---|
| module* | string | Pipeline module/stage id, e.g. 'idee', 'storyboard', 'shots' |
| project_id* | string | The FilmLab project id |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"module"
],
"properties": {
"module": {
"type": "string",
"description": "Pipeline module/stage id, e.g. 'idee', 'storyboard', 'shots'"
},
"project_id": {
"type": "string",
"description": "The FilmLab project id"
}
},
"additionalProperties": false
}Poll a dossier job by job_id (from lab_dossier). status is queued|processing|done|failed. When done, result.archiv_url is a signed https link to the ZIP that loads WITHOUT a login and expires after 24 h — call again for a fresh one. result.fehlend lists takes that could not be loaded.
| Argument | Type | Description |
|---|---|---|
| job_id* | string | The dossier job id returned by lab_dossier |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "The dossier job id returned by lab_dossier"
}
},
"additionalProperties": false
}Poll an export render job by job_id (from lab_start_export). status is queued|processing|done|failed; when done, the result (output url) is included. Call repeatedly until done|failed. NB: this is the EXPORT poll — different from lab_get_generation. Where the address sits depends on the profile: a rendered film is in video_url, but profile alpha_png_sequence delivers a ZIP of RGBA PNGs and therefore leaves video_url null and puts the address in archiv_url — a .zip under a key called video_url would read as something playable to every client. download_url carries the address in both cases and is the safe one to hand to a user. All result addresses — video_url, download_url, archiv_url and the provenance record herkunft.pdf_url / herkunft.json_url — are signed https links that load WITHOUT a login and expire after 24 h; call lab_get_export again for fresh ones. view_url is the short session-bound form for the web app only. A finished result also carries zielmarkt_checkliste: per target market of the project (see lab_zielgruppe) what the customer must observe when delivering there — empty without markets. Pass it on to the person with the download; it is not legal advice.
| Argument | Type | Description |
|---|---|---|
| job_id* | string | The export job id returned by lab_start_export |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "The export job id returned by lab_start_export"
}
},
"additionalProperties": false
}Get the status of a background job by job_id (status is queued|processing|done|failed). Poll this after starting a long-running generate/export. Returns the job record; its payload and result are objects (for a clip chain: result.zusammenfassung, result.ketten, result.clips). For an Umzug import (lab_umzug): result.bildzeilen (image_id, datei), result.gesperrt (datei, grund), result.ungeprueft. Pass wait_s (up to 80) to wait inside the call until the job is done or failed instead of polling in quick succession. Rights class is returned as rechte_klasse (klasse + regelstand) on result rows: mitgebracht, generiert, abgeleitet or lizenziert; nicht_festgehalten means the old row has no recorded class. This is separate from eigen/ki (AI Act).
| Argument | Type | Description |
|---|---|---|
| job_id* | string | The job id returned by a generate/export tool |
| wait_s | integer | Wait up to this many seconds (max 80) for done/failed; the answer says gewartet_s |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "The job id returned by a generate/export tool"
},
"wait_s": {
"type": "integer",
"maximum": 80,
"minimum": 0,
"description": "Wait up to this many seconds (max 80) for done/failed; the answer says gewartet_s"
}
},
"additionalProperties": false
}Get ONE location board in full — all descriptive fields plus its reference images and `prompt_prefix_en`. Use it to check what a board actually anchors before generating with it.
| Argument | Type | Description |
|---|---|---|
| loc_id* | string | Location board id (from lab_list_locations) — positive integer as string |
| preview | boolean | Attach the board's reference images themselves, downscaled to 512px — the fastest way to check whether a board anchors the place you mean before spending quota on it. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"loc_id"
],
"properties": {
"loc_id": {
"type": "string",
"description": "Location board id (from lab_list_locations) — positive integer as string"
},
"preview": {
"type": "boolean",
"description": "Attach the board's reference images themselves, downscaled to 512px — the fastest way to check whether a board anchors the place you mean before spending quota on it."
}
},
"additionalProperties": false
}Use when the person asks where a project stands: its current stage, what is finished, what comes next, or wants the project's state before continuing. Get a single FilmLab project with its full stage state, including its `datenmodus` ('eu' or 'frei'). Needs the project_id (from lab_list_projects).
| Argument | Type | Description |
|---|---|---|
| project_id* | string | The FilmLab project id |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "The FilmLab project id"
}
},
"additionalProperties": false
}Get ONE sequence in full — timeline (the shots in order), audio_tracks, transitions, duration_seconds and render status. Check `timeline` here before starting a continuity pass: an empty one is the single most common reason the pass refuses.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
}
},
"additionalProperties": false
}One call, the whole picture: account (tier, credits, reset), default models with price, jobs in flight, catalog freshness, project count. Pass project_id for that project's sequences, assets and review-queue length. Read-only, free.
| Argument | Type | Description |
|---|---|---|
| project_id | string | Add a project view |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"project_id": {
"type": "string",
"description": "Add a project view"
}
},
"additionalProperties": false
}Apply a deterministic colour grade to an image already in the project gallery — no re-generation, no provider, no credits. The look is computed pixel-exact (exposure, saturation, hue, lightness, contrast, film grain) and lands as a new gallery image the sequencer and export pick up. For full control the recipe fields are open too: levels and curves per channel (rgb, r, g, b — values 0..255), a gradient map (luminance → colour ramp), exposure offset/gamma and grain seed/colour. Fixed order, like a node stack: exposure → levels → curves → hue/saturation → look (LUT) → gradient map → grain. The same recipe on the same image always yields the same result (grain is fixed-seed), so a look is reproducible across every still. Set from_colorist to start from the project's colorist crew brief (look, contrast, grain) and tune with the sliders on top. At least one adjustment OR from_colorist is required. Synchronous — the graded image_url comes straight back, no polling.
| Argument | Type | Description |
|---|---|---|
| hue | number | Hue rotation in degrees; zero neutral |
| look | string | Named look from the bundle catalogue (e.g. filmlab-neutral, filmlab-warm, filmlab-cool, filmlab-soft, filmlab-punch) — a hand-authored LUT, applied on top of the sliders. The server resolves the name; a path is never accepted. |
| grain | number | Film grain amount; zero is none, monochrome |
| curves | object | Curves per channel as [x, y] points 0..255, x strictly increasing (smooth monotone curve through them). Example warm highlights: {"r": [[0,0],[192,205],[255,255]], "b": [[0,0],[192,180],[255,255]]}. Not together with contrast on the rgb channel |
| levels | object |
Set the colour grade of ONE clip in the edit (Murnau), or of the whole programme with `programm: true`. It is the same grade a colourist sets in Murnau's colour workspace, and the export uses it: Murnau bakes the export LUTs when you set it. Only the fields you send change (`zuruecksetzen: true` starts from neutral). Fields and ranges, as the sliders: exposure in stops (−5..5); contrast, saturation, temperature, tint −100..100; hue −180..180; lift/gamma/gain/offset as {x, y, master}, each −1..1 (x/y move the colour wheel); log as {shadow|midtone|highlight: {x, y, master}} — the Log wheels in ACEScct, each acting only on its range of brightness (shadows below ~4 % linear, highlights above ~50 %), master ±1 ≈ ±2 stops there; schritte as a list (≤ 8) of further grades with the same fields (plus name), applied in series after this grade — serial nodes; creative and sek stay on the grade itself; hdr as {dark|shadow|light|highlight: {x, y, master}} — HDR zone wheels centred −3/−1/+1/+3 stops around 18 % grey, master ±1 ≈ ±1.5 stops; curves as {master|r|g|b: [[x,y],…]} 0..1; boost, shadows, highlights −100..100; intensity 0..100 (0 = original). Per clip also `sek` — a secondary correction: {qualifier: {hueCenter, hueWidth, satLow, satHigh, lumLow, lumHigh, softness, invert}, fenster: [{form: kreis|rechteck|verlauf|polygon|bezier, cx, cy, rx, ry, winkel, weich, invert, modus: add|sub, punkte (polygon/bezier only: 3–16 resp. 3–12 [x, y] in the window's own unit space, scaled by rx/ry and turned by winkel — bezier draws a smooth closed curve through them)}], grade: {saturation, exposure, temperature, tint, hue, schutz}} — schutz 0..100 takes the clip's and the programme's grade back inside the selection (100 = ungraded there, e.g. to protect skin); `sek: null` removes it. Any level also takes `warper` — the Color Warper: {huesat|chromalum: {res: 6|12|24, punkte: [[i, j, dx, dy], …]}}; node (row i, column j) sits at (j/res, i/res) and is moved by (dx, dy) in −1..1. huesat plane: x = 0.5 + 0.5·sat·cos(hue), y = 0.5 + 0.5·sat·sin(hue) (lightness kept); chromalum plane: x = sat, y = 1 − lightness (hue kept). Only moved nodes; `warper: null` removes it. Call WITHOUT `grade` to list the clips (clip_id, start/end, current grade, input profile) and the programme grade. Measure first and after with lab_scopes — its `vorschlag.grade` is a computed correction you can pass here as `grade`. Writes state, costs nothing.
| Argument | Type | Description |
|---|
Suggest a FilmLab grade look from a Resolve LUT name or path — read-only, no cost. First call resolve_node_graph_lesen on the local FilmLab bridge (filmlab_bruecke_mcp.py), a SEPARATE MCP server, and pass its lut field here. The agent connects these two calls: this Cloudflare Worker cannot reach the bridge at 127.0.0.1 on the user's computer or fetch the name itself. Only the name is matched; the Resolve LUT file is neither read nor imported, and its exact grade is not reproduced. Returns look, reason, alternatives and sicher (false means the name was not confidently recognised). Apply with lab_grade(look: …) only once the user agrees.
| Argument | Type | Description |
|---|---|---|
| lut* | string | LUT name or path from the lut field returned by resolve_node_graph_lesen on the local FilmLab bridge |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lut"
],
"properties": {
"lut": {
"type": "string",
"description": "LUT name or path from the lut field returned by resolve_node_graph_lesen on the local FilmLab bridge"
}
},
"additionalProperties": false
}Suggest a grade look for a project — read-only, no cost. With image_id, includes the image prompt; without it, uses only the colorist crew brief. Returns a recommended look name with a reason and alternatives; apply it with lab_grade(look: …) once the user agrees.
| Argument | Type | Description |
|---|---|---|
| image_id | integer | Generated image id belonging to the project; includes its prompt in the suggestion |
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"image_id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Generated image id belonging to the project; includes its prompt in the suggestion"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}Declare or list where a project's material comes from: 'eigen' (made by people) or 'ki' (AI-generated or AI-altered). Exports decide the EU AI Act Art. 50 disclosure per clip from this — undeclared material counts as AI. With asset_key + herkunft: declare (the file is checked for an AI mark; one found keeps it AI and the answer says why). With project_id only: list every declaration with its effect (`wirksam`). Rights class is returned as rechte_klasse (klasse + regelstand) on result rows: mitgebracht, generiert, abgeleitet or lizenziert; nicht_festgehalten means the old row has no recorded class. This is separate from eigen/ki (AI Act).
| Argument | Type | Description |
|---|---|---|
| herkunft | string | Required together with asset_key |
| asset_key | string | Storage key of the file (from lab_add_media or lab_list_assets). Omit to list |
| project_id* | string | Project id — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"herkunft": {
"enum": [
"eigen",
"ki"
],
"type": "string",
"description": "Required together with asset_key"
},
"asset_key": {
"type": "string",
"description": "Storage key of the file (from lab_add_media or lab_list_assets). Omit to list"
},
"project_id": {
"type": "string",
"description": "Project id — positive integer as string"
}
},
"additionalProperties": false
}Check whether still images of a project show the SAME character as its character sheet — before paid clips are made from them. Each image is compared against the sheet's detail and fullbody views. It judges identity (species, build, colouring, head shape, large markings), NOT style, light, pose, place or props — and deliberately NOT micro details like an ear notch or the number of fingers, which no image model holds reliably. Free. Runs in the BACKGROUND: returns a `job_id` with HTTP 202 — poll it with lab_get_job until status is done|failed; the result lists per image passt, sicherheit, abweichungen, begruendung (or error). Up to 16 images per call, all completed images of this project. The sheet needs a reference image (lab_generate_character_reference).
| Argument | Type | Description |
|---|---|---|
| image_ids* | array | Completed image ids of this project (from lab_generate/lab_auto_storyboard) — positive integer strings, no duplicates |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
| character_id* | string | Character sheet id the images should show — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"character_id",
"image_ids"
],
"properties": {
"image_ids": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 16,
"minItems": 1,
"description": "Completed image ids of this project (from lab_generate/lab_auto_storyboard) — positive integer strings, no duplicates"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"character_id": {
"type": "string",
"description": "Character sheet id the images should show — positive integer as string"
}
},
"additionalProperties": false
}Poll the status of an image or video generation started by lab_generate/lab_animate. There is no single-item status endpoint on the backend — this fetches the project's full image/video list and filters by id, so it needs BOTH project_id and id (the id alone is not enough). status inside the returned item's metadata is 'pending'|'completed'|'failed'; when completed, image_url/video_url is populated. While a run is not done, the answer carries naechste_abfrage_s — wait that long before the next call instead of polling in quick succession; spaetestens_s (video) is the point after which the backend gives up and refunds. When the run is done, the answer may additionally carry schritte (the step trail of this run) and review (a machine check against the confirmed brief, present once for a run that went through lab_propose/lab_run_proposal — absent for a still-pending run, a run started outside a proposal, or a repeat poll after the one-time check already ran): abweichung lists every field that came back different from what was confirmed, possibly empty; tool means the provider fallback chain degraded the run; bild means a vision check found the result contradicting an explicit statement of the brief (free for the user, once per run; bild_review says why it was skipped). A failed or degraded proposal run may also carry plan_b: alternatives with checked prices and proposal IDs, offered once. Present them to the human; never start them yourself. Call lab_run_proposal only after the human confirms an alternative.For a run that carried a kamerapfad, metadata.kamerapfad says what arrived and, as `angewandt`, WHICH form of it was actually sent — als_text (prose), als_regiebefehle (bracketed director commands, MiniMax/Hailuo), eigene_regie (the prompt already carried bracketed commands, so the path was deliberately NOT added next to them — eigene_regiebefehle lists them) or nicht_ableitbar (nothing of the path reached the prompt, with the reason). `sprache` next to it names the family rule and the source it rests on. For a reference-motion run, metadata.referenz_masse is the reference trail: one row per reference with what it measured (original_px) and, if the backend had to scale it to the model's limits, what it became (angepasst_px) and the address that was actually sent. Look there when a result ignores a reference or looks softer than the source — a silently rescaled reference looks like a badly generated one. Rights class is returned as rechte_klasse (klasse + regelstand) on result rows: mitgebracht, generiert, abgeleitet or lizenziert; nicht_festgehalten means the old row has no recorded class. This is separate from eigen/ki (AI Act).
Are any FilmLab generations running RIGHT NOW, across ALL projects? Use this before anything that restarts the backend (a deploy recreates the container and kills running jobs, losing paid credits), or to answer 'is the lab busy?'. Unlike lab_job_status/lab_batch_status this needs no id — it lists every job in queued/processing PLUS every direct image/video/audio/vfx generation currently within its own grace window (lab_generate/lab_animate write straight to their result tables, never to the job queue — omitting job_type covers both; naming one job_type narrows to the job queue only, since a direct generation's type vocabulary differs). Returns {busy, warm[], stale_count, stale[]}: `warm` are jobs that actually progressed within stale_after_seconds and are the ones a restart would destroy; `stale` are queue jobs stuck longer than that (a crashed worker leaves rows in 'processing' forever) — they are reported but do NOT make busy true, otherwise one zombie would block every future deploy. A direct generation is never 'stale' here — past its own grace window it stops being reported by the backend entirely and becomes the orphan sweep's job, not this check's. Carries no prompts or payloads: this view crosses project boundaries, so it returns only type, state and age.
| Argument | Type | Description |
|---|---|---|
| job_type | string | Only this job type, e.g. 'video_generate', from the JOB QUEUE. Omit to see everything, including direct generations — for a pre-deploy check, omitting is usually right. |
| stale_after_seconds | integer | How long without progress before a job counts as a zombie rather than running. Default 1800, same threshold as the deploy guard in scripts/deploy_backend.sh. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"job_type": {
"type": "string",
"description": "Only this job type, e.g. 'video_generate', from the JOB QUEUE. Omit to see everything, including direct generations — for a pre-deploy check, omitting is usually right."
},
"stale_after_seconds": {
"type": "integer",
"maximum": 86400,
"minimum": 60,
"description": "How long without progress before a job counts as a zombie rather than running. Default 1800, same threshold as the deploy guard in scripts/deploy_backend.sh."
}
},
"additionalProperties": false
}Produce ONE element on a transparent background: generated, then cut out and stored as an alpha PNG in the project's gallery. This is the counter-move to the one-shot lottery — cast, props and foreground pieces are made SEPARATELY, arranged as a set afterwards and only then unified, so a wrong hand or a wrong lamp is re-made alone instead of re-rolling the whole frame. The element is deliberately optics-neutral: no lens or camera suffix travels with it, because the look is decided later on the finished comp and every layer is meant to inherit the same focal length and bokeh. ASYNCHRONOUS: the row comes back with status pending and image_url null; poll lab_job_status (type 'image') with its id until the url is populated — that url IS the cut-out. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| prompt* | string | What the element shows — the OBJECT or the PERSON, not the scene around them. The backend appends an isolation clause (single subject, plain seamless background, even light) so the cut-out has a clean edge; describing a set as well fights that clause and produces an element that cannot be freed. |
| get_cost | boolean | Preflight: return the price in credits and your balance WITHOUT generating and without spending anything. There is no model to pick on this path — the cut-out runs on the house image model and is billed at the plain image rate, so lab_list_models has no entry that would change this number; it comes from the same cost endpoint as everywhere else. |
| metadata | object | Free-form JSON stored alongside the element (e.g. { note, parent_id }). Merged into the row's metadata next to the fields the pipeline sets itself (kind, layer_type, status) — not interpreted. |
| scene_ref | string |
Extract the LAST frame of a finished clip as a new image (returns image_id, image_url, zu_dunkel). Use it as source_image_id to continue one unbroken take, or as a still. A frame that is too dark (zu_dunkel: true) makes a bad start image — say so instead of animating from black. Free (no credits). Chains (lab_verkettung) do this themselves for uebergang=durchgehend.
| Argument | Type | Description |
|---|---|---|
| video_id* | string | A finished clip (positive integer as string) |
| scene_ref | string | Scene for the new image; default: the clip's |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"video_id"
],
"properties": {
"video_id": {
"type": "string",
"description": "A finished clip (positive integer as string)"
},
"scene_ref": {
"type": "string",
"description": "Scene for the new image; default: the clip's"
}
},
"additionalProperties": false
}List a project's media assets (image/video/audio) with their URLs. Optionally filter by asset_type. Returns up to 20 with a total count. Each row carries its address as `url` (with `type` and `key`). Project assets come back as SIGNED addresses on https://thefilmradar.com/api/lab/assets/abruf/… — valid for 24 h, loadable without a session and with any User-Agent. Only older rows may still point at media.andresmarder.com, whose CDN WAF answers some non-browser User-Agents (e.g. curl's default) with 403 — prefer a browser UA there, or hand the URL to the user.
| Argument | Type | Description |
|---|---|---|
| preview | boolean | Attach the pictures themselves, downscaled to 512px, so you can look at them without fetching anything (older media.andresmarder.com addresses answer a non-browser User-Agent with 403). The worker fetches with a browser UA. At most 4 per call; audio and video rows carry no picture and are named in preview.notes. |
| asset_type | string | Filter by asset type (optional) |
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"preview": {
"type": "boolean",
"description": "Attach the pictures themselves, downscaled to 512px, so you can look at them without fetching anything (older media.andresmarder.com addresses answer a non-browser User-Agent with 403). The worker fetches with a browser UA. At most 4 per call; audio and video rows carry no picture and are named in preview.notes."
},
"asset_type": {
"enum": [
"image",
"video",
"audio"
],
"type": "string",
"description": "Filter by asset type (optional)"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}List a project's generated audio rows, newest first — this is the POLL for lab_generate_music. Read two fields: `metadata.status` ('pending' | 'completed' | 'failed') and `audio_url`. A pending row has no url, and nothing downstream works with it: lab_bind_audio silently leaves the track empty (resolve-stems only takes rows that carry a url) and lab_beatgrid has no file to open. A failed row carries `metadata.error` and its credits were refunded. Reads only — no state changes, no credits.
| Argument | Type | Description |
|---|---|---|
| limit | integer | How many rows to FETCH from the backend (default 50). At most 20 are SHOWN; `total` is what the fetch found, so a total of exactly `limit` means there may be more |
| scene_ref | string | Filter by the scene label passed at generation time |
| audio_type | string | Filter by kind. Pass 'music' when polling a lab_generate_music call — that is the kind the music track binds. The repair kinds (denoise, split, isolation) come from lab_sfx and hold cleaned-up versions of recordings you already had; a 'split' run leaves one row per stem, each naming its part |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"limit": {
"type": "integer",
"maximum": 200,
"minimum": 1,
"description": "How many rows to FETCH from the backend (default 50). At most 20 are SHOWN; `total` is what the fetch found, so a total of exactly `limit` means there may be more"
},
"scene_ref": {
"type": "string",
"description": "Filter by the scene label passed at generation time"
},
"audio_type": {
"enum": [
"music",
"sfx",
"ambience",
"dialog",
"voiceover",
"foley",
"denoise",
"split",
"isolation"
],
"type": "string",
"description": "Filter by kind. Pass 'music' when polling a lab_generate_music call — that is the kind the music track binds. The repair kinds (denoise, split, isolation) come from lab_sfx and hold cleaned-up versions of recordings you already had; a 'split' run leaves one row per stem, each naming its part"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}List the character sheets in the FilmLab cast register (id, name, age, gender, project_name, prompt_prefix_en). Call this BEFORE lab_generate whenever a person must look the same across several images: pass the id you find here as `character_refs` instead of re-describing the person in every prompt. Optional `search` matches name or project name (substring, case-insensitive). It shows YOUR OWN characters plus the public catalog (sheets explicitly made public and bound to no project). Characters of other accounts' projects never appear — projects can be confidential. New characters are private. The register returns at most 50 sheets, newest first, and offers NO paging — 20 are shown here. `total` therefore counts at most 50: with more than 50 sheets it understates, and `search` is the only way to reach the older ones. Nobody there yet? Create a sheet with lab_create_character.
| Argument | Type | Description |
|---|---|---|
| search | string | Substring of the character name or project name (case-insensitive) |
| preview | boolean | Attach each sheet's reference portrait, downscaled to 512px. A cast list read as text is a list of names; seen as faces it is a cast. At most 4 per call. A sheet WITHOUT a reference image shows none — and that is the same emptiness lab_generate rejects, so an absent picture here is a finding, not a glitch. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"search": {
"type": "string",
"description": "Substring of the character name or project name (case-insensitive)"
},
"preview": {
"type": "boolean",
"description": "Attach each sheet's reference portrait, downscaled to 512px. A cast list read as text is a list of names; seen as faces it is a cast. At most 4 per call. A sheet WITHOUT a reference image shows none — and that is the same emptiness lab_generate rejects, so an absent picture here is a finding, not a glitch."
}
},
"additionalProperties": false
}List past export jobs for a project (each with job_id, status, profile). Returns up to 20 with a total count. Finished rows carry the same signed, login-free result addresses as lab_get_export (download_url, herkunft.pdf_url, …; valid 24 h).
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}List a project's location scans with their reconstruction state — this is the POLL for lab_location_recon and the place the scan ids come from. Read `status`: 'pending' (clip uploaded, nothing reconstructed yet), 'reconstructing', 'done' (splat_url/glb_url/board_url are filled), 'failed' (error says why). Listing is what ADVANCES a running reconstruction from the outside world into the row, so calling this is how the state moves at all. Reads only — no credits.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}List location boards, newest first — START HERE to find a `location_id` for lab_generate / lab_animate. Each entry carries its `prompt_prefix_en`: that is the exact text appended to your prompt as 'Location: …', so you can judge whether the board fits BEFORE spending quota. A board with an EMPTY prefix anchors nothing — it will silently do nothing. Fix it with lab_enrich_location_prefix or pick another board. Only public boards are returned, max 50 from the backend and 20 shown here; if your place is not listed that is a limit, not a failure — create it with lab_create_location.
| Argument | Type | Description |
|---|---|---|
| search | string | Filter by board name or project name (substring, case-insensitive) |
| preview | boolean | Attach each board's reference picture, downscaled to 512px. A location board is a PLACE — its prefix text says what was written down, the picture says what the board actually anchors, and those two disagree more often than the text admits. At most 4 per call; narrow with `search` rather than expecting more. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"search": {
"type": "string",
"description": "Filter by board name or project name (substring, case-insensitive)"
},
"preview": {
"type": "boolean",
"description": "Attach each board's reference picture, downscaled to 512px. A location board is a PLACE — its prefix text says what was written down, the picture says what the board actually anchors, and those two disagree more often than the text admits. At most 4 per call; narrow with `search` rather than expecting more."
}
},
"additionalProperties": false
}The model catalog: what every video or image model can do — resolutions, durations, aspect ratios, audio, reference limits, price per second or per image — grouped by capability class with a one-line explanation per group. Read this before choosing a `tool` for lab_generate/lab_animate/lab_propose; copy ids verbatim. Cached briefly; `stand` says how fresh. Without `klasse` the answer is COMPACT (one line per model, cheapest first, plus `klassen` with every valid class name): fields that are null, false or empty are left out, and a model that also belongs to another class shows there only as a short stub whose `wie_in` names the class holding its full entry. `form: knapp` means the catalog was too large for one answer — set `klasse` for the details. With `klasse` or `voll: true` every field. Filter to answer the usual question directly: `aufloesung`, `dauer_s`, `ton` (ohne = models that can run silent, including switchable audio). A model with switchable audio is listed under the class with AND without sound (i2v_ton and i2v).
| Argument | Type | Description |
|---|---|---|
| eu | boolean | Only models allowed in EU mode projects (data stays inside the EU data path). Every model carries `eu_modus` and, where false, `eu_grund` — the first missing proof (`form: knapp` carries only `eu_modus`) |
| ton | string | ohne = can run without sound (audio nie or schaltbar); mit = can make sound |
| kind* | string | Which catalog |
| voll | boolean | Every field of every model even without klasse — large, use a filter with it |
Location boards bound to ONE project. Narrower and more correct than lab_list_locations when you already know the project: it cannot leak boards from a different project with the same title. Boards created before the project binding existed (project_id NULL) deliberately do NOT appear here — use lab_list_locations for those.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}List FilmLab projects (id, title, current pipeline stage). Call this first to find a project_id for the other tools. Shows up to 20 items; `total` counts your active projects (up to 200) and `truncated: true` means the list is cut off — sorted by last change, so older projects drop out of the shown window first.
No arguments.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {},
"additionalProperties": false
}List the props (Requisiten) of this project, including @prop_ tags, descriptions and generated views. Free. Use the tags in image and video prompts: the description always goes into the prompt, the front view goes along as a reference image in lab_generate and lab_reference_motion.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id, positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id, positive integer as string"
}
},
"additionalProperties": false
}List a project's sequences, newest first. Use it to find a `seq_id` instead of creating a second sequence for the same cut.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}List the style kits of this project (rules, palette, up to three reference images, @stil_ tag) and which one is the project default (standard_id). Free. Write the tag into an image or video prompt to use that kit for this one call instead of the default.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id, positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id, positive integer as string"
}
},
"additionalProperties": false
}List a project's cloned voices (lab_voice_clone results) — id, label, voice house, sample and preview URLs. This is where a `voice_id` for lab_voice_tts or lab_dub comes from, and where you look up which house a voice belongs to before pricing it. Both houses are listed unless you narrow it. Reads only — no state changes, no credits.
| Argument | Type | Description |
|---|---|---|
| provider | string | Narrow the list to one voice house. Omit it and both come back, which is the normal case — a project can hold voices from either. An unknown value is refused rather than answered with an empty list: 'none found' and 'no such house' are different answers. What the houses can do: lab_list_models. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"provider": {
"enum": [
"minimax",
"fish_audio"
],
"type": "string",
"description": "Narrow the list to one voice house. Omit it and both come back, which is the normal case — a project can hold voices from either. An unknown value is refused rather than answered with an empty list: 'none found' and 'no such house' are different answers. What the houses can do: lab_list_models."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}Decline an offered license addressed to your account. Free.
| Argument | Type | Description |
|---|---|---|
| lizenz_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lizenz_id"
],
"properties": {
"lizenz_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"additionalProperties": false
}Offer a license for your own asset or an asset of a studio you can edit. The recipient must be an existing account. No charge until the recipient accepts. Credits go to the platform, not to the asset owner. No marketplace or catalog.
| Argument | Type | Description |
|---|---|---|
| gebiet* | string | |
| endet_am | string | |
| local_id* | integer | |
| beginnt_am* | string | |
| nutzungsart* | string | |
| plattformen | array | |
| preis_credits* | integer | |
| lokale_tabelle* | string | |
| lizenznehmer_email* | string |
Accept a license addressed to your own account (studio: owner account). First call get_cost:true, show the exact credits, and obtain confirmation before accepting. Acceptance spends credits through the platform and creates a private PDF certificate. No transfer to the asset owner. A zero-price license is free.
| Argument | Type | Description |
|---|---|---|
| get_cost | boolean | Return price without accepting or charging |
| lizenz_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lizenz_id"
],
"properties": {
"get_cost": {
"type": "boolean",
"description": "Return price without accepting or charging"
},
"lizenz_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"additionalProperties": false
}Get a private PDF certificate URL, valid for five minutes. Only for the parties to this license.
| Argument | Type | Description |
|---|---|---|
| lizenz_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lizenz_id"
],
"properties": {
"lizenz_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"additionalProperties": false
}Revoke an active license you offered. Marks the revocation, never deletes the evidence. No refund.
| Argument | Type | Description |
|---|---|---|
| lizenz_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lizenz_id"
],
"properties": {
"lizenz_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"additionalProperties": false
}Withdraw your license offer before acceptance. Free.
| Argument | Type | Description |
|---|---|---|
| lizenz_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lizenz_id"
],
"properties": {
"lizenz_id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"additionalProperties": false
}List received and offered asset licenses with their status in the current workspace. Free.
No arguments.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {},
"additionalProperties": false
}Generate a 360° equirectangular HDRI panorama of a place (costs credits — exact price via get_cost: true). Pass loc_id to persist it on that board, so the viewer finds it again; without loc_id it is a one-off.
| Argument | Type | Description |
|---|---|---|
| mood | string | Mood/atmosphere (default 'neutral') |
| intent | string | What the place is — the panorama prompt |
| loc_id | string | Persist the panorama on this location board (from lab_list_locations) |
| get_cost | boolean | Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run. |
| camera_height | number | Camera height in metres (default 1.6, roughly eye level) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"mood": {
"type": "string",
"description": "Mood/atmosphere (default 'neutral')"
},
"intent": {
"type": "string",
"minLength": 1,
"description": "What the place is — the panorama prompt"
},
"loc_id": {
"type": "string",
"description": "Persist the panorama on this location board (from lab_list_locations)"
},
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run."
},
"camera_height": {
"type": "number",
"description": "Camera height in metres (default 1.6, roughly eye level)"
}
},
"additionalProperties": false
}Reconstruct a real place in 3D from a scan clip: keyframes are pulled from the clip server-side and turned into a point cloud / splat on the location board, so a scouted room can be looked at from angles nobody filmed. THE SCAN IS NOT MADE HERE — it is a walk-around clip uploaded by the FilmLab camera app together with the phone's lens data. This tool takes one that already exists; lab_list_location_scans gives you the ids. SLOW AND NOT A JOB ROW: the answer comes back as 'reconstructing', and the finished state is picked up ONLY by lab_list_location_scans — lab_job_status and lab_get_job will never show this one finish. If the call times out, the reconstruction normally keeps running on the server: list the scans instead of firing it again. Re-running is allowed from any state that is not done.
| Argument | Type | Description |
|---|---|---|
| engine | string | Which reconstruction path. Omit for the backend default. 'vggt' is scene-scale and fast but gives a point cloud; 'vggt-splat' trains a walkable Gaussian splat from the same scan (minutes on a GPU, not seconds). Both need their own model configured on the backend — where it is not, the answer says so instead of falling back silently. |
| image_id* | string | Id of the SCAN row (from lab_list_location_scans) — positive integer as string. It is a row in the image gallery whose tool is the location scanner; a normal generated image with that id is rejected, not reconstructed. |
| max_photos | integer | How many keyframes are pulled from the clip and handed to the reconstruction. Omit to let the engine use its own count — more frames mean a wider parallax baseline and a slower run. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string. The scan must belong to it; a scan of another project is not found rather than borrowed. |
Read or set a project's 'look in post' flag. Use it when the person grades in DaVinci Resolve with the FilmLab Look plugin and wants the lens and film look applied there instead of baked into the generated pictures. With project_id only: read. With look_in_post true, image and video prompts of the project no longer ask for grain, halation, vignette, chromatic aberration or soft edges — the plugin adds them in Resolve; false puts them back. It takes effect only while a LensLab lens set is chosen for the project (Pose Director / camera panel in the FilmLab app); without one the flag is stored but changes nothing. The answer always carries look_in_post, optik (the chosen lens set or null), wirkt (whether it takes effect now) and a sentence to pass on.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) |
| look_in_post | boolean | true: leave the look terms out of prompts and let the Resolve plugin add them; false: keep them in the prompt. Omit to only read |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"look_in_post": {
"type": "boolean",
"description": "true: leave the look terms out of prompts and let the Resolve plugin add them; false: keep them in the prompt. Omit to only read"
}
},
"additionalProperties": false
}Check the colour CONTINUITY of a scene in the edit (Murnau): every shot is compared with the scene's reference shot (the first, or `referenz_clip_id`) at the SAME scene points — found by feature matching (ORB + homography) between the two frames, each rendered through its export path. Image-wide statistics would measure the framing, not the grade (measured: up to 5000 K apart with no grade change). At those points: white point (Kelvin and mired), lightness (L*), colour distance (ΔE). Over the threshold (8 mired, L* 4, ΔE 3) a shot gets a `befund` with the number (e.g. '150 K wärmer als die Szene (13 Mired)') and a `vorschlag.grade`: a clip grade fitted so the shot's source colours land on the reference colours, with the deviation expected afterwards. Pass it to lab_grade_clip, then run lab_look_match again. Shots without enough shared content (reverse shot, different subject) come back as `vergleichbar: false` with the reason — nothing is guessed. Scenes come from the sequence's shots (lab_shots); without them give the shots as `clip_ids`. At most 24 shots per call. Writes nothing, costs nothing.
| Argument | Type | Description |
|---|---|---|
| szene | string | Only this scene (scene_ref from lab_shots); default: every scene with two or more shots |
| seq_id* | string | Sequence id — positive integer as string |
| clip_ids | array | Compare exactly these shots instead of the scenes from lab_shots |
| referenz_clip_id | string | The shot the others are matched to; default the first of the scene |
Set the sound mix of a sequence: per track `volume_db`, `pan`, `mute`, `solo` and `effects`. Every field you send is rendered — the backend rejects what it cannot render (422 with the reason), so a saved mix is the mix you hear. Tracks are the stems `dialog`, `music`, `sfx`, `atmo`, plus `master` (effects only: eq, compressor, limiter). Only the fields you send change; `effects` replaces that track's whole effect list. Effects: gate, eq, deesser, compressor, reverb, limiter — fixed order, one of each per track; give a `preset` (with its written reasoning), a one-knob `knopf` (0–1) or explicit values. Ducking is an effect too: {type:'ducking', quelle:'dialog', amount_db, attack_ms, release_ms} lowers this track while `quelle` is active. `naht_ms` (0–500) fades every seam between the clips of a stem with an equal-power fade — against clicks and room jumps at the cuts of a stem built from pieces; 0 = hard edges. The sfx stem of lab_ton_durchlauf comes with 30. Volume `automation` replaces the track's breakpoints {t_s, db}, in seconds from the sequence start, interpolated linearly in dB; [] clears it, and master does not support it. Call with `katalog: true` (and no tracks) to get every effect, its ranges and the presets with their reasoning. Any change discards the rendered mixdown. Changes state, costs nothing. Next: lab_mix_render, then lab_mix_messen.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
| tracks | array | Tracks to change — only the given fields are written |
| katalog | boolean | Return the effect catalogue instead of changing anything |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
},
"tracks": {
"type": "array",
"items": {
"type": "object",
"required": [
"track_id"
],
"properties": {
"pan": {
"type": "number",
"description": "-1 left … 1 right"
},
"mute": {
"type": "boolean"
},
"solo": {
"type": "boolean"
},
"effects": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"description": "Replaces the track's effect list. Each item has `type`; see katalog:true"
},
"naht_ms": {
"type": "number",
"description": "Fade at every seam between this stem's clips, 0–500 ms (0 = hard edges)"
},
"track_id": {
"type": "string",
"description": "dialog, music, sfx, atmo or master"
},
"volume_db": {
"type": "number",
"description": "Fader in dB"
},
"automation": {
"type": "array",
"items": {
"type": "object",
"required": [
"t_s",
"db"
],
"properties": {
"db": {
"type": "number",
"maximum": 12,
"minimum": -60
},
"t_s": {
"type": "number",
"minimum": 0
}
}
},
"maxItems": 500,
"description": "Volume breakpoints over time, seconds from sequence start, interpolated linearly in dB. Replaces the track's whole automation; [] clears it. Not on master."
}
}
},
"description": "Tracks to change — only the given fields are written"
},
"katalog": {
"type": "boolean",
"description": "Return the effect catalogue instead of changing anything"
}
},
"additionalProperties": false
}Measure the RENDERED mix: integrated loudness (LUFS), true peak (dBTP) and loudness range (LU) of the master, measured on the mixdown file itself. Per stem the same values, speech-gated (under the dialog / in its pauses), and how many LU each stem sits under (or over) the dialog in dialog passages — exact. A stem's level inside the master is an estimate (stem plus the gain the master applied) and is labelled as such. Needs a current render whose stems are still the ones it was made from: any change with lab_mix discards it, and a newer audio file in a stem makes it stale (409 then — render first). Runs as a BACKGROUND JOB: you get a job_id at once; poll lab_get_job until done — the measurement is in job.result. Measure before and after a change to prove what it did. Writes nothing, costs nothing.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
}
},
"additionalProperties": false
}Render the sequence's mixdown, normalised to a loudness target: `broadcast` (EBU R128), `web` or `youtube`. With `auto_preset` it runs the AUTO-MIX instead: it measures every stem speech-gated (under the dialog vs. in its pauses), sets faders, ducking and a master limiter by the preset (`spielfilm`: music clearly under the dialog; `doku_vo`: the narrator leads), renders, and measures the result. Every auto-mix decision comes back as one sentence with its number, plus missed lower loudness bounds — read them, they are the reasoning you can pass on. Runs as a BACKGROUND JOB: the answer is a job_id at once; poll lab_get_job until status is done (the result — mixdown, measured loudness, the auto-mix report — is in job.result) or failed (job.result.status and detail say why: 422 solo on / dialog muted / no speech, 409 the tracks were edited meanwhile — nothing was overwritten). If a render of the same kind is already running for this sequence, you get THAT job_id back instead of a second render. Costs nothing. Next: lab_get_job, then lab_mix_messen.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
| target | string | Loudness target, default broadcast |
| auto_preset | string | Run the auto-mix with this preset instead of rendering the mix as set |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
},
"target": {
"enum": [
"broadcast",
"web",
"youtube"
],
"type": "string",
"description": "Loudness target, default broadcast"
},
"auto_preset": {
"enum": [
"spielfilm",
"doku_vo"
],
"type": "string",
"description": "Run the auto-mix with this preset instead of rendering the mix as set"
}
},
"additionalProperties": false
}Re-shoot one clip from NEW camera angles: same people, same words in the same timing, same place — only the camera changes, cutting on the spoken lines, as if several cameras had been rolling. This tool plans and checks; it never generates and never charges. Workflow: look at the clip with lab_bilder at exact seconds across its whole length (denser over fast moments), get the spoken lines with lab_wortzeiten, then ask the person ONE question — keep the original look, or a cinematic interpretation (then pick a kino_kopf from aktion optionen). Then call aktion plan with the shot list: every shot says WHERE the camera physically stands, neighbouring shots differ in size, the shots cover the clip without gaps, and original_kamera describes the original angle so the prompt can forbid it. The answer lists findings (hart = fix before anything costs) and the ready runs in stuecke[].lauf. With preis: true it also returns the price of all pieces and an estimate for upscaling afterwards — name it and wait for the yes. Then pass each stuecke[].lauf UNCHANGED to lab_reference_motion, in order. Every run is a low-resolution draft where the model offers one; upscale only what the person likes (lab_upscale). Clips longer than the chosen model can take are split at shot boundaries into several runs (zusammensetzen: true) — for more than a handful of runs propose one lab_auto_lauf with a cap. When all pieces are finished, aktion zusammensetzen puts them into ONE new sequence without gaps, each piece with its original sound. If a result comes back wrong, ONE retry: original angle came back → shorter shot list, describe the original angle more physically, make the first shot radically different (profile or top-down); timing drifted → put the spoken line in quotes on its shot; left and right swapped → describe positions by landmarks, not screen directions; people changed → add a clean still as an image with the job of how the person looks.
| Argument | Type | Description |
|---|---|---|
| name | string | zusammensetzen: title of the new sequence |
| plan | object | |
The rules of one working surface, generated from the code: what the surface is, what an agent can trigger itself, what exists but only by hand, and what does not exist at all. Call this BEFORE answering what is possible on a surface — the three lists come from a table with a code reference per entry, checked in every PR, not from memory. Saying "that does not exist" about something the editor can do is the failure this tool prevents. Surfaces: edit, vfx, audio, idea, script, axis, location, crew, cast, pose, capture.
| Argument | Type | Description |
|---|---|---|
| flaeche* | string | The working surface |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"flaeche"
],
"properties": {
"flaeche": {
"enum": [
"edit",
"vfx",
"audio",
"idea",
"script",
"axis",
"location",
"crew",
"cast",
"pose",
"capture"
],
"type": "string",
"description": "The working surface"
}
},
"additionalProperties": false
}Turn a loose idea into a production-ready image or video prompt — the same Dreamer engine the FilmLab web UI uses (modes: beautify, storyboard, polish, format, shorten, expand, simplify, custom, stiluebernahme). beautify GENERATES (a 50-character thought comes back as a full prompt); polish/format/shorten/expand/simplify PRESERVE what is there. medium=video plus depth=skeleton yields the 15-block production prompt; depth only applies to video. anlass only applies to beautify. custom and stiluebernahme require instruction. For video the answer carries clip_duration_seconds — hand THAT number to lab_animate(duration_seconds), otherwise a prompt timed to 12s gets rendered as lab_animate's 5s default. In an Apps-capable host this opens as a form you can adjust and re-run; elsewhere it answers with the prompt as plain text. Pass `tool` (the registry key you will hand to lab_animate/lab_generate) and the finished prompt is laid out in the skeleton of that model family — location, camera, lens and light land where that family reads them, worded the way it reads them; families without a measured rule keep them appended as before, and the answer says so (prompt_skelett).
| Argument | Type | Description |
|---|---|---|
| mode | string | Default beautify (generates). beautify: Aus der Idee einen vollständigen Prompt bauen — ergänzt Licht, Optik, Kamera, Stimmung | storyboard: Szene in 4–8 nummerierte Panels zerlegen — je Panel ein eigenständiger Bild-Prompt | polish: Wortwahl polieren, Flow verbessern, Redundanz entfernen — Länge bleibt | format: In // Subject // Lighting // Camera Sektionen ordnen, Wortlaut bleibt | shorten: ~30% kürzer, jedes Wort muss Gewicht tragen | expand: Texturen, Lichtreflexe, atmosphärische Details | simplify: Direkt und klar, weniger Fachjargon | custom: Eigene Anweisung — z.B. 'Mach es düsterer' | stiluebernahme: Licht, Optik und Look aus einer Beschreibung — Motiv und Bildaufbau bleiben wörtlich |
| tool | string | Optional: the model you will render with, as the registry key from lab_list_models. Decides which family skeleton lays out camera, lens and light — and whether it is a measured rule or the plain appended form. |
Generate product-photo views of a prop using Flux 2 Pro. Costs credits per view: front is text-to-image, side/back/top/detail edit the front anchor. First call with get_cost:true, name the exact credits to the person, and obtain confirmation before generating. Missing images are refunded.
| Argument | Type | Description |
|---|---|---|
| anker | boolean | |
| views | array | |
| prop_id* | string | |
| get_cost | boolean | Free price query: no generation, no charge |
| project_id* | string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prop_id"
],
"properties": {
"anker": {
"type": "boolean"
},
"views": {
"type": "array",
"items": {
"enum": [
"front",
"side",
"back",
"top",
"detail"
],
"type": "string"
},
"minItems": 1
},
"prop_id": {
"type": "string"
},
"get_cost": {
"type": "boolean",
"description": "Free price query: no generation, no charge"
},
"project_id": {
"type": "string"
}
},
"additionalProperties": false
}Propose a generation for the user to confirm: validates the parameters against the catalog, computes the price, and shows a card (widget hosts) or returns the same object as text. Every prompt goes through the Dreamer first: run it through lab_prompt_transform before proposing (lab_generate/lab_animate enforce this; proposals and batches do not yet). NOTHING runs until lab_run_proposal is called with the returned vorschlag_id — by the card's button, or by you after the user said yes. Without a measured price there is no vorschlag_id and no button: the answer then says so instead of offering an estimate as if it were a quote. Free.
| Argument | Type | Description |
|---|---|---|
| kind* | string | Which kind of generation to propose |
| tool | string | Model id, copied verbatim from lab_list_models. Omit to use the default for this kind — the card names it and prices it either way. |
| in_sec | number | Video: start of the excerpt cut out of the reference clip, in seconds. Only together with out_sec and source_video_id. |
| intent | string | Video intent from the routing rules. Used to choose a model when tool is omitted and retained for Plan B; an explicit tool wins. |
| prompt* | string | What to generate. The user can edit it on the card; lab_run_proposal takes the edited text as prompt_override without a new proposal. Video only: For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: "<line>" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there. |
Render a new clip FROM the motion of an existing one: your reference clip leads (camera move, blocking, timing), the model restyles what happens in it. Pass the clip's URL directly — the one you already have from lab_list_assets (asset_type 'video'), lab_job_status or lab_review_queue; nothing is looked up. Reference images bind cast, place and look; in_sec/out_sec cut the reference server-side before it ever reaches the provider, so you pay for the excerpt, not the source. Same backend path as lab_animate's source_video_id, with the fields that only exist there. Costs credits when called without get_cost. WITHOUT a reference clip (leave reference_video_url out), the images and sounds in `referenzen` alone guide the run — only for models whose lab_list_models entry says referenzen_ohne_video: true, and only FACELESS references (ort, stil, bild, stimme, ton): Seedance 2.5 rejects faces as reference images, even invented characters (measured 2026-09-27), so `figur` is refused before anything is charged. For a person, build a still with them first and animate it with lab_animate. Name the references in the prompt as 'reference only, never shown on screen', and avoid the word 'static' for short shots — it tends to return frozen frames.
| Argument | Type | Description |
|---|---|---|
| ton | string | Which sound the result carries. quelle: the reference clip's own audio is laid back under the new picture after generation — the way to keep a spoken take, since models that regenerate audio replace the voice. generiert: keep the model's sound (needs generate_audio: true). keiner: no sound. Omitted: quelle when a reference clip is given and generate_audio is off, generiert when generate_audio is on, keiner otherwise. The finished run reports in metadata what actually lies underneath (ton) and how far picture and sound differ in length (ton_drift_s). |
| task | string | reference (default): the references guide a new clip. editing: the picture of the reference clip stays and only what the prompt names changes — e.g. 'keep the picture, generate matching sound effects, no music' with generate_audio: true. Needs a reference clip. Edit verbs in the prompt are expected here, not refused. |
Attach an existing project image (an image_id from lab_umzug, lab_get_job result.bildzeilen or lab_generate) as reference: `ziel: 'figur'` makes it a view of a character (default view 'detail', the face anchor). `personenbezug` is REQUIRED for a character and has no default: 'keine_reale_person' or 'reale_person' — the latter needs a likeness_ref from the Likeness Register and turns the character into a digital double, checked before every later generation. `ziel: 'location'` stores it as the location's reference (viewer, continuity check). It does NOT steer generation — only the description does; `beschreibung_ableiten: true` derives the description from the image (does not overwrite an existing one unless `ersetzen`). An occupied character view answers 409 unless `ersetzen: true`. Free, except the description derivation (one vision call).
| Argument | Type | Description |
|---|---|---|
| view | string | |
| ziel* | string | |
| ziel_id* | string | Character id or location id |
| ersetzen | boolean | |
| image_id* | string | Image id from lab_umzug {bilder}, lab_get_job result.bildzeilen or lab_generate |
| likeness_ref | string | Character + reale_person only: Likeness Register id |
| personenbezug | string |
Use when the person asks HOW to stage or shoot a feeling or effect (which angle, movement, framing or light makes a scene feel tense, tender, triumphant, uneasy) or wants directing options for a shot. Directing techniques: camera angle, framing, composition, movement, lighting and atmosphere (haze, dust, embers, backlit rain), each with the emotional effect it creates (wirkung), when to use it (zweck_de), when it backfires (vermeiden_de) and a model-neutral prompt fragment (prompt_en). Search by effect (e.g. wirkung=bedrohung, isolation, sehnsucht) to find the ways to get there, then pass up to three `schluessel` to lab_prompt_transform(techniken) — the fragments are appended verbatim, not rewritten. One angle, one movement, one lighting setup and one atmosphere per shot; framing and composition stack. With handwerk=true it also returns the camera craft: how to state a shot so a model renders it (rules), look-alike pairs with a check sentence each (verwechslungen — e.g. low angle vs Dutch angle, OTS vs POV), focal lengths and the frame boundary of every shot size. Read-only, free.
| Argument | Type | Description |
|---|---|---|
| gruppe | string | Only one group |
| wirkung | string | Only techniques with this effect key — the answer lists all keys under `wirkungen` |
| handwerk | boolean | Also return the camera craft (rules, look-alike pairs, focal lengths, frame boundaries) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"gruppe": {
"enum": [
"winkel",
"kadrage",
"komposition",
"bewegung",
"licht",
"luft"
],
"type": "string",
"description": "Only one group"
},
"wirkung": {
"type": "string",
"description": "Only techniques with this effect key — the answer lists all keys under `wirkungen`"
},
"handwerk": {
"type": "boolean",
"description": "Also return the camera craft (rules, look-alike pairs, focal lengths, frame boundaries)"
}
},
"additionalProperties": false
}See the DELIVERED film: up to 12 frames from the finished export (lab_start_export → lab_get_export status done), tiled into one image and shown. This is the rendered picture WITH everything the render adds — lens/grade, lower thirds, title cards and the AI badge — so use it to check the result, e.g. 'is the lower third readable at 7 s, does the badge cover anything'. lab_capture shows the cut BEFORE rendering (computed from the timeline, without those layers); this tool shows what was actually delivered. Give `sekunden` (seconds into the exported film) or `verteilt` (n frames spread evenly; default 6 when both are missing). A second past the end is an error with the film's length, not a silently clipped frame. Only finished exports with a video (not archive/PNG-sequence profiles). Free (no credits).
| Argument | Type | Description |
|---|---|---|
| job_id* | string | Export job id from lab_start_export / lab_list_exports — positive integer as string |
| preview | boolean | Show the image inline (default true) |
| sekunden | array | Seconds into the exported film, e.g. [0.5, 7.0]. Order is kept (4 per row) |
| verteilt | integer | Instead of sekunden: this many frames spread evenly over the film |
| kachel_breite | integer | Width of one frame in px (default 480) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "Export job id from lab_start_export / lab_list_exports — positive integer as string"
},
"preview": {
"type": "boolean",
"description": "Show the image inline (default true)"
},
"sekunden": {
"type": "array",
"items": {
"type": "number",
"minimum": 0
},
"maxItems": 12,
"minItems": 1,
"description": "Seconds into the exported film, e.g. [0.5, 7.0]. Order is kept (4 per row)"
},
"verteilt": {
"type": "integer",
"maximum": 12,
"minimum": 1,
"description": "Instead of sekunden: this many frames spread evenly over the film"
},
"kachel_breite": {
"type": "integer",
"maximum": 960,
"minimum": 240,
"description": "Width of one frame in px (default 480)"
}
},
"additionalProperties": false
}Restyle an existing board towards a wish (costs credits — exact price via get_cost: true) — same place, different look, e.g. 'the same flat, twenty years later'. Changes the board itself, so images generated afterwards follow the new style. Also the repair path for a board whose prompt_prefix_en is a raw field dump (lab_create_location returned prefix_generated: false): restyle it with a wish like 'keep the place, write a proper image prompt'.
| Argument | Type | Description |
|---|---|---|
| wish | string | The desired change, in prose |
| loc_id* | string | Location board id (from lab_list_locations) |
| get_cost | boolean | Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"loc_id"
],
"properties": {
"wish": {
"type": "string",
"maxLength": 600,
"minLength": 3,
"description": "The desired change, in prose"
},
"loc_id": {
"type": "string",
"description": "Location board id (from lab_list_locations)"
},
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this operation; nothing is rendered or charged. Name it to the person before the real run."
}
},
"additionalProperties": false
}Review generated assets: set verdict (accepted|rejected|pending) and/or selected. Pass ONE id, or `ids` for a bulk review — after a generation run there are usually dozens to sort, and clicking through them one by one is why the review fields went unused. Rejected assets drop out of the default listings and become prunable. Note: single-id calls make `selected` exclusive per scene (one chosen variant per scene_ref); bulk calls do NOT, because a bulk over one scene would keep resetting itself and only the last id would stay selected.
| Argument | Type | Description |
|---|---|---|
| id | string | Single asset id (positive integer as string). Use this for the per-scene selection |
| ids | array | Many asset ids for a bulk review (max 200). Mutually exclusive with `id` |
| reason | string | Why — stored as verdict_reason, useful when reviewing a rejection later |
| verdict | string | Acceptance verdict. Feeds the acceptance telemetry (which provider/mode/price actually produced usable shots) |
| selected | boolean | Mark as the chosen variant |
| reference_type* | string | What kind of asset these ids refer to |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"reference_type"
],
"properties": {
"id": {
"type": "string",
"description": "Single asset id (positive integer as string). Use this for the per-scene selection"
},
"ids": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 200,
"minItems": 1,
"description": "Many asset ids for a bulk review (max 200). Mutually exclusive with `id`"
},
"reason": {
"type": "string",
"maxLength": 500,
"description": "Why — stored as verdict_reason, useful when reviewing a rejection later"
},
"verdict": {
"enum": [
"accepted",
"rejected",
"pending"
],
"type": "string",
"description": "Acceptance verdict. Feeds the acceptance telemetry (which provider/mode/price actually produced usable shots)"
},
"selected": {
"type": "boolean",
"description": "Mark as the chosen variant"
},
"reference_type": {
"enum": [
"video",
"image"
],
"type": "string",
"description": "What kind of asset these ids refer to"
}
},
"additionalProperties": false
}Browse GENERATED rows (images or videos) — newest first, each with the INPUTS it ran on. Every row carries `inputs[]` (start/reference images, reference videos, character refs — role + url) and `params` (model, mode, resolution, seed…). Read those before comparing two runs: a run without its inputs is a claim with a picture attached, and a comparison described as one-variable has more than once turned out to differ in three. An input whose URL was never recorded appears as `origin: 'unrecorded'` — that is a gap in the write path, NOT a text-to-* run. Two modes. PER PROJECT (pass project_id + type): adds the review filters — verdict, selected_only, scene_ref — and carries `selected`. ACROSS PROJECTS (omit project_id, or pass since/until/tool_used/cursor): the time-range view for 'what ran on August 3rd?'. The review filters do not exist there and are rejected rather than silently dropped. Page with `next_cursor`.
| Argument | Type | Description |
|---|---|---|
| type | string | Which generated rows. Required per project; across projects, omit for both |
| limit | integer | Max rows (per project default 50). Across projects the page is capped at 20 so next_cursor stays exact |
| since | string | ACROSS PROJECTS. created_at >= this ISO-8601 timestamp (e.g. 2026-08-03T00:00:00Z) |
| until | string | ACROSS PROJECTS. created_at <= this ISO-8601 timestamp |
| cursor | string | ACROSS PROJECTS. The next_cursor of the previous page. Opaque — do not build one by hand |
The Montage recipe: one recurring character in 8–16 independent everyday gags, cut on the beat into a 9:16 reel of about 6–11 seconds with a music bed. It runs the existing steps in order — gags (lab_situationen), stills with the character and the phone-flash look (auto-storyboard 9:16), identity check with ONE retry per deviating still, a STOP until the human accepts the stills (lab_review_asset), music, sequence, beat grid and rhythm, beat-length clips, placing them, export (social_vertical). If the export contains a synthetic PERSON or VOICE, it needs a release only a signed-in human can give: the recipe STOPS with stopp freigabe_noetig — the human exports that sequence with social_vertical in the web app and confirms the wording; the next weiter takes over that export and checks it. Each call with aktion weiter does exactly ONE step and stores the state in the project, so an interrupted run continues where it stopped; call it again until phase is fertig. MONEY: aktion preis (or get_cost) returns the expected and the maximum price per item and a recommended cap — free. Paid steps run only inside a released lab_auto_lauf (lauf_id) and the recipe checks its free amount before every paid step. The character must already have a sheet with a reference image (lab_create_character with gattung, then lab_generate_character_reference) — it is not part of the price. aktion stand shows the state, abbrechen deletes it (generated material stays).
| Argument | Type | Description |
|---|---|---|
| bpm | integer | Start only: tempo written into the default music prompt (default 120) |
| thema | string | Start only: theme or setting for the gags, e.g. 'big city at night' |
| aktion* | string | preis: price per item; weiter: one step; stand: show state; abbrechen: delete the state |
| anzahl |
Run ONE pipeline module for a project (context is injected from upstream modules + crew agents). SYNCHRONOUS and LLM-heavy: light modules return in seconds; heavy ones can exceed ~55s and then time out (504) — run those in the FilmLab web app instead. Pass an invalid module name to get the list of valid module keys. Returns the module output.
| Argument | Type | Description |
|---|---|---|
| module* | string | Pipeline module/stage key, e.g. mod_concept, mod_storyboard, mod_shotlist, mod_script |
| message* | string | Your instruction/prompt for this module (the user turn in the module chat) |
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"module",
"message"
],
"properties": {
"module": {
"type": "string",
"description": "Pipeline module/stage key, e.g. mod_concept, mod_storyboard, mod_shotlist, mod_script"
},
"message": {
"type": "string",
"description": "Your instruction/prompt for this module (the user turn in the module chat)"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}Run a proposal exactly as shown — same model, same parameters, same price. Called by the card's button, or by you after the user confirmed. Single use: the proposal is consumed BEFORE the run, so a second call is refused instead of paying twice. Valid 15 minutes, for the account that created it. Costs credits. The run is checked against what you confirmed only when it FINISHES; poll lab_job_status and read review there. This answer carries review only if the run completed synchronously.
| Argument | Type | Description |
|---|---|---|
| vorschlag_id* | string | The id from lab_propose or plan_b in lab_job_status. Do not construct one — an unknown id answers 'vorschlag_abgelaufen', not an error you can work around. |
| ohne_keyframe | boolean | Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check. |
| prompt_override | string | Replace ONLY the prompt text of the stored proposal. Every other parameter — model, resolution, duration, references — and the price stay exactly as shown. This is the path for a prompt the user edited on the card: the price does not depend on the text, so no new proposal is needed. Standbild-Bewegung uses no model prompt; confirm that proposal unchanged. An edited text goes through the Dreamer first: without a matching dreamer_signatur the call does NOT run, the proposal stays valid, and the answer carries the Dreamer version to confirm. |
| dreamer_signatur | string | Dreamer proof for prompt_override (from the previous answer's dreamer_signatur). |
Export ONE sequence as an editing file for DaVinci Resolve, Premiere or Final Cut, so the cut can be finished there. Costs nothing. fcpxml (recommended for Resolve): V1 as the main storyline with gaps, the sequence's transitions as centered cross dissolves (dropped where the next clip has no head or at a gap), audio stems and V2+ as connected clips. otio: OpenTimelineIO JSON (video and gaps, speed; no audio, no transitions). edl: CMX 3600 (video plus up to 4 audio channels; no transitions). The answer carries the whole file in `inhalt` and a file name — write it to disk for the person. Media are referenced by their FilmLab URLs: in Resolve, File > Import > Timeline, then relink offline clips by file name.
| Argument | Type | Description |
|---|---|---|
| format* | string | fcpxml for Resolve/Final Cut (carries transitions), otio for OpenTimelineIO tools, edl as the lowest common denominator |
| seq_id* | string | Sequence id (from lab_list_sequences/lab_get_sequence) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"format"
],
"properties": {
"format": {
"enum": [
"fcpxml",
"otio",
"edl"
],
"type": "string",
"description": "fcpxml for Resolve/Final Cut (carries transitions), otio for OpenTimelineIO tools, edl as the lowest common denominator"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_list_sequences/lab_get_sequence) — positive integer as string"
}
},
"additionalProperties": false
}Measure ONE clip of the edit as numbers instead of a picture: a frame is rendered through the clip's export path (input profile, grade LUT, secondary correction) and measured — luma percentiles (P1/P50/P99, Rec.709), mean R/G/B, green excess (G minus the mean of R and B: >0 green cast, <0 magenta), vectorscope centroid (Cb, Cr), clipped highlights and crushed shadows. `befund` says what is off in words with the number; `vorschlag.grade` is a correction computed on a sample of the source through the clip's full grade (gamma wheel master first, then exposure, temperature, tint, gain wheel) towards median luma 0.42 and no cast WITHOUT pushing highlights into clipping, with the values it expects — pass it to lab_grade_clip, then measure again. When nothing more is gained, `vorschlag.grade` is null and `grund` says why. Give clip_id (from lab_grade_clip without grade) or zeit_s (seconds on the timeline); default is the middle of the clip. Writes nothing, costs nothing.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
| zeit_s | number | Timeline position in seconds (picks the clip there; within a clip_id: the frame) |
| clip_id | string | Clip to measure |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
},
"zeit_s": {
"type": "number",
"description": "Timeline position in seconds (picks the clip there; within a clip_id: the frame)"
},
"clip_id": {
"type": "string",
"description": "Clip to measure"
}
},
"additionalProperties": false
}Search the FilmLab universe catalog of AI tools, news and companies. Query must be at least 2 characters. Returns up to 20 matches with a total count.
| Argument | Type | Description |
|---|---|---|
| q* | string | Search query, min 2 characters |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"q"
],
"properties": {
"q": {
"type": "string",
"minLength": 2,
"description": "Search query, min 2 characters"
}
},
"additionalProperties": false
}Generate or CLEAN UP a SOUND track other than music — sound effects, ambience/atmosphere, dialog, a voiceover, FOLEY scored to an existing clip, or the repair kinds that take an EXISTING recording apart instead of inventing one: denoise (take the noise out), split (separate it into stems), isolation (keep the voice, drop the rest). All of it goes through the same audio pipeline as lab_generate_music. ASYNCHRONOUS: the call returns immediately with a row whose `audio_url` is null and `metadata.status` is 'pending'. Nothing is playable yet. Poll lab_list_audio (matching audio_type) until that row's status is 'completed' and audio_url is set; a failed row refunds its credits and carries metadata.error. The backend picks the provider for the chosen `art` on its own — there is no `tool` parameter here.
| Argument | Type | Description |
|---|---|---|
| art* | string | Kind of track — sfx (a single sound effect), ambience (a continuous atmosphere/soundscape), dialog (a spoken line), voiceover (narration), foley (sound derived FROM an existing clip and synced to what happens in it — needs `video_id`, ignores `duration_seconds`, and takes its length from the source). The repair kinds work on a recording you already have and need exactly one source (`audio_id`, `video_id` or `audio_url`), never a prompt: denoise (take out hiss, hum and room noise), split (separate the recording into stems — one audio row per stem), isolation (keep only the voice, drop room, noise and music). Their length follows the source, so `duration_seconds` is ignored. Not 'music' — that stays lab_generate_music, it binds a different stem |
| stems | array | SPLIT ONLY: which parts to separate out. Left out, you get the voice and the rest of the recording, each part as its own audio row. Asking for a subset drops what you leave out — there is no combined everything-but-the-voice file, that would be a mixdown the provider never made. |
| prompt | string |
Read the SHOT layer of a sequence — which clips form one shot, which scene it plays in, who is in it, and what the continuity pass found. This is the layer above the timeline: lab_get_sequence gives you clips in a row, this one gives you the cut. Costs nothing, writes nothing. Use it before changing a cut: a shot binds clip_ids, so the times come out of the timeline and a shot whose clips you shorten simply gets shorter. Read `clips_ohne_shot` as well — it says how far the meaning lags behind the timeline, and an empty `shots` list usually means nobody has derived them yet (lab_shots_ableiten does that, for free). Every shot reports `clips_verwaist` (clip_ids that no longer exist in the timeline) and `zusammenhaengend` (false when another shot's clip sits between this one's): a torn shot is allowed — one location cut into twice — but it says so, instead of showing a duration longer than the sum of its clips.
| Argument | Type | Description |
|---|---|---|
| rung | string | Which level to read. 'shot' (the default) is one take; 'movement' groups shots into arcs. They are independent layers over the same clips — reading one never shows the other |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"rung": {
"enum": [
"shot",
"movement"
],
"type": "string",
"description": "Which level to read. 'shot' (the default) is one take; 'movement' groups shots into arcs. They are independent layers over the same clips — reading one never shows the other"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
}
},
"additionalProperties": false
}Derive the shot layer from what the clips already say about themselves — consecutive clips of the same scene become one shot, and a scene that returns later becomes a second one (one location cut into twice is two shots). Costs nothing: it groups rows and reads titles, it generates nothing. Titles come from the project's shotlist where the scene is in there, so this is more than a regrouping — it fetches meaning the pipeline already produced. SHOTS YOU SET BY HAND SURVIVE. Only derived ones are replaced, and clips already held by a manual shot are left out of the grouping entirely — a derivation does not take handwork's material away. Read `clips_ohne_szene` in the answer: those clips say nothing about where they belong, so each became its own shot. That number is a job for the pipeline, not a fault of this call — and grouping two nameless clips would be a claim about connection that nobody made.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
}
},
"additionalProperties": false
}State the shot cut yourself: which clips form one shot, what it is called, which scene it plays in. This is the counterpart to lab_shots_ableiten — that one groups by the scene the clips already name, this one is for the judgement no field carries. Costs nothing. Use it where derivation cannot reach: ingested footage has no shotlist by nature, and a sequence whose clips name no scene would come out of a derivation as one shot per clip — the timeline under another name. It is also the ONLY way to build the 'movement' layer (arcs over shots); the derivation only ever writes 'shot'. REPLACES the whole level in one go: every shot of this `rung` that you do not list is gone. The other level is untouched — recutting shots does not throw the arcs away. Refusals happen BEFORE anything is written: an unknown clip_id, or the same clip in two shots of one level. A half-set shot list looks like a result and is none. Read lab_shots first and send its clip_ids back — a shot binds clip identities, not time ranges, so shortening a clip later simply shortens the shot. What you set here counts as handwork: a later lab_shots_ableiten leaves these shots alone and does not touch their clips.
| Argument | Type | Description |
|---|---|---|
| rung | string | Which level to replace. 'shot' (the default) is one take; 'movement' groups shots into arcs. Independent layers over the same clips — writing one never touches the other |
| shots* | array | The whole level, in order. The order here IS the order of the shots |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
Suggest independent visual gags for a character and theme. Free text generation, no images or credits spent. Returns anzahl entries with titel, bild_en and bewegung_en: one moment and one strong action each, no story sequence or references between entries. figur accepts a character-sheet id (uses its prompt_prefix_en) or a free-text character description. Select 8–16 suggestions for lab_auto_storyboard with figur_id.
| Argument | Type | Description |
|---|---|---|
| figur* | any | Existing character-sheet id or free-text character description |
| thema* | string | Theme or setting for the independent gags |
| anzahl | integer | Number of suggestions, default 20 |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"figur",
"thema"
],
"properties": {
"figur": {
"anyOf": [
{
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
{
"type": "string",
"maxLength": 4000,
"minLength": 1
}
],
"description": "Existing character-sheet id or free-text character description"
},
"thema": {
"type": "string",
"maxLength": 2000,
"minLength": 1,
"description": "Theme or setting for the independent gags"
},
"anzahl": {
"type": "integer",
"maximum": 40,
"minimum": 1,
"description": "Number of suggestions, default 20"
}
},
"additionalProperties": false
}Start an ASYNC export render of a project sequence. Returns a job_id immediately (status 'queued') — does NOT block. Then poll lab_get_export with that job_id until status is done|failed. Needs the sequence_id to render (an integer from the project's sequences). `derivatives` asks for extra cut-downs of the SAME render (portrait for social) alongside the main file — their URLs come back in the finished job's `derivatives`. The finished job also carries `zielmarkt_checkliste` (delivery checklist per target market, set with lab_zielgruppe).
| Argument | Type | Description |
|---|---|---|
| profile | string | Delivery profile (default 'web'). See lab_export_profiles. 'alpha_prores4444' and 'alpha_png_sequence' keep transparency and are refused with an explanation unless a clip in the sequence really carries an alpha channel. |
| quality | string | Quality preset (default 'standard') |
| project_id* | string | Project id (from lab_list_projects) |
| derivatives | array | Extra cut-downs rendered from the FINISHED export, on top of the main file. '9x16' is a deterministic crop of the wide render into portrait — what falls outside the portrait frame is gone, not filled in. Their URLs arrive in the completed job's `derivatives`. |
| sequence_id* | string | Sequence id to render (positive integer as string, e.g. '27') |
| ohne_clipton | boolean | Leave the clips' OWN audio out of the export (e.g. a video model whose sound is always on) — the film then sounds only from the bound stems and the mixdown (music bed, narrator). Default false: the clips' own audio is mixed under the bed as before. |
Hand the sound over to whoever mixes next (Resolve/Fairlight, Pro Tools, a dubbing studio): every stem of the sequence (dialog, music, sfx, atmo) as a 24-bit / 48 kHz stereo WAV, rendered the way it goes into the mix — effects, fader, pan and ducking baked in — plus an M&E file (music + effects + atmo WITHOUT the ducking on the dialog, for dubbed versions). All files start at the start of the sequence and are exactly the same length, so they line up at 00:00 in any DAW. Delivered as ONE zip with stems.json (format, length in samples, loudness/true peak per file) and LIESMICH.txt. The master (EQ/compressor/limiter) and the loudness normalisation are NOT in the stems: their sum is the mix before the master. Needs bound stems (lab_bind_audio). Runs as a BACKGROUND JOB: poll lab_get_job — stems_zip_url is in job.result. Changes nothing in the mix, costs nothing. Picture handover is lab_schnitt_export.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
}
},
"additionalProperties": false
}Set the project's default style kit (it then runs in every image and clip of the project), or clear the default by omitting stil_kit_id. Free.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | |
| stil_kit_id | string | Kit id from lab_list_stil_kits; omit to clear the default |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string"
},
"stil_kit_id": {
"type": "string",
"description": "Kit id from lab_list_stil_kits; omit to clear the default"
}
},
"additionalProperties": false
}Ask what clip durations the project's cutting preset WOULD give a sequence — and change nothing (`angewendet: false`). Use it to see the consequence before lab_apply_rhythm writes it, or to size a sequence that has no clips yet (pass `anzahl`). It knows nothing about the measured beat grid: the tempo it uses is the text from the preset's music brief ('mittel bis schnell, 100–128'), which is a promise, not a measurement. Only lab_apply_rhythm reaches for the grid that lab_beatgrid measured. Reads only — no state, no credits.
| Argument | Type | Description |
|---|---|---|
| anzahl | integer | How many shots to distribute. Omit to use the sequence's existing clip count — required if the timeline is still empty, which otherwise answers 400 |
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"anzahl": {
"type": "integer",
"maximum": 500,
"minimum": 1,
"description": "How many shots to distribute. Omit to use the sequence's existing clip count — required if the timeline is still empty, which otherwise answers 400"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
}
},
"additionalProperties": false
}Write a WHOLE cut into a sequence in one call — clips in order with their lengths, an optional dissolve between them, an optional bed underneath. This is the counterpart to lab_get_sequence: that one reads the timeline, this one writes it. Prefer it over a chain of lab_add_clip calls whenever you already know the whole cut: a chain that breaks in the middle leaves half a sequence behind, this either applies or refuses. Costs nothing — it validates and writes rows, it generates nothing. Every video_id is checked against the project BEFORE anything is written, so a wrong id is a refusal here and not a gap in the cut nobody notices. Pass `sequence_id` to write into THAT sequence; omit it and a new one is created. Pass `sequence_id` WITHOUT a manifest to read back the stored cut as a manifest — for an agent that already has the sequence_id and wants to see what is in there before changing it. (The Resolve bridge does NOT go through here: it talks to the FilmLab server directly with its own bridge key and does not know this MCP.) Reworking a cut you read back? Keep each clip's `clip_id`. A clip that comes back with its identity keeps its track, its in-point in the source and its scene, and the shots bound to it stay bound; a clip without one is treated as new and gets a fresh identity, which orphans whatever pointed at the old one. Two honest limits: the Resolve bridge applies the clips but reports `uebergang` and the bed's level/fade as not applied (its scripting API has neither a transition call nor a clip volume property). The FilmLab sequence render applies the bed with its level and fade, but NOT `uebergang` yet — dissolves are stored and render as hard cuts (checked 2026-10-03).
| Argument | Type | Description |
|---|---|---|
| title | string | Title for the sequence created when `sequence_id` is omitted |
| manifest | object | The cut as one object. Omit it only together with a `sequence_id`, to read back what is stored |
| project_id* |
Generate SOUND EFFECTS for a whole sequence, shot by shot — Simon Meyer's pass: every shot is muted, padded with black frames up to the model's minimum length, re-rendered by Seedance 2.5 in editing mode at its low resolution with sound on, and only the generated sound is kept, stretched back to the original timing (the black padding is the sync marker) and laid at the shot's position. The result is ONE sfx track in sequence length, bound to the sequence's sfx stem; the other stems stay as they are. Each piece and its offset are listed in the audio row's metadata (stuecke). Shots come from the sequence itself, in export order and export length — no cut detection. Costs credits, priced per shot over the seconds actually sent (the padding counts); call with get_cost first. ASYNCHRONOUS: returns a job_id; poll the job until done. A shot that fails is refunded on its own and named in fehlende_einstellungen; if the whole run fails, all of it is refunded. The model is told: keep the picture, diegetic sound only, no music, no dialogue, no voices — `hinweis` adds a setting line for every shot.
| Argument | Type | Description |
|---|---|---|
| seq_id* | string | Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string |
| hinweis | string | One line about the setting that applies to every shot, e.g. 'kitchen, wooden floor, rain on the window'. Helps the model pick the right room sound. Leave it out if the shots differ too much. |
| get_cost | boolean | Return the price in credits WITHOUT starting anything — per shot over the seconds actually sent, at the rate lab_list_models lists for the model. Do this first; long sequences add up. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"hinweis": {
"type": "string",
"maxLength": 500,
"description": "One line about the setting that applies to every shot, e.g. 'kitchen, wooden floor, rain on the window'. Helps the model pick the right room sound. Leave it out if the shots differ too much."
},
"get_cost": {
"type": "boolean",
"description": "Return the price in credits WITHOUT starting anything — per shot over the seconds actually sent, at the rate lab_list_models lists for the model. Do this first; long sequences add up."
}
},
"additionalProperties": false
}Relight and restyle an EXISTING clip while the performance stays untouched (Beeble Switch X): lighting, background and props change, the subject and its motion do not. The source is the clip of a generated-video row — or, with asset_id, an uploaded file that stands in for it. in_sec/out_sec cut the source server-side first, so a long take can be transformed in pieces. ASYNCHRONOUS: a new video row comes back as pending; poll it with lab_job_status. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| in_sec | number | Cut the source server-side before it reaches the provider: start of the excerpt, in seconds. Only together with out_sec. What is billed is the MEASURED length of the cut, not out_sec minus in_sec. |
| out_sec | number | End of the excerpt, in seconds. Must lie behind in_sec; both are set together or neither. |
| asset_id | string | R2 asset key of an uploaded video (the `key` from lab_list_assets, asset_type 'video') to transform INSTEAD of the row's own clip. The row from video_id still supplies project and scene; only the pixels come from the asset. The key must belong to that same project. |
| get_cost | boolean | `true` returns the published rate per unit and your balance WITHOUT transforming and without spending anything — NOT a total. Switch X charges by the MEASURED length of the source, which only the server knows before the run, and it is not priced from the lab_list_models catalog (that covers generation models). The real amount comes back with the actual call. |
| video_id* | string | Generated-video id (from lab_animate/lab_job_status/lab_review_queue) — positive integer as string. ALWAYS required, also together with asset_id: this row anchors the run (project, scene, billing) and the backend answers 404 without it. |
Bring Midjourney exports into a project (FilmLab calls this 'Umzug'). Midjourney has no API, so the person downloads their images or ZIP and this tool carries them on. Give EXACTLY ONE of: `dateien` (names/types/sizes of local files → signed PUT addresses with a ready curl line; PUT the raw bytes, then call again with `quellen` from `weiter`), `quellen` (after the PUT: starts the import job → job_id; poll lab_get_job — result.bildzeilen lists image_id and datei per image (the prompt per image comes with `bilder: true`), result.gesperrt names refused images with the reason, result.ungeprueft images whose check was unreachable), `prompts` (pasted Midjourney prompts, one per line → parameters split and what does not carry over, e.g. --sref codes; free, no job), `anker` ({name, image_ids ≤ 12} → a look anchor for lab_generate's anker_id; images of real people are refused) or `bilder: true` (the project's imported images with image_id and prompt). Every imported image is declared AI material (EU AI Act Art. 50) — no herkunft question. An image_id works directly in lab_animate (source_image_id), as a seed in lab_generate and in lab_referenz_aus_bild. Uploading is free.
| Argument | Type | Description |
|---|---|---|
| anker | object | Build a look anchor from imported images |
| bilder | boolean | true: list the project's imported images |
| dateien | array | Local files to upload: ZIP archives or single PNG/JPG/WEBP |
| prompts | string | Pasted Midjourney prompts, one per line |
| quellen | array | After the PUT: exactly the `quellen` from the previous answer's weiter.argumente |
Enlarge an EXISTING clip (Topaz, passed through fal): same shot, same length, same sound — more pixels. There is no prompt here; nothing is re-imagined, the frames are scaled. The source is the clip of a generated-video row. Both the length and the source height are measured server-side before anything is charged, and a source too tall for the larger factor is refused rather than run. ASYNCHRONOUS: a new video row comes back as pending; poll it with lab_job_status. Costs credits when called without get_cost.
| Argument | Type | Description |
|---|---|---|
| faktor | string | How many times larger, per side. Omit for the smaller one. Whichever you pick, the RESULT has to stay inside the published rate tiers — a source too tall for the factor you asked for is refused by name, with the ceiling in the message, instead of billing a run nobody can price. What the tier costs is in lab_list_models and in the preflight, not here. |
| get_cost | boolean | `true` returns what upscaling THIS clip costs (`cost_generations`, measured server-side from its length and the height of the RESULT, exactly what the run would charge) plus the published rate per output second per tier and your balance — WITHOUT scaling and without spending anything. For a clip that is still pending there is no total yet: then `cost_generations` is null and `preis_grund` says why. Which tiers are measured and which are catalog values is the same distinction lab_list_models makes. |
| video_id* | string | Generated-video id (from lab_animate/lab_job_status/lab_review_queue) — positive integer as string. The row must already have its clip; a run still pending is refused, and nothing is charged for asking. |
| max_credits | integer | The most you agree to pay — pass the amount from get_cost that the person confirmed. If the measured price is higher when the run starts, it is refused before anything is charged. |
Render several clips as CHAINS: within a scene one after another (continuity), across scenes in parallel (speed) — the plan follows scene_ref of each job in order. aktion: plan (free: shows the chains) · preis (free: same checks as a real start; returns preis_geschaetzt_credits, preis_hoechstens_credits (with every allowed retry) and `reicht` against balance, project cap and an active auto run, plus startbilder_offen when a chain's first start image is not approved yet — nothing is created) · starten (COSTS CREDITS, ONLY after the person confirmed the price from aktion=preis). Each clip is charged when it starts; an active lab_auto_lauf cap applies. The FIRST clip of every chain needs an approved start image (keyframe checkpoint, as in lab_animate) unless ohne_keyframe; later clips derive from the chain. Sending starten twice returns the running chain (wiederverwendet: true) instead of paying twice. Transition between clips of a chain (uebergang): schnitt (default) = hard cut to a NEW camera angle — the next clip does NOT start from the last frame; its prompt begins with the previous clip's end state (wardrobe, props, place, light), characters and location come from each job's own character_refs/location_id. A schnitt follow-up clip is TEXT-TO-VIDEO: without character_refs/location_id the person and place hang on one sentence of text — give them for real continuity. CONTINUITY CHECK: in a chain with more than one clip every clip is checked against its prompt BEFORE the next one starts (status pruefen, wartet_auf: abgleich while the check runs — that is not a hung job). On mismatch the clip is re-rendered at most neuversuche times (default 1, prompt led by the failed claims, charged like the clip); if it still deviates the chain STOPS before the next clip is paid (chain status abgebrochen, clip status abweichung, issues and verworfen at the clip). The last clip of a chain gets its retry too but the chain then ends fertig with the verdict at the clip. Single clips are only checked in the background. durchgehend = the last frame becomes the start image — only for one unbroken take. Follow a started chain with lab_get_job(job_id): result.zusammenfassung (laeuft/fertig/abgebrochen), result.ketten (status and grund per chain) and result.clips (keyed by clip index: video_id, status, fehler_klasse). Job status done only means nothing runs any more — check result.zusammenfassung.abgebrochen before reporting success. A failure stops only its own chain. Dispatch rule: continuous scene → one chain with schnitt; one unbroken motion → a single clip ≤ 15 s, else durchgehend; separate scenes, time jumps, costume changes or montage → separate scenes (parallel). Over ~3 minutes total: suggest narrowing the scope first.
Run a VFX operation on an image already in the project gallery — the same pipeline the Resolve bridge uses. inpaint (clean plate: what is inside the box disappears, the surroundings close over it) · replace (same path, other intent: something new goes into the box) · relight (no box — the light changes across the whole frame; by default the scene stays exactly as it is, licht_weg 'iclight' instead repaints the background and may change the scenery) · mask (no box — you describe what should be cut out and get the matte back). inpaint/replace need `box` AND `prompt`; relight needs `light_preset` or `prompt` and REJECTS a box; mask needs `prompt` and REJECTS a box too. `box` is given in FRACTIONS of the image (0..1), not pixels — the worker builds the mask. inpaint/replace start at 5 credits per run on the reference area (larger areas cost more); relight costs 4 flat per image (licht_weg 'iclight': from 9, by area); mask costs 1, flat per request. Returns immediately with an operation_id; poll lab_vfx_status until status is done|failed. NOT offered here: composite, move, color_grade — the server knows them and has no consumer for them.
| Argument | Type | Description |
|---|---|---|
| box_x | number | Box left edge as a fraction of image width (0..1) |
| box_y | number | Box top edge as a fraction of image height (0..1) |
| prompt | string | inpaint/replace: what should be seen there instead — required. mask: what should be cut out (e.g. 'the red toolbox') — required. relight: optional, replaces the preset |
| image_id* | string | Id of the source image in the project gallery (from lab_list_assets / lab_generate) |
| box_hoehe |
Run a video VFX preset on a whole clip. Today only 'gesichter_verpixeln' (pixelate every face) is live. Costs credits per frame — call with dry_run=true first to see the price. With rerender_from (a finished job_id) it re-renders for free with other settings, e.g. exceptions: object ids that stay sharp. Tell the person: "Automatisch verpixelt, ohne Garantie — vor Veröffentlichung prüfen." Poll lab_vfx_preset_status until done|failed.
| Argument | Type | Description |
|---|---|---|
| effekt | string | |
| preset | string | |
| dry_run | boolean | Only return the price |
| ausnahmen | array | Object ids to leave unpixelated (rerender only) |
| video_url | string | HTTPS URL of the clip (project asset) |
| project_id* | string | Project id |
| rerender_from | string | job_id of a finished run to re-render for free |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"effekt": {
"enum": [
"mosaik",
"weich",
"schwarz"
],
"type": "string",
"default": "mosaik"
},
"preset": {
"enum": [
"gesichter_verpixeln"
],
"type": "string",
"default": "gesichter_verpixeln"
},
"dry_run": {
"type": "boolean",
"default": false,
"description": "Only return the price"
},
"ausnahmen": {
"type": "array",
"items": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991
},
"description": "Object ids to leave unpixelated (rerender only)"
},
"video_url": {
"type": "string",
"description": "HTTPS URL of the clip (project asset)"
},
"project_id": {
"type": "string",
"description": "Project id"
},
"rerender_from": {
"type": "string",
"description": "job_id of a finished run to re-render for free"
}
},
"additionalProperties": false
}Poll a video VFX preset job. status is queued|proxy|spur|render|done|failed; progress shows frames done. When done: result_url is the finished clip (give it to the person as karte_ansehen), kontakt_url is the review contact sheet, spur.objekte lists detected faces with ids usable as exceptions in lab_vfx_preset(rerender_from, ausnahmen).
| Argument | Type | Description |
|---|---|---|
| job_id* | string | job_id returned by lab_vfx_preset |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"job_id"
],
"properties": {
"job_id": {
"type": "string",
"description": "job_id returned by lab_vfx_preset"
}
},
"additionalProperties": false
}Poll a VFX operation started with lab_vfx. status is pending|processing|done|failed; when done, result_url carries the finished image. Call repeatedly until done|failed — an image run usually takes under 30 seconds.
| Argument | Type | Description |
|---|---|---|
| operation_id* | string | The operation_id returned by lab_vfx |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"operation_id"
],
"properties": {
"operation_id": {
"type": "string",
"description": "The operation_id returned by lab_vfx"
}
},
"additionalProperties": false
}Clone a voice from real audio or video material into a project (fal.ai minimax voice-clone) — the source for lab_voice_tts and for lab_dub's voice_id+dub_text path. SYNCHRONOUS: the clone runs inside the request and can take a while to return; there is no job_id to poll. The source needs a clean speech track of workable length — too short or silent material is rejected before anything is spent. Voice cloning from real material is consent-sensitive: only clone voices you have the right to use, and the Fish Audio path requires you to say so explicitly.
| Argument | Type | Description |
|---|---|---|
| name* | string | A label for this cloned voice, shown in lab_list_voices — needed to tell several clones of one project apart later. |
| get_cost | boolean | `true` reports the price and your balance WITHOUT cloning anything and without spending quota. A clone is billed per clone (a MiniMax clone costs far more than a Fish clone — ask with get_cost) — the length of the source material does not change it, and the clone is then reusable for as many readings as you like. Not drawn from the lab_list_models catalog (that covers image and video models only); there is no model to pick here. |
| provider | string | Which voice house clones it. Omit to stay on the default path — an existing workflow does not change vendor just because another one exists. The choice sticks to the voice: whichever house made it is the one that later speaks with it, so lab_voice_tts has no say in this. What each house can do and what it charges is not stated here — lab_list_models carries that. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
Speak text in a project's cloned voice (via lab_voice_clone) — SYNCHRONOUS, returns a ready audio_url, no job_id. The voice house is whichever one cloned this voice; you do not pick it here. Consent-sensitive like the clone itself: only speak text you have the right to put in that voice's mouth.
| Argument | Type | Description |
|---|---|---|
| text* | string | The line(s) to speak in this voice. |
| get_cost | boolean | Price this call instead of running it: returns the estimate and the balance, charges nothing, produces no audio. Worth doing before a long passage, because both houses bill by the size of the text, not per run — so the same tool can cost wildly different amounts depending on what you hand it. The rates behind the estimate are not repeated here; lab_list_models carries them. |
| provider | string | Cross-check, not a choice: the house that cloned this voice is the one that speaks with it, because a voice id from one house means nothing to the other. Pass it only to assert what you believe the voice is; a mismatch is refused before anything is charged. Omit it and the stored voice decides. Which house a voice belongs to: lab_list_voices. What each house can do: lab_list_models. |
| voice_id* | string | A cloned voice's id (lab_cloned_voices.id, from lab_voice_clone or lab_list_voices) — positive integer as string, NOT the raw provider voice id. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
Read the sound of a clip (video_id), an audio track (audio_id) or an uploaded file (project_id + asset_key from lab_add_media) as numbers: peak level 0..1 per channel, 16 values per second, up to 60 s per page. Use it to find where music or speech starts, where silence is (below `stille_unter`), and whether a cut lands on a beat or mid-word — before you move cut points. Continue with `weiter_von_s` for the next page. Read-only, free.
| Argument | Type | Description |
|---|---|---|
| bis_s | number | End of the page; at most 60 s after von_s |
| von_s | number | Start of the page in seconds (default 0) |
| audio_id | string | Audio track id (from lab_list_audio). Give exactly one of video_id, audio_id, asset_key |
| video_id | string | Generated video id — its own soundtrack. Give this OR audio_id |
| asset_key | string | Storage key of an uploaded sound or video file (from lab_add_media). Needs project_id |
| project_id | string | Project the asset_key belongs to — required with asset_key |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"bis_s": {
"type": "number",
"minimum": 0,
"description": "End of the page; at most 60 s after von_s"
},
"von_s": {
"type": "number",
"minimum": 0,
"description": "Start of the page in seconds (default 0)"
},
"audio_id": {
"type": "string",
"description": "Audio track id (from lab_list_audio). Give exactly one of video_id, audio_id, asset_key"
},
"video_id": {
"type": "string",
"description": "Generated video id — its own soundtrack. Give this OR audio_id"
},
"asset_key": {
"type": "string",
"description": "Storage key of an uploaded sound or video file (from lab_add_media). Needs project_id"
},
"project_id": {
"type": "string",
"description": "Project the asset_key belongs to — required with asset_key"
}
},
"additionalProperties": false
}Build a walkable 3D WORLD (Gaussian splat, World Labs Marble) from ONE location picture (costs credits — exact price via get_cost: true). Source: a location board's time variant (loc_id + variante) OR a gallery image of the project (quell_bild_id). stufe 'entwurf' is a cheap draft (about a minute), 'voll' the full world (5-15 min). ASYNCHRONOUS: returns welt_id; poll lab_welt_status. When done, open the studio link, place a camera and save angles — they land in the project gallery and work as seed_image_url for lab_generate. The world only holds near the spot the picture was taken from: reverse shots from inside the room work, flying through walls does not.
| Argument | Type | Description |
|---|---|---|
| stufe | string | 'entwurf' = cheap draft, 'voll' = full world (default) |
| loc_id | string | Location board id (from lab_list_locations); needs variante |
| get_cost | boolean | Free: the exact price of this operation; nothing is built or charged. Name it to the person before the real run. |
| variante | string | Which picture of the board: a time variant or its 360° panorama |
| project_id* | string | Project id (from lab_list_projects) — the world is filed in this project's gallery |
| text_prompt | string | Optional description of the place; for a board its prompt prefix is used when left out |
| quell_bild_id |
Status of a world from lab_welt: processing | completed | failed. When completed, `studio` is the link to walk it and save angles (free). A failed world has been refunded. Read-only, free. Pass wait_s (up to 15) to wait inside the call.
| Argument | Type | Description |
|---|---|---|
| wait_s | integer | Wait up to this many seconds (max 15) for completed/failed; the answer says gewartet_s |
| welt_id* | string | The welt_id returned by lab_welt |
| project_id* | string | Project id the world belongs to |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"welt_id"
],
"properties": {
"wait_s": {
"type": "integer",
"maximum": 15,
"minimum": 0,
"description": "Wait up to this many seconds (max 15) for completed/failed; the answer says gewartet_s"
},
"welt_id": {
"type": "string",
"description": "The welt_id returned by lab_welt"
},
"project_id": {
"type": "string",
"description": "Project id the world belongs to"
}
},
"additionalProperties": false
}Who is connected: the FilmLab account (email, user id, name, tier, credits) and how this connection signed in (oauth, oauth_geraet, bridge_key, legacy service user, or anonym). For a device sign-in it also lists the device limits (projects, credits, end date). Call it when results look like someone else's — two clients signed in with two accounts see different projects. Works without an account. Read-only, free.
No arguments.
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {},
"additionalProperties": false
}Record a correction in the FilmOS knowledge layer when the user contradicts something you asserted from it. Use this the moment a contradiction is stated — not at the end of the session, and not instead of accepting the correction in your answer. It stores the claim AND what is actually true, owned by the user who said it, so lab_wissen surfaces it first next time. It does not delete anything: a deleted false claim returns with the next ingestion, a corrected one does not. Quote the user's own wording for `korrektur` where you can.
| Argument | Type | Description |
|---|---|---|
| url | string | Source URL of the claim, if a hit carried one |
| thema* | string | Short topic, so the correction is findable later |
| quelle* | string | The source_slug the wrong claim came from — take it from a lab_wissen hit. A source that does not exist is refused, not silently stored. |
| korrektur* | string | What is actually true — the user's wording where possible |
| behauptung* | string | The claim that is wrong, as it was stated |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"quelle",
"thema",
"behauptung",
"korrektur"
],
"properties": {
"url": {
"type": "string",
"description": "Source URL of the claim, if a hit carried one"
},
"thema": {
"type": "string",
"description": "Short topic, so the correction is findable later"
},
"quelle": {
"type": "string",
"description": "The source_slug the wrong claim came from — take it from a lab_wissen hit. A source that does not exist is refused, not silently stored."
},
"korrektur": {
"type": "string",
"description": "What is actually true — the user's wording where possible"
},
"behauptung": {
"type": "string",
"description": "The claim that is wrong, as it was stated"
}
},
"additionalProperties": false
}Use when the person asks a film-craft question: what a term means, which rule or convention applies, how something is usually done in camera, editing, sound or production — answer from here, not from memory. Ask the FilmOS knowledge layer — measured craft knowledge from vetted sources, with the KNOWN CORRECTIONS first: documented false assumptions plus what is actually true. Call this before asserting a craft fact you did not measure yourself in this session. Every hit carries its source and its usage_policy; quote within that policy and name the source. An empty result means nothing matched — it never means the claim is true.
| Argument | Type | Description |
|---|---|---|
| frage* | string | The question, in the language the sources are written in |
| anzahl | integer | How many knowledge hits at most (default 8). Corrections come on top. |
| mindest_autoritaet | number | Authority floor. Defaults to 0.85 — expert voices only. Below that the corpus holds forum members: useful as context, not as the ground for a craft claim. Lower it only deliberately, and say so in the answer. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"frage"
],
"properties": {
"frage": {
"type": "string",
"description": "The question, in the language the sources are written in"
},
"anzahl": {
"type": "integer",
"maximum": 25,
"minimum": 1,
"description": "How many knowledge hits at most (default 8). Corrections come on top."
},
"mindest_autoritaet": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Authority floor. Defaults to 0.85 — expert voices only. Below that the corpus holds forum members: useful as context, not as the ground for a craft claim. Lower it only deliberately, and say so in the answer."
}
},
"additionalProperties": false
}Every spoken word with its start and end time, for a clip (video_id), an audio track (audio_id) or an uploaded file (project_id + asset_key). Use it to cut pauses without cutting into a word, to time captions, or to land a graphic on an exact word — instead of guessing seconds from the waveform. Up to 20 min of sound per source; the first call transcribes (Fish Audio, about 0.36 USD per audio hour, booked to the operator, not your credits), every later call for the same sound is served from the cache for free. Returns at most 300 s of words per page; continue with `weiter_von_s`. Speech recognition spells names the way they sound — check names before you put them on screen.
| Argument | Type | Description |
|---|---|---|
| bis_s | number | End of the page; at most 300 s after von_s |
| von_s | number | Start of the page in seconds (default 0) |
| sprache | string | Spoken language (default de) — the same list the backend accepts |
| audio_id | string | Audio track id (from lab_list_audio) |
| video_id | string | Generated video id — its own soundtrack. Give exactly one of video_id, audio_id, asset_key |
| asset_key | string | Storage key of an uploaded sound or video file (from lab_add_media). Needs project_id |
| project_id |
Read or set a project's audience (zielgruppe) and target markets (zielmaerkte). With project_id only: read. Set zielgruppe 'kinder' or 'jugendliche' whenever the project is made for children, families or teenagers — do it BEFORE creating characters or generating. That switches on blocks for the whole project: the adult mode is off even for unlocked accounts, real people are refused (likeness photos, voice clones, lipsync with a consent id, digital doubles as characters), generation stops instead of running unchecked when the content check is down, and the export must mark the whole film as AI. Setting it fails with a readable conflict while a digital double sits in the cast — take it out first. An empty string clears the audience. zielmaerkte (ISO country codes of the researched European countries plus 'EU'; 'UK' is accepted for GB) blocks nothing: it adds a checklist per country of what the customer must observe when delivering there (age rating scale, national AI label, registration duties for creator channels, advertising rules, warnings). The list replaces the previous one; an empty list clears it. The answer always carries zielgruppe, zielmaerkte, zielgruppe_regeln (the active blocks as sentences) and zielmarkt_checkliste — pass the checklist on to the person, it is not legal advice.
| Argument | Type | Description |
|---|---|---|
| project_id* | string | Project id (from lab_list_projects) |
| zielgruppe | string | Audience of the project: 'kinder', 'jugendliche' or 'erwachsene'; an empty string clears it. Omit to leave it unchanged |
| zielmaerkte | array | Target markets as ISO country codes plus 'EU', e.g. ['DE', 'AT', 'CH']. Replaces the whole list; an empty list clears it. Omit to leave it unchanged |
| start_time | number | Absolute start on the timeline. Omit to append after the last clip (the normal case); set it only to place a clip deliberately |
| duration_seconds | number | Clip duration in seconds. Backend default is 5.0 — pass the clip's REAL length, otherwise the sequence duration and the render drift apart |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"type"
],
"properties": {
"text": {
"type": "string",
"description": "Text content for type 'title', 'credits' or 'lower_third'"
},
"type": {
"enum": [
"video",
"image",
"title",
"credits",
"lower_third"
],
"type": "string",
"description": "What this clip is. 'image' references a generated image via source_id; 'title', 'credits' and 'lower_third' carry `text` instead. 'lower_third' is an OVERLAY: appended without start_time it is laid over the clip before it (track V2), takes no time of its own and never runs past that clip's end; metadata.versatz_s delays it inside that clip. As the very first clip it is a card on black. 'video' is where it gets sharp — read the source_id note before using it"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"metadata": {
"type": "object",
"description": "JSON stored alongside the clip. The continuity pass reads video_id (a generated video's id — it checks the start image), image_url (a frame URL), location_id and scene_num from here; for a 'lower_third', versatz_s (seconds into the clip it sits on) sets when it appears; other keys are stored as-is",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"source_id": {
"type": "string",
"description": "For type 'image': the id of a generated image (from lab_generate). For type 'video': source_id is IGNORED by the continuity pass (a video id used to be misread as an unrelated image, see vault/followups/2026-08-04-continuity-loest-source-id-blind-auf.md). Give a video clip its frame via metadata instead: metadata.video_id = the generated video's id (the pass checks the video's start image), or metadata.image_url = a frame URL. Without either, the shot is counted as 'not checked' (summary.ohne_frame)."
},
"start_time": {
"type": "number",
"minimum": 0,
"description": "Absolute start on the timeline. Omit to append after the last clip (the normal case); set it only to place a clip deliberately"
},
"duration_seconds": {
"type": "number",
"maximum": 300,
"minimum": 0.1,
"description": "Clip duration in seconds. Backend default is 5.0 — pass the clip's REAL length, otherwise the sequence duration and the render drift apart"
}
},
"additionalProperties": false
}| string |
| Project id (from lab_list_projects) — positive integer as string |
| content_type | string | Media type of the upload, e.g. video/mp4, audio/mpeg — required with file_name |
| abschluss_key | string | Only after an upload whose answer said `abschluss_noetig`: the `abschluss_key` from that answer, given AFTER the PUT returned 200. Give it instead of media_url and file_name |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"art",
"herkunft"
],
"properties": {
"art": {
"enum": [
"video",
"audio",
"image"
],
"type": "string",
"description": "What the file is"
},
"herkunft": {
"enum": [
"eigen",
"ki"
],
"type": "string",
"description": "Required, no default: 'eigen' = shot, recorded or made by people (no AI generation); 'ki' = generated or altered with AI. Decides the EU AI Act Art. 50 disclosure of every export that uses this file. If the file itself carries an AI mark (C2PA or IPTC source type), it stays AI material whatever you declare, and the answer says so."
},
"file_name": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "Name of a local file to upload, with extension. Give this OR media_url"
},
"media_url": {
"type": "string",
"format": "uri",
"description": "Public http(s) URL of the file. Give this OR file_name"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"content_type": {
"type": "string",
"description": "Media type of the upload, e.g. video/mp4, audio/mpeg — required with file_name"
},
"abschluss_key": {
"type": "string",
"maxLength": 300,
"minLength": 1,
"description": "Only after an upload whose answer said `abschluss_noetig`: the `abschluss_key` from that answer, given AFTER the PUT returned 200. Give it instead of media_url and file_name"
}
},
"additionalProperties": false
}| Cut the reference clip server-side before it reaches the provider: start of the excerpt, in seconds from the clip's beginning. Only together with out_sec, and only with source_video_id. |
| intent | string | Say WHAT the shot is instead of picking a model — the router chooses by priority and a cost cap. 'draft' and 'ambient' route to the cheapest provider, 'dialog' to the one with the steadiest faces, 'hero' to the most expensive on purpose, 'atmo' for atmosphere. Prefer intent over tool: it also gives you an automatic fallback to the next provider if the first one hard-fails. An explicitly set `tool` always wins over intent. |
| prompt* | string | Motion/camera instruction for the animation. For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: "<line>" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there. |
| familie | string | Pick the MODEL, not the access route: 'seedance-2.0' or 'seedance-2.5'. The server takes the cheapest route that this account may use and that can do the job (OpenRouter only with the user's consent; references, 1080p for 2.5, or audio switched off go to fal) and falls back to fal if a cheaper route fails — the fallback can cost more than the preflight price. Mutually exclusive with tool and intent. |
| out_sec | number | End of the excerpt in the reference clip, in seconds. Must lie behind in_sec; both are set together or neither. Only with source_video_id. |
| get_cost | boolean | Preflight: return the price in credits and your balance WITHOUT generating, reading the same catalog as lab_list_models. Do this before a batch or when the user wants to see the cost. For a card the user confirms, use lab_propose instead. |
| metadata | object | Free-form JSON stored alongside the video. Not interpreted by the pipeline. |
| prop_ids | array | Prop ids from lab_list_props, belonging to this project (an @prop_ tag in the prompt does the same). Animating an image: the descriptions go into the prompt as text, no prop image is sent. From a clip (source_video_id): the front views join the reference images on free slots, as in lab_reference_motion. |
| audio_url | string | URL of an existing audio track to lay under the clip. Independent of generate_audio, which asks the PROVIDER to synthesise its own. |
| scene_ref | string | Scene label (e.g. 'SZ 04'). Stored on the row, so clips can be matched back to a shot list and reviewed per scene with lab_review_queue. |
| shot_type | string | How to cut a multi-shot clip. 'customize' (default) renders exactly the shots you gave. 'intelligent' lets the model decide the cutting — your shot prompts become a suggestion, not a spec. Belongs to `shots`; alone it is rejected. |
| apply_lens | boolean | Defaults to TRUE on the backend: the project's lens_state UrPrompt is appended to the prompt. Pass false only to deliberately opt out for this one call. |
| kamerapfad | object | The camera move as INPUT instead of prose: an object following shared/kamerapfad/schema.json — poses over time (position, look-at or quaternion, metres, Y up) from the Kadrage terrain flight or drawn by hand, or motion phases as lab_kamerafahrt reads them from a clip (its answer carries a ready `kamerapfad`). The backend validates it, derives phases, and then writes the move into the prompt IN THE FORM THIS MODEL UNDERSTANDS: prose at the camera slot for most families, bracketed director commands inside the prompt string for the MiniMax/Hailuo family, which parses them. NO MODEL RECEIVES THE POSES TODAY — the answer says which form went out (kamerapfad.angewandt = als_text or als_regiebefehle), names the family rule and the source it rests on (kamerapfad.sprache), and lists what that form could not carry (kamerapfad.nicht_gesendet, kamerapfad.befehle_weggefallen next to kamerapfad.befehle_gesamt). IF YOUR OWN PROMPT ALREADY CARRIES BRACKETED COMMANDS and the model parses them, the path is NOT sent on top of them — two chains would contradict each other at the same parser; the answer then says angewandt = eigene_regie and names what it found in kamerapfad.eigene_regiebefehle, and your prompt goes out unchanged. A `zusammenfassung` inside the path is display only and is NEVER sent — the move is always rebuilt from the phases; if nothing can be built (phases without tempo/verlauf), the answer says angewandt = nicht_ableitbar with the reason, and nothing of the path is in the prompt. Mutually exclusive with kamerapfad_url. |
| project_id* | string | Project id (from lab_list_projects) |
| referenzen | array | Typed references for the clip path (source_video_id). The position IS the address: the first image is @Image1, the first extra clip is @Video2 (the reference clip itself is @Video1), the first sound is @Audio1. Write @bild1/@video1/@ton1 in the prompt and they are resolved to exactly these positions HERE, before anything is sent — no detour through lab_prompt_transform needed (it resolves them too when it rewrites a prompt). Every image reference is MEASURED before it is sent and scaled up to the model's minimum if it is below it; nothing is ever rejected for being small. |
| resolution | string | Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default. Some models have no resolution field at all — then leave it out. |
| location_id | string | Location board id (from lab_list_locations). The board's `prompt_prefix_en` is appended to your prompt as 'Location: …', so a clip stays in the same place as the images shot there. A board WITHOUT a prefix anchors nothing and fails silently — check with lab_list_locations. |
| stil_kit_id | string | Style kit id from lab_list_stil_kits (an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies (lab_stil_kit_standard); its rules and palette go into the prompt as text, and its reference images join only where the call already sends reference images (characters or props) and the model has a free slot after them. The answer says which way it went (stil_kit.weg). |
| end_image_id | string | id of a completed image to land the clip on (last frame). REQUIRES a start frame (source_image_id or character_refs) — an end frame alone is rejected before quota is spent. Only kling_3_pro. |
| camera_motion | string | Structured camera move sent to the model itself — only models whose lab_list_models entry lists camera_motions. Prefer this over prose when a keyframe chain must MOVE rather than cross-fade. |
| ohne_keyframe | boolean | Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check. |
| ohne_stil_kit | boolean | true switches the project's default style kit off for this one call. |
| character_refs | array | Character ids (positive integer strings) whose reference image locks the cast's identity for this clip. On most providers only the FIRST character's image is used, as the start frame — with two characters in frame the second is unbound. On kling_3_pro each character additionally becomes its own reference element (frontal view plus up to 3 further angles from its sheet), so all of them are bound. The response reports `elements_bound` and `characters_without_reference`. |
| generate_audio | boolean | Ask for the model's native audio track. Only meaningful where lab_list_models says audio: schaltbar — models with audio: immer always have it, audio: nie never. |
| kamerapfad_url | string | Address of a kamerapfad JSON instead of the object — the url of a file in THIS project (lab_list_assets), e.g. the kamerapfad JSON that Kadrage writes next to its flight video. Fetched by the backend under the same ownership rule as every reference: a file of another project is a named rejection before anything is charged. Mutually exclusive with kamerapfad. |
| source_image_id | string | id of a completed image (from lab_generate/lab_job_status) to animate — positive integer as string. Required unless get_cost is true. Imported Midjourney images work directly: use an image_id from lab_umzug. |
| source_video_id | string | Animate FROM A CLIP instead of a still: the id of a generated video (from lab_animate/lab_job_status) or the asset key of an uploaded one (from lab_list_assets, asset_type 'video'). The run then goes to the reference-motion path — your clip leads, the model restyles it. Mutually exclusive with source_image_id, and only for models that take a video reference (lab_list_models says v2v); The picture-side fields (end frame, shots, audio, location) do not travel this path and are rejected rather than dropped; character references exist here under a different name (ready image URLs instead of sheet ids) and are rejected too, with that reason. An intent IS resolved before the run and travels as the chosen model. |
| character_states | object | Which STATE each character is in for this clip, as { characterId: stateKey } — e.g. { "12": "wet" }. A state is its own asset with its own full descriptor (clean / soaked / bloodied are three assets, not one with a note), and it is the ONLY way that descriptor reaches a generation: the reference image always carries the BASE state, so without this field you get dry Cal in a scene where he just came out of the Thames — and the clip looks correct. Get the keys from lab_get_character_sheet. Every id here must also appear in character_refs (who is not in the frame is not described), and an unknown key is rejected before any quota is spent. |
| character_voices | object | Bind a voice per character: character id -> fal voice id. Voice ids come from fal-ai/kling-video/create-voice — FilmLab keeps no voice registry of its own, the id is passed through unverified. Every character named here must also be in character_refs AND must have a generated reference image (no image → no element → nothing to bind the voice to; rejected before quota is spent). Only kling_3_pro. |
| dreamer_signatur | string | Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first. |
| duration_seconds | number | Clip length in seconds. Must lie inside the model's duration grid from lab_list_models; inside the grid the provider may snap to its nearest step. IGNORED when `shots` is given — then the rendered length is the sum of the shot durations. |
| abdruck_startframe | boolean | Generate frame one WITH the character's trained Figurenabdruck (LoRA) instead of taking the flat studio portrait from the character sheet — the clip then starts IN the scene rather than in a photo studio. Opt-in: it costs one extra image (3 credits). Needs EXACTLY ONE character_ref that has a finished Abdruck, and is rejected together with source_image_id (that one PICKS frame one, this one MAKES it). Refused before any quota is spent. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"seed": {
"type": "integer",
"maximum": 4294967295,
"minimum": 0,
"description": "Seed for a repeatable run. Omit it and the project's stored seed applies (POST /api/lab/projects/{id}/einstellungen), and without that the provider picks one. The seed that actually rendered comes back in lab_job_status as `seed` with `seed_quelle` ('provider' = read from the provider's answer, 'gesetzt' = the one sent, null = the model tells no seed). Models whose endpoint has no seed field do not drop a given seed silently: the run proceeds and the row carries `seed_hinweis: seed_nicht_unterstuetzt` — some models tell their seed but take none, others take one but tell none."
},
"tool": {
"type": "string",
"description": "Video model id — copy it verbatim from lab_list_models (kind: video). Omit to let the intent router choose. Every model has its own resolutions, duration grid, audio behaviour and price; the catalog is the only place those live, this text deliberately names none."
},
"shots": {
"type": "array",
"items": {
"type": "object",
"required": [
"prompt"
],
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"description": "What happens in THIS shot"
},
"duration_seconds": {
"type": "integer",
"maximum": 15,
"minimum": 1,
"description": "Length of this shot (default 5)"
}
}
},
"description": "Multi-shot: a sequence of shots rendered as ONE output, holding character, place and voice across the cuts. This is NOT the same as several clips stitched together — those you pay for individually and they do not hold the cast. PRICE: the billed length is the SUM of the shot durations, not duration_seconds. Three 5s shots cost the same as one 15s clip (140 credits at the reference resolution), not the same as one 5s clip (47). The sum must fit the provider's 3-15s window — each shot may be legal on its own and the sum still too long. Only kling_3_pro. Requesting it elsewhere is rejected before quota is spent."
},
"in_sec": {
"type": "number",
"description": "Cut the reference clip server-side before it reaches the provider: start of the excerpt, in seconds from the clip's beginning. Only together with out_sec, and only with source_video_id."
},
"intent": {
"type": "string",
"description": "Say WHAT the shot is instead of picking a model — the router chooses by priority and a cost cap. 'draft' and 'ambient' route to the cheapest provider, 'dialog' to the one with the steadiest faces, 'hero' to the most expensive on purpose, 'atmo' for atmosphere. Prefer intent over tool: it also gives you an automatic fallback to the next provider if the first one hard-fails. An explicitly set `tool` always wins over intent."
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "Motion/camera instruction for the animation. For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: \"<line>\" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there."
},
"familie": {
"enum": [
"seedance-2.0",
"seedance-2.5"
],
"type": "string",
"description": "Pick the MODEL, not the access route: 'seedance-2.0' or 'seedance-2.5'. The server takes the cheapest route that this account may use and that can do the job (OpenRouter only with the user's consent; references, 1080p for 2.5, or audio switched off go to fal) and falls back to fal if a cheaper route fails — the fallback can cost more than the preflight price. Mutually exclusive with tool and intent."
},
"out_sec": {
"type": "number",
"description": "End of the excerpt in the reference clip, in seconds. Must lie behind in_sec; both are set together or neither. Only with source_video_id."
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price in credits and your balance WITHOUT generating, reading the same catalog as lab_list_models. Do this before a batch or when the user wants to see the cost. For a card the user confirms, use lab_propose instead."
},
"metadata": {
"type": "object",
"description": "Free-form JSON stored alongside the video. Not interpreted by the pipeline.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"prop_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Prop ids from lab_list_props, belonging to this project (an @prop_ tag in the prompt does the same). Animating an image: the descriptions go into the prompt as text, no prop image is sent. From a clip (source_video_id): the front views join the reference images on free slots, as in lab_reference_motion."
},
"audio_url": {
"type": "string",
"description": "URL of an existing audio track to lay under the clip. Independent of generate_audio, which asks the PROVIDER to synthesise its own."
},
"scene_ref": {
"type": "string",
"description": "Scene label (e.g. 'SZ 04'). Stored on the row, so clips can be matched back to a shot list and reviewed per scene with lab_review_queue."
},
"shot_type": {
"enum": [
"customize",
"intelligent"
],
"type": "string",
"description": "How to cut a multi-shot clip. 'customize' (default) renders exactly the shots you gave. 'intelligent' lets the model decide the cutting — your shot prompts become a suggestion, not a spec. Belongs to `shots`; alone it is rejected."
},
"apply_lens": {
"type": "boolean",
"description": "Defaults to TRUE on the backend: the project's lens_state UrPrompt is appended to the prompt. Pass false only to deliberately opt out for this one call."
},
"kamerapfad": {
"type": "object",
"description": "The camera move as INPUT instead of prose: an object following shared/kamerapfad/schema.json — poses over time (position, look-at or quaternion, metres, Y up) from the Kadrage terrain flight or drawn by hand, or motion phases as lab_kamerafahrt reads them from a clip (its answer carries a ready `kamerapfad`). The backend validates it, derives phases, and then writes the move into the prompt IN THE FORM THIS MODEL UNDERSTANDS: prose at the camera slot for most families, bracketed director commands inside the prompt string for the MiniMax/Hailuo family, which parses them. NO MODEL RECEIVES THE POSES TODAY — the answer says which form went out (kamerapfad.angewandt = als_text or als_regiebefehle), names the family rule and the source it rests on (kamerapfad.sprache), and lists what that form could not carry (kamerapfad.nicht_gesendet, kamerapfad.befehle_weggefallen next to kamerapfad.befehle_gesamt). IF YOUR OWN PROMPT ALREADY CARRIES BRACKETED COMMANDS and the model parses them, the path is NOT sent on top of them — two chains would contradict each other at the same parser; the answer then says angewandt = eigene_regie and names what it found in kamerapfad.eigene_regiebefehle, and your prompt goes out unchanged. A `zusammenfassung` inside the path is display only and is NEVER sent — the move is always rebuilt from the phases; if nothing can be built (phases without tempo/verlauf), the answer says angewandt = nicht_ableitbar with the reason, and nothing of the path is in the prompt. Mutually exclusive with kamerapfad_url.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"referenzen": {
"type": "array",
"items": {
"type": "object",
"required": [
"art"
],
"properties": {
"id": {
"type": "string",
"description": "The reference as an ID instead of an address, looked up in this project: an image id (lab_generate/lab_job_status), a video id (lab_animate), an audio id (lab_list_audio), or an asset key (lab_list_assets). The URL is never built here — it comes from the backend's answer, because the bucket is not guessable and a hand-built address renders, bills, and then leads nowhere."
},
"art": {
"enum": [
"bild",
"video",
"ton"
],
"type": "string",
"description": "Which channel this reference goes into: bild an image, video a clip, ton a sound. The channel is not the same as the role — an image can be a character, a place or a look, and only you know which; say it in `rolle`. How many of each a model takes is in lab_list_models (referenz_grenzen); a channel the model has no slot for is rejected before anything is charged."
},
"url": {
"type": "string",
"description": "Address of the reference. Exactly one of url or id — both together means two sources for one address, and one of them would silently lose."
},
"rolle": {
"type": "string",
"description": "WHICH character or place this reference means. Free text, carried through — not derivable from `art`, and guessing it would be a claim about the cast nobody made."
}
}
},
"description": "Typed references for the clip path (source_video_id). The position IS the address: the first image is @Image1, the first extra clip is @Video2 (the reference clip itself is @Video1), the first sound is @Audio1. Write @bild1/@video1/@ton1 in the prompt and they are resolved to exactly these positions HERE, before anything is sent — no detour through lab_prompt_transform needed (it resolves them too when it rewrites a prompt). Every image reference is MEASURED before it is sent and scaled up to the model's minimum if it is below it; nothing is ever rejected for being small."
},
"resolution": {
"type": "string",
"description": "Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default. Some models have no resolution field at all — then leave it out."
},
"location_id": {
"type": "string",
"description": "Location board id (from lab_list_locations). The board's `prompt_prefix_en` is appended to your prompt as 'Location: …', so a clip stays in the same place as the images shot there. A board WITHOUT a prefix anchors nothing and fails silently — check with lab_list_locations."
},
"stil_kit_id": {
"type": "string",
"description": "Style kit id from lab_list_stil_kits (an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies (lab_stil_kit_standard); its rules and palette go into the prompt as text, and its reference images join only where the call already sends reference images (characters or props) and the model has a free slot after them. The answer says which way it went (stil_kit.weg)."
},
"end_image_id": {
"type": "string",
"description": "id of a completed image to land the clip on (last frame). REQUIRES a start frame (source_image_id or character_refs) — an end frame alone is rejected before quota is spent. Only kling_3_pro."
},
"camera_motion": {
"enum": [
"dolly_in",
"dolly_out",
"dolly_left",
"dolly_right",
"jib_up",
"jib_down",
"static",
"focus_shift"
],
"type": "string",
"description": "Structured camera move sent to the model itself — only models whose lab_list_models entry lists camera_motions. Prefer this over prose when a keyframe chain must MOVE rather than cross-fade."
},
"ohne_keyframe": {
"type": "boolean",
"description": "Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check."
},
"ohne_stil_kit": {
"type": "boolean",
"description": "true switches the project's default style kit off for this one call."
},
"character_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Character ids (positive integer strings) whose reference image locks the cast's identity for this clip. On most providers only the FIRST character's image is used, as the start frame — with two characters in frame the second is unbound. On kling_3_pro each character additionally becomes its own reference element (frontal view plus up to 3 further angles from its sheet), so all of them are bound. The response reports `elements_bound` and `characters_without_reference`."
},
"generate_audio": {
"type": "boolean",
"description": "Ask for the model's native audio track. Only meaningful where lab_list_models says audio: schaltbar — models with audio: immer always have it, audio: nie never."
},
"kamerapfad_url": {
"type": "string",
"description": "Address of a kamerapfad JSON instead of the object — the url of a file in THIS project (lab_list_assets), e.g. the kamerapfad JSON that Kadrage writes next to its flight video. Fetched by the backend under the same ownership rule as every reference: a file of another project is a named rejection before anything is charged. Mutually exclusive with kamerapfad."
},
"source_image_id": {
"type": "string",
"description": "id of a completed image (from lab_generate/lab_job_status) to animate — positive integer as string. Required unless get_cost is true. Imported Midjourney images work directly: use an image_id from lab_umzug."
},
"source_video_id": {
"type": "string",
"description": "Animate FROM A CLIP instead of a still: the id of a generated video (from lab_animate/lab_job_status) or the asset key of an uploaded one (from lab_list_assets, asset_type 'video'). The run then goes to the reference-motion path — your clip leads, the model restyles it. Mutually exclusive with source_image_id, and only for models that take a video reference (lab_list_models says v2v); The picture-side fields (end frame, shots, audio, location) do not travel this path and are rejected rather than dropped; character references exist here under a different name (ready image URLs instead of sheet ids) and are rejected too, with that reason. An intent IS resolved before the run and travels as the chosen model."
},
"character_states": {
"type": "object",
"description": "Which STATE each character is in for this clip, as { characterId: stateKey } — e.g. { \"12\": \"wet\" }. A state is its own asset with its own full descriptor (clean / soaked / bloodied are three assets, not one with a note), and it is the ONLY way that descriptor reaches a generation: the reference image always carries the BASE state, so without this field you get dry Cal in a scene where he just came out of the Thames — and the clip looks correct. Get the keys from lab_get_character_sheet. Every id here must also appear in character_refs (who is not in the frame is not described), and an unknown key is rejected before any quota is spent.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"character_voices": {
"type": "object",
"description": "Bind a voice per character: character id -> fal voice id. Voice ids come from fal-ai/kling-video/create-voice — FilmLab keeps no voice registry of its own, the id is passed through unverified. Every character named here must also be in character_refs AND must have a generated reference image (no image → no element → nothing to bind the voice to; rejected before quota is spent). Only kling_3_pro.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"dreamer_signatur": {
"type": "string",
"description": "Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first."
},
"duration_seconds": {
"type": "number",
"maximum": 30,
"minimum": 1,
"description": "Clip length in seconds. Must lie inside the model's duration grid from lab_list_models; inside the grid the provider may snap to its nearest step. IGNORED when `shots` is given — then the rendered length is the sum of the shot durations."
},
"abdruck_startframe": {
"type": "boolean",
"description": "Generate frame one WITH the character's trained Figurenabdruck (LoRA) instead of taking the flat studio portrait from the character sheet — the clip then starts IN the scene rather than in a photo studio. Opt-in: it costs one extra image (3 credits). Needs EXACTLY ONE character_ref that has a finished Abdruck, and is rejected together with source_image_id (that one PICKS frame one, this one MAKES it). Refused before any quota is spent."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"jobs"
],
"properties": {
"jobs": {
"type": "array",
"items": {
"type": "object",
"required": [
"prompt"
],
"properties": {
"seed": {
"type": "integer",
"maximum": 4294967295,
"minimum": 0,
"description": "Seed for THIS clip — see lab_animate for the full rule"
},
"tool": {
"type": "string",
"description": "Video model id for THIS clip — copy it verbatim from lab_list_models (kind: video); providers can differ per job. Omit to let the intent router choose — see lab_animate"
},
"shots": {
"type": "array",
"items": {
"type": "object",
"required": [
"prompt"
],
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"description": "What happens in THIS shot"
},
"duration_seconds": {
"type": "integer",
"maximum": 15,
"minimum": 1,
"description": "Length of this shot (default 5)"
}
}
},
"description": "Multi-shot for THIS clip (kling_3_pro only). The billed length is the SUM of the shot durations, NOT duration_seconds — see lab_animate"
},
"intent": {
"type": "string",
"description": "Route by intent instead of naming a model: 'draft'|'ambient' (cheapest), 'dialog', 'hero', 'atmo'. An explicit tool wins over intent"
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "Motion/camera instruction for this clip. For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: \"<line>\" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there."
},
"familie": {
"enum": [
"seedance-2.0",
"seedance-2.5"
],
"type": "string",
"description": "Pick the MODEL, not the access route: 'seedance-2.0' or 'seedance-2.5'. The server takes the cheapest route that this account may use and that can do the job (OpenRouter only with the user's consent; references, 1080p for 2.5, or audio switched off go to fal) and falls back to fal if a cheaper route fails — the fallback can cost more than the preflight price. Mutually exclusive with tool and intent."
},
"metadata": {
"type": "object",
"description": "Free-form JSON stored alongside this clip. Not interpreted by the pipeline",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"prop_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Prop ids from lab_list_props, belonging to this project (an @prop_ tag in the prompt does the same). On this path their descriptions go into the prompt as text; prop images are sent by lab_generate and lab_reference_motion."
},
"audio_url": {
"type": "string",
"description": "URL of an existing audio track to lay under THIS clip. Independent of generate_audio"
},
"scene_ref": {
"type": "string",
"description": "Optional scene label, useful to match results back to a shot list"
},
"shot_type": {
"enum": [
"customize",
"intelligent"
],
"type": "string",
"description": "Cutting mode for THIS clip's shots — see lab_animate"
},
"apply_lens": {
"type": "boolean",
"description": "Defaults to TRUE on the backend (project lens_state is appended). Pass false to opt out for this clip only"
},
"kamerapfad": {
"type": "object",
"description": "Camera path for THIS clip (object following shared/kamerapfad/schema.json) — never as poses, but in the form this clip's model understands (prose, or director commands for MiniMax/Hailuo); the answer names the form as kamerapfad.angewandt. See lab_animate",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"resolution": {
"type": "string",
"description": "Resolution tier for THIS clip, exactly as listed in lab_list_models. Omit for the model's default — see lab_animate"
},
"location_id": {
"type": "string",
"description": "Location board id for THIS clip (from lab_list_locations) — its `prompt_prefix_en` is appended to this job's prompt"
},
"stil_kit_id": {
"type": "string",
"description": "Style kit id for THIS clip (lab_list_stil_kits; an @stil_ tag in the prompt does the same); omit for the project default"
},
"end_image_id": {
"type": "string",
"description": "Last frame for THIS clip — only models with supports_end_frame (lab_list_models). Needs a start frame — see lab_animate"
},
"camera_motion": {
"enum": [
"dolly_in",
"dolly_out",
"dolly_left",
"dolly_right",
"jib_up",
"jib_down",
"static",
"focus_shift"
],
"type": "string",
"description": "Camera move for THIS clip — see lab_animate"
},
"ohne_stil_kit": {
"type": "boolean",
"description": "true switches the project's default style kit off for this one call."
},
"character_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Character ids (positive integer strings) locking the cast's identity for THIS clip"
},
"generate_audio": {
"type": "boolean",
"description": "Ask for the model's native audio track for THIS clip — only meaningful where lab_list_models says audio: schaltbar, see lab_animate for the full rule"
},
"kamerapfad_url": {
"type": "string",
"description": "Address of a kamerapfad JSON in this project for THIS clip — see lab_animate. Mutually exclusive with kamerapfad"
},
"source_image_id": {
"type": "string",
"description": "id of a completed image to animate — positive integer as string. Omit for text-to-video"
},
"character_states": {
"type": "object",
"description": "State key per character for THIS clip, { characterId: stateKey } — the ONLY way a state descriptor reaches the generation; the reference image always carries the base state",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"character_voices": {
"type": "object",
"description": "Voice per character for THIS clip (kling_3_pro only) — see lab_animate",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"dreamer_signatur": {
"type": "string",
"description": "Dreamer proof for THIS clip's prompt (lab_prompt_transform). Without valid signatures on all jobs, the whole batch returns Dreamer versions first and generates nothing."
},
"duration_seconds": {
"type": "number",
"maximum": 30,
"minimum": 1,
"description": "Clip length in seconds for THIS clip — must lie inside the model's duration grid from lab_list_models. Ignored when shots is given — see lab_animate"
},
"abdruck_startframe": {
"type": "boolean",
"description": "Frame one from the character's trained Abdruck for THIS clip — see lab_animate for the full rule"
}
}
},
"maxItems": 25,
"minItems": 1,
"description": "One entry per clip, max 25. All jobs are validated before any quota is spent"
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the summed price in credits for the WHOLE batch and your balance WITHOUT generating, reading the same catalog as lab_list_models. For a card the user confirms, use lab_propose instead."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"ohne_keyframe": {
"type": "boolean",
"description": "Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"toleranz_s": {
"type": "number",
"maximum": 2,
"minimum": 0,
"description": "How far a cut point may travel to land on a beat, in seconds (default 0.12 — three frames at 25 fps). 0 disables snapping entirely and only recomputes the durations"
},
"trotz_unsicherem_tempo": {
"type": "boolean",
"description": "Snap onto a grid whose own measurement reports `verlaesslich: false`. Default false, and that default is the honest one: below the threshold the detector has not confirmed it found the pulse. Set it only as a deliberate decision — the answer then carries raster.unsicheres_tempo_uebergangen: true"
}
},
"additionalProperties": false
}| uebernehmen | boolean | Default false. true appends the shots to the project's shot list — ask first. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"art": {
"type": "string",
"minLength": 1,
"description": "Which breakdown. Default coverage. Known: coverage, dialog, winkel, hoehen, orbit, pov, naehe, gesicht, haende, reaktionen, zwischenschnitte (aufloesung); ursache_wirkung, entdeckung, zeitverlauf, gefuehlswende, kleiner_bogen (montage)."
},
"laenge": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Number of shots: 2, 4 or 6. Default 4. Never odd — a breakdown runs in pairs."
},
"prompt": {
"type": "string",
"maxLength": 8000,
"minLength": 1,
"description": "The scene or the master prompt to break down"
},
"scene_ref": {
"type": "string",
"maxLength": 64,
"description": "Optional scene reference the shots are filed under when uebernehmen=true"
},
"project_id": {
"type": "integer",
"maximum": 9007199254740991,
"description": "The project the scene belongs to",
"exclusiveMinimum": 0
},
"uebernehmen": {
"type": "boolean",
"description": "Default false. true appends the shots to the project's shot list — ask first."
}
},
"additionalProperties": false
}| figur_id | integer | Existing character-sheet id with a reference image; required for situationen, only used with situationen |
| creativity | string | Krea 2 creativity setting; ignored by flux_pro. Default medium. |
| max_scenes | integer | Upper limit for the number of scenes, and therefore images charged. Backend default when omitted. |
| project_id* | string | Project id |
| situationen | array | Independent situations, each exactly one frame. Requires figur_id; excludes script and figuren_raten. |
| figuren_raten | boolean | Opt-out, off by default: true skips the character references and lets the image model invent every character from the text — a different face in every frame. Only set it when the human explicitly asks for that; it is recorded on every frame (figuren_geraten). |
| style_reference_url | string | Shared moodboard image applied to ALL frames for one consistent look. Used by Krea 2 and by the Seedream character-reference path (one additional reference, included in pricing). Must be an asset of this project or an absolute URL — anything else is refused before credits are spent. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"model": {
"enum": [
"krea_2_medium",
"krea_2_large",
"krea_2_turbo",
"flux_pro"
],
"type": "string",
"description": "Image model for the frames. Default krea_2_medium. Prices per model: lab_list_models."
},
"format": {
"enum": [
"16:9",
"9:16"
],
"type": "string",
"description": "Frame aspect ratio for every model, default 16:9"
},
"script": {
"type": "string",
"maxLength": 80000,
"minLength": 20,
"description": "Screenplay or scene description; provide either script or situationen. The backend splits script into scenes; each scene becomes one image."
},
"figur_id": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Existing character-sheet id with a reference image; required for situationen, only used with situationen",
"exclusiveMinimum": 0
},
"creativity": {
"enum": [
"raw",
"low",
"medium",
"high"
],
"type": "string",
"description": "Krea 2 creativity setting; ignored by flux_pro. Default medium."
},
"max_scenes": {
"type": "integer",
"maximum": 20,
"minimum": 1,
"description": "Upper limit for the number of scenes, and therefore images charged. Backend default when omitted."
},
"project_id": {
"type": "string",
"description": "Project id"
},
"situationen": {
"type": "array",
"items": {
"type": "object",
"required": [
"bild_en",
"bewegung_en"
],
"properties": {
"bild_en": {
"type": "string",
"maxLength": 4000,
"minLength": 1,
"description": "English still prompt: one moment, one action"
},
"bewegung_en": {
"type": "string",
"maxLength": 1000,
"minLength": 1,
"description": "English motion prompt with a strong movement verb"
}
}
},
"maxItems": 16,
"minItems": 8,
"description": "Independent situations, each exactly one frame. Requires figur_id; excludes script and figuren_raten."
},
"figuren_raten": {
"type": "boolean",
"description": "Opt-out, off by default: true skips the character references and lets the image model invent every character from the text — a different face in every frame. Only set it when the human explicitly asks for that; it is recorded on every frame (figuren_geraten)."
},
"style_reference_url": {
"type": "string",
"description": "Shared moodboard image applied to ALL frames for one consistent look. Used by Krea 2 and by the Seedream character-reference path (one additional reference, included in pricing). Must be an asset of this project or an absolute URL — anything else is refused before credits are spent."
}
},
"additionalProperties": false
}| neu_messen | boolean | Measure again even though a result for this exact file already exists. Default false. The measurement is deterministic — the same file gives the same number, so this only helps after the track behind the url was swapped |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"takt": {
"type": "integer",
"maximum": 16,
"minimum": 1,
"description": "Beats per bar (default 4). INPUT, not detection — time-signature detection is explicitly out of scope. It only affects where the downbeats fall, which is what the rhythm 'auf die Songstruktur' snaps to"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"neu_messen": {
"type": "boolean",
"description": "Measure again even though a result for this exact file already exists. Default false. The measurement is deterministic — the same file gives the same number, so this only helps after the track behind the url was swapped"
}
},
"additionalProperties": false
}| clothing |
| string |
| Default costume the character is seen in |
| demeanor | string | How the character moves and behaves, in prose |
| project_id | string | Bind the sheet to this project (from lab_list_projects) — positive integer as string. PASS THIS whenever the character belongs to a project. Without it the sheet stays an orphan: lab_cast_gaps will not count it as created, and image generation will not find it as a char_ref. |
| project_name | string | Free-text project label shown in listings (does NOT bind the sheet — that is project_id) |
| facial_features | string | Face shape, nose, mouth, jaw — what makes this face recognisable |
| distinctive_features | string | Scars, tattoos, glasses, anything that must appear in every image |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"name"
],
"properties": {
"age": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Age in years, as a number"
},
"eyes": {
"type": "string",
"description": "Eye colour and expression"
},
"hair": {
"type": "string",
"description": "Hair colour, length, cut"
},
"name": {
"type": "string",
"minLength": 1,
"description": "Character name, e.g. 'Maren' — the only required field"
},
"skin": {
"type": "string",
"description": "Skin tone and texture"
},
"build": {
"type": "string",
"description": "Body build and posture"
},
"gender": {
"type": "string",
"description": "e.g. 'female', 'male', 'non-binary'"
},
"gattung": {
"enum": [
"mensch",
"tier",
"kreatur",
""
],
"type": "string",
"description": "Character kind: human, animal or creature. Omitted or empty means mensch. For tier/kreatur, begin the description with the species and its distinctive features."
},
"clothing": {
"type": "string",
"description": "Default costume the character is seen in"
},
"demeanor": {
"type": "string",
"description": "How the character moves and behaves, in prose"
},
"project_id": {
"type": "string",
"description": "Bind the sheet to this project (from lab_list_projects) — positive integer as string. PASS THIS whenever the character belongs to a project. Without it the sheet stays an orphan: lab_cast_gaps will not count it as created, and image generation will not find it as a char_ref."
},
"project_name": {
"type": "string",
"description": "Free-text project label shown in listings (does NOT bind the sheet — that is project_id)"
},
"facial_features": {
"type": "string",
"description": "Face shape, nose, mouth, jaw — what makes this face recognisable"
},
"distinctive_features": {
"type": "string",
"description": "Scars, tattoos, glasses, anything that must appear in every image"
}
},
"additionalProperties": false
}| location_type | string | e.g. 'interior', 'exterior', 'apartment', 'street' |
| lighting_notes | string | Light direction, quality, sources |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "Board name, e.g. 'Wohnung Hanna — Balkon'"
},
"props": {
"type": "string",
"description": "Objects that must be present"
},
"colors": {
"type": "string",
"description": "Dominant palette"
},
"atmosphere": {
"type": "string",
"description": "Mood, weather, sound of the place"
},
"project_id": {
"type": "string",
"description": "Bind the board to this project (from lab_list_projects) — positive integer as string"
},
"description": {
"type": "string",
"description": "What the place is, in prose"
},
"time_of_day": {
"type": "string",
"description": "e.g. 'morning', 'blue hour', 'night'"
},
"architecture": {
"type": "string",
"description": "Building style, era, materials"
},
"project_name": {
"type": "string",
"description": "Free-text project label shown in listings"
},
"location_type": {
"type": "string",
"description": "e.g. 'interior', 'exterior', 'apartment', 'street'"
},
"lighting_notes": {
"type": "string",
"description": "Light direction, quality, sources"
}
},
"additionalProperties": false
}| boolean |
| Only read: never create a job. Without an existing job the answer is status 'keine' (default false) |
| project_id* | string | Project id (from lab_list_projects) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"video_id"
],
"properties": {
"neu": {
"type": "boolean",
"description": "Start a new depth job instead of reusing the existing map or running job (default false)"
},
"modell": {
"enum": [
"v2",
"vda"
],
"type": "string",
"description": "Depth engine: v2 (default, sharpest) or vda (steady over time, 16:9 or 9:16 only)"
},
"wait_s": {
"type": "number",
"maximum": 15,
"minimum": 0,
"description": "Wait up to this many seconds for completion (default 0, maximum 15)"
},
"video_id": {
"type": "integer",
"maximum": 9007199254740991,
"description": "Completed video id belonging to this project",
"exclusiveMinimum": 0
},
"nur_lesen": {
"type": "boolean",
"description": "Only read: never create a job. Without an existing job the answer is status 'keine' (default false)"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
}
},
"additionalProperties": false
}| language* | string | Target language for the dub, e.g. 'de', 'fr', 'es' — drives the cloned voice's TTS. Required: there is no silent default here. |
| voice_id | string | A cloned voice's id (lab_cloned_voices.id, from lab_voice_clone or lab_list_voices) — positive integer as string. Required together with dub_text, and mutually exclusive with en_audio_url. |
| einwilligung | boolean | Consent of the person whose mouth this dub re-syncs, when there is no Likeness Register id for them yet. Required together with einwilligung_notiz if likeness_ref is not given — a dub always alters a real person's mouth in existing footage, so one of the two consent forms is mandatory, checked before anything is spent. This is a caller attestation, not a registry check like likeness_ref — logged, not verified against likeness.thefilmradar.com. Do not set it to be rid of the error — set it because the consent exists. |
| en_audio_url | string | URL of a READY dub track to lay under the sequence — typically the audio_url lab_dub_tts just returned. Mutually exclusive with voice_id+dub_text: a dub has exactly one audio source, and picking one silently would throw away the other after it was paid for. |
| likeness_ref | string | A Likeness Register consent id (likeness.thefilmradar.com) for the person whose mouth this dub re-syncs. Checked live against the register before anything is spent; a revoked or unknown id refuses the call. Mutually exclusive with einwilligung+einwilligung_notiz — one form of consent is required, not both. |
| enable_lipsync | boolean | Whether to run the visual lipsync pass at all. Omit to use the backend default (on). Off only swaps the audio track and skips the mouth-sync render entirely. |
| einwilligung_notiz | string | Free text: who consented, when, and for what — required together with einwilligung. Goes on record with the generated clip. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"language"
],
"properties": {
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
},
"backend": {
"enum": [
"replicate",
"inhouse",
"veed_v2"
],
"type": "string",
"description": "Lipsync engine. Omit to use the backend default (replicate — ROI-crop, best for one long static talking-head shot). veed_v2 renders the full frame instead and is the pick for multi-shot sequences or a partly hidden mouth. 'inhouse' shares replicate's ROI-crop path."
},
"dub_text": {
"type": "string",
"maxLength": 5000,
"minLength": 1,
"description": "The line(s) this cloned voice should read, in the target `language`. Required together with voice_id."
},
"get_cost": {
"type": "boolean",
"description": "`true` reports the price and your balance WITHOUT starting a dub and without spending quota. The number is computed by the same function that later charges you, from THIS sequence: a sound rate per video clip, plus the chosen lipsync engine's own rate over the sequence's running time, plus a surcharge for the cloned-voice reading. `preisbasis` in the answer breaks it into those parts, so you can see which switch moves which share — turning the lipsync pass off removes its whole item. Not drawn from the lab_list_models catalog (that covers image and video models only; the lipsync engine is chosen with `backend` here, and dub has no catalog entry)."
},
"language": {
"type": "string",
"minLength": 1,
"description": "Target language for the dub, e.g. 'de', 'fr', 'es' — drives the cloned voice's TTS. Required: there is no silent default here."
},
"voice_id": {
"type": "string",
"description": "A cloned voice's id (lab_cloned_voices.id, from lab_voice_clone or lab_list_voices) — positive integer as string. Required together with dub_text, and mutually exclusive with en_audio_url."
},
"einwilligung": {
"type": "boolean",
"description": "Consent of the person whose mouth this dub re-syncs, when there is no Likeness Register id for them yet. Required together with einwilligung_notiz if likeness_ref is not given — a dub always alters a real person's mouth in existing footage, so one of the two consent forms is mandatory, checked before anything is spent. This is a caller attestation, not a registry check like likeness_ref — logged, not verified against likeness.thefilmradar.com. Do not set it to be rid of the error — set it because the consent exists."
},
"en_audio_url": {
"type": "string",
"minLength": 1,
"description": "URL of a READY dub track to lay under the sequence — typically the audio_url lab_dub_tts just returned. Mutually exclusive with voice_id+dub_text: a dub has exactly one audio source, and picking one silently would throw away the other after it was paid for."
},
"likeness_ref": {
"type": "string",
"description": "A Likeness Register consent id (likeness.thefilmradar.com) for the person whose mouth this dub re-syncs. Checked live against the register before anything is spent; a revoked or unknown id refuses the call. Mutually exclusive with einwilligung+einwilligung_notiz — one form of consent is required, not both."
},
"enable_lipsync": {
"type": "boolean",
"description": "Whether to run the visual lipsync pass at all. Omit to use the backend default (on). Off only swaps the audio track and skips the mouth-sync render entirely."
},
"einwilligung_notiz": {
"type": "string",
"maxLength": 2000,
"description": "Free text: who consented, when, and for what — required together with einwilligung. Goes on record with the generated clip."
}
},
"additionalProperties": false
}| string |
| What to do. Default 'liste' |
| seq_id* | string | Sequence id (from lab_list_sequences) — positive integer as string |
| checkpoint_id | integer | The state (id from 'liste'). Needed for markieren, vergleichen, zurueckholen; optional for abzweigen (without: the current cut) |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"gegen": {
"type": "integer",
"maximum": 9007199254740991,
"description": "vergleichen: a second state instead of the current cut",
"exclusiveMinimum": 0
},
"marke": {
"type": "string",
"maxLength": 200,
"description": "markieren: the name. Empty removes it"
},
"titel": {
"type": "string",
"maxLength": 200,
"minLength": 1,
"description": "abzweigen: the name of the new version"
},
"aktion": {
"enum": [
"liste",
"markieren",
"vergleichen",
"zurueckholen",
"abzweigen"
],
"type": "string",
"description": "What to do. Default 'liste'"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_list_sequences) — positive integer as string"
},
"checkpoint_id": {
"type": "integer",
"maximum": 9007199254740991,
"description": "The state (id from 'liste'). Needed for markieren, vergleichen, zurueckholen; optional for abzweigen (without: the current cut)",
"exclusiveMinimum": 0
}
},
"additionalProperties": false
}| prompt* | string | What to generate (required) |
| wait_s | number | Seconds to wait for the finished image before answering (default 45, max 80). 0 = answer immediately with pending rows. |
| anker_id | string | Look anchor id (positive integer string) whose CHOSEN image guides this generation as img-to-img. An anchor is a look imported from Midjourney: a handful of images plus the grammar of the old `--profile` code, stored per project. It resolves to exactly ONE image — the one the anchor's `gewaehlt` points at — because the provider payload carries one reference, not a set. So this is MUTUALLY EXCLUSIVE with seed_image_url (two sources for one field), and character_refs still wins over both: identity beats look. Build one from imported images with lab_umzug {anker}; list anchors with GET /api/lab/projects/<id>/umzug/anker. |
| get_cost | boolean | Preflight: return the price in credits and your balance WITHOUT generating, reading the same catalog as lab_list_models. Do this before a batch or when the user wants to see the cost. For a card the user confirms, use lab_propose instead. |
| metadata | object | Free-form JSON stored alongside the image (e.g. { parent_id, note }). Not interpreted by the pipeline. |
| prop_ids | array | Prop ids from lab_list_props, belonging to this project (an @prop_ tag in the prompt does the same). Their descriptions go into the prompt, and each prop's FRONT view goes along as a reference image — after the characters, before the style kit; a prop alone may switch a text-to-image call to image-to-image (not on the abdruck path). The answer says which way it went (props.weg, props.grund). |
| variants | integer | How many variants to generate (default 1, max 5) |
| scene_ref | string | Scene label (e.g. 'SZ 04'). Stored on the row, so results can be matched back to a shot list and reviewed per scene with lab_review_queue. Omit it and the image is an orphan. |
| apply_lens | boolean | Defaults to TRUE on the backend: the project's lens_state UrPrompt is appended to the prompt. Pass false only to deliberately opt out for this one call. |
| project_id* | string | Project id (from lab_list_projects) — generation is always project-scoped |
| resolution | string | Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default; the aspect ratio still comes from the project's pose_state — this only sets the area. Some models have no resolution field at all — then leave it out. |
| location_id | string | Location board id (from lab_list_locations). Anchors the shot: the board's `prompt_prefix_en` is appended to your prompt as 'Location: …' before generation, so every image of the same place stays in the same place. Check the prefix with lab_list_locations first — a board WITHOUT a prefix anchors nothing and fails silently. |
| stil_kit_id | string | Style kit id from lab_list_stil_kits (an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies (lab_stil_kit_standard); its rules and palette go into the prompt as text, and its reference images join only where the call already sends reference images (characters or props) and the model has a free slot after them. The answer says which way it went (stil_kit.weg). |
| aspect_ratio | string | Aspect ratio as W:H (e.g. 16:9, 9:16). Must be one of the model's aspect_ratios where the model has an enum; models without an enum accept any W:H. Omit to use the project's camera aspect. |
| costume_refs | array | The costume plate of these characters goes along as an extra reference image — the outfit on an invisible mannequin, NO person, so it carries the costume, never the face. Every id must also be in character_refs. Needs a model whose max_references covers characters + plates (lab_list_models); otherwise rejected before any quota is spent. Each plate is one more reference and may cost credits; get_cost prices it. |
| ohne_stil_kit | boolean | true switches the project's default style kit off for this one call. |
| character_refs | array | Character ids (positive integer strings) whose reference images lock the cast's identity. Routes the call to img-to-img and sends EVERY listed character's reference image, in the order given, up to the max_references (lab_list_models) of the model that actually runs. WITH A CHARACTER, flux_pro — the default — RUNS AS flux_2_pro (FLUX.2 [pro] edit), which takes up to 9 references: the answer names the switch in `umgeschaltet` (von/nach), and get_cost prices flux_2_pro. flux_pro's own img-to-img variant (redux) only varies its source image and dropped the scene in every measured case; it is still used for seed_image_url without characters, where the source's composition is wanted. A model that takes fewer references than you name: naming more with that tool explicitly is rejected before any quota is spent; without an explicit tool the image is made with the first max_references and the answer names the rest in `referenz_warnung` and `referenzen.nicht_gesendet` — nothing is dropped silently. Each additional reference may cost credits; get_cost with character_refs prices exactly that. This OVERRIDES seed_image_url: the characters fill the reference slots, and identity wins over composition. A character without a generated reference image is rejected before any quota is spent. GET IDS FROM lab_list_characters, or create one with lab_create_character followed by lab_generate_character_reference. Prefer character_refs over seed_image_url whenever a person must stay the same across images: seed_image_url holds identity only for the single call that passes it, character_refs is a register entry that every later call can name. SCENE AND LIGHT FOLLOW THE PROMPT: the reference holds the person, not the shot — harbour at night, stairwell under a top light, neon in the rain all came out as asked with the face kept (measured 2026-10-05 on flux_2_pro). What the reference does NOT change is the character's condition: it always shows the BASE state. For a wet, injured or otherwise changed character pass character_states instead of describing it in the prompt (lab_get_character_sheet returns the state keys and the descriptors behind them); to hold the outfit itself, add its costume plate with costume_refs. |
| seed_image_url | string | Optional: run img-to-img guided by this existing image URL (e.g. a prior generation's url). For a PERSON who must look the same across several images, use character_refs instead — this URL binds one call, a register entry binds all of them. |
| character_states | object | Which STATE each character is in for this image, as { characterId: stateKey } — e.g. { "12": "wet" }. A state is its own asset with its own full descriptor (clean / soaked / bloodied are three assets, not one with a note), and it is the ONLY way that descriptor reaches a generation: the reference image always carries the BASE state, so without this field you get dry Cal in a scene where he just came out of the Thames — and the image looks correct. Get the keys from lab_get_character_sheet; create one with the character state endpoint. Every id here must also appear in character_refs (who is not in the frame is not described), and an unknown key is rejected before any quota is spent. |
| dreamer_signatur | string | Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"seed": {
"type": "integer",
"maximum": 4294967295,
"minimum": 0,
"description": "Seed for a repeatable run. Omit it and the project's stored seed applies (POST /api/lab/projects/{id}/einstellungen), and without that the provider picks one. The seed that actually rendered comes back in lab_job_status as `seed` with `seed_quelle` ('provider' = read from the provider's answer, 'gesetzt' = the one sent, null = the model tells no seed). Models whose endpoint has no seed field do not drop a given seed silently: the run proceeds and the row carries `seed_hinweis: seed_nicht_unterstuetzt` — some models tell their seed but take none, others take one but tell none."
},
"tool": {
"type": "string",
"description": "Image model id — copy it verbatim from lab_list_models (kind: image). Omit to use the default. Every model has its own resolutions, aspect handling and price; the catalog is the only place those live, this text deliberately names none."
},
"aspect": {
"type": "array",
"items": {
"type": "number"
},
"maxItems": 2,
"minItems": 2,
"description": "Frame as [width, height], e.g. [9, 16]. Overrides the project's pose_state — pass it when the Pose-Director's blockout is set but not yet saved. Measured 2026-07-31: without it a 9:16 blockout came back as 16:9, because pose_state was still NULL."
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "What to generate (required)"
},
"wait_s": {
"type": "number",
"maximum": 80,
"minimum": 0,
"description": "Seconds to wait for the finished image before answering (default 45, max 80). 0 = answer immediately with pending rows."
},
"anker_id": {
"type": "string",
"description": "Look anchor id (positive integer string) whose CHOSEN image guides this generation as img-to-img. An anchor is a look imported from Midjourney: a handful of images plus the grammar of the old `--profile` code, stored per project. It resolves to exactly ONE image — the one the anchor's `gewaehlt` points at — because the provider payload carries one reference, not a set. So this is MUTUALLY EXCLUSIVE with seed_image_url (two sources for one field), and character_refs still wins over both: identity beats look. Build one from imported images with lab_umzug {anker}; list anchors with GET /api/lab/projects/<id>/umzug/anker."
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price in credits and your balance WITHOUT generating, reading the same catalog as lab_list_models. Do this before a batch or when the user wants to see the cost. For a card the user confirms, use lab_propose instead."
},
"metadata": {
"type": "object",
"description": "Free-form JSON stored alongside the image (e.g. { parent_id, note }). Not interpreted by the pipeline.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"prop_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Prop ids from lab_list_props, belonging to this project (an @prop_ tag in the prompt does the same). Their descriptions go into the prompt, and each prop's FRONT view goes along as a reference image — after the characters, before the style kit; a prop alone may switch a text-to-image call to image-to-image (not on the abdruck path). The answer says which way it went (props.weg, props.grund)."
},
"variants": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "How many variants to generate (default 1, max 5)"
},
"scene_ref": {
"type": "string",
"description": "Scene label (e.g. 'SZ 04'). Stored on the row, so results can be matched back to a shot list and reviewed per scene with lab_review_queue. Omit it and the image is an orphan."
},
"apply_lens": {
"type": "boolean",
"description": "Defaults to TRUE on the backend: the project's lens_state UrPrompt is appended to the prompt. Pass false only to deliberately opt out for this one call."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — generation is always project-scoped"
},
"resolution": {
"type": "string",
"description": "Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default; the aspect ratio still comes from the project's pose_state — this only sets the area. Some models have no resolution field at all — then leave it out."
},
"location_id": {
"type": "string",
"description": "Location board id (from lab_list_locations). Anchors the shot: the board's `prompt_prefix_en` is appended to your prompt as 'Location: …' before generation, so every image of the same place stays in the same place. Check the prefix with lab_list_locations first — a board WITHOUT a prefix anchors nothing and fails silently."
},
"stil_kit_id": {
"type": "string",
"description": "Style kit id from lab_list_stil_kits (an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies (lab_stil_kit_standard); its rules and palette go into the prompt as text, and its reference images join only where the call already sends reference images (characters or props) and the model has a free slot after them. The answer says which way it went (stil_kit.weg)."
},
"aspect_ratio": {
"type": "string",
"description": "Aspect ratio as W:H (e.g. 16:9, 9:16). Must be one of the model's aspect_ratios where the model has an enum; models without an enum accept any W:H. Omit to use the project's camera aspect."
},
"costume_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "The costume plate of these characters goes along as an extra reference image — the outfit on an invisible mannequin, NO person, so it carries the costume, never the face. Every id must also be in character_refs. Needs a model whose max_references covers characters + plates (lab_list_models); otherwise rejected before any quota is spent. Each plate is one more reference and may cost credits; get_cost prices it."
},
"ohne_stil_kit": {
"type": "boolean",
"description": "true switches the project's default style kit off for this one call."
},
"character_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Character ids (positive integer strings) whose reference images lock the cast's identity. Routes the call to img-to-img and sends EVERY listed character's reference image, in the order given, up to the max_references (lab_list_models) of the model that actually runs. WITH A CHARACTER, flux_pro — the default — RUNS AS flux_2_pro (FLUX.2 [pro] edit), which takes up to 9 references: the answer names the switch in `umgeschaltet` (von/nach), and get_cost prices flux_2_pro. flux_pro's own img-to-img variant (redux) only varies its source image and dropped the scene in every measured case; it is still used for seed_image_url without characters, where the source's composition is wanted. A model that takes fewer references than you name: naming more with that tool explicitly is rejected before any quota is spent; without an explicit tool the image is made with the first max_references and the answer names the rest in `referenz_warnung` and `referenzen.nicht_gesendet` — nothing is dropped silently. Each additional reference may cost credits; get_cost with character_refs prices exactly that. This OVERRIDES seed_image_url: the characters fill the reference slots, and identity wins over composition. A character without a generated reference image is rejected before any quota is spent. GET IDS FROM lab_list_characters, or create one with lab_create_character followed by lab_generate_character_reference. Prefer character_refs over seed_image_url whenever a person must stay the same across images: seed_image_url holds identity only for the single call that passes it, character_refs is a register entry that every later call can name. SCENE AND LIGHT FOLLOW THE PROMPT: the reference holds the person, not the shot — harbour at night, stairwell under a top light, neon in the rain all came out as asked with the face kept (measured 2026-10-05 on flux_2_pro). What the reference does NOT change is the character's condition: it always shows the BASE state. For a wet, injured or otherwise changed character pass character_states instead of describing it in the prompt (lab_get_character_sheet returns the state keys and the descriptors behind them); to hold the outfit itself, add its costume plate with costume_refs."
},
"seed_image_url": {
"type": "string",
"description": "Optional: run img-to-img guided by this existing image URL (e.g. a prior generation's url). For a PERSON who must look the same across several images, use character_refs instead — this URL binds one call, a register entry binds all of them."
},
"character_states": {
"type": "object",
"description": "Which STATE each character is in for this image, as { characterId: stateKey } — e.g. { \"12\": \"wet\" }. A state is its own asset with its own full descriptor (clean / soaked / bloodied are three assets, not one with a note), and it is the ONLY way that descriptor reaches a generation: the reference image always carries the BASE state, so without this field you get dry Cal in a scene where he just came out of the Thames — and the image looks correct. Get the keys from lab_get_character_sheet; create one with the character state endpoint. Every id here must also appear in character_refs (who is not in the frame is not described), and an unknown key is rejected before any quota is spent.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {
"type": "string"
}
},
"dreamer_signatur": {
"type": "string",
"description": "Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first."
}
},
"additionalProperties": false
}| views | array | Which views to render. Omit for the core views (six for humans, five without costume for tier/kreatur). Allowed: detail, frontal, profile, fullbody, fullbody_back, costume, pose_sitting, pose_walking, pose_action. Fewer views = fewer credits; `detail` and `fullbody` alone are usually enough to lock an identity. |
| get_cost | boolean | Free: the exact price of this run (per view and total, anchor and edit rate included) — nothing is rendered or charged. Name it to the person before the real run. |
| character_id* | string | Character sheet id (from lab_list_characters or lab_create_character) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"character_id"
],
"properties": {
"views": {
"type": "array",
"items": {
"type": "string"
},
"description": "Which views to render. Omit for the core views (six for humans, five without costume for tier/kreatur). Allowed: detail, frontal, profile, fullbody, fullbody_back, costume, pose_sitting, pose_walking, pose_action. Fewer views = fewer credits; `detail` and `fullbody` alone are usually enough to lock an identity."
},
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this run (per view and total, anchor and edit rate included) — nothing is rendered or charged. Name it to the person before the real run."
},
"character_id": {
"type": "string",
"description": "Character sheet id (from lab_list_characters or lab_create_character) — positive integer as string"
}
},
"additionalProperties": false
}| scene_ref | string | Optional scene label, stored on the row. Useful to tell several tracks of one project apart |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
| duration_seconds | number | Track length in seconds (default 30). ElevenLabs takes 3-300 s (shorter is raised to 3), Stable Audio up to 190 s. Make it at least as long as the sequence you want to cut — lab_apply_rhythm computes durations for the clips that exist and neither adds nor deletes any, so a short track under a long timeline stays a short track |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"tool": {
"enum": [
"elevenlabs_music",
"stable_audio"
],
"type": "string",
"description": "Music engine. Default 'elevenlabs_music' (ElevenLabs Music v2.5, per second, 3-300 s). 'stable_audio' is the older flat-priced engine (1-190 s)"
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "What the music should be. Name genre, instrumentation, mood AND the tempo in bpm if the cut is supposed to sit on the beat — e.g. 'driving techno, four-on-the-floor kick, 128 bpm, no vocals'. A prompt without a stated pulse is the usual reason lab_beatgrid comes back unreliable"
},
"get_cost": {
"type": "boolean",
"description": "`true` preflights the credit price and your balance WITHOUT generating anything and without spending quota."
},
"scene_ref": {
"type": "string",
"description": "Optional scene label, stored on the row. Useful to tell several tracks of one project apart"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"duration_seconds": {
"type": "number",
"maximum": 300,
"minimum": 1,
"description": "Track length in seconds (default 30). ElevenLabs takes 3-300 s (shorter is raised to 3), Stable Audio up to 190 s. Make it at least as long as the sequence you want to cut — lab_apply_rhythm computes durations for the clips that exist and neither adds nor deletes any, so a short track under a long timeline stays a short track"
}
},
"additionalProperties": false
}| Levels per channel (rgb = all three, r/g/b single), values 0..255: input black/white point, gamma (above 1 brightens midtones), output black/white. Example: {"rgb": {"black": 12, "white": 240, "gamma": 1.1}} |
| contrast | string | Contrast curve: high is an S-curve, low flattens it |
| exposure | number | Exposure in stops; positive brightens, zero is neutral |
| image_id* | string | Id of the source image in the project gallery (from lab_list_assets / lab_generate) |
| lightness | number | Lightness; negative darkens, zero neutral |
| grain_mono | boolean | Grain in black and white (default true); false gives colour grain |
| grain_seed | integer | Seed of the grain pattern (default 0) — same seed, same grain |
| project_id* | string | Project id (from lab_list_projects) |
| saturation | number | Colour saturation; minimum is greyscale, zero neutral |
| gradient_map | object | Gradient map: luminance 0..1 → colour ramp, blended with opacity (default 1). Example teal-orange: {"stops": [[0, "#0b2a33"], [0.5, "#7a7a72"], [1, "#ffd9a8"]], "opacity": 0.3} |
| look_opacity | number | Blend of the named look, 0..1 (default 1) |
| from_colorist | boolean | Start from the project's colorist crew brief (look/contrast/grain), then apply the sliders on top |
| exposure_gamma | number | Exposure gamma; 1 neutral, above 1 brightens midtones |
| exposure_offset | number | Exposure offset added after the stops (lifts or crushes blacks); zero neutral |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"image_id"
],
"properties": {
"hue": {
"type": "number",
"maximum": 180,
"minimum": -180,
"description": "Hue rotation in degrees; zero neutral"
},
"look": {
"type": "string",
"description": "Named look from the bundle catalogue (e.g. filmlab-neutral, filmlab-warm, filmlab-cool, filmlab-soft, filmlab-punch) — a hand-authored LUT, applied on top of the sliders. The server resolves the name; a path is never accepted."
},
"grain": {
"type": "number",
"maximum": 30,
"minimum": 0,
"description": "Film grain amount; zero is none, monochrome"
},
"curves": {
"type": "object",
"required": [
"rgb",
"r",
"g",
"b"
],
"description": "Curves per channel as [x, y] points 0..255, x strictly increasing (smooth monotone curve through them). Example warm highlights: {\"r\": [[0,0],[192,205],[255,255]], \"b\": [[0,0],[192,180],[255,255]]}. Not together with contrast on the rgb channel",
"propertyNames": {
"enum": [
"rgb",
"r",
"g",
"b"
],
"type": "string"
},
"additionalProperties": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "number",
"maximum": 255,
"minimum": 0
},
"maxItems": 2,
"minItems": 2
},
"maxItems": 16,
"minItems": 2
}
},
"levels": {
"type": "object",
"required": [
"rgb",
"r",
"g",
"b"
],
"description": "Levels per channel (rgb = all three, r/g/b single), values 0..255: input black/white point, gamma (above 1 brightens midtones), output black/white. Example: {\"rgb\": {\"black\": 12, \"white\": 240, \"gamma\": 1.1}}",
"propertyNames": {
"enum": [
"rgb",
"r",
"g",
"b"
],
"type": "string"
},
"additionalProperties": {
"type": "object",
"properties": {
"black": {
"type": "number",
"maximum": 254,
"minimum": 0
},
"gamma": {
"type": "number",
"maximum": 10,
"minimum": 0.1
},
"white": {
"type": "number",
"maximum": 255,
"minimum": 1
},
"out_black": {
"type": "number",
"maximum": 255,
"minimum": 0
},
"out_white": {
"type": "number",
"maximum": 255,
"minimum": 0
}
}
}
},
"contrast": {
"enum": [
"high",
"low"
],
"type": "string",
"description": "Contrast curve: high is an S-curve, low flattens it"
},
"exposure": {
"type": "number",
"maximum": 3,
"minimum": -3,
"description": "Exposure in stops; positive brightens, zero is neutral"
},
"image_id": {
"type": "string",
"description": "Id of the source image in the project gallery (from lab_list_assets / lab_generate)"
},
"lightness": {
"type": "number",
"maximum": 100,
"minimum": -100,
"description": "Lightness; negative darkens, zero neutral"
},
"grain_mono": {
"type": "boolean",
"description": "Grain in black and white (default true); false gives colour grain"
},
"grain_seed": {
"type": "integer",
"maximum": 1000000,
"minimum": 0,
"description": "Seed of the grain pattern (default 0) — same seed, same grain"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"saturation": {
"type": "number",
"maximum": 100,
"minimum": -100,
"description": "Colour saturation; minimum is greyscale, zero neutral"
},
"gradient_map": {
"type": "object",
"required": [
"stops"
],
"properties": {
"stops": {
"type": "array",
"items": {
"type": "array",
"items": {
"anyOf": [
{
"type": "number",
"maximum": 1,
"minimum": 0
},
{
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
}
]
},
"maxItems": 2,
"minItems": 2
},
"maxItems": 8,
"minItems": 2
},
"opacity": {
"type": "number",
"maximum": 1,
"minimum": 0
}
},
"description": "Gradient map: luminance 0..1 → colour ramp, blended with opacity (default 1). Example teal-orange: {\"stops\": [[0, \"#0b2a33\"], [0.5, \"#7a7a72\"], [1, \"#ffd9a8\"]], \"opacity\": 0.3}"
},
"look_opacity": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Blend of the named look, 0..1 (default 1)"
},
"from_colorist": {
"type": "boolean",
"description": "Start from the project's colorist crew brief (look/contrast/grain), then apply the sliders on top"
},
"exposure_gamma": {
"type": "number",
"maximum": 10,
"minimum": 0.1,
"description": "Exposure gamma; 1 neutral, above 1 brightens midtones"
},
"exposure_offset": {
"type": "number",
"maximum": 0.5,
"minimum": -0.5,
"description": "Exposure offset added after the stops (lifts or crushes blacks); zero neutral"
}
},
"additionalProperties": false
}| grade | object | Fields to set — see description; omit to list clips and grades |
| seq_id* | string | Sequence id — positive integer as string (lab_list_sequences) |
| clip_id | string | The clip to grade (from the listing); omit with programm:true |
| programm | boolean | Grade the whole programme instead of one clip |
| zuruecksetzen | boolean | Start from a neutral grade before applying `grade` |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"grade": {
"type": "object",
"description": "Fields to set — see description; omit to list clips and grades",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string (lab_list_sequences)"
},
"clip_id": {
"type": "string",
"description": "The clip to grade (from the listing); omit with programm:true"
},
"programm": {
"type": "boolean",
"description": "Grade the whole programme instead of one clip"
},
"zuruecksetzen": {
"type": "boolean",
"description": "Start from a neutral grade before applying `grade`"
}
},
"additionalProperties": false
}| Argument | Type | Description |
|---|---|---|
| id* | string | The id returned by lab_generate (image) or lab_animate (video) |
| type* | string | Whether the id refers to a generated image or a generated video |
| wait_s | integer | Wait up to this many seconds (max 80) until the result is rendered AND its prompt check (abgleich) is in, or it failed — instead of polling in quick succession. The answer says gewartet_s. tool_id is the catalog id even after tool_used switched to the display label. |
| preview | boolean | Attach the image itself, downscaled to 512px, so it can be LOOKED at instead of described. Costs one extra fetch and adds roughly 15-25 KB to the answer. Videos carry no still and return no picture — that needs a contact sheet, which is not built yet. Without this flag the answer is unchanged. |
| project_id* | string | Project id — required, since there is no global id lookup |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"type",
"id"
],
"properties": {
"id": {
"type": "string",
"description": "The id returned by lab_generate (image) or lab_animate (video)"
},
"type": {
"enum": [
"image",
"video"
],
"type": "string",
"description": "Whether the id refers to a generated image or a generated video"
},
"wait_s": {
"type": "integer",
"maximum": 80,
"minimum": 0,
"description": "Wait up to this many seconds (max 80) until the result is rendered AND its prompt check (abgleich) is in, or it failed — instead of polling in quick succession. The answer says gewartet_s. tool_id is the catalog id even after tool_used switched to the display label."
},
"preview": {
"type": "boolean",
"description": "Attach the image itself, downscaled to 512px, so it can be LOOKED at instead of described. Costs one extra fetch and adds roughly 15-25 KB to the answer. Videos carry no still and return no picture — that needs a contact sheet, which is not built yet. Without this flag the answer is unchanged."
},
"project_id": {
"type": "string",
"description": "Project id — required, since there is no global id lookup"
}
},
"additionalProperties": false
}| Scene label (e.g. 'SZ 04'). Stored on the row, so the element can be matched back to a shot list and reviewed per scene with lab_review_queue. Omit it and the element is an orphan in the gallery. |
| layer_type | string | Which kind of element this is — it picks the isolation clause: 'cast' full-body person, 'prop' single object, 'foreground' a foreground piece, 'background' a wide backdrop plate WITHOUT people, 'crowd' one background performer head to feet. 'stage' is accepted by the backend but has no clause of its own and falls back to the generic one. Omit for the backend default ('prop'). |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string. Travels in the URL; the element is filed in this project's gallery. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"prompt": {
"type": "string",
"minLength": 1,
"description": "What the element shows — the OBJECT or the PERSON, not the scene around them. The backend appends an isolation clause (single subject, plain seamless background, even light) so the cut-out has a clean edge; describing a set as well fights that clause and produces an element that cannot be freed."
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price in credits and your balance WITHOUT generating and without spending anything. There is no model to pick on this path — the cut-out runs on the house image model and is billed at the plain image rate, so lab_list_models has no entry that would change this number; it comes from the same cost endpoint as everywhere else."
},
"metadata": {
"type": "object",
"description": "Free-form JSON stored alongside the element (e.g. { note, parent_id }). Merged into the row's metadata next to the fields the pipeline sets itself (kind, layer_type, status) — not interpreted.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"scene_ref": {
"type": "string",
"description": "Scene label (e.g. 'SZ 04'). Stored on the row, so the element can be matched back to a shot list and reviewed per scene with lab_review_queue. Omit it and the element is an orphan in the gallery."
},
"layer_type": {
"enum": [
"cast",
"prop",
"foreground",
"background",
"stage",
"crowd"
],
"type": "string",
"description": "Which kind of element this is — it picks the isolation clause: 'cast' full-body person, 'prop' single object, 'foreground' a foreground piece, 'background' a wide backdrop plate WITHOUT people, 'crowd' one background performer head to feet. 'stage' is accepted by the backend but has no clause of its own and falls back to the generic one. Omit for the backend default ('prop')."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string. Travels in the URL; the element is filed in this project's gallery."
}
},
"additionalProperties": false
}| klasse |
| string |
| Only one capability class — the valid names are listed under `klassen` in every answer |
| dauer_s | number | Only models that can render this duration in seconds |
| aufloesung | string | Only models offering this resolution, e.g. 540p |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"kind"
],
"properties": {
"eu": {
"type": "boolean",
"description": "Only models allowed in EU mode projects (data stays inside the EU data path). Every model carries `eu_modus` and, where false, `eu_grund` — the first missing proof (`form: knapp` carries only `eu_modus`)"
},
"ton": {
"enum": [
"mit",
"ohne"
],
"type": "string",
"description": "ohne = can run without sound (audio nie or schaltbar); mit = can make sound"
},
"kind": {
"enum": [
"video",
"image"
],
"type": "string",
"description": "Which catalog"
},
"voll": {
"type": "boolean",
"description": "Every field of every model even without klasse — large, use a filter with it"
},
"klasse": {
"type": "string",
"description": "Only one capability class — the valid names are listed under `klassen` in every answer"
},
"dauer_s": {
"type": "number",
"description": "Only models that can render this duration in seconds",
"exclusiveMinimum": 0
},
"aufloesung": {
"type": "string",
"description": "Only models offering this resolution, e.g. 540p"
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"lokale_tabelle",
"local_id",
"lizenznehmer_email",
"nutzungsart",
"gebiet",
"beginnt_am",
"preis_credits"
],
"properties": {
"gebiet": {
"type": "string",
"maxLength": 500,
"minLength": 1
},
"endet_am": {
"type": "string"
},
"local_id": {
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
"beginnt_am": {
"type": "string"
},
"nutzungsart": {
"type": "string",
"maxLength": 500,
"minLength": 1
},
"plattformen": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100,
"minLength": 1
},
"maxItems": 30
},
"preis_credits": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0
},
"lokale_tabelle": {
"enum": [
"character_sheets",
"location_boards",
"lab_props",
"lab_stil_kits",
"lab_cloned_voices"
],
"type": "string"
},
"lizenznehmer_email": {
"type": "string",
"format": "email",
"pattern": "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"image_id"
],
"properties": {
"engine": {
"enum": [
"fal-sam3d",
"vggt",
"vggt-splat"
],
"type": "string",
"description": "Which reconstruction path. Omit for the backend default. 'vggt' is scene-scale and fast but gives a point cloud; 'vggt-splat' trains a walkable Gaussian splat from the same scan (minutes on a GPU, not seconds). Both need their own model configured on the backend — where it is not, the answer says so instead of falling back silently."
},
"image_id": {
"type": "string",
"description": "Id of the SCAN row (from lab_list_location_scans) — positive integer as string. It is a row in the image gallery whose tool is the location scanner; a normal generated image with that id is rejected, not reconstructed."
},
"max_photos": {
"type": "integer",
"maximum": 64,
"minimum": 0,
"description": "How many keyframes are pulled from the clip and handed to the reconstruction. Omit to let the engine use its own count — more frames mean a wider parallax baseline and a slower run."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string. The scan must belong to it; a scan of another project is not found rather than borrowed."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id"
],
"properties": {
"szene": {
"type": "string",
"description": "Only this scene (scene_ref from lab_shots); default: every scene with two or more shots"
},
"seq_id": {
"type": "string",
"description": "Sequence id — positive integer as string"
},
"clip_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Compare exactly these shots instead of the scenes from lab_shots"
},
"referenz_clip_id": {
"type": "string",
"description": "The shot the others are matched to; default the first of the scene"
}
},
"additionalProperties": false
}| tool |
| string |
| Model key from aktion optionen (only models that take a clip as reference). Default: the first one listed there |
| preis | boolean | Also price all pieces (free, books nothing) |
| aktion | string | optionen: models with their limits, shot sizes, cinematic headers (free, no project needed). plan (default): check the shot list and build the runs. zusammensetzen: finished pieces into one new sequence |
| bild_urls | array | Image addresses for plan.bilder, same order |
| laengen_s | array | zusammensetzen: the planned length of each piece (stuecke[].bis_s - von_s), same order |
| video_ids | array | zusammensetzen: the finished pieces, in order |
| project_id | string | Project — required for plan and zusammensetzen |
| rechte_bestaetigt | boolean | The person confirmed they hold the rights to an uploaded clip — passed into every run |
| reference_video_url | string | The clip to re-shoot: an uploaded file (lab_add_media / lab_list_assets) or a finished clip of this project |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"name": {
"type": "string",
"description": "zusammensetzen: title of the new sequence"
},
"plan": {
"type": "object",
"required": [
"dauer_s",
"original_kamera",
"personen",
"einstellungen"
],
"properties": {
"look": {
"enum": [
"gleich",
"kino"
],
"type": "string",
"description": "gleich: keep the original look. kino: re-shoot it like a film (needs kino_kopf)"
},
"bilder": {
"type": "array",
"items": {
"type": "string"
},
"description": "One job per extra reference image, e.g. 'how the woman looks'. Same order as bild_urls"
},
"dauer_s": {
"type": "number",
"description": "Length of the clip in seconds",
"exclusiveMinimum": 0
},
"personen": {
"type": "string",
"description": "Who is in the clip, half a sentence — for naming only, no character paragraphs"
},
"kino_kopf": {
"type": "string",
"description": "Cinematic header key from aktion optionen"
},
"einstellungen": {
"type": "array",
"items": {
"type": "object",
"required": [
"von_s",
"bis_s",
"groesse",
"kamera_ort"
],
"properties": {
"bild": {
"type": "string",
"description": "Optional: one short sentence on what we see"
},
"bis_s": {
"type": "number",
"exclusiveMinimum": 0
},
"von_s": {
"type": "number",
"minimum": 0
},
"zeile": {
"type": "string",
"description": "The words spoken during this shot, verbatim — the cut lands on them"
},
"groesse": {
"type": "string",
"description": "Shot size key from aktion optionen"
},
"kamera_ort": {
"type": "string",
"description": "Where the camera physically stands in ONE sentence — side, height, distance, by landmarks"
}
}
},
"description": "The shot list, in order, covering the clip without gaps"
},
"anfang_behalten": {
"type": "boolean",
"description": "The first shot stays identical to the original"
},
"original_kamera": {
"type": "string",
"description": "The ORIGINAL angle in physical words: where the camera stands, its height, what it points at. The prompt forbids exactly this"
}
}
},
"tool": {
"type": "string",
"description": "Model key from aktion optionen (only models that take a clip as reference). Default: the first one listed there"
},
"preis": {
"type": "boolean",
"description": "Also price all pieces (free, books nothing)"
},
"aktion": {
"enum": [
"optionen",
"plan",
"zusammensetzen"
],
"type": "string",
"description": "optionen: models with their limits, shot sizes, cinematic headers (free, no project needed). plan (default): check the shot list and build the runs. zusammensetzen: finished pieces into one new sequence"
},
"bild_urls": {
"type": "array",
"items": {
"type": "string"
},
"description": "Image addresses for plan.bilder, same order"
},
"laengen_s": {
"type": "array",
"items": {
"type": "number",
"exclusiveMinimum": 0
},
"description": "zusammensetzen: the planned length of each piece (stuecke[].bis_s - von_s), same order"
},
"video_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "zusammensetzen: the finished pieces, in order"
},
"project_id": {
"type": "string",
"description": "Project — required for plan and zusammensetzen"
},
"rechte_bestaetigt": {
"type": "boolean",
"description": "The person confirmed they hold the rights to an uploaded clip — passed into every run"
},
"reference_video_url": {
"type": "string",
"description": "The clip to re-shoot: an uploaded file (lab_add_media / lab_list_assets) or a finished clip of this project"
}
},
"additionalProperties": false
}| depth | string | video only: sketch (5 blocks, default) or skeleton (15 blocks) |
| anlass | string | beautify only: what the image is FOR. Default frei |
| medium | string | photo (default) or video — decides the section layout |
| prompt* | string | The text to work on — a loose thought is enough for beautify |
| winkel | string | Optional: change the camera angle of the EXISTING prompt — a key such as augen, unter, auf, vogel, frosch, schraeg, ots, pov, profil. Implies mode=custom and needs no instruction; combinable with groesse. |
| familie | string | Optional, instead of tool: the model family by name when no registry key is at hand. Medium decides whether the image or the video skeleton applies. |
| groesse | string | Optional: change the shot size of the EXISTING prompt, keeping subject, light and place — a key such as ews, ws, mws, ms, mcu, cu, ecu, macro. Implies mode=custom and needs no instruction. An unknown key is rejected with the known ones. |
| techniken | array | Up to three directing technique keys from lab_regie_techniken. Their prompt fragments are appended verbatim after the transform, ordered angle, framing, composition, movement. One angle and one movement at most; movement only with medium=video. |
| project_id | integer | Optional: the project this prompt is for. With tool or familie, its Lens Lab optics, Pose Director framing and gaffer light are laid into the prompt. If the project has look_in_post on and a Lens Lab set chosen, grain, halation, vignetting, chromatic aberration and edge softness are left out — the FilmLab Look plugin adds them in Resolve; the answer lists what was removed. |
| referenzen | array | The references the prompt may point at, IN THE ORDER you will pass them to lab_animate/lab_propose. Write @bild1, @video1, @ton1 in your prompt and they come back as the markers the model actually reads (@Image1, @Video2, @Audio1) — the reference clip itself is @Video1, so the first video reference is @Video2. A mention with no matching reference is rejected instead of being left in the text, where it would reach the model as a word. |
| instruction | string | Required for custom (an order, e.g. 'make it darker') and stiluebernahme (a style description) |
| location_id | integer | Optional, only with tool or familie: the location board whose prompt prefix becomes the place block — the same board you will hand to lab_animate/lab_generate. |
| duration_seconds | integer | video only: the clip length in seconds the ACTION TIMING beats are written to. Default 5 (what lab_animate renders without an explicit duration). Rejected for medium=photo. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"prompt"
],
"properties": {
"mode": {
"enum": [
"beautify",
"storyboard",
"polish",
"format",
"shorten",
"expand",
"simplify",
"custom",
"stiluebernahme"
],
"type": "string",
"description": "Default beautify (generates). beautify: Aus der Idee einen vollständigen Prompt bauen — ergänzt Licht, Optik, Kamera, Stimmung | storyboard: Szene in 4–8 nummerierte Panels zerlegen — je Panel ein eigenständiger Bild-Prompt | polish: Wortwahl polieren, Flow verbessern, Redundanz entfernen — Länge bleibt | format: In // Subject // Lighting // Camera Sektionen ordnen, Wortlaut bleibt | shorten: ~30% kürzer, jedes Wort muss Gewicht tragen | expand: Texturen, Lichtreflexe, atmosphärische Details | simplify: Direkt und klar, weniger Fachjargon | custom: Eigene Anweisung — z.B. 'Mach es düsterer' | stiluebernahme: Licht, Optik und Look aus einer Beschreibung — Motiv und Bildaufbau bleiben wörtlich"
},
"tool": {
"type": "string",
"description": "Optional: the model you will render with, as the registry key from lab_list_models. Decides which family skeleton lays out camera, lens and light — and whether it is a measured rule or the plain appended form."
},
"depth": {
"enum": [
"sketch",
"skeleton"
],
"type": "string",
"description": "video only: sketch (5 blocks, default) or skeleton (15 blocks)"
},
"anlass": {
"enum": [
"frei",
"interview",
"szene",
"portrait",
"street",
"editorial",
"handy"
],
"type": "string",
"description": "beautify only: what the image is FOR. Default frei"
},
"medium": {
"enum": [
"photo",
"video"
],
"type": "string",
"description": "photo (default) or video — decides the section layout"
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "The text to work on — a loose thought is enough for beautify"
},
"winkel": {
"type": "string",
"description": "Optional: change the camera angle of the EXISTING prompt — a key such as augen, unter, auf, vogel, frosch, schraeg, ots, pov, profil. Implies mode=custom and needs no instruction; combinable with groesse."
},
"familie": {
"type": "string",
"description": "Optional, instead of tool: the model family by name when no registry key is at hand. Medium decides whether the image or the video skeleton applies."
},
"groesse": {
"type": "string",
"description": "Optional: change the shot size of the EXISTING prompt, keeping subject, light and place — a key such as ews, ws, mws, ms, mcu, cu, ecu, macro. Implies mode=custom and needs no instruction. An unknown key is rejected with the known ones."
},
"techniken": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 3,
"description": "Up to three directing technique keys from lab_regie_techniken. Their prompt fragments are appended verbatim after the transform, ordered angle, framing, composition, movement. One angle and one movement at most; movement only with medium=video."
},
"project_id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Optional: the project this prompt is for. With tool or familie, its Lens Lab optics, Pose Director framing and gaffer light are laid into the prompt. If the project has look_in_post on and a Lens Lab set chosen, grain, halation, vignetting, chromatic aberration and edge softness are left out — the FilmLab Look plugin adds them in Resolve; the answer lists what was removed."
},
"referenzen": {
"type": "array",
"items": {
"type": "object",
"required": [
"art"
],
"properties": {
"id": {
"type": "string",
"description": "The reference as an ID — optional here for the same reason."
},
"art": {
"enum": [
"bild",
"video",
"ton"
],
"type": "string",
"description": "Which channel this reference goes into: bild an image, video a clip, ton a sound."
},
"url": {
"type": "string",
"description": "Address of the reference — optional here: the Dreamer only needs the ORDER and the channel, not the file."
},
"rolle": {
"type": "string",
"description": "WHICH character or place this reference means. Carried into the mapping the answer returns."
}
}
},
"description": "The references the prompt may point at, IN THE ORDER you will pass them to lab_animate/lab_propose. Write @bild1, @video1, @ton1 in your prompt and they come back as the markers the model actually reads (@Image1, @Video2, @Audio1) — the reference clip itself is @Video1, so the first video reference is @Video2. A mention with no matching reference is rejected instead of being left in the text, where it would reach the model as a word."
},
"instruction": {
"type": "string",
"description": "Required for custom (an order, e.g. 'make it darker') and stiluebernahme (a style description)"
},
"location_id": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"description": "Optional, only with tool or familie: the location board whose prompt prefix becomes the place block — the same board you will hand to lab_animate/lab_generate."
},
"duration_seconds": {
"type": "integer",
"maximum": 30,
"minimum": 1,
"description": "video only: the clip length in seconds the ACTION TIMING beats are written to. Default 5 (what lab_animate renders without an explicit duration). Rejected for medium=photo."
}
},
"additionalProperties": false
}| familie | string | Video: pick the model ('seedance-2.0', 'seedance-2.5') instead of a route — the card then names and prices the route the server takes for this account, and the run is PINNED to it (a card promises one price, so no silent fallback). Mutually exclusive with tool and intent. |
| out_sec | number | Video: end of the excerpt in the reference clip, in seconds. Must lie behind in_sec. |
| variants | integer | Image: how many variants (default 1). Multiplies the price — the card shows the multiplication. |
| audio_url | string | Video: URL of an existing audio track to lay under the clip. Independent of generate_audio. |
| scene_ref | string | Scene label (e.g. 'SZ 04'). Stored on the row, so the result can be matched back to a shot list. |
| project_id* | string | Project id (from lab_list_projects) |
| referenzen | array | Video, clip path (source_video_id): typed references. The position IS the address — first image @Image1, first extra clip @Video2, first sound @Audio1. Image references are measured before sending and scaled up to the model's minimum rather than rejected. |
| resolution | string | Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default. |
| location_id | string | Location board id (from lab_list_locations) — anchors the shot in the same place. |
| aspect_ratio | string | Image: aspect ratio as W:H, from the model's aspect_ratios in lab_list_models. |
| costume_refs | array | The costume plate of these characters goes along as an extra reference image — the outfit on an invisible mannequin, NO person. Every id must also be in character_refs. Needs a model whose max_references covers characters + plates. Each plate is one more reference and may cost credits. |
| end_image_id | string | Video: the completed image to land the clip on (last frame). Only where lab_list_models says supports_end_frame. |
| camera_motion | string | Video: Structured camera move sent to the model itself — only models whose lab_list_models entry lists camera_motions. Prefer this over prose when a keyframe chain must MOVE rather than cross-fade. |
| character_refs | array | Character ids (from lab_list_characters) whose reference image locks the cast's identity. Counted against the model's max_references and shown on the card by name. |
| generate_audio | boolean | Video: ask for the model's native audio. Only meaningful where lab_list_models says audio: schaltbar. |
| seed_image_url | string | Image: run img-to-img guided by this existing image URL. Shown on the card as the 'before'. |
| source_image_id | string | Video: the completed image to animate (positive integer as string). Required for a video proposal — unless source_video_id proposes a clip instead. |
| source_video_id | string | Video: propose a run FROM A CLIP instead of a still — a generated video id (lab_animate/lab_job_status) or an asset key (lab_list_assets). Mutually exclusive with source_image_id; only for models that take a video reference (lab_list_models says v2v). |
| dreamer_signatur | string | Dreamer proof (lab_prompt_transform) for THIS prompt. Without it the proposal runs the Dreamer itself and the card shows its version — that card is what the person confirms. |
| duration_seconds | number | Video: clip length in seconds, inside the model's duration grid from lab_list_models. The card shows the DELIVERED seconds and says so when the provider snaps. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"kind",
"project_id",
"prompt"
],
"properties": {
"kind": {
"enum": [
"video",
"image"
],
"type": "string",
"description": "Which kind of generation to propose"
},
"tool": {
"type": "string",
"description": "Model id, copied verbatim from lab_list_models. Omit to use the default for this kind — the card names it and prices it either way."
},
"in_sec": {
"type": "number",
"description": "Video: start of the excerpt cut out of the reference clip, in seconds. Only together with out_sec and source_video_id."
},
"intent": {
"type": "string",
"description": "Video intent from the routing rules. Used to choose a model when tool is omitted and retained for Plan B; an explicit tool wins."
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "What to generate. The user can edit it on the card; lab_run_proposal takes the edited text as prompt_override without a new proposal. Video only: For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: \"<line>\" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there."
},
"familie": {
"enum": [
"seedance-2.0",
"seedance-2.5"
],
"type": "string",
"description": "Video: pick the model ('seedance-2.0', 'seedance-2.5') instead of a route — the card then names and prices the route the server takes for this account, and the run is PINNED to it (a card promises one price, so no silent fallback). Mutually exclusive with tool and intent."
},
"out_sec": {
"type": "number",
"description": "Video: end of the excerpt in the reference clip, in seconds. Must lie behind in_sec."
},
"variants": {
"type": "integer",
"maximum": 5,
"minimum": 1,
"description": "Image: how many variants (default 1). Multiplies the price — the card shows the multiplication."
},
"audio_url": {
"type": "string",
"description": "Video: URL of an existing audio track to lay under the clip. Independent of generate_audio."
},
"scene_ref": {
"type": "string",
"description": "Scene label (e.g. 'SZ 04'). Stored on the row, so the result can be matched back to a shot list."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"referenzen": {
"type": "array",
"items": {
"type": "object",
"required": [
"art"
],
"properties": {
"id": {
"type": "string",
"description": "The reference as an ID, looked up in this project: an image id, a video id, an audio id, or an asset key. The URL is never built here."
},
"art": {
"enum": [
"bild",
"video",
"ton"
],
"type": "string",
"description": "Which channel this reference goes into: bild an image, video a clip, ton a sound. How many of each a model takes is in lab_list_models (referenz_grenzen); a channel the model has no slot for is rejected before a card is even offered."
},
"url": {
"type": "string",
"description": "Address of the reference. Exactly one of url or id."
},
"rolle": {
"type": "string",
"description": "WHICH character or place this reference means. Free text, carried through."
}
}
},
"description": "Video, clip path (source_video_id): typed references. The position IS the address — first image @Image1, first extra clip @Video2, first sound @Audio1. Image references are measured before sending and scaled up to the model's minimum rather than rejected."
},
"resolution": {
"type": "string",
"description": "Resolution tier, exactly as listed for the model in lab_list_models. Omit for the model's default."
},
"location_id": {
"type": "string",
"description": "Location board id (from lab_list_locations) — anchors the shot in the same place."
},
"aspect_ratio": {
"type": "string",
"description": "Image: aspect ratio as W:H, from the model's aspect_ratios in lab_list_models."
},
"costume_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "The costume plate of these characters goes along as an extra reference image — the outfit on an invisible mannequin, NO person. Every id must also be in character_refs. Needs a model whose max_references covers characters + plates. Each plate is one more reference and may cost credits."
},
"end_image_id": {
"type": "string",
"description": "Video: the completed image to land the clip on (last frame). Only where lab_list_models says supports_end_frame."
},
"camera_motion": {
"enum": [
"dolly_in",
"dolly_out",
"dolly_left",
"dolly_right",
"jib_up",
"jib_down",
"static",
"focus_shift"
],
"type": "string",
"description": "Video: Structured camera move sent to the model itself — only models whose lab_list_models entry lists camera_motions. Prefer this over prose when a keyframe chain must MOVE rather than cross-fade."
},
"character_refs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Character ids (from lab_list_characters) whose reference image locks the cast's identity. Counted against the model's max_references and shown on the card by name."
},
"generate_audio": {
"type": "boolean",
"description": "Video: ask for the model's native audio. Only meaningful where lab_list_models says audio: schaltbar."
},
"seed_image_url": {
"type": "string",
"description": "Image: run img-to-img guided by this existing image URL. Shown on the card as the 'before'."
},
"source_image_id": {
"type": "string",
"description": "Video: the completed image to animate (positive integer as string). Required for a video proposal — unless source_video_id proposes a clip instead."
},
"source_video_id": {
"type": "string",
"description": "Video: propose a run FROM A CLIP instead of a still — a generated video id (lab_animate/lab_job_status) or an asset key (lab_list_assets). Mutually exclusive with source_image_id; only for models that take a video reference (lab_list_models says v2v)."
},
"dreamer_signatur": {
"type": "string",
"description": "Dreamer proof (lab_prompt_transform) for THIS prompt. Without it the proposal runs the Dreamer itself and the card shows its version — that card is what the person confirms."
},
"duration_seconds": {
"type": "number",
"description": "Video: clip length in seconds, inside the model's duration grid from lab_list_models. The card shows the DELIVERED seconds and says so when the provider snaps."
}
},
"additionalProperties": false
}| tool | string | Video model id — copy it verbatim from lab_list_models (kind: video), and only models the catalog marks v2v take a reference clip at all. Omit to let the backend use its own default for this path. Durations, resolutions and price live in the catalog, this text names none. |
| draft | boolean | Render a cheap DRAFT first (Seedance 2.5 and wan_vace only — other models refuse it before anything is charged). On Seedance 2.5 the draft is 480p, priced at that tier, and carries a draft_id that stays valid for 7 days and only for the account that ordered it. Finish an accepted draft in 1080p ONLY with lab_fertigstellen — never by downloading and re-uploading it: that is a new run with new motion, not the draft. Any `resolution` you pass is ignored for a draft, and the answer says so. |
| in_sec | number | Cut the reference clip server-side before it reaches the provider: start of the excerpt, in seconds from the clip's beginning. Only together with out_sec. |
| prompt* | string | What the new clip should show. Describe it — do NOT phrase it as an instruction to edit or extend the reference; some models classify the run by the verb and then reject the aspect ratio and duration after queueing. |
| out_sec | number | End of the excerpt in the reference clip, in seconds. Must lie behind in_sec; both are set together or neither. |
| get_cost | boolean | Preflight: return the price in credits and your balance WITHOUT generating and without spending anything, reading the same catalog as lab_list_models. Do this before a long take or when the user should confirm the cost. |
| prop_ids | array | Prop ids from lab_list_props (an @prop_ tag in the prompt does the same). Their descriptions go into the prompt; each prop's front view joins the reference images after yours, on free slots only, with its own @Image line (never on wan_vace). The answer says which way it went (props.weg). |
| use_depth | any | Condition the run on a depth pass of the reference clip. Defaults to ON (true = 'voll': depth of the whole frame); pass false to opt out. Models that do not have a depth stage ignore it — the answer says so rather than pretending it happened. 'personen': depth ONLY on the people, the original footage around them — the model stops copying build, hair and clothing of the original performers and the room keeps its colours. With 'personen' the run PAUSES after the mask: only the mask (SAM) is charged, the answer carries cost (mask) and cost_lauf (the run), and the clip waits as wartet_auf_freigabe until the human has looked at the preview (metadata.personen_referenz_url) and you call lab_freigeben. Needs a reference clip and a model with a depth stage; not together with face masks or task='editing'. To RE-FILM a take from new camera angles, pass false: a depth pass locks the geometry of the reference, and the new angles cannot happen. task='editing' ignores it anyway. |
| kamerapfad | object | The camera move as INPUT instead of prose: an object following shared/kamerapfad/schema.json — poses over time (position, look-at or quaternion, metres, Y up) from the Kadrage terrain flight or drawn by hand, or motion phases as lab_kamerafahrt reads them from a clip (its answer carries a ready `kamerapfad`). The backend validates it, derives phases, and adds the move as a paragraph after your prompt, before the reference markers (the reference clip still leads the motion as the first video reference). WHICH FORM that paragraph takes depends on the model: prose for most families, bracketed director commands for the MiniMax/Hailuo family, which parses them. NO MODEL RECEIVES THE POSES TODAY — the answer says which form went out (kamerapfad.angewandt = als_text or als_regiebefehle), names the family rule and the source it rests on (kamerapfad.sprache), and lists what that form could not carry. If your own prompt already carries bracketed commands, the path is NOT sent on top of them (angewandt = eigene_regie, with kamerapfad.eigene_regiebefehle naming what it found). A `zusammenfassung` inside the path is display only and is NEVER sent — the move is always rebuilt from the phases; if nothing can be built, the answer says angewandt = nicht_ableitbar with the reason, and nothing of the path is in the prompt. Mutually exclusive with kamerapfad_url. |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string. On this path it travels in the request BODY, not in the URL, but it is just as required: the new clip is filed under this project. |
| referenzen | array | The typed successor of character_ref_urls: each reference says WHAT it is, so a place or a look no longer has to travel disguised as a character. Since 2026-09-03 it carries all three channels, not just images: motion references ride alongside the reference clip, sound references go into the model's audio slots. The position IS the address — the images are @Image1..n, the reference clip is @Video1 and the extra motion references follow it, the sounds are @Audio1..n. Image references are MEASURED before they are sent and scaled up to the model's minimum if they fall below it; a reference is never rejected for being small. Mutually exclusive with character_ref_urls — two sources for one list means one of them silently loses. What a model takes per channel is in lab_list_models (referenz_grenzen). |
| resolution | string | Render resolution on this path, copied verbatim from the model's resolutions in lab_list_models. The run is priced at the resolution you choose — the low tier costs a fraction of the default — so pass it for sound-only or test runs. Omitted, the model's default applies. |
| kamera_frei | boolean | New camera angles (lab_neue_winkel): @Video1 carries the action, the words and the timing, but NOT the camera — the camera follows the timestamped shot list in the prompt. Replaces the default sentence 'follow the motion, camera movement and timing of @Video1 exactly', which would pull the model back to the original angle. Needs a reference clip and use_depth=false, not with task='editing'. Normally you pass the ready `lauf` object from lab_neue_winkel unchanged instead of setting this by hand. |
| stil_kit_id | string | Style kit id (lab_list_stil_kits; an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies. Its rules and palette go into the prompt; its images join the reference images AFTER yours and the props, only on slots the model still has free (never on wan_vace, where an image would be read as a character). The answer says which way it went (stil_kit.weg). |
| wechsel_von | string | The model id this call switches AWAY from (as listed in lab_list_models), with the same references as before. The answer then carries `wechsel`: which references the switch cuts off and why, and which ones the new model picks up. Nothing is sent to the old model; the field only names the comparison. |
| aspect_ratio | string | Output aspect ratio on this path, written as width:height or 'auto'. Only models whose lab_list_models entry takes it on the reference path accept it; for any other model the backend refuses it before anything is charged, and the refusal lists the allowed values. Omitted, the model's fixed default applies. |
| ton_fuehrung | string | What the model HEARS as guidance while it renders — not what the result carries (that is `ton`). voll (default, same as omitting it): unchanged behaviour. stimme: the backend splits the reference excerpt (after in_sec/out_sec) with demucs and sends ONLY the vocal stem, unchanged, as the single audio reference @Audio1 — so a speaking or singing performer moves the lips without the music bed dragging along. The stem is never pitch-shifted or altered in any way. The demucs split is charged per second of the excerpt and shows up in get_cost (ton_fuehrung.cost); it is cached per clip and in/out, a repeat costs nothing. Needs a reference clip and a model with an audio slot; not together with sound references in `referenzen`. keine: no audio reference at all. With ton='quelle' the original mix of the clip — not the stem — is laid back under the result. |
| ohne_stil_kit | boolean | true switches the project's default style kit off for this one call. |
| generate_audio | boolean | Let the model generate sound with the clip — only models whose lab_list_models entry can output audio take it on this path. Omitted = no generated sound. Generated audio REPLACES any voice in the reference — to keep a spoken take, leave it off. |
| kamerapfad_url | string | Address of a kamerapfad JSON instead of the object — the url of a file in THIS project (lab_list_assets), e.g. the kamerapfad JSON that Kadrage writes next to its flight video. Fetched by the backend under the same ownership rule as every reference: a file of another project is a named rejection before anything is charged. Mutually exclusive with kamerapfad. |
| last_frame_url | string | Guiding frame for the END of the new clip (image URL). Same rule as first_frame_url: wan_vace only, rejected elsewhere rather than dropped. |
| first_frame_url | string | Guiding frame for the START of the new clip (image URL). Only the wan_vace path binds guiding frames to the provider — with any other model the backend REJECTS this field instead of swallowing it, because a swallowed guiding frame means paying for a run identical to the one without it. |
| dreamer_signatur | string | Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first. |
| duration_seconds | number | Length of the NEW clip. Must lie inside the chosen model's duration grid from lab_list_models — the per-model ceiling is enforced by the backend and rejected rather than silently clamped. |
| raum_beschreiben | boolean | With depth on, a vision call describes ONLY the room of a still from the reference (place, light, colours, materials — no people) and adds it as a paragraph to the prompt, so a depth run does not come back grey. ON by default; false switches it off. The setting is remembered per project — omit it to use the project's last choice. Skipped automatically when an `ort` reference is given. The answer reports raum.angewandt and the text; metadata.prompt_gesendet holds the prompt as sent. |
| rechte_bestaetigt | boolean | REQUIRED when the reference clip or a character image was uploaded (not made in FilmLab), including characters whose sheet comes from an uploaded photo. It stands for this statement by the HUMAN: 'Ich habe die Rechte an Bild und Ton dieses Clips und die Einwilligung der gezeigten Personen.' Never set it on your own — show the human the statement and pass true only after they agreed. Without it the backend refuses with rechte_fehlen before anything is charged; get_cost tells you up front (rechte_noetig). |
| character_ref_urls | array | Ready image URLs that bind the cast's identity. The name says URLs on purpose: lab_animate's `character_refs` takes character sheet IDS, this path takes ADDRESSES, and the two are not interchangeable. Get them from lab_get_character_sheet. Omit to let the backend use the project's world-bible characters. Mutually exclusive with `referenzen`. |
| reference_video_url | string | URL of the clip whose motion is taken. Use the url/video_url a previous answer gave you (lab_list_assets with asset_type 'video', lab_job_status, lab_review_queue) — never a hand-built address: the bucket is not guessable and a wrong one renders and bills into a 404. Leave it out to run on `referenzen` alone (at least one image reference, no `figur`, no in_sec/out_sec, no depth) — the models that allow it carry referenzen_ohne_video in lab_list_models. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"prompt"
],
"properties": {
"ton": {
"enum": [
"quelle",
"generiert",
"keiner"
],
"type": "string",
"description": "Which sound the result carries. quelle: the reference clip's own audio is laid back under the new picture after generation — the way to keep a spoken take, since models that regenerate audio replace the voice. generiert: keep the model's sound (needs generate_audio: true). keiner: no sound. Omitted: quelle when a reference clip is given and generate_audio is off, generiert when generate_audio is on, keiner otherwise. The finished run reports in metadata what actually lies underneath (ton) and how far picture and sound differ in length (ton_drift_s)."
},
"task": {
"enum": [
"reference",
"editing"
],
"type": "string",
"description": "reference (default): the references guide a new clip. editing: the picture of the reference clip stays and only what the prompt names changes — e.g. 'keep the picture, generate matching sound effects, no music' with generate_audio: true. Needs a reference clip. Edit verbs in the prompt are expected here, not refused."
},
"tool": {
"type": "string",
"description": "Video model id — copy it verbatim from lab_list_models (kind: video), and only models the catalog marks v2v take a reference clip at all. Omit to let the backend use its own default for this path. Durations, resolutions and price live in the catalog, this text names none."
},
"draft": {
"type": "boolean",
"description": "Render a cheap DRAFT first (Seedance 2.5 and wan_vace only — other models refuse it before anything is charged). On Seedance 2.5 the draft is 480p, priced at that tier, and carries a draft_id that stays valid for 7 days and only for the account that ordered it. Finish an accepted draft in 1080p ONLY with lab_fertigstellen — never by downloading and re-uploading it: that is a new run with new motion, not the draft. Any `resolution` you pass is ignored for a draft, and the answer says so."
},
"in_sec": {
"type": "number",
"description": "Cut the reference clip server-side before it reaches the provider: start of the excerpt, in seconds from the clip's beginning. Only together with out_sec."
},
"prompt": {
"type": "string",
"minLength": 1,
"description": "What the new clip should show. Describe it — do NOT phrase it as an instruction to edit or extend the reference; some models classify the run by the verb and then reject the aspect ratio and duration after queueing."
},
"out_sec": {
"type": "number",
"description": "End of the excerpt in the reference clip, in seconds. Must lie behind in_sec; both are set together or neither."
},
"get_cost": {
"type": "boolean",
"description": "Preflight: return the price in credits and your balance WITHOUT generating and without spending anything, reading the same catalog as lab_list_models. Do this before a long take or when the user should confirm the cost."
},
"prop_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "Prop ids from lab_list_props (an @prop_ tag in the prompt does the same). Their descriptions go into the prompt; each prop's front view joins the reference images after yours, on free slots only, with its own @Image line (never on wan_vace). The answer says which way it went (props.weg)."
},
"use_depth": {
"anyOf": [
{
"type": "boolean"
},
{
"enum": [
"voll",
"personen"
],
"type": "string"
}
],
"description": "Condition the run on a depth pass of the reference clip. Defaults to ON (true = 'voll': depth of the whole frame); pass false to opt out. Models that do not have a depth stage ignore it — the answer says so rather than pretending it happened. 'personen': depth ONLY on the people, the original footage around them — the model stops copying build, hair and clothing of the original performers and the room keeps its colours. With 'personen' the run PAUSES after the mask: only the mask (SAM) is charged, the answer carries cost (mask) and cost_lauf (the run), and the clip waits as wartet_auf_freigabe until the human has looked at the preview (metadata.personen_referenz_url) and you call lab_freigeben. Needs a reference clip and a model with a depth stage; not together with face masks or task='editing'. To RE-FILM a take from new camera angles, pass false: a depth pass locks the geometry of the reference, and the new angles cannot happen. task='editing' ignores it anyway."
},
"kamerapfad": {
"type": "object",
"description": "The camera move as INPUT instead of prose: an object following shared/kamerapfad/schema.json — poses over time (position, look-at or quaternion, metres, Y up) from the Kadrage terrain flight or drawn by hand, or motion phases as lab_kamerafahrt reads them from a clip (its answer carries a ready `kamerapfad`). The backend validates it, derives phases, and adds the move as a paragraph after your prompt, before the reference markers (the reference clip still leads the motion as the first video reference). WHICH FORM that paragraph takes depends on the model: prose for most families, bracketed director commands for the MiniMax/Hailuo family, which parses them. NO MODEL RECEIVES THE POSES TODAY — the answer says which form went out (kamerapfad.angewandt = als_text or als_regiebefehle), names the family rule and the source it rests on (kamerapfad.sprache), and lists what that form could not carry. If your own prompt already carries bracketed commands, the path is NOT sent on top of them (angewandt = eigene_regie, with kamerapfad.eigene_regiebefehle naming what it found). A `zusammenfassung` inside the path is display only and is NEVER sent — the move is always rebuilt from the phases; if nothing can be built, the answer says angewandt = nicht_ableitbar with the reason, and nothing of the path is in the prompt. Mutually exclusive with kamerapfad_url.",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string. On this path it travels in the request BODY, not in the URL, but it is just as required: the new clip is filed under this project."
},
"referenzen": {
"type": "array",
"items": {
"type": "object",
"required": [
"art",
"url"
],
"properties": {
"art": {
"enum": [
"figur",
"ort",
"stil",
"motion",
"raum",
"stimme",
"clay",
"kreativ",
"bild",
"video",
"ton"
],
"type": "string",
"description": "What this reference IS: figur a character, ort a place, stil a look, motion a movement, raum a spatial/white-model pass, stimme a voice, clay an untextured geometry render, kreativ a free creative reference. The last three say only the CHANNEL, for when the role is not the point: bild any image, video any clip, ton any sound. Not every class has a slot on every model, and which ones do changes as models land: the backend answers a class it cannot route with a named rejection that says why, instead of accepting the reference and quietly ignoring it. Nothing is charged for such a rejection."
},
"url": {
"type": "string",
"minLength": 1,
"description": "URL of the reference — an image, a clip or a sound file, matching `art`"
},
"rolle": {
"type": "string",
"description": "WHICH character or place this reference means. Free text, carried through — it is not derivable from `art`, and guessing it would be a claim about the cast nobody made."
}
}
},
"description": "The typed successor of character_ref_urls: each reference says WHAT it is, so a place or a look no longer has to travel disguised as a character. Since 2026-09-03 it carries all three channels, not just images: motion references ride alongside the reference clip, sound references go into the model's audio slots. The position IS the address — the images are @Image1..n, the reference clip is @Video1 and the extra motion references follow it, the sounds are @Audio1..n. Image references are MEASURED before they are sent and scaled up to the model's minimum if they fall below it; a reference is never rejected for being small. Mutually exclusive with character_ref_urls — two sources for one list means one of them silently loses. What a model takes per channel is in lab_list_models (referenz_grenzen)."
},
"resolution": {
"type": "string",
"description": "Render resolution on this path, copied verbatim from the model's resolutions in lab_list_models. The run is priced at the resolution you choose — the low tier costs a fraction of the default — so pass it for sound-only or test runs. Omitted, the model's default applies."
},
"kamera_frei": {
"type": "boolean",
"description": "New camera angles (lab_neue_winkel): @Video1 carries the action, the words and the timing, but NOT the camera — the camera follows the timestamped shot list in the prompt. Replaces the default sentence 'follow the motion, camera movement and timing of @Video1 exactly', which would pull the model back to the original angle. Needs a reference clip and use_depth=false, not with task='editing'. Normally you pass the ready `lauf` object from lab_neue_winkel unchanged instead of setting this by hand."
},
"stil_kit_id": {
"type": "string",
"description": "Style kit id (lab_list_stil_kits; an @stil_ tag in the prompt does the same). Omit it and the project's default kit applies. Its rules and palette go into the prompt; its images join the reference images AFTER yours and the props, only on slots the model still has free (never on wan_vace, where an image would be read as a character). The answer says which way it went (stil_kit.weg)."
},
"wechsel_von": {
"type": "string",
"description": "The model id this call switches AWAY from (as listed in lab_list_models), with the same references as before. The answer then carries `wechsel`: which references the switch cuts off and why, and which ones the new model picks up. Nothing is sent to the old model; the field only names the comparison."
},
"aspect_ratio": {
"type": "string",
"description": "Output aspect ratio on this path, written as width:height or 'auto'. Only models whose lab_list_models entry takes it on the reference path accept it; for any other model the backend refuses it before anything is charged, and the refusal lists the allowed values. Omitted, the model's fixed default applies."
},
"ton_fuehrung": {
"enum": [
"voll",
"stimme",
"keine"
],
"type": "string",
"description": "What the model HEARS as guidance while it renders — not what the result carries (that is `ton`). voll (default, same as omitting it): unchanged behaviour. stimme: the backend splits the reference excerpt (after in_sec/out_sec) with demucs and sends ONLY the vocal stem, unchanged, as the single audio reference @Audio1 — so a speaking or singing performer moves the lips without the music bed dragging along. The stem is never pitch-shifted or altered in any way. The demucs split is charged per second of the excerpt and shows up in get_cost (ton_fuehrung.cost); it is cached per clip and in/out, a repeat costs nothing. Needs a reference clip and a model with an audio slot; not together with sound references in `referenzen`. keine: no audio reference at all. With ton='quelle' the original mix of the clip — not the stem — is laid back under the result."
},
"ohne_stil_kit": {
"type": "boolean",
"description": "true switches the project's default style kit off for this one call."
},
"generate_audio": {
"type": "boolean",
"description": "Let the model generate sound with the clip — only models whose lab_list_models entry can output audio take it on this path. Omitted = no generated sound. Generated audio REPLACES any voice in the reference — to keep a spoken take, leave it off."
},
"kamerapfad_url": {
"type": "string",
"description": "Address of a kamerapfad JSON instead of the object — the url of a file in THIS project (lab_list_assets), e.g. the kamerapfad JSON that Kadrage writes next to its flight video. Fetched by the backend under the same ownership rule as every reference: a file of another project is a named rejection before anything is charged. Mutually exclusive with kamerapfad."
},
"last_frame_url": {
"type": "string",
"description": "Guiding frame for the END of the new clip (image URL). Same rule as first_frame_url: wan_vace only, rejected elsewhere rather than dropped."
},
"first_frame_url": {
"type": "string",
"description": "Guiding frame for the START of the new clip (image URL). Only the wan_vace path binds guiding frames to the provider — with any other model the backend REJECTS this field instead of swallowing it, because a swallowed guiding frame means paying for a run identical to the one without it."
},
"dreamer_signatur": {
"type": "string",
"description": "Proof from the Dreamer (lab_prompt_transform, or the dreamer_fassung of a previous call) that THIS prompt went through it. Without a valid one the tool does not generate: it runs the Dreamer and returns its version first."
},
"duration_seconds": {
"type": "number",
"maximum": 30,
"minimum": 1,
"description": "Length of the NEW clip. Must lie inside the chosen model's duration grid from lab_list_models — the per-model ceiling is enforced by the backend and rejected rather than silently clamped."
},
"raum_beschreiben": {
"type": "boolean",
"description": "With depth on, a vision call describes ONLY the room of a still from the reference (place, light, colours, materials — no people) and adds it as a paragraph to the prompt, so a depth run does not come back grey. ON by default; false switches it off. The setting is remembered per project — omit it to use the project's last choice. Skipped automatically when an `ort` reference is given. The answer reports raum.angewandt and the text; metadata.prompt_gesendet holds the prompt as sent."
},
"rechte_bestaetigt": {
"type": "boolean",
"description": "REQUIRED when the reference clip or a character image was uploaded (not made in FilmLab), including characters whose sheet comes from an uploaded photo. It stands for this statement by the HUMAN: 'Ich habe die Rechte an Bild und Ton dieses Clips und die Einwilligung der gezeigten Personen.' Never set it on your own — show the human the statement and pass true only after they agreed. Without it the backend refuses with rechte_fehlen before anything is charged; get_cost tells you up front (rechte_noetig)."
},
"character_ref_urls": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ready image URLs that bind the cast's identity. The name says URLs on purpose: lab_animate's `character_refs` takes character sheet IDS, this path takes ADDRESSES, and the two are not interchangeable. Get them from lab_get_character_sheet. Omit to let the backend use the project's world-bible characters. Mutually exclusive with `referenzen`."
},
"reference_video_url": {
"type": "string",
"minLength": 1,
"description": "URL of the clip whose motion is taken. Use the url/video_url a previous answer gave you (lab_list_assets with asset_type 'video', lab_job_status, lab_review_queue) — never a hand-built address: the bucket is not guessable and a wrong one renders and bills into a 404. Leave it out to run on `referenzen` alone (at least one image reference, no `figur`, no in_sec/out_sec, no depth) — the models that allow it carry referenzen_ohne_video in lab_list_models."
}
},
"additionalProperties": false
}| Character only, REQUIRED there: does the image show a real person? |
| beschreibung_ableiten | boolean | Location only |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"ziel",
"ziel_id",
"image_id"
],
"properties": {
"view": {
"enum": [
"detail",
"frontal",
"profile",
"fullbody",
"fullbody_back",
"costume"
],
"type": "string"
},
"ziel": {
"enum": [
"figur",
"location"
],
"type": "string"
},
"ziel_id": {
"type": "string",
"description": "Character id or location id"
},
"ersetzen": {
"type": "boolean"
},
"image_id": {
"type": "string",
"description": "Image id from lab_umzug {bilder}, lab_get_job result.bildzeilen or lab_generate"
},
"likeness_ref": {
"type": "string",
"maxLength": 128,
"minLength": 3,
"description": "Character + reale_person only: Likeness Register id"
},
"personenbezug": {
"enum": [
"keine_reale_person",
"reale_person"
],
"type": "string",
"description": "Character only, REQUIRED there: does the image show a real person?"
},
"beschreibung_ableiten": {
"type": "boolean",
"description": "Location only"
}
},
"additionalProperties": false
}| preview | boolean | Attach the pictures themselves, downscaled to 512px, so a generation run can be SORTED by looking instead of by reading prompts. At most 4 per call — narrow the list with scene_ref/limit rather than expecting more. Rows without an image URL (all videos) are named in preview.notes, not silently dropped. |
| verdict | string | PER PROJECT ONLY. Exact verdict filter. Omit to get everything except rejected |
| scene_ref | string | PER PROJECT ONLY. Only this scene |
| tool_used | string | ACROSS PROJECTS. Exact tool/provider name, e.g. seedance-2.5 |
| project_id | string | Project id (from lab_list_projects). Omit to search across all projects |
| selected_only | boolean | PER PROJECT ONLY. Only the chosen variants |
| include_rejected | boolean | PER PROJECT ONLY. Include rejected in the default listing |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"type": {
"enum": [
"image",
"video"
],
"type": "string",
"description": "Which generated rows. Required per project; across projects, omit for both"
},
"limit": {
"type": "integer",
"maximum": 200,
"minimum": 1,
"description": "Max rows (per project default 50). Across projects the page is capped at 20 so next_cursor stays exact"
},
"since": {
"type": "string",
"description": "ACROSS PROJECTS. created_at >= this ISO-8601 timestamp (e.g. 2026-08-03T00:00:00Z)"
},
"until": {
"type": "string",
"description": "ACROSS PROJECTS. created_at <= this ISO-8601 timestamp"
},
"cursor": {
"type": "string",
"description": "ACROSS PROJECTS. The next_cursor of the previous page. Opaque — do not build one by hand"
},
"preview": {
"type": "boolean",
"description": "Attach the pictures themselves, downscaled to 512px, so a generation run can be SORTED by looking instead of by reading prompts. At most 4 per call — narrow the list with scene_ref/limit rather than expecting more. Rows without an image URL (all videos) are named in preview.notes, not silently dropped."
},
"verdict": {
"enum": [
"accepted",
"rejected",
"pending"
],
"type": "string",
"description": "PER PROJECT ONLY. Exact verdict filter. Omit to get everything except rejected"
},
"scene_ref": {
"type": "string",
"description": "PER PROJECT ONLY. Only this scene"
},
"tool_used": {
"type": "string",
"description": "ACROSS PROJECTS. Exact tool/provider name, e.g. seedance-2.5"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects). Omit to search across all projects"
},
"selected_only": {
"type": "boolean",
"description": "PER PROJECT ONLY. Only the chosen variants"
},
"include_rejected": {
"type": "boolean",
"description": "PER PROJECT ONLY. Include rejected in the default listing"
}
},
"additionalProperties": false
}| integer |
| Number of gags and shots, 8–16 (default 10) |
| lauf_id | string | Released auto run (lab_auto_lauf) whose cap pays for the steps — required for paid steps |
| figur_id | string | Start only: character sheet id with a reference image — positive integer as string |
| get_cost | boolean | Same as aktion preis — nothing is generated or charged |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
| situationen | array | Start only: your own gags instead of suggested ones (e.g. picked from lab_situationen) |
| musik_prompt | string | Start only: music prompt; default is an instrumental groove with a steady beat |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"aktion"
],
"properties": {
"bpm": {
"type": "integer",
"maximum": 180,
"minimum": 70,
"description": "Start only: tempo written into the default music prompt (default 120)"
},
"thema": {
"type": "string",
"maxLength": 2000,
"minLength": 1,
"description": "Start only: theme or setting for the gags, e.g. 'big city at night'"
},
"aktion": {
"enum": [
"preis",
"weiter",
"stand",
"abbrechen"
],
"type": "string",
"description": "preis: price per item; weiter: one step; stand: show state; abbrechen: delete the state"
},
"anzahl": {
"type": "integer",
"maximum": 16,
"minimum": 8,
"description": "Number of gags and shots, 8–16 (default 10)"
},
"lauf_id": {
"type": "string",
"description": "Released auto run (lab_auto_lauf) whose cap pays for the steps — required for paid steps"
},
"figur_id": {
"type": "string",
"description": "Start only: character sheet id with a reference image — positive integer as string"
},
"get_cost": {
"type": "boolean",
"description": "Same as aktion preis — nothing is generated or charged"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"situationen": {
"type": "array",
"items": {
"type": "object",
"required": [
"bild_en",
"bewegung_en"
],
"properties": {
"bild_en": {
"type": "string",
"maxLength": 4000,
"minLength": 1,
"description": "English still prompt: one moment, one action"
},
"bewegung_en": {
"type": "string",
"maxLength": 1000,
"minLength": 1,
"description": "English motion with a strong movement verb"
}
}
},
"maxItems": 16,
"minItems": 8,
"description": "Start only: your own gags instead of suggested ones (e.g. picked from lab_situationen)"
},
"musik_prompt": {
"type": "string",
"maxLength": 1000,
"minLength": 1,
"description": "Start only: music prompt; default is an instrumental groove with a steady beat"
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"vorschlag_id"
],
"properties": {
"vorschlag_id": {
"type": "string",
"description": "The id from lab_propose or plan_b in lab_job_status. Do not construct one — an unknown id answers 'vorschlag_abgelaufen', not an error you can work around."
},
"ohne_keyframe": {
"type": "boolean",
"description": "Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check."
},
"prompt_override": {
"type": "string",
"minLength": 1,
"description": "Replace ONLY the prompt text of the stored proposal. Every other parameter — model, resolution, duration, references — and the price stay exactly as shown. This is the path for a prompt the user edited on the card: the price does not depend on the text, so no new proposal is needed. Standbild-Bewegung uses no model prompt; confirm that proposal unchanged. An edited text goes through the Dreamer first: without a matching dreamer_signatur the call does NOT run, the proposal stays valid, and the answer carries the Dreamer version to confirm."
},
"dreamer_signatur": {
"type": "string",
"description": "Dreamer proof for prompt_override (from the previous answer's dreamer_signatur)."
}
},
"additionalProperties": false
}| What the sound should be. For sfx/ambience: name the source and character of the sound. For dialog/voiceover: the line or text to speak. Required for every art EXCEPT foley — foley watches the clip, so leaving it empty means 'take what you see', and a prompt merely steers it |
| audio_id | string | REPAIR KINDS ONLY, and the best of the three ways to name a source: an existing audio row of this project (from lab_list_audio), which has an owner and an address that belongs to us. Chain it — denoise a row, then split the result. The row must already carry an audio_url; a pending one has no file yet. |
| get_cost | boolean | `true` reports the price and your balance WITHOUT generating anything and without spending quota. Not drawn from the lab_list_models catalog (that covers image/video models only) — audio pricing follows the provider FilmLab picks for `art`, and each kind is billed by its own unit: per run, per second, or per character. For the spoken kinds the price follows the length of your text, which the cost endpoint cannot take: the answer then names the provider and your balance and reports `cost_generations` as null rather than inventing a number — the real amount comes back with the call. |
| video_id | string | The clip to work from. For FOLEY it is required to RUN one; for the repair kinds it is one of the three ways to name a source, and the backend extracts the audio track from it. From lab_list_assets or the row an animate call returned; the clip must belong to this project. The backend measures its length and rejects sources it will not process, before anything is charged. A price question (`get_cost: true`) does NOT need it — the price follows a length, not a particular clip. |
| audio_url | string | REPAIR KINDS ONLY, and the last resort: a bare address of a sound file. It carries no owner, so nothing can be checked against the project — prefer `audio_id` or `video_id`. A provider address is copied into our own storage first, and if that fails the call is refused rather than stored with a deadline. |
| scene_ref | string | Optional scene label, stored on the row. Useful to tell several tracks of one project apart |
| project_id* | string | Project id (from lab_list_projects) — positive integer as string |
| duration_seconds | number | Desired length in seconds. The backend clamps this to what the chosen provider actually supports for this `art` — dialog/voiceover ignore it entirely, the length follows the text, and foley ignores it too, its length follows the clip. The repair kinds ignore it as well: you clean up a file, not a stretch of it. One exception, and only for asking: in a `get_cost` query on foley or a repair kind, naming it prices THAT length instead of the ceiling. There is no lab_list_models entry for audio: the provider isn't a choice you make here, it's picked automatically per `art`. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"art"
],
"properties": {
"art": {
"enum": [
"sfx",
"ambience",
"dialog",
"voiceover",
"foley",
"denoise",
"split",
"isolation"
],
"type": "string",
"description": "Kind of track — sfx (a single sound effect), ambience (a continuous atmosphere/soundscape), dialog (a spoken line), voiceover (narration), foley (sound derived FROM an existing clip and synced to what happens in it — needs `video_id`, ignores `duration_seconds`, and takes its length from the source). The repair kinds work on a recording you already have and need exactly one source (`audio_id`, `video_id` or `audio_url`), never a prompt: denoise (take out hiss, hum and room noise), split (separate the recording into stems — one audio row per stem), isolation (keep only the voice, drop room, noise and music). Their length follows the source, so `duration_seconds` is ignored. Not 'music' — that stays lab_generate_music, it binds a different stem"
},
"stems": {
"type": "array",
"items": {
"enum": [
"vocals",
"drums",
"bass",
"other",
"guitar",
"piano"
],
"type": "string"
},
"description": "SPLIT ONLY: which parts to separate out. Left out, you get the voice and the rest of the recording, each part as its own audio row. Asking for a subset drops what you leave out — there is no combined everything-but-the-voice file, that would be a mixdown the provider never made."
},
"prompt": {
"type": "string",
"description": "What the sound should be. For sfx/ambience: name the source and character of the sound. For dialog/voiceover: the line or text to speak. Required for every art EXCEPT foley — foley watches the clip, so leaving it empty means 'take what you see', and a prompt merely steers it"
},
"audio_id": {
"type": "string",
"description": "REPAIR KINDS ONLY, and the best of the three ways to name a source: an existing audio row of this project (from lab_list_audio), which has an owner and an address that belongs to us. Chain it — denoise a row, then split the result. The row must already carry an audio_url; a pending one has no file yet."
},
"get_cost": {
"type": "boolean",
"description": "`true` reports the price and your balance WITHOUT generating anything and without spending quota. Not drawn from the lab_list_models catalog (that covers image/video models only) — audio pricing follows the provider FilmLab picks for `art`, and each kind is billed by its own unit: per run, per second, or per character. For the spoken kinds the price follows the length of your text, which the cost endpoint cannot take: the answer then names the provider and your balance and reports `cost_generations` as null rather than inventing a number — the real amount comes back with the call."
},
"video_id": {
"type": "string",
"description": "The clip to work from. For FOLEY it is required to RUN one; for the repair kinds it is one of the three ways to name a source, and the backend extracts the audio track from it. From lab_list_assets or the row an animate call returned; the clip must belong to this project. The backend measures its length and rejects sources it will not process, before anything is charged. A price question (`get_cost: true`) does NOT need it — the price follows a length, not a particular clip."
},
"audio_url": {
"type": "string",
"description": "REPAIR KINDS ONLY, and the last resort: a bare address of a sound file. It carries no owner, so nothing can be checked against the project — prefer `audio_id` or `video_id`. A provider address is copied into our own storage first, and if that fails the call is refused rather than stored with a deadline."
},
"scene_ref": {
"type": "string",
"description": "Optional scene label, stored on the row. Useful to tell several tracks of one project apart"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"duration_seconds": {
"type": "number",
"maximum": 600,
"minimum": 0.5,
"description": "Desired length in seconds. The backend clamps this to what the chosen provider actually supports for this `art` — dialog/voiceover ignore it entirely, the length follows the text, and foley ignores it too, its length follows the clip. The repair kinds ignore it as well: you clean up a file, not a stretch of it. One exception, and only for asking: in a `get_cost` query on foley or a repair kind, naming it prices THAT length instead of the ceiling. There is no lab_list_models entry for audio: the provider isn't a choice you make here, it's picked automatically per `art`."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"seq_id",
"shots"
],
"properties": {
"rung": {
"enum": [
"shot",
"movement"
],
"type": "string",
"description": "Which level to replace. 'shot' (the default) is one take; 'movement' groups shots into arcs. Independent layers over the same clips — writing one never touches the other"
},
"shots": {
"type": "array",
"items": {
"type": "object",
"required": [
"titel",
"clip_ids"
],
"properties": {
"titel": {
"type": "string",
"maxLength": 300,
"minLength": 1,
"description": "What this shot is, in the cutting room's words. Shown in the editor's shot band"
},
"clip_ids": {
"type": "array",
"items": {
"type": "string"
},
"description": "The clips that form this shot, as lab_shots and lab_timeline_apply report them. They need not be adjacent — one location cut into twice is one shot, and the answer marks it `zusammenhaengend: false` instead of hiding it"
},
"eltern_id": {
"type": "number",
"description": "For rung 'shot': the id of the movement it belongs to (from lab_shots with rung 'movement'). Leave it out for a movement"
},
"scene_ref": {
"type": "string",
"description": "Scene this shot plays in, if you know it"
},
"beschreibung": {
"type": "string",
"description": "Longer note about the shot. Optional"
}
}
},
"maxItems": 500,
"minItems": 0,
"description": "The whole level, in order. The order here IS the order of the shots"
},
"seq_id": {
"type": "string",
"description": "Sequence id (from lab_create_sequence/lab_list_sequences) — positive integer as string"
}
},
"additionalProperties": false
}| include_stems | boolean | Also export audio stems separately (default false) |
| ki_kennzeichnung | string | How the visible AI disclosure (EU AI Act Art. 50(4)) appears when the sequence contains AI material. Default 'minimal': a small 'KI' mark in the corner for the whole film. 'durchgehend': the larger 'KI-verändert' box. 'leitfaden': an 'AI' mark top right for the whole film, as in the German regulator's (Bundesnetzagentur) transparency guide. 'anfang': the mark for the first 4 s plus a 4 s end card. 'abspann': only the 4 s end card. 'anfang' and 'abspann' require werk_kuenstlerisch=true; there is no option without any disclosure. |
| derivative_region | string | Which part of the wide frame the portrait crop keeps. Default 'center' — pick 'left' or 'right' when the subject stands off to one side, otherwise the crop cuts it in half. |
| werk_kuenstlerisch | boolean | Confirm the work is artistic, fictional or satirical — only then may the disclosure be limited to the opening or the end card. Set it only when that is true; the choice is recorded with the export. |
| kennzeichnung_umfang | string | For a film that mixes people-made material ('eigen', see lab_herkunft) with AI material: 'film' (default) marks the whole film; 'abschnitte' shows the mark only while an AI or undeclared clip is on screen. Only with ki_kennzeichnung minimal, durchgehend or leitfaden. Audio counts too: an AI or undeclared sound track (bed, stems, mixdown) runs under the whole film, so the export then marks the whole film and reports umfang_bestellt. Declare own music or dialogue with lab_add_media(herkunft) or lab_herkunft. A film with no AI clip at all carries no mark either way. The choice and the clip-by-clip origin are recorded in the provenance record. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"sequence_id"
],
"properties": {
"profile": {
"enum": [
"web",
"social_vertical",
"social_youtube",
"streaming_hdr",
"cinema",
"master",
"alpha_prores4444",
"alpha_png_sequence"
],
"type": "string",
"description": "Delivery profile (default 'web'). See lab_export_profiles. 'alpha_prores4444' and 'alpha_png_sequence' keep transparency and are refused with an explanation unless a clip in the sequence really carries an alpha channel."
},
"quality": {
"enum": [
"draft",
"standard",
"high"
],
"type": "string",
"description": "Quality preset (default 'standard')"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"derivatives": {
"type": "array",
"items": {
"enum": [
"9x16"
],
"type": "string"
},
"description": "Extra cut-downs rendered from the FINISHED export, on top of the main file. '9x16' is a deterministic crop of the wide render into portrait — what falls outside the portrait frame is gone, not filled in. Their URLs arrive in the completed job's `derivatives`."
},
"sequence_id": {
"type": "string",
"description": "Sequence id to render (positive integer as string, e.g. '27')"
},
"ohne_clipton": {
"type": "boolean",
"description": "Leave the clips' OWN audio out of the export (e.g. a video model whose sound is always on) — the film then sounds only from the bound stems and the mixdown (music bed, narrator). Default false: the clips' own audio is mixed under the bed as before."
},
"include_stems": {
"type": "boolean",
"description": "Also export audio stems separately (default false)"
},
"ki_kennzeichnung": {
"enum": [
"minimal",
"durchgehend",
"anfang",
"abspann",
"leitfaden"
],
"type": "string",
"description": "How the visible AI disclosure (EU AI Act Art. 50(4)) appears when the sequence contains AI material. Default 'minimal': a small 'KI' mark in the corner for the whole film. 'durchgehend': the larger 'KI-verändert' box. 'leitfaden': an 'AI' mark top right for the whole film, as in the German regulator's (Bundesnetzagentur) transparency guide. 'anfang': the mark for the first 4 s plus a 4 s end card. 'abspann': only the 4 s end card. 'anfang' and 'abspann' require werk_kuenstlerisch=true; there is no option without any disclosure."
},
"derivative_region": {
"enum": [
"center",
"left",
"right"
],
"type": "string",
"description": "Which part of the wide frame the portrait crop keeps. Default 'center' — pick 'left' or 'right' when the subject stands off to one side, otherwise the crop cuts it in half."
},
"werk_kuenstlerisch": {
"type": "boolean",
"description": "Confirm the work is artistic, fictional or satirical — only then may the disclosure be limited to the opening or the end card. Set it only when that is true; the choice is recorded with the export."
},
"kennzeichnung_umfang": {
"enum": [
"film",
"abschnitte"
],
"type": "string",
"description": "For a film that mixes people-made material ('eigen', see lab_herkunft) with AI material: 'film' (default) marks the whole film; 'abschnitte' shows the mark only while an AI or undeclared clip is on screen. Only with ki_kennzeichnung minimal, durchgehend or leitfaden. Audio counts too: an AI or undeclared sound track (bed, stems, mixdown) runs under the whole film, so the export then marks the whole film and reports umfang_bestellt. Declare own music or dialogue with lab_add_media(herkunft) or lab_herkunft. A film with no AI clip at all carries no mark either way. The choice and the clip-by-clip origin are recorded in the provenance record."
}
},
"additionalProperties": false
}| string |
| Project id (from lab_list_projects) — positive integer as string |
| sequence_id | string | Sequence to write into (from lab_create_sequence/lab_list_sequences). Omit to create a new one. Given WITHOUT a manifest, the stored manifest is read back instead of written |
| erwartete_revision | string | The `revision` you read (from lab_get_sequence or this tool's read-back). The cut is written only if the sequence still has exactly that content — otherwise nothing is written and you get `revision_veraltet` with the current revision: read again, merge, write again. Without it this tool still protects the moment between its own read and write, but not the time since YOU read the sequence. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"title": {
"type": "string",
"description": "Title for the sequence created when `sequence_id` is omitted"
},
"manifest": {
"type": "object",
"required": [
"clips"
],
"properties": {
"bett": {
"type": "object",
"required": [
"pegel_db",
"fade_s"
],
"properties": {
"url": {
"type": "string",
"description": "Direct URL of the bed (external). A signed FilmLab asset URL is turned into its storage key, so it does not expire inside the sequence"
},
"fade_s": {
"type": "number",
"maximum": 30,
"minimum": 0,
"description": "Fade in and out of the bed"
},
"audio_id": {
"type": "string",
"description": "Id of an audio row in this project (from lab_list_audio). One of audio_id, asset_key, url"
},
"pegel_db": {
"type": "number",
"maximum": 6,
"minimum": -60,
"description": "Bed level relative to the clips"
},
"asset_key": {
"type": "string",
"description": "Storage key of an audio file in this project (from lab_add_media or lab_list_assets). Stable — prefer it over a URL: the URLs lab_list_assets hands out are signed and expire after 24 h"
}
},
"description": "A bed under the whole cut. Stored on the sequence; the Resolve bridge places the clip but can set neither level nor fade"
},
"clips": {
"type": "array",
"items": {
"type": "object",
"properties": {
"in_s": {
"type": "number",
"maximum": 3600,
"minimum": 0,
"description": "In point inside the source clip"
},
"out_s": {
"type": "number",
"maximum": 3600,
"minimum": 0,
"description": "Out point inside the source clip — must be greater than in_s"
},
"clip_id": {
"type": "string",
"description": "Identity of an EXISTING clip in this sequence, as read back by this tool or by lab_get_sequence. Pass it whenever you are reworking a cut you read: the clip keeps its track, its in-point in the source and its scene, and every shot bound to it stays bound. Omit it for a clip you are adding — one is assigned"
},
"dauer_s": {
"type": "number",
"maximum": 300,
"minimum": 0.1,
"description": "Length of this clip on the timeline. Required unless you give both in_s and out_s — a clip without a length would silently inherit the backend default, and the sequence duration and the render would drift apart"
},
"video_id": {
"type": "string",
"description": "Id of a generated video in THIS project (from lab_list_assets). Either this or asset_key"
},
"asset_key": {
"type": "string",
"description": "Storage key of a clip that has no video row. Either this or video_id"
}
}
},
"maxItems": 200,
"minItems": 1,
"description": "The cut, in cutting order. The order here IS the order on the timeline"
},
"uebergang": {
"type": "object",
"required": [
"art",
"dauer_s"
],
"properties": {
"art": {
"type": "string",
"const": "dissolve",
"description": "Only 'dissolve' — nothing else is implemented downstream"
},
"dauer_s": {
"type": "number",
"maximum": 10,
"minimum": 0.1,
"description": "Length of each dissolve"
}
},
"description": "One transition kind between ALL clips. Stored on the sequence — but neither the Resolve bridge nor the FilmLab render applies it yet: the cut renders with hard cuts"
}
},
"description": "The cut as one object. Omit it only together with a `sequence_id`, to read back what is stored"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"sequence_id": {
"type": "string",
"description": "Sequence to write into (from lab_create_sequence/lab_list_sequences). Omit to create a new one. Given WITHOUT a manifest, the stored manifest is read back instead of written"
},
"erwartete_revision": {
"type": "string",
"maxLength": 64,
"description": "The `revision` you read (from lab_get_sequence or this tool's read-back). The cut is written only if the sequence still has exactly that content — otherwise nothing is written and you get `revision_veraltet` with the current revision: read again, merge, write again. Without it this tool still protects the moment between its own read and write, but not the time since YOU read the sequence."
}
},
"additionalProperties": false
}| alpha_mode | string | How the subject is cut out from its background. Omit to let the backend decide ('auto'); the other modes fill, take a supplied matte, or work on a selection. |
| resolution | string | Output tier of the transform. This is Switch X's OWN tier list, not one of the video-model resolutions in lab_list_models — that catalog covers generation models and has no entry for this path. The tier drives the price; omit it for the backend default. |
| transformation_prompt* | string | What the clip should look like afterwards — light, place, props. The subject and its performance are preserved by the model; describing them again does not help. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"video_id",
"transformation_prompt"
],
"properties": {
"in_sec": {
"type": "number",
"description": "Cut the source server-side before it reaches the provider: start of the excerpt, in seconds. Only together with out_sec. What is billed is the MEASURED length of the cut, not out_sec minus in_sec."
},
"out_sec": {
"type": "number",
"description": "End of the excerpt, in seconds. Must lie behind in_sec; both are set together or neither."
},
"asset_id": {
"type": "string",
"description": "R2 asset key of an uploaded video (the `key` from lab_list_assets, asset_type 'video') to transform INSTEAD of the row's own clip. The row from video_id still supplies project and scene; only the pixels come from the asset. The key must belong to that same project."
},
"get_cost": {
"type": "boolean",
"description": "`true` returns the published rate per unit and your balance WITHOUT transforming and without spending anything — NOT a total. Switch X charges by the MEASURED length of the source, which only the server knows before the run, and it is not priced from the lab_list_models catalog (that covers generation models). The real amount comes back with the actual call."
},
"video_id": {
"type": "string",
"description": "Generated-video id (from lab_animate/lab_job_status/lab_review_queue) — positive integer as string. ALWAYS required, also together with asset_id: this row anchors the run (project, scene, billing) and the backend answers 404 without it."
},
"alpha_mode": {
"enum": [
"auto",
"fill",
"custom",
"select"
],
"type": "string",
"description": "How the subject is cut out from its background. Omit to let the backend decide ('auto'); the other modes fill, take a supplied matte, or work on a selection."
},
"resolution": {
"enum": [
"720p",
"1080p"
],
"type": "string",
"description": "Output tier of the transform. This is Switch X's OWN tier list, not one of the video-model resolutions in lab_list_models — that catalog covers generation models and has no entry for this path. The tier drives the price; omit it for the backend default."
},
"transformation_prompt": {
"type": "string",
"maxLength": 2000,
"minLength": 3,
"description": "What the clip should look like afterwards — light, place, props. The subject and its performance are preserved by the model; describing them again does not help."
}
},
"additionalProperties": false
}| project_id* |
| string |
| Project id (from lab_list_projects) — positive integer as string |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"anker": {
"type": "object",
"required": [
"name",
"image_ids"
],
"properties": {
"name": {
"type": "string",
"maxLength": 120,
"minLength": 1
},
"image_ids": {
"type": "array",
"items": {
"type": "string"
},
"maxItems": 12,
"minItems": 1
}
},
"description": "Build a look anchor from imported images"
},
"bilder": {
"type": "boolean",
"description": "true: list the project's imported images"
},
"dateien": {
"type": "array",
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"maxLength": 200,
"minLength": 1
},
"bytes": {
"type": "integer",
"maximum": 9007199254740991,
"exclusiveMinimum": 0
},
"content_type": {
"type": "string"
}
}
},
"maxItems": 20,
"minItems": 1,
"description": "Local files to upload: ZIP archives or single PNG/JPG/WEBP"
},
"prompts": {
"type": "string",
"maxLength": 200000,
"minLength": 6,
"description": "Pasted Midjourney prompts, one per line"
},
"quellen": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"key"
],
"properties": {
"key": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"maxItems": 20,
"minItems": 1,
"description": "After the PUT: exactly the `quellen` from the previous answer's weiter.argumente"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"video_id"
],
"properties": {
"faktor": {
"enum": [
"2",
"4"
],
"type": "string",
"description": "How many times larger, per side. Omit for the smaller one. Whichever you pick, the RESULT has to stay inside the published rate tiers — a source too tall for the factor you asked for is refused by name, with the ceiling in the message, instead of billing a run nobody can price. What the tier costs is in lab_list_models and in the preflight, not here."
},
"get_cost": {
"type": "boolean",
"description": "`true` returns what upscaling THIS clip costs (`cost_generations`, measured server-side from its length and the height of the RESULT, exactly what the run would charge) plus the published rate per output second per tier and your balance — WITHOUT scaling and without spending anything. For a clip that is still pending there is no total yet: then `cost_generations` is null and `preis_grund` says why. Which tiers are measured and which are catalog values is the same distinction lab_list_models makes."
},
"video_id": {
"type": "string",
"description": "Generated-video id (from lab_animate/lab_job_status/lab_review_queue) — positive integer as string. The row must already have its clip; a run still pending is refused, and nothing is charged for asking."
},
"max_credits": {
"type": "integer",
"maximum": 9007199254740991,
"minimum": 0,
"description": "The most you agree to pay — pass the amount from get_cost that the person confirmed. If the measured price is higher when the run starts, it is refused before anything is charged."
}
},
"additionalProperties": false
}| Argument | Type | Description |
|---|---|---|
| jobs* | array | One entry per clip, IN ORDER — the same fields as lab_animate (prompt, scene_ref, tool, duration_seconds, resolution, character_refs, location_id, source_image_id …). Each prompt: For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: "<line>" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there. scene_ref decides the chains: consecutive clips with the same scene_ref chain, a clip without scene_ref stands alone. |
| modus | string | hybrid (default): chain within scenes; parallel: every clip alone; sequenziell: one chain over everything |
| aktion* | string | plan and preis are free; starten costs credits |
| uebergang | string | Transition at each cut inside a chain; default schnitt |
| project_id* | string | Project id (from lab_list_projects) |
| neuversuche | integer | Retries per clip after a failed continuity check: 1 (default) or 0 (stop at the first deviation). Name preis_hoechstens_credits to the person before starten. |
| ohne_keyframe | boolean | Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"aktion",
"project_id",
"jobs"
],
"properties": {
"jobs": {
"type": "array",
"items": {
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"maxItems": 25,
"minItems": 1,
"description": "One entry per clip, IN ORDER — the same fields as lab_animate (prompt, scene_ref, tool, duration_seconds, resolution, character_refs, location_id, source_image_id …). Each prompt: For a clip of 4 s or longer, write the action as timed beats, one per line under a '// Action Timing' header: '[0-2s] [CAM 01] <framing> -> [ACT] <action> -> [DIAL] <who>: \"<line>\" -> [AUDIO] <sound>'. The spans start at 0 and cover duration_seconds; keep the same CAM number while the camera does not cut. lab_prompt_transform (medium video) writes exactly this form. Murnau's SceneFlow lights each beat while the clip plays, and the crew judges it there. scene_ref decides the chains: consecutive clips with the same scene_ref chain, a clip without scene_ref stands alone."
},
"modus": {
"enum": [
"hybrid",
"parallel",
"sequenziell"
],
"type": "string",
"description": "hybrid (default): chain within scenes; parallel: every clip alone; sequenziell: one chain over everything"
},
"aktion": {
"enum": [
"plan",
"preis",
"starten"
],
"type": "string",
"description": "plan and preis are free; starten costs credits"
},
"uebergang": {
"enum": [
"schnitt",
"durchgehend"
],
"type": "string",
"description": "Transition at each cut inside a chain; default schnitt"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"neuversuche": {
"type": "integer",
"maximum": 1,
"minimum": 0,
"description": "Retries per clip after a failed continuity check: 1 (default) or 0 (stop at the first deviation). Name preis_hoechstens_credits to the person before starten."
},
"ohne_keyframe": {
"type": "boolean",
"description": "Skip the keyframe checkpoint on purpose. By default a paid clip only starts from a start image the user has approved (lab_review_asset verdict: accepted) — look at the frame first, then spend on the clip. Set true only when the user explicitly asked to skip the frame check."
}
},
"additionalProperties": false
}| number |
| Box height as a fraction of image height |
| licht_weg | string | relight only. kontext (default): only the light changes, scene, people and framing stay. iclight: the background is repainted from the light description — the scenery may change |
| operation* | string | inpaint: Die rote Werkzeugkiste wird durch eine Holzkiste auf der Werkbank ersetzt. | replace: Derselbe Weg, andere Absicht: in die Box kommt etwas Neues. | relight: Keine Box: die Lichtrichtung wandert über das ganze Bild. | mask: Keine Box: du beschreibst, was heraus soll, und bekommst die Maske. |
| box_breite | number | Box width as a fraction of image width |
| project_id* | string | Project id (from lab_list_projects) |
| light_preset | string | relight only. There is no 'bottom' — the server does not carry one |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"image_id",
"operation"
],
"properties": {
"box_x": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Box left edge as a fraction of image width (0..1)"
},
"box_y": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Box top edge as a fraction of image height (0..1)"
},
"prompt": {
"type": "string",
"description": "inpaint/replace: what should be seen there instead — required. mask: what should be cut out (e.g. 'the red toolbox') — required. relight: optional, replaces the preset"
},
"image_id": {
"type": "string",
"description": "Id of the source image in the project gallery (from lab_list_assets / lab_generate)"
},
"box_hoehe": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Box height as a fraction of image height"
},
"licht_weg": {
"enum": [
"kontext",
"iclight"
],
"type": "string",
"description": "relight only. kontext (default): only the light changes, scene, people and framing stay. iclight: the background is repainted from the light description — the scenery may change"
},
"operation": {
"enum": [
"inpaint",
"replace",
"relight",
"mask"
],
"type": "string",
"description": "inpaint: Die rote Werkzeugkiste wird durch eine Holzkiste auf der Werkbank ersetzt. | replace: Derselbe Weg, andere Absicht: in die Box kommt etwas Neues. | relight: Keine Box: die Lichtrichtung wandert über das ganze Bild. | mask: Keine Box: du beschreibst, was heraus soll, und bekommst die Maske."
},
"box_breite": {
"type": "number",
"maximum": 1,
"minimum": 0,
"description": "Box width as a fraction of image width"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"light_preset": {
"enum": [
"left",
"right",
"top",
"golden_hour",
"blue_night"
],
"type": "string",
"description": "relight only. There is no 'bottom' — the server does not carry one"
}
},
"additionalProperties": false
}| einwilligung |
| boolean |
| Consent of the person whose voice is being uploaded. Required on the fish_audio path, which refuses without it before anything is charged. This is not the moderation gate: that one reads the label for abuse, this one asserts you are allowed to use this person's voice at all. Do not set it to be rid of the error — set it because the consent exists. Provider capabilities and prices: lab_list_models. |
| likeness_ref | string | Consent id from the Likeness Register (LK-XXXXX-XXXXX-XXXXX-XXXXX) for the person whose voice this is. When set, the register is checked live (active, purpose voice_synthesis, the project's target markets) and the clone is refused if the consent does not hold. Needed for a real person's voice in a project for children or young people. |
| sample_audio_url* | string | URL of the source audio or video the voice is cloned from. Needs a real, sufficiently long speech track — material without one is rejected before anything is spent. |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"name",
"sample_audio_url"
],
"properties": {
"name": {
"type": "string",
"minLength": 1,
"description": "A label for this cloned voice, shown in lab_list_voices — needed to tell several clones of one project apart later."
},
"get_cost": {
"type": "boolean",
"description": "`true` reports the price and your balance WITHOUT cloning anything and without spending quota. A clone is billed per clone (a MiniMax clone costs far more than a Fish clone — ask with get_cost) — the length of the source material does not change it, and the clone is then reusable for as many readings as you like. Not drawn from the lab_list_models catalog (that covers image and video models only); there is no model to pick here."
},
"provider": {
"enum": [
"minimax",
"fish_audio"
],
"type": "string",
"description": "Which voice house clones it. Omit to stay on the default path — an existing workflow does not change vendor just because another one exists. The choice sticks to the voice: whichever house made it is the one that later speaks with it, so lab_voice_tts has no say in this. What each house can do and what it charges is not stated here — lab_list_models carries that."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
},
"einwilligung": {
"type": "boolean",
"description": "Consent of the person whose voice is being uploaded. Required on the fish_audio path, which refuses without it before anything is charged. This is not the moderation gate: that one reads the label for abuse, this one asserts you are allowed to use this person's voice at all. Do not set it to be rid of the error — set it because the consent exists. Provider capabilities and prices: lab_list_models."
},
"likeness_ref": {
"type": "string",
"description": "Consent id from the Likeness Register (LK-XXXXX-XXXXX-XXXXX-XXXXX) for the person whose voice this is. When set, the register is checked live (active, purpose voice_synthesis, the project's target markets) and the clone is refused if the consent does not hold. Needed for a real person's voice in a project for children or young people."
},
"sample_audio_url": {
"type": "string",
"minLength": 1,
"description": "URL of the source audio or video the voice is cloned from. Needs a real, sufficiently long speech track — material without one is rejected before anything is spent."
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id",
"voice_id",
"text"
],
"properties": {
"text": {
"type": "string",
"maxLength": 5000,
"minLength": 1,
"description": "The line(s) to speak in this voice."
},
"get_cost": {
"type": "boolean",
"description": "Price this call instead of running it: returns the estimate and the balance, charges nothing, produces no audio. Worth doing before a long passage, because both houses bill by the size of the text, not per run — so the same tool can cost wildly different amounts depending on what you hand it. The rates behind the estimate are not repeated here; lab_list_models carries them."
},
"provider": {
"enum": [
"minimax",
"fish_audio"
],
"type": "string",
"description": "Cross-check, not a choice: the house that cloned this voice is the one that speaks with it, because a voice id from one house means nothing to the other. Pass it only to assert what you believe the voice is; a mismatch is refused before anything is charged. Omit it and the stored voice decides. Which house a voice belongs to: lab_list_voices. What each house can do: lab_list_models."
},
"voice_id": {
"type": "string",
"description": "A cloned voice's id (lab_cloned_voices.id, from lab_voice_clone or lab_list_voices) — positive integer as string, NOT the raw provider voice id."
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — positive integer as string"
}
},
"additionalProperties": false
}| string |
| Instead of a board: a finished gallery image of THIS project |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"stufe": {
"enum": [
"entwurf",
"voll"
],
"type": "string",
"description": "'entwurf' = cheap draft, 'voll' = full world (default)"
},
"loc_id": {
"type": "string",
"description": "Location board id (from lab_list_locations); needs variante"
},
"get_cost": {
"type": "boolean",
"description": "Free: the exact price of this operation; nothing is built or charged. Name it to the person before the real run."
},
"variante": {
"enum": [
"day",
"night",
"golden_hour",
"rain",
"panorama"
],
"type": "string",
"description": "Which picture of the board: a time variant or its 360° panorama"
},
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects) — the world is filed in this project's gallery"
},
"text_prompt": {
"type": "string",
"maxLength": 2000,
"description": "Optional description of the place; for a board its prompt prefix is used when left out"
},
"quell_bild_id": {
"type": "string",
"description": "Instead of a board: a finished gallery image of THIS project"
}
},
"additionalProperties": false
}| string |
| Project the asset_key belongs to — required with asset_key |
{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"bis_s": {
"type": "number",
"minimum": 0,
"description": "End of the page; at most 300 s after von_s"
},
"von_s": {
"type": "number",
"minimum": 0,
"description": "Start of the page in seconds (default 0)"
},
"sprache": {
"enum": [
"de",
"en",
"fr",
"es",
"it",
"nl",
"pl",
"pt",
"tr",
"ja",
"zh",
"ko"
],
"type": "string",
"description": "Spoken language (default de) — the same list the backend accepts"
},
"audio_id": {
"type": "string",
"description": "Audio track id (from lab_list_audio)"
},
"video_id": {
"type": "string",
"description": "Generated video id — its own soundtrack. Give exactly one of video_id, audio_id, asset_key"
},
"asset_key": {
"type": "string",
"description": "Storage key of an uploaded sound or video file (from lab_add_media). Needs project_id"
},
"project_id": {
"type": "string",
"description": "Project the asset_key belongs to — required with asset_key"
}
},
"additionalProperties": false
}{
"type": "object",
"$schema": "http://json-schema.org/draft-07/schema#",
"required": [
"project_id"
],
"properties": {
"project_id": {
"type": "string",
"description": "Project id (from lab_list_projects)"
},
"zielgruppe": {
"enum": [
"kinder",
"jugendliche",
"erwachsene",
""
],
"type": "string",
"description": "Audience of the project: 'kinder', 'jugendliche' or 'erwachsene'; an empty string clears it. Omit to leave it unchanged"
},
"zielmaerkte": {
"type": "array",
"items": {
"type": "string"
},
"description": "Target markets as ISO country codes plus 'EU', e.g. ['DE', 'AT', 'CH']. Replaces the whole list; an empty list clears it. Omit to leave it unchanged"
}
},
"additionalProperties": false
}