Switcher

Requires: control (operator+) for the endpoints that drive the switcher; /api/switcher/tally only reads state and requires read (viewer+).

Ergonomic control endpoints for the Switcher input — pick the PGM (on-air) and Preview (next) sources and take Preview to air with a cut, crossfade, or fade-to-black, plus a status read for what is currently on air. The underlying writes and commands can also be reached through the generic /api/setproperty (set ForegroundInputName / BackgroundInputName to a source name, or to a multiviewer slot such as MvInput3) and /api/invokecommand (CutCommand / CrossfadeCommand / FadeToBlackCommand), but the two paths are not equivalent. The routes below validate the source they are given and answer 400 when they refuse it; a generic property write is not validated, can leave the property unchanged, and still answers 200. A control surface — and anything that selects sources by multiviewer slot — should use these routes; see Choosing by multiviewer slot.

Choosing the switcher. A project can contain several switchers. Identify one with target (GUID), targetname (its name), or targettype; discover them with /api/objects/list?type=SwitcherInput. If you omit all three, the project's single switcher is used automatically — but if there is more than one switcher (or none), the call returns 400 and you must name one. Selection is scoped to switchers, so a targetname/targettype that also matches a layer, target, or operator of the same name is not a conflict — only switchers are considered (a layer hosting the switcher is often named after it). A targetname matching more than one switcher is still a 400.

Every change made here is observable in real time via the switcher SSE topic (/api/events/subscribe?topics=switcher) or the dedicated WebSocket Switcher subscription ({ "Type": "Subscribe", "Content": "Switcher" }), both pre-filtered to switchers. The WebSocket subscription also sends the current value of each of those properties, for every switcher in the project, immediately after confirming the subscription — so a panel opened mid-show is populated at once rather than after the next cut. They arrive as ordinary PropertyChanged messages, indistinguishable from a live change, so no special handling is needed. The SSE topic has no such snapshot (there is no replay window on that transport): seed it with /api/switcher/tally or /api/getproperty after subscribing.

Overlays. The four overlay layers have no dedicated route — show, hide, or toggle one with /api/invokecommand on Overlay1ShowCommand / Overlay1HideCommand / Overlay1CrossFadeInOutCommand (…Overlay4…), and point a slot at a source by setting MvOverlay1…MvOverlay4. Their state is carried on the same two streams as everything else here: OverlayNActive (boolean — shown, fading in, or fading out) and OverlayNOpacity (0–100), published from the render loop, so an overlay faded in from Desktop, a script, or the API reaches every subscriber. Both are reports, not controls — the render loop republishes the real state every frame, so an /api/setproperty write to one is accepted but overwritten within a frame; use the commands above to actually change an overlay. An overlay that is up also puts Program tally on its source, so a tally display sees it on the tally topic too.

GET /api/switcher/program

Sets the switcher's foreground (PGM, on-air) source.

Parameters:

