Skip to content

Client API ​

The Lumora Controller desktop app runs a lightweight HTTP server on your local machine that local tools (Stream Deck plugin, Companion, scripts) can call to trigger hotkey-equivalent actions and query app state. No API key required.

Server API

Looking for the cloud-side API for triggering specific graphics by ID from anywhere on the internet? See the Server API.

When to use which ​

Use thisIf you want to…
Server APITrigger a specific graphic by ID from anywhere on the internet, regardless of what the operator has selected. Authenticated, internet-exposed.
Client APIDrive whatever the operator currently has selected. Same outcome as pressing a hotkey. Local-only, no auth.

The Client API is the right fit for hardware controllers (Stream Deck, foot pedals, deck builders) that sit on the same machine as the controller and act as remote hotkeys. Pressing a Stream Deck button feels exactly like pressing Numpad +.

Network access ​

The controller binds the API server to 127.0.0.1 (loopback) by default, so only processes running on the same machine can reach it:

text
http://127.0.0.1:<port>
  • No authentication by default. Matches Companion / vMix / OBS-WebSocket norms for broadcast-tool local APIs. See Enhanced security below for the opt-in token gate.
  • HTTP, not HTTPS. Loopback-only, plain HTTP.
  • Loopback by default. Stream Deck and Companion run locally and connect via 127.0.0.1. Cross-device access (e.g. a Stream Deck on a separate box) is not supported in the current release.
  • No browser callers. Any request carrying an Origin header (which browsers add to every cross-site request) is refused with 403 Forbidden, and so is a request whose Host isn't 127.0.0.1:<port> or localhost:<port>. Native tools (Companion, Stream Deck, curl, scripts) send neither and are unaffected. The API sends no CORS headers, so a web page can't read its responses either. The one exception: with Enhanced security on, a browser request that presents the token is accepted. It has to be a simple request carrying the token as ?key=<token> (an Authorization header makes the browser send a preflight first, which is refused), and the page still can't read the response.

Security model

By default the Client API is unauthenticated. The loopback bind ensures only local processes can reach it, and browser requests are refused so a website you visit can't trigger take / clear / panic. Do not expose the port to the network or the internet. Any program running on the controller machine can still call it — enable Enhanced security if that matters on your setup.

Port configuration ​

The default port is 3031. To change it, open the controller and go to Settings → Client Configuration → Client API.

  • Port range: 1024–65535
  • Default: 3031
  • Changing the port restarts the local API server immediately. Any in-flight Stream Deck / Companion bindings need updating to the new port.

Enhanced security ​

A vMix-style opt-in token gate. Off by default. When enabled, every request must present a shared token, so only tools you've given the token to can drive the controller, not just any program on the machine.

Enabling ​

Settings → Client Configuration → Client API → tick Enhanced security. The controller mints a 256-bit token (43-char base64url) once per install and shows it in the same panel, where you can Reveal, Copy, or Regenerate it. Paste the token into your Stream Deck plugin / Companion module config.

Presenting the token ​

Either form is accepted, header takes precedence:

text
Authorization: Bearer <token>
text
http://127.0.0.1:<port>/v1/take/on?key=<token>

Requests without a valid token return 401 Unauthorized.

Regenerating ​

Click Regenerate in the Client Configuration panel. The previous token is invalidated immediately. Update any Stream Deck / Companion bindings before regenerating, or you'll lock them out until they're re-pasted.

API versioning ​

The Client API uses path versioning. All canonical endpoints are prefixed with /v1/:

text
http://127.0.0.1:<port>/v1/<endpoint>

Un-versioned paths (e.g. /take/on) continue to work and return the same response, but include Deprecation: true and Link: </v1/...>; rel="successor-version" headers per RFC 9745. Update your integrations to the /v1/ paths at your convenience. No removal date is set.

Endpoints ​

All endpoints are rooted at http://127.0.0.1:<port>/v1. None of them take path or query parameters. They all act on the operator's currently-selected graphic and active channel.

GET Requests ​

