Docs API reference
Scenes and shots
A SCENE is a named piece of a story; a SHOT is one timed composition inside it. A shot’s timeline is the whole authoring model — canvas, tracks, blocks, audio, rules — the same shape the editor saves, so a timeline read here can be edited and put straight back. **The loop is read, edit, PUT.** GET /shots/{id}, change what you want, PUT /shots/{id}. There is no per-block route on purpose: a block only means something inside its timeline, and one whole-shot write is simpler and cannot interleave with another. **Reads are forgiving, writes are strict.** A screen must never break on a stored shot, so reading drops what it cannot use; writing instead tells you — an unknown block kind or an unknown rule sensor is a 400 naming it. GET /named-conditions/sensors lists the sensors; block kinds come from the app.
Every call needs an API key in the Authorization header. New to the API? Start with
the API overview.
List block kinds and rule sensors
/block-kinds authoringStart here when building a shot. Every kind a block can be, each with a template — a new block of that kind exactly as the editor’s palette makes it — so you can see which fields a caption carries versus a still rather than guessing from the Block schema, which lists every field that exists for any kind.
There is deliberately no hand-written “fields this kind uses” list: a kind’s renderer is built from shared components, so such a list would be incomplete the day it was written. The template is generated by the same call the editor makes, so it cannot drift.
?width=&height= measures the templates against your canvas (a template’s box is a fraction of it); the default is 1920×1080. sensors is the same vocabulary GET /named-conditions/sensors returns, included so one call sets you up for both blocks and rules.
Media kinds (still, clip) carry no asset in their template: upload one through /assets and set its id.
Parameters
widthinteger queryCanvas width to measure templates against.
heightinteger queryCanvas height to measure templates against.
Responses
- 200 The palette and the sensor vocabulary.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
Response fields (200)
kindsobject[]11 fields in
kindskindstringWhat goes in
Block.kind.labelstringiconstringhintstringtrackstringThe visual row a new one lands on.
One of
main,overlaypaletteintegerWhere it sits in the editor’s palette; absent = not offered there.
uses_timebooleanReads the playhead, so the stage keeps time for it.
compose_onlystring[]Fields the server fills when a screen asks. Never send these — they are dropped.
setsstring[]The fields this kind carries beyond the generic five, derived from
template.templateobjectOne thing on screen.
kinddecides which of the optional fields mean anything — acaptionreadstext, astillreadsasset, apresenterreadspresenter_source.GET /sceneswill not tell you the kinds; the app’s block palette and the MCP’slist_block_kindsdo, and an unknown kind is refused on write. Fields not listed here are dropped on write. Two are COMPOSE-ONLY and can never be stored:image_urlandagenda_vieware filled by the server when a screen asks for the shot.45 fields in
templateuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
rule_presetsobject[]3 fields in
rule_presetskeystringlabelstringdescriptionstring
sensorsobject[]3 fields in
sensorssensorstringlabelstringreadingsobject[]One per reading the sensor offers, each with its own operators and a ready-made example.
4 fields in
readingspropertystringlabelstringopsobject[]2 fields in
opsopstringlabelstring
exampleobjectOne reading, compared.
sensorandopmust come from/named-conditions/sensors.5 fields in
exampleidstringAny stable string. Generated if you leave it out.
sensorstring requiredWhat to read, e.g.
clock,weekday,session,field,trait.propertystringWHICH reading, for a sensor that has several (the session's
statusvs itsstart). Omit for a sensor with one.opstring requiredThe comparison, e.g.
after,is,in.valueobjectOperator-specific, e.g.
{ "time": "18:00" }.
curl "https://www.wunderframe.com/api/v1/block-kinds" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"kinds": [
{
"kind": "caption",
"label": "Caption",
"icon": "text-t",
"track": "overlay",
"palette": 10,
"uses_time": false,
"compose_only": [],
"sets": [
"text",
"font_weight",
"color",
"align"
],
"template": {
"uuid": "caption_mu6nc9x01",
"kind": "caption",
"track_id": "visual-overlay",
"timing": {
"in_ms": 0,
"animation": "fade",
"anim_ms": 700
},
"transform": {
"x": 384,
"y": 459,
"w": 1152,
"h": 162,
"rotate": 0,
"fit": "cover",
"scale": 1,
"opacity": 1
},
"text": "{title}",
"font_weight": 700,
"color": "#ffffff",
"align": "center",
"rules": [
{
"id": "r1",
"label": "Hide once live",
"enabled": true,
"match": "all",
"when": [
{
"id": "c1",
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
],
"set": {
"hidden": true
},
"fade_ms": 1200
}
]
},
"rule_presets": []
}
],
"sensors": [
{
"sensor": "session",
"label": "The live session",
"readings": [
{
"property": "status",
"label": "where it is",
"ops": [
{
"op": "is",
"label": "is"
},
{
"op": "is_not",
"label": "is not"
}
],
"example": {
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
}
]
}
]
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}List scenes
/scenes authoringThe scene library, newest first.
Parameters
qstring querySearch titles.
folderstring queryOnly this folder, or
rootfor the top level.limitinteger queryPage size, 1–200.
1–200. Default
50cursorstring queryThe
next_cursorof the previous page.
Responses
- 200 A page of scenes.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
Response fields (200)
itemsobject[] required11 fields in
itemsidstring requiredtitlestring requiredkindstringA free-text label for filtering the library. Never behavior.
descriptionstringfolder_idstring or nulltemplate_idstring or nullSet on a FILLED scene: it has no shots of its own and plays this template’s, with its
fieldsfilling the blanks. Add shots to the template, not to the filled scene.fieldsobjectA filled scene’s values, by blank name.
refstring or nullYour own key for this scene.
shotsobject[] read-onlyPresent on a single-scene read, in playback order.
4 fields in
shotsidstringpositionnumberduration_msintegerlayout_idstring or null
created_atinteger read-onlyupdated_atinteger read-only
next_cursorstring or null requiredPass as
cursorfor the next page;nullon the last page.
curl "https://www.wunderframe.com/api/v1/scenes?limit=50" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"items": [
{
"id": "scn_welcome",
"title": "Welcome",
"kind": "Hero",
"description": "",
"folder_id": null,
"template_id": null,
"fields": {},
"ref": null,
"shots": [
{
"id": "sht_a1",
"position": 1,
"duration_ms": 8000,
"layout_id": null
}
],
"created_at": 1789372800000,
"updated_at": 1789372800000
}
],
"next_cursor": null
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Create a scene
/scenes authoringIt is born with one empty shot, exactly as the library’s New scene is — a scene with no shots plays nothing.
Request body required
titlestring requiredkindstringdescriptionstringfolder_idstring or nullrefstring or null
Responses
- 201 The new scene, with its shot.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
Response fields (201)
idstring requiredtitlestring requiredkindstringA free-text label for filtering the library. Never behavior.
descriptionstringfolder_idstring or nulltemplate_idstring or nullSet on a FILLED scene: it has no shots of its own and plays this template’s, with its
fieldsfilling the blanks. Add shots to the template, not to the filled scene.fieldsobjectA filled scene’s values, by blank name.
refstring or nullYour own key for this scene.
shotsobject[] read-onlyPresent on a single-scene read, in playback order.
4 fields in
shotsidstringpositionnumberduration_msintegerlayout_idstring or null
created_atinteger read-onlyupdated_atinteger read-only
curl -X POST "https://www.wunderframe.com/api/v1/scenes" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Welcome",
"kind": "Hero"
}'{
"id": "scn_welcome",
"title": "Welcome",
"kind": "Hero",
"description": "",
"folder_id": null,
"template_id": null,
"fields": {},
"ref": null,
"shots": [
{
"id": "sht_a1",
"position": 1,
"duration_ms": 8000,
"layout_id": null
}
],
"created_at": 1789372800000,
"updated_at": 1789372800000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Get a scene and its shots
/scenes/{id} authoringA filled scene lists its template’s shots, because those are what it plays.
Parameters
idstring required pathThe scene’s id.
Responses
- 200 The scene.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
Response fields (200)
idstring requiredtitlestring requiredkindstringA free-text label for filtering the library. Never behavior.
descriptionstringfolder_idstring or nulltemplate_idstring or nullSet on a FILLED scene: it has no shots of its own and plays this template’s, with its
fieldsfilling the blanks. Add shots to the template, not to the filled scene.fieldsobjectA filled scene’s values, by blank name.
refstring or nullYour own key for this scene.
shotsobject[] read-onlyPresent on a single-scene read, in playback order.
4 fields in
shotsidstringpositionnumberduration_msintegerlayout_idstring or null
created_atinteger read-onlyupdated_atinteger read-only
curl "https://www.wunderframe.com/api/v1/scenes/scn_welcome" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "scn_welcome",
"title": "Welcome",
"kind": "Hero",
"description": "",
"folder_id": null,
"template_id": null,
"fields": {},
"ref": null,
"shots": [
{
"id": "sht_a1",
"position": 1,
"duration_ms": 8000,
"layout_id": null
}
],
"created_at": 1789372800000,
"updated_at": 1789372800000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Add a shot to a scene
/scenes/{id}/shots authoringAppended at the end. Send a timeline to set it up in the same call. Refused for a filled scene: add the shot to its template instead.
Parameters
idstring required pathThe scene’s id.
Request body
timelineobjectThe shot document. Canvas, rows, what is on them, sound, and the rules that change it — the same shape the editor saves, read by the same
readTimeline. Always start from aGET. Fields this version does not know are dropped on write, so building one by hand loses whatever you did not think to include; reading, editing and putting back never does.8 fields in
timelinecanvasobject requiredThe frame everything is positioned inside.
width/heightare the composition size, not the screen’s.5 fields in
canvaswidthintegerheightintegerfpsintegerComposition frame rate. 30 unless set.
backgroundstringCSS color under everything.
backdropobjectPainted over the background where no block is:
styleofsolid|gradient|orbs, with its own colors.
tracksobject[] required9 fields in
tracksuuidstring requiredWhat a block’s
track_idpoints at.labelstringkindstring requiredOne of
visual,audiorolestringOne of
main,overlay,voiceover,music,sfxorderinteger requiredBack to front among visual rows.
hiddenbooleanlockedbooleanmutedbooleanheightintegerEditor row height. Cosmetic.
blocksobject[] requiredOrder within a track does not matter;
track_idand the track’sorderdecide what is in front.45 fields in
blocksuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
audioobject[]13 fields in
audiouuidstring requiredtrack_idstring requiredAn audio
Track.uuid.assetstringAn asset id.
labelstringat_msinteger requiredWhere it starts in the shot.
out_msintegervolumenumberduckingbooleanDrop under a voiceover.
fade_in_msintegerfade_out_msintegertrim_in_msintegerInto the source file.
trim_out_msintegerWith
loop: the end of the section that repeats.loopbooleanRepeat the
trim_in_ms…trim_out_mssection untilout_ms.
groupsobject[]Editor groupings. A block joins one with
group_id.canvas_rulesobject[]Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.
8 fields in
canvas_rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
safe_areaobjectLayouts only: a guide rectangle shot content should stay inside.
fontsobjectHeading and body families. A linked shot plays its layout’s unless it sets its own.
duration_msintegerAt least 500
layout_idstring or nulllayout_optsobjectWhat the shot takes from its layout:
background"layout" (default) or "own",hide— layout block uuids this shot leaves out (e.g. a logo the shot shows larger itself),audio"layout" | "own" | "mute".3 fields in
layout_optsbackgroundstringOne of
layout,ownhidestring[]audiostringOne of
layout,own,mute
Responses
- 201 The new shot.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
Response fields (201)
idstring requiredscene_idstring requiredpositionnumberOrder within the scene. Fractional, so a shot can be inserted between two.
duration_msinteger requiredHow long it holds. At least 500.
layout_idstring or nullA linked layout whose blocks show behind or over this shot’s.
layout_optsobjectWhat the shot takes from its layout:
background"layout" (default) or "own",hide— layout block uuids this shot leaves out (e.g. a logo the shot shows larger itself),audio"layout" | "own" | "mute".3 fields in
layout_optsbackgroundstringOne of
layout,ownhidestring[]audiostringOne of
layout,own,mute
timelineobject requiredThe shot document. Canvas, rows, what is on them, sound, and the rules that change it — the same shape the editor saves, read by the same
readTimeline. Always start from aGET. Fields this version does not know are dropped on write, so building one by hand loses whatever you did not think to include; reading, editing and putting back never does.8 fields in
timelinecanvasobject requiredThe frame everything is positioned inside.
width/heightare the composition size, not the screen’s.5 fields in
canvaswidthintegerheightintegerfpsintegerComposition frame rate. 30 unless set.
backgroundstringCSS color under everything.
backdropobjectPainted over the background where no block is:
styleofsolid|gradient|orbs, with its own colors.
tracksobject[] required9 fields in
tracksuuidstring requiredWhat a block’s
track_idpoints at.labelstringkindstring requiredOne of
visual,audiorolestringOne of
main,overlay,voiceover,music,sfxorderinteger requiredBack to front among visual rows.
hiddenbooleanlockedbooleanmutedbooleanheightintegerEditor row height. Cosmetic.
blocksobject[] requiredOrder within a track does not matter;
track_idand the track’sorderdecide what is in front.45 fields in
blocksuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
audioobject[]13 fields in
audiouuidstring requiredtrack_idstring requiredAn audio
Track.uuid.assetstringAn asset id.
labelstringat_msinteger requiredWhere it starts in the shot.
out_msintegervolumenumberduckingbooleanDrop under a voiceover.
fade_in_msintegerfade_out_msintegertrim_in_msintegerInto the source file.
trim_out_msintegerWith
loop: the end of the section that repeats.loopbooleanRepeat the
trim_in_ms…trim_out_mssection untilout_ms.
groupsobject[]Editor groupings. A block joins one with
group_id.canvas_rulesobject[]Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.
8 fields in
canvas_rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
safe_areaobjectLayouts only: a guide rectangle shot content should stay inside.
fontsobjectHeading and body families. A linked shot plays its layout’s unless it sets its own.
created_atinteger read-onlyupdated_atinteger read-only
curl -X POST "https://www.wunderframe.com/api/v1/scenes/scn_welcome/shots" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"duration_ms": 8000
}'{
"id": "sht_a1",
"scene_id": "scn_welcome",
"position": 1,
"duration_ms": 8000,
"layout_id": null,
"timeline": {
"canvas": {
"width": 1920,
"height": 1080,
"fps": 30,
"background": "#000000"
},
"tracks": [
{
"uuid": "visual-main",
"label": "Main",
"kind": "visual",
"role": "main",
"order": 0
},
{
"uuid": "visual-overlay",
"label": "Overlay",
"kind": "visual",
"role": "overlay",
"order": 1
},
{
"uuid": "audio-voiceover",
"label": "Voiceover",
"kind": "audio",
"role": "voiceover",
"order": 2
},
{
"uuid": "audio-music",
"label": "Music",
"kind": "audio",
"role": "music",
"order": 3
}
],
"blocks": [
{
"uuid": "caption_mu6nc9x01",
"kind": "caption",
"track_id": "visual-overlay",
"timing": {
"in_ms": 0,
"animation": "fade",
"anim_ms": 700
},
"transform": {
"x": 384,
"y": 459,
"w": 1152,
"h": 162,
"rotate": 0,
"fit": "cover",
"scale": 1,
"opacity": 1
},
"text": "{title}",
"font_weight": 700,
"color": "#ffffff",
"align": "center",
"rules": [
{
"id": "r1",
"label": "Hide once live",
"enabled": true,
"match": "all",
"when": [
{
"id": "c1",
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
],
"set": {
"hidden": true
},
"fade_ms": 1200
}
]
}
],
"audio": [],
"groups": []
},
"created_at": 1789372800000,
"updated_at": 1789372800000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}Get a shot
/shots/{id} authoringThe whole timeline. Edit it and PUT it back.
Parameters
idstring required pathThe shot’s id.
Responses
- 200 The shot.
- 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
Response fields (200)
idstring requiredscene_idstring requiredpositionnumberOrder within the scene. Fractional, so a shot can be inserted between two.
duration_msinteger requiredHow long it holds. At least 500.
layout_idstring or nullA linked layout whose blocks show behind or over this shot’s.
layout_optsobjectWhat the shot takes from its layout:
background"layout" (default) or "own",hide— layout block uuids this shot leaves out (e.g. a logo the shot shows larger itself),audio"layout" | "own" | "mute".3 fields in
layout_optsbackgroundstringOne of
layout,ownhidestring[]audiostringOne of
layout,own,mute
timelineobject requiredThe shot document. Canvas, rows, what is on them, sound, and the rules that change it — the same shape the editor saves, read by the same
readTimeline. Always start from aGET. Fields this version does not know are dropped on write, so building one by hand loses whatever you did not think to include; reading, editing and putting back never does.8 fields in
timelinecanvasobject requiredThe frame everything is positioned inside.
width/heightare the composition size, not the screen’s.5 fields in
canvaswidthintegerheightintegerfpsintegerComposition frame rate. 30 unless set.
backgroundstringCSS color under everything.
backdropobjectPainted over the background where no block is:
styleofsolid|gradient|orbs, with its own colors.
tracksobject[] required9 fields in
tracksuuidstring requiredWhat a block’s
track_idpoints at.labelstringkindstring requiredOne of
visual,audiorolestringOne of
main,overlay,voiceover,music,sfxorderinteger requiredBack to front among visual rows.
hiddenbooleanlockedbooleanmutedbooleanheightintegerEditor row height. Cosmetic.
blocksobject[] requiredOrder within a track does not matter;
track_idand the track’sorderdecide what is in front.45 fields in
blocksuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
audioobject[]13 fields in
audiouuidstring requiredtrack_idstring requiredAn audio
Track.uuid.assetstringAn asset id.
labelstringat_msinteger requiredWhere it starts in the shot.
out_msintegervolumenumberduckingbooleanDrop under a voiceover.
fade_in_msintegerfade_out_msintegertrim_in_msintegerInto the source file.
trim_out_msintegerWith
loop: the end of the section that repeats.loopbooleanRepeat the
trim_in_ms…trim_out_mssection untilout_ms.
groupsobject[]Editor groupings. A block joins one with
group_id.canvas_rulesobject[]Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.
8 fields in
canvas_rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
safe_areaobjectLayouts only: a guide rectangle shot content should stay inside.
fontsobjectHeading and body families. A linked shot plays its layout’s unless it sets its own.
created_atinteger read-onlyupdated_atinteger read-only
curl "https://www.wunderframe.com/api/v1/shots/sht_a1" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY"{
"id": "sht_a1",
"scene_id": "scn_welcome",
"position": 1,
"duration_ms": 8000,
"layout_id": null,
"timeline": {
"canvas": {
"width": 1920,
"height": 1080,
"fps": 30,
"background": "#000000"
},
"tracks": [
{
"uuid": "visual-main",
"label": "Main",
"kind": "visual",
"role": "main",
"order": 0
},
{
"uuid": "visual-overlay",
"label": "Overlay",
"kind": "visual",
"role": "overlay",
"order": 1
},
{
"uuid": "audio-voiceover",
"label": "Voiceover",
"kind": "audio",
"role": "voiceover",
"order": 2
},
{
"uuid": "audio-music",
"label": "Music",
"kind": "audio",
"role": "music",
"order": 3
}
],
"blocks": [
{
"uuid": "caption_mu6nc9x01",
"kind": "caption",
"track_id": "visual-overlay",
"timing": {
"in_ms": 0,
"animation": "fade",
"anim_ms": 700
},
"transform": {
"x": 384,
"y": 459,
"w": 1152,
"h": 162,
"rotate": 0,
"fit": "cover",
"scale": 1,
"opacity": 1
},
"text": "{title}",
"font_weight": 700,
"color": "#ffffff",
"align": "center",
"rules": [
{
"id": "r1",
"label": "Hide once live",
"enabled": true,
"match": "all",
"when": [
{
"id": "c1",
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
],
"set": {
"hidden": true
},
"fade_ms": 1200
}
]
}
],
"audio": [],
"groups": []
},
"created_at": 1789372800000,
"updated_at": 1789372800000
}Error example
{
"error": {
"code": "unauthorized",
"message": "Missing or invalid API key."
}
}Replace a shot’s timeline
/shots/{id} authoringSend back a timeline you read from GET /shots/{id}. Leaving duration_ms out re-derives it from the timeline, which is what the editor does. An unknown block kind or rule sensor is refused with its name.
Parameters
idstring required pathThe shot’s id.
Request body required
timelineobjectThe shot document. Canvas, rows, what is on them, sound, and the rules that change it — the same shape the editor saves, read by the same
readTimeline. Always start from aGET. Fields this version does not know are dropped on write, so building one by hand loses whatever you did not think to include; reading, editing and putting back never does.8 fields in
timelinecanvasobject requiredThe frame everything is positioned inside.
width/heightare the composition size, not the screen’s.5 fields in
canvaswidthintegerheightintegerfpsintegerComposition frame rate. 30 unless set.
backgroundstringCSS color under everything.
backdropobjectPainted over the background where no block is:
styleofsolid|gradient|orbs, with its own colors.
tracksobject[] required9 fields in
tracksuuidstring requiredWhat a block’s
track_idpoints at.labelstringkindstring requiredOne of
visual,audiorolestringOne of
main,overlay,voiceover,music,sfxorderinteger requiredBack to front among visual rows.
hiddenbooleanlockedbooleanmutedbooleanheightintegerEditor row height. Cosmetic.
blocksobject[] requiredOrder within a track does not matter;
track_idand the track’sorderdecide what is in front.45 fields in
blocksuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
audioobject[]13 fields in
audiouuidstring requiredtrack_idstring requiredAn audio
Track.uuid.assetstringAn asset id.
labelstringat_msinteger requiredWhere it starts in the shot.
out_msintegervolumenumberduckingbooleanDrop under a voiceover.
fade_in_msintegerfade_out_msintegertrim_in_msintegerInto the source file.
trim_out_msintegerWith
loop: the end of the section that repeats.loopbooleanRepeat the
trim_in_ms…trim_out_mssection untilout_ms.
groupsobject[]Editor groupings. A block joins one with
group_id.canvas_rulesobject[]Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.
8 fields in
canvas_rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
safe_areaobjectLayouts only: a guide rectangle shot content should stay inside.
fontsobjectHeading and body families. A linked shot plays its layout’s unless it sets its own.
duration_msintegerAt least 500
layout_idstring or nulllayout_optsobjectWhat the shot takes from its layout:
background"layout" (default) or "own",hide— layout block uuids this shot leaves out (e.g. a logo the shot shows larger itself),audio"layout" | "own" | "mute".3 fields in
layout_optsbackgroundstringOne of
layout,ownhidestring[]audiostringOne of
layout,own,mute
Responses
- 200 The saved shot.
- 400 The request is invalid —
details.fieldnames the field when there is one. - 401 Missing or invalid API key.
- 403 The key lacks the scope, or the role is read-only.
- 404 Not found in this workspace.
Response fields (200)
idstring requiredscene_idstring requiredpositionnumberOrder within the scene. Fractional, so a shot can be inserted between two.
duration_msinteger requiredHow long it holds. At least 500.
layout_idstring or nullA linked layout whose blocks show behind or over this shot’s.
layout_optsobjectWhat the shot takes from its layout:
background"layout" (default) or "own",hide— layout block uuids this shot leaves out (e.g. a logo the shot shows larger itself),audio"layout" | "own" | "mute".3 fields in
layout_optsbackgroundstringOne of
layout,ownhidestring[]audiostringOne of
layout,own,mute
timelineobject requiredThe shot document. Canvas, rows, what is on them, sound, and the rules that change it — the same shape the editor saves, read by the same
readTimeline. Always start from aGET. Fields this version does not know are dropped on write, so building one by hand loses whatever you did not think to include; reading, editing and putting back never does.8 fields in
timelinecanvasobject requiredThe frame everything is positioned inside.
width/heightare the composition size, not the screen’s.5 fields in
canvaswidthintegerheightintegerfpsintegerComposition frame rate. 30 unless set.
backgroundstringCSS color under everything.
backdropobjectPainted over the background where no block is:
styleofsolid|gradient|orbs, with its own colors.
tracksobject[] required9 fields in
tracksuuidstring requiredWhat a block’s
track_idpoints at.labelstringkindstring requiredOne of
visual,audiorolestringOne of
main,overlay,voiceover,music,sfxorderinteger requiredBack to front among visual rows.
hiddenbooleanlockedbooleanmutedbooleanheightintegerEditor row height. Cosmetic.
blocksobject[] requiredOrder within a track does not matter;
track_idand the track’sorderdecide what is in front.45 fields in
blocksuuidstring requiredUnique within the shot. Keep it stable across edits.
kindstring requirede.g.
caption,still,clip,widget,presenter,agenda.track_idstring requiredA
Track.uuid. Which row it sits on, and so what it paints in front of.timingobject requiredWhen the block is on screen, in ms from the start of the shot. Leaving
out_msempty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.4 fields in
timingin_msinteger requiredout_msintegerOmit for “to the end of the shot”.
animationstringLegacy entrance.
transition_inwins over it.anim_msinteger
transformobject requiredWhere the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas,
x: 384, w: 1152is a band 60% wide, centered.8 fields in
transformxnumber requiredLeft edge, in canvas pixels.
ynumber requiredTop edge, in canvas pixels.
wnumberWidth in canvas pixels. Omit for the block’s natural width.
hnumberHeight in canvas pixels.
rotatenumberDegrees.
fitstringHow media fills the box.
One of
cover,containscalenumberMultiplier, 1 = as laid out.
opacitynumber0–1
labelstringShown in the editor’s layer list.
hiddenbooleanlockedbooleantextstringCaption and widget text.
{blanks}fill from the record.font_sizenumberfont_weightnumberfont_familystringheading,body, a device stack (system-ui,serif,monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.colorstringalignstringOne of
left,center,rightbackdropstringOne of
none,shadow,scrim,boxassetstringClip / still: an asset id from
/assets. Never a URL or storage key.image_blankstringStill: a blank holding a picture URL, filled per record.
altstringken_burnsstringOne of
none,in,out,pan-left,pan-rightaudiblebooleanA clip’s own sound, opt-in.
volumenumbertrim_in_msintegerClip: where it starts in the file.
trim_out_msintegerClip with
loop: the end of the section that repeats (the file’s own time).loopbooleanRepeat for as long as the block runs: a clip its
trim_in_ms…trim_out_mssection (at least 200 ms), a Ken Burns still its drift everyloop_ms.loop_msintegerA looping Ken Burns still: one drift there and back. Default 8000.
freezebooleanClip: a freeze frame — shows the frame at
trim_in_msand never plays.keep_runningbooleanClip with a
morph_key: the same video with the same name andkeep_runningin the next shot is this element, still playing — its box moves, it never restarts.widgetstringOne of
clock,date,countdown,progressformatstringOne of
short,medium,longtarget_atintegerCountdown / progress target, epoch ms.
target_blankstringCountdown / progress target from a blank; wins over
target_at. Every screen fillsnext_session_at(its stage’s next session); a session slot fillsstarts_at,ends_at,phase_from.from_atintegerProgress: when it starts filling, epoch ms. Missing =
props.span_min(default 10) minutes before the target.from_blankstringProgress start from a blank; wins over
from_at.presenter_sourcestringPresenter: camera, shared screen, or both.
presenter_slotinteger0 = whoever is live; n = the session’s n-th presenter.
name_labelbooleanPresenter: a lower third with name, title and organization.
maskobjectshapeofnone|circle|rounded, with radius, border and shadow.transition_inobject{ kind, ms }— cut, dissolve, fade, slide-*, scale.transition_outobjectmorph_keystringThe same name in the next shot is the same thing, moved.
show_whenobjectAlias, accepted on write:
{ test: filled },{ test: has|empty, field }or{ test: is|is_not, field, value }. Stored as the equivalent "Show only when…" rule — first inrules,id: show_when,phase: fill,set: { hidden: true }— and never written back. Older blocks may still carry it; it decides exactly as that rule does.agendaobjectAgenda board settings (ADR-0023).
propsobjectA block kind’s own settings.
rulesobject[]Sensor-driven changes laid over this block WHILE they hold.
8 fields in
rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
layerstringLayouts only: under or over the shot’s own blocks.
One of
under,over
audioobject[]13 fields in
audiouuidstring requiredtrack_idstring requiredAn audio
Track.uuid.assetstringAn asset id.
labelstringat_msinteger requiredWhere it starts in the shot.
out_msintegervolumenumberduckingbooleanDrop under a voiceover.
fade_in_msintegerfade_out_msintegertrim_in_msintegerInto the source file.
trim_out_msintegerWith
loop: the end of the section that repeats.loopbooleanRepeat the
trim_in_ms…trim_out_mssection untilout_ms.
groupsobject[]Editor groupings. A block joins one with
group_id.canvas_rulesobject[]Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.
8 fields in
canvas_rulesidstringlabelstringFor the author; never shown on screen.
enabledbooleanDefault true.
matchstringHow the top-level entries of
whencombine.One of
all,anywhenobject[] requiredEmpty never holds.
setobject requiredWhat changes WHILE the rule holds. Every field optional; only the ones set apply. Nothing is written to the block — when the rule stops holding, the block is itself again.
9 fields in
setopacitynumberMultiplied with the block’s own.
0–1
hiddenbooleancolorstringtextstringReplaces the text;
{blanks}fill as the block’s own do.scalenumberMultiplied with the block’s own.
filterstringCSS filter, e.g.
grayscale(1).backgroundstringCanvas rules only.
backdropobjectCanvas rules only.
propsobjectA block kind’s own settings to override.
fade_msintegerHow long the change takes, 0–60000. Default 800.
phasestringMissing = judged on the screen, continuously.
fill= judged once where the scene is filled with its record, never on the screen: onlyfieldconditions mean anything and onlyset.hiddenapplies (the block is left out). A "Show only when…" rule — what a block’sshow_whenis stored as. Thefieldoperatorsfilled/not_filledask about the block’s own{blanks}.One of
fill
safe_areaobjectLayouts only: a guide rectangle shot content should stay inside.
fontsobjectHeading and body families. A linked shot plays its layout’s unless it sets its own.
created_atinteger read-onlyupdated_atinteger read-only
curl -X PUT "https://www.wunderframe.com/api/v1/shots/sht_a1" \
-H "Authorization: Bearer $WUNDERFRAME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"timeline": {
"canvas": {
"width": 1920,
"height": 1080,
"fps": 30,
"background": "#000000"
},
"tracks": [
{
"uuid": "visual-main",
"label": "Main",
"kind": "visual",
"role": "main",
"order": 0
},
{
"uuid": "visual-overlay",
"label": "Overlay",
"kind": "visual",
"role": "overlay",
"order": 1
},
{
"uuid": "audio-voiceover",
"label": "Voiceover",
"kind": "audio",
"role": "voiceover",
"order": 2
},
{
"uuid": "audio-music",
"label": "Music",
"kind": "audio",
"role": "music",
"order": 3
}
],
"blocks": [
{
"uuid": "caption_mu6nc9x01",
"kind": "caption",
"track_id": "visual-overlay",
"timing": {
"in_ms": 0,
"animation": "fade",
"anim_ms": 700
},
"transform": {
"x": 384,
"y": 459,
"w": 1152,
"h": 162,
"rotate": 0,
"fit": "cover",
"scale": 1,
"opacity": 1
},
"text": "{title}",
"font_weight": 700,
"color": "#ffffff",
"align": "center",
"rules": [
{
"id": "r1",
"label": "Hide once live",
"enabled": true,
"match": "all",
"when": [
{
"id": "c1",
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
],
"set": {
"hidden": true
},
"fade_ms": 1200
}
]
}
],
"audio": [],
"groups": []
}
}'{
"id": "sht_a1",
"scene_id": "scn_welcome",
"position": 1,
"duration_ms": 8000,
"layout_id": null,
"timeline": {
"canvas": {
"width": 1920,
"height": 1080,
"fps": 30,
"background": "#000000"
},
"tracks": [
{
"uuid": "visual-main",
"label": "Main",
"kind": "visual",
"role": "main",
"order": 0
},
{
"uuid": "visual-overlay",
"label": "Overlay",
"kind": "visual",
"role": "overlay",
"order": 1
},
{
"uuid": "audio-voiceover",
"label": "Voiceover",
"kind": "audio",
"role": "voiceover",
"order": 2
},
{
"uuid": "audio-music",
"label": "Music",
"kind": "audio",
"role": "music",
"order": 3
}
],
"blocks": [
{
"uuid": "caption_mu6nc9x01",
"kind": "caption",
"track_id": "visual-overlay",
"timing": {
"in_ms": 0,
"animation": "fade",
"anim_ms": 700
},
"transform": {
"x": 384,
"y": 459,
"w": 1152,
"h": 162,
"rotate": 0,
"fit": "cover",
"scale": 1,
"opacity": 1
},
"text": "{title}",
"font_weight": 700,
"color": "#ffffff",
"align": "center",
"rules": [
{
"id": "r1",
"label": "Hide once live",
"enabled": true,
"match": "all",
"when": [
{
"id": "c1",
"sensor": "session",
"property": "status",
"op": "is",
"value": {
"status": "live"
}
}
],
"set": {
"hidden": true
},
"fade_ms": 1200
}
]
}
],
"audio": [],
"groups": []
},
"created_at": 1789372800000,
"updated_at": 1789372800000
}Error example
{
"error": {
"code": "invalid",
"message": "The request is invalid — `details.field` names the field when there is one."
}
}