Skip to content

Web Control API

Laika includes a full browser-based control interface served directly from the application. Open it in any browser:

http://<laika-host>:1928

The interface shows the current Laika version in the header. It works from any device on the same network — phone, tablet, laptop, or another computer — with no additional software required. In headless / server mode deployments this is your primary control surface.

The header also contains a Shutdown button, which gracefully stops a headless Laika instance from the browser.

The Layout tab shows your current launchpad and viewer assignments:

  • Assign any discovered NDI® source to any viewer slot using the dropdowns
  • Switch between saved launchpads with a single click
  • Enable or disable individual launchpads using their toggle switch
  • Rename launchpads inline
  • Toggle NDI® and DeckLink output per launchpad
  • Control Solo Mode and send audio to outputs

The Router tab provides a full crosspoint matrix identical to the in-app router:

  • View all virtual outputs as columns and all NDI® sources as rows
  • Click any cell to assign a route
  • Connection count badges show how many receivers are connected to each output
  • Routes persist and are shown as they are stored

The Preferences tab exposes the main application settings from the browser:

  • Bandwidth mode and latency settings
  • Viewer info display and display mode
  • Audio meter and monitoring settings
  • Output configuration

Changes apply to the running Laika instance immediately.

The PTZ tab provides pan, tilt, and zoom camera control:

  • Select which viewer’s source to control
  • Directional pad for pan/tilt
  • Zoom in/out controls
  • Recall and store preset positions

The Captions tab lets you view and set UMD captions for all sources:

  • Lists all sources currently assigned to viewers
  • Edit caption expressions inline
  • Changes apply immediately to the live display and output burns

The Discovery tab replaces the in-app Discovery Server Config dialog:

  • Add or remove NDI® discovery server addresses
  • Configure NDI® groups (All / Public / User Defined)
  • Changes write directly to the NDI® configuration file

The Licensing tab provides license management from the browser:

  • View current license status
  • Activate or deactivate a license key
  • Useful for headless deployments where there is no GUI

Laika’s REST API enables programmatic control. Enable it via Settings > API > Enable Web API. Default port is 1928.

All responses include Access-Control-Allow-Origin: * for browser access.

Base URL: http://<laika-host>:1928

Download OpenAPI Spec (YAML) — import into Postman, Insomnia, or any OpenAPI-compatible tool.

Get the current application state.

GET /api/status

Response:

{
"layout": "2x2",
"launchpad": "Studio A",
"viewer_count": 4,
"projected_viewer": null,
"version": "1.0.57"
}
FieldTypeDescription
layoutstringCurrent layout name
launchpadstringActive launchpad name (empty if none)
viewer_countintegerNumber of viewer slots in the current layout
projected_viewerinteger or nullIndex of the viewer currently projected fullscreen, or null if not projecting
versionstringApplication version

List all NDI® sources currently visible on the network.

GET /api/sources

Response:

{
"sources": [
{
"name": "CAMERA-1 (Studio)",
"url": "ndi://192.168.1.10/CAMERA-1"
},
{
"name": "GRAPHICS (Control Room)",
"url": "ndi://192.168.1.20/GRAPHICS"
}
]
}
FieldTypeDescription
namestringNDI® source display name
urlstringNDI® source URL
GET /api/viewers

Response:

{
"viewers": [
{
"index": 0,
"source": "CAMERA-1 (Studio)",
"connected": true,
"tally": "program",
"caption": "{source} LIVE",
"ptz_supported": true,
"resolution": "1920x1080",
"fps": 59.94
},
{
"index": 1,
"source": null,
"connected": false,
"tally": "none",
"caption": null,
"ptz_supported": false,
"resolution": null,
"fps": null
}
]
}
FieldTypeDescription
indexintegerZero-based viewer slot index
sourcestring or nullAssigned source name, or null if empty
connectedbooleanWhether the viewer is actively receiving frames
tallystringTally state: "program", "preview", or "none"
captionstring or nullCaption expression for this viewer, or null if not set
ptz_supportedbooleanWhether the assigned source supports PTZ control
resolutionstring or nullCurrent frame resolution (e.g. "1920x1080"), or null if not connected
fpsnumber or nullCurrent frame rate, or null if not connected
POST /api/viewers/{index}

Request body:

{
"source": "CAMERA-1 (Studio)"
}

The source value must match a name from /api/sources.

Responses:

StatusBodyWhen
200{ "ok": true }Source assigned
400{ "ok": false, "error": "Viewer index out of range" }Invalid index or missing source
404{ "ok": false, "error": "Source not found" }Source name not found on network
DELETE /api/viewers/{index}

Responses:

StatusBodyWhen
200{ "ok": true }Viewer cleared
400{ "ok": false, "error": "..." }Invalid viewer index

Switch to a different layout.

POST /api/layout

Request body:

{
"name": "2x2"
}

The name can be a built-in layout or a custom layout name.

Responses:

StatusBodyWhen
200{ "ok": true }Layout switched
404{ "ok": false, "error": "Layout not found" }Layout name not found
GET /api/launchpads

Response:

{
"launchpads": [
{
"name": "Studio A",
"layout": "2x2",
"active": true
},
{
"name": "Studio B",
"layout": "1+5",
"active": false
}
]
}
FieldTypeDescription
namestringLaunchpad preset name
layoutstringLayout used by this launchpad
activebooleanWhether this is the currently active launchpad
POST /api/launchpad

Request body:

{
"name": "Studio B"
}

Responses:

StatusBodyWhen
200{ "ok": true }Launchpad activated
404{ "ok": false, "error": "Launchpad not found" }Launchpad name not found

List all available layouts, including both built-in presets and custom layouts.

GET /api/layouts

Response:

{
"layouts": [
{
"name": "2x2",
"viewers": 4,
"custom": false
},
{
"name": "My Custom Layout",
"viewers": 6,
"custom": true
}
]
}
FieldTypeDescription
namestringLayout name
viewersintegerNumber of viewer slots in this layout
custombooleantrue for user-created layouts, false for built-in presets

Set or clear a caption overlay on a specific viewer. Captions support expression variables that are evaluated in real time.

POST /api/viewers/{index}/caption

Request body (set a caption):

{
"caption": "{source} LIVE"
}

Request body (clear a caption):

{
"caption": null
}

Expression variables:

VariableDescription
{source}NDI® source name assigned to the viewer
{viewer}Viewer number (1-based)
{time}Current time (HH:MM:SS)
{date}Current date (DD/MM/YYYY)
{tally}Tally state: PROGRAM, PREVIEW, or empty when off
{fps}Source frame rate (1 decimal place, e.g. 25.0)
{res}Source resolution (e.g. 1920x1080)

Override the bandwidth mode for a specific viewer independently of the global preference.

POST /api/viewers/{index}/bandwidth

Query parameter:

ParameterValuesDescription
modeHighest, Low, LowestBandwidth tier for this viewer

Example:

POST /api/viewers/2/bandwidth?mode=Highest

Responses:

StatusBodyWhen
200{ "ok": true }Bandwidth mode set
400{ "ok": false, "error": "..." }Invalid viewer index or mode
POST /api/launchpad/{name}/enabled

Query parameter:

ParameterValuesDescription
enabledtrue, falseEnable or disable the launchpad

Responses:

StatusBodyWhen
200{ "ok": true }State updated
404{ "ok": false, "error": "Launchpad not found" }Launchpad not found

Enable or disable NDI® output for a launchpad

Section titled “Enable or disable NDI® output for a launchpad”
POST /api/launchpad/{name}/ndi-output

Query parameter: enabled=true|false

Section titled “Enable or disable DeckLink output for a launchpad”
POST /api/launchpad/{name}/decklink-output

Query parameter: enabled=true|false

Shuts down Laika cleanly — stops all outputs, releases DeckLink devices, saves configuration, then exits.

POST /api/shutdown

Responses:

StatusBodyWhen
200{ "ok": true }Shutdown initiated

Responses:

StatusBodyWhen
200{ "ok": true }Caption updated
400{ "ok": false, "error": "Viewer index out of range" }Invalid viewer index

Project a single viewer fullscreen, or exit back to the multiviewer layout. The currently projected viewer is also reported in the projected_viewer field of the /api/status response.

POST /api/project/{index}

Responses:

StatusBodyWhen
200{ "ok": true }Viewer projected
400{ "ok": false, "error": "Viewer index out of range" }Invalid viewer index
DELETE /api/project

Responses:

StatusBodyWhen
200{ "ok": true }Returned to multiviewer layout

Get or set the NDI® output state. When enabled, Laika sends the multiviewer as an NDI® source that other receivers on the network can pick up.

GET /api/output

Response:

{
"enabled": true,
"fps": 60,
"name": "LAIKA"
}
FieldTypeDescription
enabledbooleanWhether NDI® output is active
fpsintegerOutput frame rate
namestringNDI® source name advertised on the network
POST /api/output

Request body:

{
"enabled": true
}

Responses:

StatusBodyWhen
200{ "ok": true }Output state updated

Send PTZ (Pan/Tilt/Zoom) commands to NDI® sources that support camera control. Check the ptz_supported field in the /api/viewers response to determine which viewers accept PTZ commands.

POST /api/ptz

Request body:

{
"viewer": 0,
"command": "pan_tilt_speed",
"pan_speed": 0.5,
"tilt_speed": 0.0
}
FieldTypeDescription
viewerintegerZero-based viewer slot index
commandstringPTZ command name (see table below)

Commands and parameters:

CommandParametersDescription
pan_tilt_speedpan_speed (float), tilt_speed (float)Continuous pan/tilt at the given speed. Range -1.0 to 1.0. Send 0.0 to stop.
pan_tiltpan (float), tilt (float)Move to an absolute pan/tilt position
zoom_speedzoom_speed (float)Continuous zoom at the given speed. Range -1.0 to 1.0. Send 0.0 to stop.
zoomzoom (float)Move to an absolute zoom position
recall_presetindex (integer), speed (float, optional)Recall a stored camera preset. Speed defaults to 1.0.
store_presetindex (integer)Store the current position as a preset
focus_auto(none)Trigger auto-focus
white_balance_auto(none)Trigger auto white balance

Responses:

StatusBodyWhen
200{ "ok": true }Command sent
400{ "ok": false, "error": "..." }Invalid viewer index, missing command, or unknown command

The router creates virtual NDI® sources that act as redirect endpoints. When a receiver connects to a routed source, it is transparently redirected to the target NDI® source with zero additional bandwidth on the sender side. This makes it possible to build flexible routing topologies without duplicating video streams.

GET /api/router

Response:

{
"routes": [
{
"index": 0,
"name": "Output 1",
"target": "CAMERA-1 (Studio)",
"connections": 2
},
{
"index": 1,
"name": "Output 2",
"target": null,
"connections": 0
}
]
}
FieldTypeDescription
indexintegerZero-based route index
namestringRoute display name (advertised as an NDI® source)
targetstring or nullCurrently routed NDI® source name, or null if unrouted
connectionsintegerNumber of NDI® receivers currently connected to this route
POST /api/router

Request body:

{
"name": "Output 1"
}

Responses:

StatusBodyWhen
200{ "ok": true, "index": 0 }Route created (index returned)
500{ "ok": false, "error": "..." }Creation failed
DELETE /api/router/{index}

Responses:

StatusBodyWhen
200{ "ok": true }Route deleted
400{ "ok": false, "error": "..." }Invalid route index

Point a route at an NDI® source. Receivers connecting to the route will be transparently redirected to this source.

POST /api/router/{index}/route

Request body:

{
"source": "CAMERA-1 (Studio)"
}

The source value must match a name from /api/sources.

Responses:

StatusBodyWhen
200{ "ok": true }Route target set
400{ "ok": false, "error": "..." }Invalid route index
404{ "ok": false, "error": "Source not found" }Source name not found on network

Remove the target from a route. Connected receivers will lose their stream.

DELETE /api/router/{index}/route

Responses:

StatusBodyWhen
200{ "ok": true }Route target cleared
400{ "ok": false, "error": "..." }Invalid route index

All error responses follow the same shape:

{
"ok": false,
"error": "Human-readable error message"
}
Terminal window
# Check what sources are available
curl http://localhost:1928/api/sources
# Assign CAMERA-2 to viewer slot 0
curl -X POST http://localhost:1928/api/viewers/0 \
-H "Content-Type: application/json" \
-d '{"source": "CAMERA-2 (Studio)"}'
# Switch to a 4-up layout
curl -X POST http://localhost:1928/api/layout \
-H "Content-Type: application/json" \
-d '{"name": "2x2"}'
# Load the Studio B launchpad
curl -X POST http://localhost:1928/api/launchpad \
-H "Content-Type: application/json" \
-d '{"name": "Studio B"}'
# Set a caption on viewer 0
curl -X POST http://localhost:1928/api/viewers/0/caption \
-H "Content-Type: application/json" \
-d '{"caption": "{source} LIVE"}'
# Clear a caption
curl -X POST http://localhost:1928/api/viewers/0/caption \
-H "Content-Type: application/json" \
-d '{"caption": null}'
# Project viewer 0 fullscreen
curl -X POST http://localhost:1928/api/project/0
# Exit projection
curl -X DELETE http://localhost:1928/api/project
# Pan a PTZ camera to the right
curl -X POST http://localhost:1928/api/ptz \
-H "Content-Type: application/json" \
-d '{"viewer": 0, "command": "pan_tilt_speed", "pan_speed": 0.5, "tilt_speed": 0.0}'
# Stop PTZ movement
curl -X POST http://localhost:1928/api/ptz \
-H "Content-Type: application/json" \
-d '{"viewer": 0, "command": "pan_tilt_speed", "pan_speed": 0.0, "tilt_speed": 0.0}'
# Recall PTZ preset 1
curl -X POST http://localhost:1928/api/ptz \
-H "Content-Type: application/json" \
-d '{"viewer": 0, "command": "recall_preset", "index": 1}'
# Create a router output
curl -X POST http://localhost:1928/api/router \
-H "Content-Type: application/json" \
-d '{"name": "Output 1"}'
# Route a source to the output
curl -X POST http://localhost:1928/api/router/0/route \
-H "Content-Type: application/json" \
-d '{"source": "CAMERA-1 (Studio)"}'
# Clear a route target
curl -X DELETE http://localhost:1928/api/router/0/route
# Delete a route
curl -X DELETE http://localhost:1928/api/router/0