Scenes and shots

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.

List block kinds and rule sensors

GET /block-kinds authoring

Start 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

  • width integer query

    Canvas width to measure templates against.

  • height integer query

    Canvas height to measure templates against.

Responses

  • 200 The palette and the sensor vocabulary.
  • 400 The request is invalid — details.field names 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)
  • kinds object[]
    11 fields in kinds
    • kind string

      What goes in Block.kind.

    • label string
    • icon string
    • hint string
    • track string

      The visual row a new one lands on.

      One of main, overlay

    • palette integer

      Where it sits in the editor’s palette; absent = not offered there.

    • uses_time boolean

      Reads the playhead, so the stage keeps time for it.

    • compose_only string[]

      Fields the server fills when a screen asks. Never send these — they are dropped.

    • sets string[]

      The fields this kind carries beyond the generic five, derived from template.

    • template object

      One thing on screen. kind decides which of the optional fields mean anything — a caption reads text, a still reads asset, a presenter reads presenter_source. GET /scenes will not tell you the kinds; the app’s block palette and the MCP’s list_block_kinds do, 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_url and agenda_view are filled by the server when a screen asks for the shot.

      45 fields in template
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • rule_presets object[]
      3 fields in rule_presets
      • key string
      • label string
      • description string
  • sensors object[]
    3 fields in sensors
    • sensor string
    • label string
    • readings object[]

      One per reading the sensor offers, each with its own operators and a ready-made example.

      4 fields in readings
      • property string
      • label string
      • ops object[]
        2 fields in ops
        • op string
        • label string
      • example object

        One reading, compared. sensor and op must come from /named-conditions/sensors.

        5 fields in example
        • id string

          Any stable string. Generated if you leave it out.

        • sensor string required

          What to read, e.g. clock, weekday, session, field, trait.

        • property string

          WHICH reading, for a sensor that has several (the session's status vs its start). Omit for a sensor with one.

        • op string required

          The comparison, e.g. after, is, in.

        • value object

          Operator-specific, e.g. { "time": "18:00" }.

curl "https://www.wunderframe.com/api/v1/block-kinds" \
  -H "Authorization: Bearer $WUNDERFRAME_API_KEY"
Response 200
{
  "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
Response 400
{
  "error": {
    "code": "invalid",
    "message": "The request is invalid — `details.field` names the field when there is one."
  }
}

List scenes

GET /scenes authoring

The scene library, newest first.

Parameters

  • q string query

    Search titles.

  • folder string query

    Only this folder, or root for the top level.

  • limit integer query

    Page size, 1–200.

    1–200. Default 50

  • cursor string query

    The next_cursor of the previous page.

Responses

  • 200 A page of scenes.
  • 400 The request is invalid — details.field names 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)
  • items object[] required
    11 fields in items
    • id string required
    • title string required
    • kind string

      A free-text label for filtering the library. Never behavior.

    • description string
    • folder_id string or null
    • template_id string or null

      Set on a FILLED scene: it has no shots of its own and plays this template’s, with its fields filling the blanks. Add shots to the template, not to the filled scene.

    • fields object

      A filled scene’s values, by blank name.

    • ref string or null

      Your own key for this scene.

    • shots object[] read-only

      Present on a single-scene read, in playback order.

      4 fields in shots
      • id string
      • position number
      • duration_ms integer
      • layout_id string or null
    • created_at integer read-only
    • updated_at integer read-only
  • next_cursor string or null required

    Pass as cursor for the next page; null on the last page.

curl "https://www.wunderframe.com/api/v1/scenes?limit=50" \
  -H "Authorization: Bearer $WUNDERFRAME_API_KEY"
Response 200
{
  "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
Response 400
{
  "error": {
    "code": "invalid",
    "message": "The request is invalid — `details.field` names the field when there is one."
  }
}

Create a scene

POST /scenes authoring

It is born with one empty shot, exactly as the library’s New scene is — a scene with no shots plays nothing.

Request body required

  • title string required
  • kind string
  • description string
  • folder_id string or null
  • ref string or null

Responses

  • 201 The new scene, with its shot.
  • 400 The request is invalid — details.field names 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)
  • id string required
  • title string required
  • kind string

    A free-text label for filtering the library. Never behavior.

  • description string
  • folder_id string or null
  • template_id string or null

    Set on a FILLED scene: it has no shots of its own and plays this template’s, with its fields filling the blanks. Add shots to the template, not to the filled scene.

  • fields object

    A filled scene’s values, by blank name.

  • ref string or null

    Your own key for this scene.

  • shots object[] read-only

    Present on a single-scene read, in playback order.

    4 fields in shots
    • id string
    • position number
    • duration_ms integer
    • layout_id string or null
  • created_at integer read-only
  • updated_at integer 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"
}'
Response 201
{
  "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
Response 400
{
  "error": {
    "code": "invalid",
    "message": "The request is invalid — `details.field` names the field when there is one."
  }
}

Get a scene and its shots

GET /scenes/{id} authoring

A filled scene lists its template’s shots, because those are what it plays.

Parameters

  • id string required path

    The 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)
  • id string required
  • title string required
  • kind string

    A free-text label for filtering the library. Never behavior.

  • description string
  • folder_id string or null
  • template_id string or null

    Set on a FILLED scene: it has no shots of its own and plays this template’s, with its fields filling the blanks. Add shots to the template, not to the filled scene.

  • fields object

    A filled scene’s values, by blank name.

  • ref string or null

    Your own key for this scene.

  • shots object[] read-only

    Present on a single-scene read, in playback order.

    4 fields in shots
    • id string
    • position number
    • duration_ms integer
    • layout_id string or null
  • created_at integer read-only
  • updated_at integer read-only
