Enable and Configure the Show-Control Protocol

Last updated 7 August 2026

Drive Backstage playback, audio, calibration, NDI input routing and node-graph endpoints from an external control system (Crestron, AMX, Q-SYS, or a custom application) using a structured JSON protocol over TCP or UDP. This is the standard remote control interface.

Looking for a different route? Backstage can be driven several ways. Choose the one that fits:

  • Show-control protocol (this page) -- structured JSON over TCP/UDP with typed domains, named addressing, and asynchronous live feedback. The standard interface, and the one to reach for when a machine drives the show.

  • Web Console -- the same control surface in a browser. Anything controllable over this protocol can be put on a page Backstage serves, using blocks that mirror these domains. Best when a person rather than a control system drives the show. See Build and Serve Web Pages with the Web Console.

  • Automation API and MCP -- HTTP, for scripts, integration tools and AI agents. Reaches work this protocol does not, such as importing media and building surfaces. See Drive Backstage Headlessly with the Automation API.

  • OSC -- built as a node chain in the Node editor, for systems that already speak OSC. See Control Backstage via OSC.

  • UDP trigger strings -- a custom command vocabulary you build yourself, via the StepperCommander patch. See Trigger Playback via UDP.


Before you start

  • Backstage is running with at least one sequence, stepper, or other entity you want to control
  • You know the IP address of the Backstage server on your control network
  • Your control system can send TCP or UDP messages (plain JSON text, newline-terminated)
  • If using the reference control-panel app:.NET 10 SDK installed on a Windows machine

Steps

1. Turn the protocol on

Open Settings and expand Remote Control. Check Enable Show-Control Protocol.

The protocol is disabled by default. Until you enable it, Backstage refuses connections — this is the first thing to check when a controller cannot reach the server.

TCP starts immediately on port 7400, on all interfaces. UDP is off by default; enable it and confirm its port (default 7401) only if you need it.

2. Pick the transports you need

TCP is the right default: it is reliable, and live feedback arrives on the same connection you already have open.

UDP suits fire-and-forget commands on a dedicated control LAN. Because it is connectionless, it also needs a decision about where replies go — the UDP feedback dropdown either answers whichever address sent the datagram, or sends everything to one fixed host and port.

3. Restrict who can reach it

Three independent layers, and it is worth setting them deliberately rather than relying on any one:

Layer

What it decides

Bind interface

Which network the protocol is reachable on at all. Set it to 127.0.0.1 to allow nothing but the machine itself

IP allowlist

Who may connect — comma-separated addresses or CIDR ranges, e.g. 10.0.0.5, 192.168.1.0/24. Empty means anyone who can reach the interface

Auth token

Who may issue commands. See Authenticating below for the exchange

UDP is connectionless and its source address can be spoofed, so the allowlist is weaker protection there than on TCP. Run UDP only on a trusted control LAN.

4. Apply the changes

Port, bind interface and transport changes need Apply / Restart Protocol Server. The auth token and IP allowlist are read live and take effect on the next connection or datagram — no restart.

5. Verify it is listening

From the control machine, open a raw TCP connection to port 7400 with a tool such as ncat, paste a line and press Enter:

{"domain":"sys","action":"ping"}

A healthy server answers:

{"ok":true,"result":{"pong":true}}

A ping works whether or not a token is set, so it is a clean test of reachability on its own.


Authenticating

Leave Auth token empty and every client that gets past the allowlist may issue commands. Set it, and a client must present the token before Backstage will accept anything else.

Send the token as the first message on the connection:

{"domain":"sys","action":"auth","args":{"token":"your-token"}}

Success returns an empty result. A wrong or missing token returns 401 with the message authentication required (sys.auth). The token may also be passed as the message's target instead of in args, for controllers whose templates cannot nest an object.

What works before authenticating

Four sys actions are exempt from the gate, so a client can identify itself and check the server is alive before holding a credential:

Action

Why it is exempt

sys.ping

Reachability check

sys.version

Protocol version query, so a client can decide whether to proceed at all

sys.auth

The gate itself — necessarily reachable

sys.client

Optional self-identification, which happens before the client is trusted

Everything else — including list and status — is refused until the token is accepted. Reading state is treated as privileged, not public.

Authentication is per connection

The authenticated flag lives on the connection, not on the client or its address. Every new TCP connection starts unauthenticated and must send sys.auth again — so a controller that reconnects after a dropped link has to re-authenticate as part of its reconnect logic, not just resume sending commands.

Backstage logs the commands it executes so an operator can see what was triggered, but never logs the token, and does not log auth, client, ping, version, list, status, subscribe or unsubscribe at all — the read-only and plumbing traffic that would otherwise flood the log.

A second, separate gate: Companion

A client that identifies itself as Companion through sys.client is also checked against the Enable Bitfocus Companion setting. If that is off, it is refused with 403 — a different code from the auth failure, and a different exemption list: only ping and version still work.

This is a convenience switch for turning Companion off without disturbing other clients. It is not a security boundary — the token and the allowlist are. A client that simply does not identify itself as Companion is unaffected by it.


Message conventions

These apply to every domain, which is why they live here rather than on the element pages.