EndpointDescription
/v1/graphic/nameName of the currently selected graphic as a JSON string, or null if nothing is selected
/v1/channel/numberActive output channel number, which is the number in its channel ID (cg2 gives 2)
/v1/channel/nameActive output channel name as a JSON string

POST Requests ​

EndpointDescriptionHotkey equivalent
/v1/take/onTake the selected graphic on. Fires on every channel in its lock setNumpad Enter
/v1/take/offTake the selected graphic off. Clears it from every channel it's live on (no advance)Numpad − (without advance)
/v1/take/on/nextTake on, then advance selection to the next graphicNumpad +
/v1/take/off/nextTake off, then advance selection to the next graphicNumpad −
/v1/clearClear every graphic from the active output channel(none)
/v1/panicInstantly clear all graphics from all channelsEsc Esc
/v1/refresh-previewReload the graphic preview iframeF8

See Hotkeys for the full keyboard reference.

Responses ​

POST requests ​

json
{ "ok": true }

GET requests ​

GET endpoints return raw JSON values, not wrapped objects:

json
"Lower Third"
json
3
json
null

The null value is returned when nothing is selected (/v1/graphic/name) or when no channel is active.

Status codes ​

CodeMeaning
200Success
401Enhanced security is on and the request didn't present the token
403The request came from a browser (Origin header) or used a host name other than 127.0.0.1 / localhost. See Network access
404Endpoint not found (typo in path)
500Controller error. Check the controller log

There's no 409 because POST actions are always accepted and behave as no-ops if there's nothing to act on (e.g. /v1/take/on with no graphic selected).

Examples ​

Read the active channel name ​

bash
curl http://127.0.0.1:3031/v1/channel/name

Take the selected graphic on-air ​

bash
curl -X POST http://127.0.0.1:3031/v1/take/on

Take on and advance (Numpad + equivalent) ​

bash
curl -X POST http://127.0.0.1:3031/v1/take/on/next

Panic, clear everything ​

bash
curl -X POST http://127.0.0.1:3031/v1/panic

Reload the preview ​

bash
curl -X POST http://127.0.0.1:3031/v1/refresh-preview

Common integrations ​

The Client API has no Lumora-specific plugins. All of these drive the endpoints above with each tool's generic HTTP action.

  • Stream Deck. Elgato's built-in HTTP Request action fires a request per button press. The Client API is particularly well-suited here: one button per hotkey-equivalent action, with no need to track graphic IDs as the show evolves.
  • Bitfocus Companion. Point Companion at http://127.0.0.1:<port> and use generic HTTP actions for each button.
  • Foot pedals / hardware controllers. Anything that can fire an HTTP request on press works. The "no auth" model is a feature here: hardware integrations can be set up in seconds.
  • Custom scripts. Useful for show-day automation that needs to read state (GET /v1/graphic/name) and react.

Troubleshooting ​

Connection refused. The controller isn't running, or it's running but the API server is bound to a different port. Open the controller and check Settings → Client Configuration → Client API for the actual port.

401 Unauthorized on every request. Enhanced security is enabled and your request isn't presenting the token. Either include Authorization: Bearer <token> or append ?key=<token> to the URL. The token is shown in Settings → Client Configuration → Client API.

403 Forbidden. The request carried an Origin header (it came from a web page or a browser-based plugin) or a Host other than 127.0.0.1:<port> / localhost:<port>. Call the API from a native tool, or turn on Enhanced security and present the token as ?key=<token>. Some older Stream Deck plugins run inside an embedded browser and hit this; Companion and current Stream Deck plugins don't.

Returns 404 on a known endpoint. Check for trailing slashes: POST /v1/take/on/ with a trailing slash may differ from POST /v1/take/on. Also verify you're using the /v1/ prefix.

Port in use. Another application on the controller machine is already bound to the same port (common conflicts: 3000, 8080, 8000). Pick a port in the range 1024–65535 that nothing else uses. The controller will refuse to start with a clear error if the port is taken.