curl "https://www.wunderframe.com/api/v1/scenes/scn_welcome" \
  -H "Authorization: Bearer $WUNDERFRAME_API_KEY"
Response 200
{
  "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
Response 401
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key."
  }
}

Add a shot to a scene

POST /scenes/{id}/shots authoring

Appended 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

  • id string required path

    The scene’s id.

Request body

  • timeline object

    The 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 a GET. 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 timeline
    • canvas object required

      The frame everything is positioned inside. width/height are the composition size, not the screen’s.

      5 fields in canvas
      • width integer
      • height integer
      • fps integer

        Composition frame rate. 30 unless set.

      • background string

        CSS color under everything.

      • backdrop object

        Painted over the background where no block is: style of solid | gradient | orbs, with its own colors.

    • tracks object[] required
      9 fields in tracks
      • uuid string required

        What a block’s track_id points at.

      • label string
      • kind string required

        One of visual, audio

      • role string

        One of main, overlay, voiceover, music, sfx

      • order integer required

        Back to front among visual rows.

      • hidden boolean
      • locked boolean
      • muted boolean
      • height integer

        Editor row height. Cosmetic.

    • blocks object[] required

      Order within a track does not matter; track_id and the track’s order decide what is in front.

      45 fields in blocks
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • audio object[]
      13 fields in audio
      • uuid string required
      • track_id string required

        An audio Track.uuid.

      • asset string

        An asset id.

      • label string
      • at_ms integer required

        Where it starts in the shot.

      • out_ms integer
      • volume number
      • ducking boolean

        Drop under a voiceover.

      • fade_in_ms integer
      • fade_out_ms integer
      • trim_in_ms integer

        Into the source file.

      • trim_out_ms integer

        With loop: the end of the section that repeats.

      • loop boolean

        Repeat the trim_in_ms…trim_out_ms section until out_ms.

    • groups object[]

      Editor groupings. A block joins one with group_id.

    • canvas_rules object[]

      Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.

      8 fields in canvas_rules
      • id string
      • label string

        For the author; never shown on screen.

      • enabled boolean

        Default true.

      • match string

        How the top-level entries of when combine.

        One of all, any

      • when object[] required

        Empty never holds.

      • set object required

        What 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 set
        • opacity number

          Multiplied with the block’s own.

          0–1

        • hidden boolean
        • color string
        • text string

          Replaces the text; {blanks} fill as the block’s own do.

        • scale number

          Multiplied with the block’s own.

        • filter string

          CSS filter, e.g. grayscale(1).

        • background string

          Canvas rules only.

        • backdrop object

          Canvas rules only.

        • props object

          A block kind’s own settings to override.

      • fade_ms integer

        How long the change takes, 0–60000. Default 800.

      • phase string

        Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

        One of fill

    • safe_area object

      Layouts only: a guide rectangle shot content should stay inside.

    • fonts object

      Heading and body families. A linked shot plays its layout’s unless it sets its own.

  • duration_ms integer

    At least 500

  • layout_id string or null
  • layout_opts object

    What 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_opts
    • background string

      One of layout, own

    • hide string[]
    • audio string

      One of layout, own, mute