Framing

The wire format is newline-delimited JSON — exactly one JSON object per line, each terminated with a newline. A message without its terminator is buffered and never processed, which is the single most common integration fault.

A request carries a domain, an action, usually a target, and per-action args. An optional id — any JSON value — is echoed back untouched, which is how an asynchronous client correlates replies when several messages are in flight.

Responses

{"id":17,"ok":true,"result":{ }}
{"id":17,"ok":false,"error":{"code":404,"message":"stepper not found"}}

Code

Meaning

400

Bad request, unknown action, or invalid JSON

401

Authentication required, or the token was wrong

403

The client identified as Companion and Enable Bitfocus Companion is off

404

Unknown domain, or no entity with that name

A control action answers {"accepted": true}, which means only that Backstage queued it. The work happens asynchronously. Confirm real state through a status read or a subscription — never by assuming the command you sent took effect.

Addressing

Every entity is addressed by the exact name you see in Backstage, case-sensitive. There are no ids or indices to track, and no stable handle behind the name — so renaming an entity silently breaks whatever pointed at it. Each domain's list action returns the current names.

Live feedback

Subscribe once per connection and Backstage pushes status instead of you polling for it:

{"domain":"sys","action":"subscribe","args":{"domains":["seq","step"]}}

Omit domains to subscribe to everything. You then get an immediate snapshot, an event whenever a subscribed entity changes, and a heartbeat roughly every 3 seconds that re-sends current status — so a dropped frame heals itself within a few seconds and you do not need a polling loop of your own. Feedback rate (Hz) controls how often the server looks for changes to push.


Options explained

Settings > Remote Control

Setting

Description

Values / Range

Enable Show-Control Protocol

Master on/off for the protocol

Checkbox (off by default)

Bind interface

Network interface the protocol listens on

Dropdown: Any (0.0.0.0) or a specific NIC

TCP

Enable or disable the TCP transport

Checkbox (on by default when the protocol is enabled)

TCP Port

Port for the TCP server

1–65535 (default 7400)

UDP

Enable or disable the UDP transport

Checkbox (off by default)

UDP Port

Port for UDP receive

1–65535 (default 7401)

UDP feedback

Where UDP responses and events are sent

Dropdown: Reply to sender / Fixed host:port

Feedback host

Address for fixed-target UDP feedback (shown when "Fixed host:port" is selected)

IP address

Feedback port

Port for fixed-target UDP feedback

1–65535 (default 7402)

CRLF line endings

Terminate responses and events with CRLF instead of LF

Checkbox (on by default)

Auth token

Require clients to send sys.auth before issuing commands. Read live, so a change applies to the next connection or datagram

Text (empty = no auth)

IP allowlist

Restrict which addresses or CIDR ranges may connect. Read live

Comma-separated (empty = allow any)

Feedback rate (Hz)

How often the server checks for and pushes status changes

1–60 Hz (default 10)

Enable Bitfocus Companion

Allows clients that identify themselves as Companion. A convenience switch for gating Companion specifically, not a security boundary — the token and allowlist are. Read live

Checkbox (on by default)

Choosing a port

7400 and 7401 were picked to stay clear of NDI, Dante, ST 2110, Q-SYS, Crestron and AMX. One known clash remains: DDS/RTPS middleware also uses the 7400 range, so change the port if your site runs it.


The reference control panel

A standalone Backstage Control Panel ships in tools/BackstageControlPanel/ — a C# WPF client that speaks the whole protocol. It exists for testing and as a reference implementation for integrators rather than for production operation.

Enter Host, Port and Token in its connection bar and click Connect; it reconnects on its own if the link drops. It has a tab per entity type, each listing live status with control buttons, and a Protocol JSON log at the bottom. That log is the fastest way to debug an integration: pause it, reproduce the problem, and read the exact JSON exchanged.


Common Mistakes

  • Nothing connects at all — the protocol is off by default. Enable it in Settings, then check the bind interface, the allowlist and the token in that order.

  • Forgetting the newline — every message needs its terminator. Without it the server holds a partial line forever and stays silent.

  • Port or bind change appears to do nothing — those need Apply / Restart Protocol Server. Only the token and allowlist apply live.

  • 401 on a list or status call — reading is privileged too. Only ping, version, auth and client work before authenticating.

  • Commands work, then stop after a reconnect — the authenticated flag is per connection. A reconnecting client must send sys.auth again.

  • 403 rather than 401 — that is the Companion gate, not the token. The client identified itself as Companion and Enable Bitfocus Companion is off.

  • Case mismatch on names — targets match exactly. Use a domain's list to read back the real spelling.

  • Treating accepted: true as done — it means queued. Subscribe or read status to confirm.

  • Running UDP on a shared network — it is spoofable. Keep it on a trusted control LAN, and prefer TCP for anything bidirectional.


Remote Control — what you can control, element by element, with domains and actions

Build and Serve Web Pages with the Web Console — the same control surface in a browser, for operators rather than control systems

Drive Backstage Headlessly with the Automation API — the Automation API and MCP, for scripts and AI agents

Control Backstage with the Bitfocus Companion Module — Bitfocus Companion, a ready-made client of this protocol