Parameter Required Description
target / targetname / targettype One of (or omit when the project has exactly one switcher) Which switcher
input One of input/inputid/cell Source input name (a scene counts as an input), or a multiviewer slot MvInput1–MvInput13
inputid One of input/inputid/cell Source input GUID, resolved to its name (use when names aren't unique)
cell One of input/inputid/cell Multiviewer slot number, 1–13 — the N of MvInputN. Selects whichever source that slot shows, provided it is live

All three forms only ever end on an input (a scene is itself an input) — layers, targets, and operators are deliberately ignored, so a name shared with one of those can never be selected as a switcher source.

Choosing by multiviewer slot. cell=3 and input=MvInput3 (any casing) both select the source set in the switcher's MvInput3, so a control surface can address its buttons by position instead of knowing every source's name. The slot is looked up once, when the call is made: the switcher then holds that source's name — never MvInput3 itself — which is what /api/switcher/tally and the switcher SSE topic report, and changing the slot afterwards does not move PGM. A display alias in the slot (Camera1|Cam 1) is dropped — the name is Camera1. Any slot 1–13 can be used, including one the current layout does not draw. These two routes only switch to a slot whose source is live — an input in the project that is rendering a picture — so nothing is ever switched to a slot that would put black on air: a slot that is empty, names an input that does not exist or is not rendering, or a number outside 1–13, is a 400 whether given as cell or as input=MvInputN. A slot reference is never looked up as an input name, and for the same reason an input whose own name is written like a slot (MvInput3) cannot be selected as a switcher source at all — inputid pointing at one is a 400 asking for it to be renamed. For a control surface, these two routes are the supported way to switch by slot, because they are the only path that validates the request and reports a refusal. ForegroundInputName / BackgroundInputName also accept slot references when written through /api/setproperty, a script, Composer Desktop or a project file, but those writes are not validated: only the slot's configuration is checked, not whether its source is rendering, and a reference to an empty slot is refused silently — the property keeps its value, a warning is logged, and /api/setproperty still answers 200 (a script's write still reports success). Read the property back if you use that path. A project file may list the settings in any order: a reference is resolved once the whole switcher has been loaded.

Response: 200 OK — Program (PGM) set to: <name>. 400 if the target isn't a Switcher, or not exactly one of input/inputid/cell is given, or it doesn't resolve to an input — including a cell outside 1–13 or on a slot with no source set.

GET /api/switcher/preview

Sets the switcher's background (Preview, next) source. Same parameters and responses as /api/switcher/program, applied to BackgroundInputName.

GET /api/switcher/cut

Hard cut between foreground (PGM) and background (Preview).

Parameters:

Parameter Required Description
target / targetname / targettype One of (or omit when the project has exactly one switcher) Which switcher

Response: 200 OK — Switcher cut performed.. 400 if the target isn't a Switcher or the command can't execute in the current state.

GET /api/switcher/crossfade

Crossfade between foreground and background over the configured crossfade duration.

Parameters:

Parameter Required Description
target / targetname / targettype One of (or omit when the project has exactly one switcher) Which switcher
durationms No Override the crossfade duration (ms, 0–5000) before transitioning

Response: 200 OK — Switcher crossfade performed.. 400 if the target isn't a Switcher or the command can't execute.

GET /api/switcher/fadetoblack

Fade the switcher output to or from black (FTB).

Parameters:

Parameter Required Description
target / targetname / targettype One of (or omit when the project has exactly one switcher) Which switcher
durationms No Override the FTB duration (ms, 0–5000) before fading

Response: 200 OK — Switcher fadetoblack performed.. 400 if the target isn't a Switcher or the command can't execute.

GET /api/switcher/tally

Reads the live tally status — which sources are on air, queued, or being used by an overlay. Unlike the control routes above, omitting the selector is not an error: with no target / targetname / targettype every switcher in the project is reported, so a tally display can poll one URL without knowing how many switchers exist.

Parameters:

Parameter Required Description
target / targetname / targettype No Report only this switcher. Omit for all of them.

Response: 200 OK — JSON. 400 if a selector is given that names something other than a switcher.

{
  "DateTime": "2026-08-29T12:00:00Z",
  "Switchers": [
    {
      "Id": "7a2b…", "Name": "Main switcher",
      "Program": "Camera 1", "Preview": "Camera 2",
      "BlendPercentage": 100.0, "FadeToBlackPercentage": 0.0,
      "MultiViewLayout": "EightInputs",
      "Overlays": [
        { "Overlay": 1, "Name": "Lower third", "Resolved": "Lower third", "Active": true, "Opacity": 100.0 },
        { "Overlay": 2, "Name": "", "Resolved": "", "Active": false, "Opacity": 0.0 }
      ],
      "MultiViewCells": [
        { "Cell": 1, "Name": "Camera 1", "Label": "Cam 1", "Resolved": true, "Tally": "Program" },
        { "Cell": 2, "Name": "Camera 2", "Label": "Cam 2", "Resolved": true, "Tally": "Preview" },
        { "Cell": 3, "Name": "Lower third", "Label": "L3", "Resolved": true, "Tally": "Program" }
      ]
    }
  ],
  "Inputs": [
    { "Id": "9f3c…", "Name": "Camera 1", "Tally": "Program" },
    { "Id": "1d5e…", "Name": "Camera 2", "Tally": "Preview" },
    { "Id": "4b8a…", "Name": "Camera 3", "Tally": "None" }
  ]
}

Tally values are Program (on air — the PGM source, or an overlay that is shown, fading in, or fading out), Preview (queued as the next source), or None. When several roles apply at once the strongest wins, which is what the multiviewer draws: a red border for Program, green for Preview.

Switchers[].MultiViewCells is the multiviewer's own view — one entry per cell that has a source configured, with the tally its border shows. An overlay has no cell of its own, so it only lights a cell up when the same source is also configured as one of that switcher's multiviewer inputs.

Inputs is the same state seen from the other side: every input's own TallyState, accumulated across all switchers in the project. This is what an external tally light or an operator UI wants to read. The same value is on the input itself, so /api/getproperty?targetname=Camera 1&property=TallyState returns it for a single source, and a script can read TallyState directly.

Don't poll this to follow tally. Subscribe to the tally SSE topic (/api/events/subscribe?topics=tally) or the WebSocket Components subscription, both of which push a tally.modified / PropertyChanged only when a source actually goes on or off air. Call this endpoint once on connect to seed the initial state, then let the stream keep it current.

Examples

All examples assume the host localhost:44433; add ?apikey=<key> (or an apikey header) if your instance requires one.

Discover the switchers in the project:

GET /api/objects/list?type=SwitcherInput
→ [ { "Id": "7a2b…", "Name": "Main switcher", "Type": "SwitcherInput" } ]

Classic Preview → Take workflow (queue the next source on Preview, then dissolve it to air):

GET /api/switcher/preview?target=7a2b…&input=Camera 2
GET /api/switcher/crossfade?target=7a2b…&durationms=1000

Hard cut, then fade to black:

GET /api/switcher/cut?target=7a2b…
GET /api/switcher/fadetoblack?target=7a2b…&durationms=500

Put a source straight on air (PGM):

GET /api/switcher/program?target=7a2b…&input=Camera 1

Selecting the switcher different ways — by name, by GUID, or (single switcher only) omit it entirely:

GET /api/switcher/preview?targetname=Main switcher&input=Camera 2
GET /api/switcher/preview?target=7a2b…&input=Camera 2
GET /api/switcher/preview?input=Camera 2          # only one switcher in the project

Selecting the Preview/PGM source by GUID instead of name (handy when input names aren't unique):

GET /api/switcher/preview?target=7a2b…&inputid=9f3c…

Equivalent calls via the generic API (the routes above are just conveniences):

GET /api/setproperty?target=7a2b…&property=BackgroundInputName&value=Camera 2
GET /api/invokecommand?target=7a2b…&command=CrossfadeCommand

Subscribe to switcher changes over SSE (fires for every origin — API, Desktop, script, or the transition animation):

curl -N "http://localhost:44433/api/events/subscribe?topics=switcher"
event: switcher.modified
data: {"targetId":"7a2b…","targetName":"Main switcher","propertyName":"ForegroundInputName","value":"Camera 2"}

event: switcher.modified
data: {"targetId":"7a2b…","targetName":"Main switcher","propertyName":"BlendPercentage","value":62.5}

event: switcher.modified
data: {"targetId":"7a2b…","targetName":"Main switcher","propertyName":"Overlay1Active","value":true}

event: switcher.modified
data: {"targetId":"7a2b…","targetName":"Main switcher","propertyName":"Overlay1Opacity","value":100.0}

Subscribe over WebSocket (ws://localhost:8081/) — send, then receive PropertyChanged messages pre-filtered to switchers:

→ { "Type": "Subscribe", "Content": "Switcher" }
← { "Type": "System", "Content": "Switcher subscription confirmed" }
← { "Type": "PropertyChanged",
    "Content": "{\"ObjectId\":\"7a2b…\",\"ObjectName\":\"Main switcher\",\"PropertyName\":\"ForegroundInputName\",\"Value\":\"Camera 2\"}" }