Responses

  • 201 The new shot.
  • 400 The request is invalid — details.field names 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)
  • id string required
  • scene_id string required
  • position number

    Order within the scene. Fractional, so a shot can be inserted between two.

  • duration_ms integer required

    How long it holds. At least 500.

  • layout_id string or null

    A linked layout whose blocks show behind or over this shot’s.

  • layout_opts object

    What 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_opts
    • background string

      One of layout, own

    • hide string[]
    • audio string

      One of layout, own, mute

  • timeline object required

    The 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 a GET. 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 timeline
    • canvas object required

      The frame everything is positioned inside. width/height are the composition size, not the screen’s.

      5 fields in canvas
      • width integer
      • height integer
      • fps integer

        Composition frame rate. 30 unless set.

      • background string

        CSS color under everything.

      • backdrop object

        Painted over the background where no block is: style of solid | gradient | orbs, with its own colors.

    • tracks object[] required
      9 fields in tracks
      • uuid string required

        What a block’s track_id points at.

      • label string
      • kind string required

        One of visual, audio

      • role string

        One of main, overlay, voiceover, music, sfx

      • order integer required

        Back to front among visual rows.

      • hidden boolean
      • locked boolean
      • muted boolean
      • height integer

        Editor row height. Cosmetic.

    • blocks object[] required

      Order within a track does not matter; track_id and the track’s order decide what is in front.

      45 fields in blocks
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • audio object[]
      13 fields in audio
      • uuid string required
      • track_id string required

        An audio Track.uuid.

      • asset string

        An asset id.

      • label string
      • at_ms integer required

        Where it starts in the shot.

      • out_ms integer
      • volume number
      • ducking boolean

        Drop under a voiceover.

      • fade_in_ms integer
      • fade_out_ms integer
      • trim_in_ms integer

        Into the source file.

      • trim_out_ms integer

        With loop: the end of the section that repeats.

      • loop boolean

        Repeat the trim_in_ms…trim_out_ms section until out_ms.

    • groups object[]

      Editor groupings. A block joins one with group_id.

    • canvas_rules object[]

      Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.

      8 fields in canvas_rules
      • id string
      • label string

        For the author; never shown on screen.

      • enabled boolean

        Default true.

      • match string

        How the top-level entries of when combine.

        One of all, any

      • when object[] required

        Empty never holds.

      • set object required

        What 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 set
        • opacity number

          Multiplied with the block’s own.

          0–1

        • hidden boolean
        • color string
        • text string

          Replaces the text; {blanks} fill as the block’s own do.

        • scale number

          Multiplied with the block’s own.

        • filter string

          CSS filter, e.g. grayscale(1).

        • background string

          Canvas rules only.

        • backdrop object

          Canvas rules only.

        • props object

          A block kind’s own settings to override.

      • fade_ms integer

        How long the change takes, 0–60000. Default 800.

      • phase string

        Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

        One of fill

    • safe_area object

      Layouts only: a guide rectangle shot content should stay inside.

    • fonts object

      Heading and body families. A linked shot plays its layout’s unless it sets its own.

  • created_at integer read-only
  • updated_at integer 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
}'
Response 201
{
  "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
Response 400
{
  "error": {
    "code": "invalid",
    "message": "The request is invalid — `details.field` names the field when there is one."
  }
}

Get a shot

GET /shots/{id} authoring

The whole timeline. Edit it and PUT it back.

Parameters

  • id string required path

    The 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)
  • id string required
  • scene_id string required
  • position number

    Order within the scene. Fractional, so a shot can be inserted between two.

  • duration_ms integer required

    How long it holds. At least 500.

  • layout_id string or null

    A linked layout whose blocks show behind or over this shot’s.

  • layout_opts object

    What 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_opts
    • background string

      One of layout, own

    • hide string[]
    • audio string

      One of layout, own, mute

  • timeline object required

    The 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 a GET. 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 timeline
    • canvas object required

      The frame everything is positioned inside. width/height are the composition size, not the screen’s.

      5 fields in canvas
      • width integer
      • height integer
      • fps integer

        Composition frame rate. 30 unless set.

      • background string

        CSS color under everything.

      • backdrop object

        Painted over the background where no block is: style of solid | gradient | orbs, with its own colors.

    • tracks object[] required
      9 fields in tracks
      • uuid string required

        What a block’s track_id points at.

      • label string
      • kind string required

        One of visual, audio

      • role string

        One of main, overlay, voiceover, music, sfx

      • order integer required

        Back to front among visual rows.

      • hidden boolean
      • locked boolean
      • muted boolean
      • height integer

        Editor row height. Cosmetic.

    • blocks object[] required

      Order within a track does not matter; track_id and the track’s order decide what is in front.

      45 fields in blocks
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • audio object[]
      13 fields in audio
      • uuid string required
      • track_id string required

        An audio Track.uuid.

      • asset string

        An asset id.

      • label string
      • at_ms integer required

        Where it starts in the shot.

      • out_ms integer
      • volume number
      • ducking boolean

        Drop under a voiceover.

      • fade_in_ms integer
      • fade_out_ms integer
      • trim_in_ms integer

        Into the source file.

      • trim_out_ms integer

        With loop: the end of the section that repeats.

      • loop boolean

        Repeat the trim_in_ms…trim_out_ms section until out_ms.

    • groups object[]

      Editor groupings. A block joins one with group_id.

    • canvas_rules object[]

      Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.

      8 fields in canvas_rules
      • id string
      • label string

        For the author; never shown on screen.

      • enabled boolean

        Default true.

      • match string

        How the top-level entries of when combine.

        One of all, any

      • when object[] required

        Empty never holds.

      • set object required

        What 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 set
        • opacity number

          Multiplied with the block’s own.

          0–1

        • hidden boolean
        • color string
        • text string

          Replaces the text; {blanks} fill as the block’s own do.

        • scale number

          Multiplied with the block’s own.

        • filter string

          CSS filter, e.g. grayscale(1).

        • background string

          Canvas rules only.

        • backdrop object

          Canvas rules only.

        • props object

          A block kind’s own settings to override.

      • fade_ms integer

        How long the change takes, 0–60000. Default 800.

      • phase string

        Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

        One of fill

    • safe_area object

      Layouts only: a guide rectangle shot content should stay inside.

    • fonts object

      Heading and body families. A linked shot plays its layout’s unless it sets its own.

  • created_at integer read-only
  • updated_at integer read-only
curl "https://www.wunderframe.com/api/v1/shots/sht_a1" \
  -H "Authorization: Bearer $WUNDERFRAME_API_KEY"
Response 200
{
  "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
Response 401
{
  "error": {
    "code": "unauthorized",
    "message": "Missing or invalid API key."
  }
}

Replace a shot’s timeline

PUT /shots/{id} authoring

Send 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

  • id string required path

    The shot’s id.

Request body required

  • timeline object

    The 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 a GET. 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 timeline
    • canvas object required

      The frame everything is positioned inside. width/height are the composition size, not the screen’s.

      5 fields in canvas
      • width integer
      • height integer
      • fps integer

        Composition frame rate. 30 unless set.

      • background string

        CSS color under everything.

      • backdrop object

        Painted over the background where no block is: style of solid | gradient | orbs, with its own colors.

    • tracks object[] required
      9 fields in tracks
      • uuid string required

        What a block’s track_id points at.

      • label string
      • kind string required

        One of visual, audio

      • role string

        One of main, overlay, voiceover, music, sfx

      • order integer required

        Back to front among visual rows.

      • hidden boolean
      • locked boolean
      • muted boolean
      • height integer

        Editor row height. Cosmetic.

    • blocks object[] required

      Order within a track does not matter; track_id and the track’s order decide what is in front.

      45 fields in blocks
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • audio object[]
      13 fields in audio
      • uuid string required
      • track_id string required

        An audio Track.uuid.

      • asset string

        An asset id.

      • label string
      • at_ms integer required

        Where it starts in the shot.

      • out_ms integer
      • volume number
      • ducking boolean

        Drop under a voiceover.

      • fade_in_ms integer
      • fade_out_ms integer
      • trim_in_ms integer

        Into the source file.

      • trim_out_ms integer

        With loop: the end of the section that repeats.

      • loop boolean

        Repeat the trim_in_ms…trim_out_ms section until out_ms.

    • groups object[]

      Editor groupings. A block joins one with group_id.

    • canvas_rules object[]

      Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.

      8 fields in canvas_rules
      • id string
      • label string

        For the author; never shown on screen.

      • enabled boolean

        Default true.

      • match string

        How the top-level entries of when combine.

        One of all, any

      • when object[] required

        Empty never holds.

      • set object required

        What 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 set
        • opacity number

          Multiplied with the block’s own.

          0–1

        • hidden boolean
        • color string
        • text string

          Replaces the text; {blanks} fill as the block’s own do.

        • scale number

          Multiplied with the block’s own.

        • filter string

          CSS filter, e.g. grayscale(1).

        • background string

          Canvas rules only.

        • backdrop object

          Canvas rules only.

        • props object

          A block kind’s own settings to override.

      • fade_ms integer

        How long the change takes, 0–60000. Default 800.

      • phase string

        Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

        One of fill

    • safe_area object

      Layouts only: a guide rectangle shot content should stay inside.

    • fonts object

      Heading and body families. A linked shot plays its layout’s unless it sets its own.

  • duration_ms integer

    At least 500

  • layout_id string or null
  • layout_opts object

    What 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_opts
    • background string

      One of layout, own

    • hide string[]
    • audio string

      One of layout, own, mute

Responses

  • 200 The saved shot.
  • 400 The request is invalid — details.field names 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)
  • id string required
  • scene_id string required
  • position number

    Order within the scene. Fractional, so a shot can be inserted between two.

  • duration_ms integer required

    How long it holds. At least 500.

  • layout_id string or null

    A linked layout whose blocks show behind or over this shot’s.

  • layout_opts object

    What 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_opts
    • background string

      One of layout, own

    • hide string[]
    • audio string

      One of layout, own, mute

  • timeline object required

    The 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 a GET. 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 timeline
    • canvas object required

      The frame everything is positioned inside. width/height are the composition size, not the screen’s.

      5 fields in canvas
      • width integer
      • height integer
      • fps integer

        Composition frame rate. 30 unless set.

      • background string

        CSS color under everything.

      • backdrop object

        Painted over the background where no block is: style of solid | gradient | orbs, with its own colors.

    • tracks object[] required
      9 fields in tracks
      • uuid string required

        What a block’s track_id points at.

      • label string
      • kind string required

        One of visual, audio

      • role string

        One of main, overlay, voiceover, music, sfx

      • order integer required

        Back to front among visual rows.

      • hidden boolean
      • locked boolean
      • muted boolean
      • height integer

        Editor row height. Cosmetic.

    • blocks object[] required

      Order within a track does not matter; track_id and the track’s order decide what is in front.

      45 fields in blocks
      • uuid string required

        Unique within the shot. Keep it stable across edits.

      • kind string required

        e.g. caption, still, clip, widget, presenter, agenda.

      • track_id string required

        A Track.uuid. Which row it sits on, and so what it paints in front of.

      • timing object required

        When the block is on screen, in ms from the start of the shot. Leaving out_ms empty means “until the end” — which is what a background or a logo wants, and stays true when the shot gets longer.

        4 fields in timing
        • in_ms integer required
        • out_ms integer

          Omit for “to the end of the shot”.

        • animation string

          Legacy entrance. transition_in wins over it.

        • anim_ms integer
      • transform object required

        Where the block sits. x, y, w and h are CANVAS PIXELS, not fractions — on a 1920×1080 canvas, x: 384, w: 1152 is a band 60% wide, centered.

        8 fields in transform
        • x number required

          Left edge, in canvas pixels.

        • y number required

          Top edge, in canvas pixels.

        • w number

          Width in canvas pixels. Omit for the block’s natural width.

        • h number

          Height in canvas pixels.

        • rotate number

          Degrees.

        • fit string

          How media fills the box.

          One of cover, contain

        • scale number

          Multiplier, 1 = as laid out.

        • opacity number

          0–1

      • label string

        Shown in the editor’s layer list.

      • hidden boolean
      • locked boolean
      • text string

        Caption and widget text. {blanks} fill from the record.

      • font_size number
      • font_weight number
      • font_family string

        heading, body, a device stack (system-ui, serif, monospace) or any Google Fonts family by name. Screens download a Google family and play it offline.

      • color string
      • align string

        One of left, center, right

      • backdrop string

        One of none, shadow, scrim, box

      • asset string

        Clip / still: an asset id from /assets. Never a URL or storage key.

      • image_blank string

        Still: a blank holding a picture URL, filled per record.

      • alt string
      • ken_burns string

        One of none, in, out, pan-left, pan-right

      • audible boolean

        A clip’s own sound, opt-in.

      • volume number
      • trim_in_ms integer

        Clip: where it starts in the file.

      • trim_out_ms integer

        Clip with loop: the end of the section that repeats (the file’s own time).

      • loop boolean

        Repeat for as long as the block runs: a clip its trim_in_ms…trim_out_ms section (at least 200 ms), a Ken Burns still its drift every loop_ms.

      • loop_ms integer

        A looping Ken Burns still: one drift there and back. Default 8000.

      • freeze boolean

        Clip: a freeze frame — shows the frame at trim_in_ms and never plays.

      • keep_running boolean

        Clip with a morph_key: the same video with the same name and keep_running in the next shot is this element, still playing — its box moves, it never restarts.

      • widget string

        One of clock, date, countdown, progress

      • format string

        One of short, medium, long

      • target_at integer

        Countdown / progress target, epoch ms.

      • target_blank string

        Countdown / progress target from a blank; wins over target_at. Every screen fills next_session_at (its stage’s next session); a session slot fills starts_at, ends_at, phase_from.

      • from_at integer

        Progress: when it starts filling, epoch ms. Missing = props.span_min (default 10) minutes before the target.

      • from_blank string

        Progress start from a blank; wins over from_at.

      • presenter_source string

        Presenter: camera, shared screen, or both.

      • presenter_slot integer

        0 = whoever is live; n = the session’s n-th presenter.

      • name_label boolean

        Presenter: a lower third with name, title and organization.

      • mask object

        shape of none | circle | rounded, with radius, border and shadow.

      • transition_in object

        { kind, ms } — cut, dissolve, fade, slide-*, scale.

      • transition_out object
      • morph_key string

        The same name in the next shot is the same thing, moved.

      • show_when object

        Alias, 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 in rules, 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.

      • agenda object

        Agenda board settings (ADR-0023).

      • props object

        A block kind’s own settings.

      • rules object[]

        Sensor-driven changes laid over this block WHILE they hold.

        8 fields in rules
        • id string
        • label string

          For the author; never shown on screen.

        • enabled boolean

          Default true.

        • match string

          How the top-level entries of when combine.

          One of all, any

        • when object[] required

          Empty never holds.

        • set object required

          What 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 set
          • opacity number

            Multiplied with the block’s own.

            0–1

          • hidden boolean
          • color string
          • text string

            Replaces the text; {blanks} fill as the block’s own do.

          • scale number

            Multiplied with the block’s own.

          • filter string

            CSS filter, e.g. grayscale(1).

          • background string

            Canvas rules only.

          • backdrop object

            Canvas rules only.

          • props object

            A block kind’s own settings to override.

        • fade_ms integer

          How long the change takes, 0–60000. Default 800.

        • phase string

          Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

          One of fill

      • layer string

        Layouts only: under or over the shot’s own blocks.

        One of under, over

    • audio object[]
      13 fields in audio
      • uuid string required
      • track_id string required

        An audio Track.uuid.

      • asset string

        An asset id.

      • label string
      • at_ms integer required

        Where it starts in the shot.

      • out_ms integer
      • volume number
      • ducking boolean

        Drop under a voiceover.

      • fade_in_ms integer
      • fade_out_ms integer
      • trim_in_ms integer

        Into the source file.

      • trim_out_ms integer

        With loop: the end of the section that repeats.

      • loop boolean

        Repeat the trim_in_ms…trim_out_ms section until out_ms.

    • groups object[]

      Editor groupings. A block joins one with group_id.

    • canvas_rules object[]

      Rules on the BACKGROUND — a different backdrop at night, on a date. Applied when the shot is composed.

      8 fields in canvas_rules
      • id string
      • label string

        For the author; never shown on screen.

      • enabled boolean

        Default true.

      • match string

        How the top-level entries of when combine.

        One of all, any

      • when object[] required

        Empty never holds.

      • set object required

        What 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 set
        • opacity number

          Multiplied with the block’s own.

          0–1

        • hidden boolean
        • color string
        • text string

          Replaces the text; {blanks} fill as the block’s own do.

        • scale number

          Multiplied with the block’s own.

        • filter string

          CSS filter, e.g. grayscale(1).

        • background string

          Canvas rules only.

        • backdrop object

          Canvas rules only.

        • props object

          A block kind’s own settings to override.

      • fade_ms integer

        How long the change takes, 0–60000. Default 800.

      • phase string

        Missing = judged on the screen, continuously. fill = judged once where the scene is filled with its record, never on the screen: only field conditions mean anything and only set.hidden applies (the block is left out). A "Show only when…" rule — what a block’s show_when is stored as. The field operators filled / not_filled ask about the block’s own {blanks}.

        One of fill

    • safe_area object

      Layouts only: a guide rectangle shot content should stay inside.

    • fonts object

      Heading and body families. A linked shot plays its layout’s unless it sets its own.

  • created_at integer read-only
  • updated_at integer 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": []
  }
}'
Response 200
{
  "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
Response 400
{
  "error": {
    "code": "invalid",
    "message": "The request is invalid — `details.field` names the field when there is one."
  }
}