Skip to content

Server API

Lumora exposes an HTTP API for triggering graphics from external tools. Stream Deck via Bitfocus Companion, automation scripts, vision-mixer macros, anything that can fire an HTTP request. Requests are authenticated with a long-lived API key minted from the control panel.

Client API

If you're running the Lumora Controller application on your Windows PC, you also have access to the Client API, documented here.

Authentication

Every /api/* endpoint requires an API key on the standard Authorization header:

http
Authorization: Bearer lumora_live_sk_<43 chars>

Keys are long-lived workspace credentials. Stream Deck, Companion, and CI scripts store one once and reuse it indefinitely.

Minting a key

  1. In the control panel, open SettingsAPI Keys.
  2. Give it a label (e.g. "Stream Deck") then click Create key
  3. The raw key is shown once. Copy it into your automation tool immediately, as you can't retrieve it again.

API keys are available on Standard, Professional, and Enterprise plans. The Free plan does not include API access. Paid plans can mint as many keys as needed.

Key format

PartLengthDetail
Prefix15 charslumora_live_sk_
Body43 charsBase64URL random
Total58 chars256 bits of entropy

Scope and safety

  • Keys can call any /api/* endpoint within the workspace they were minted in.
  • Keys only work against their own workspace. Using a key from one workspace against a different workspace's URL returns 403.
  • Keys cannot mint or revoke other keys. Only a signed-in human operator can manage keys via the modal.
  • A leaked key's blast radius is bounded to /api/* and can be cut off with one click from SettingsAPI KeysRevoke.
  • Treat keys like passwords: don't commit them to git, don't paste them in screenshots, don't share them between workspaces.

Endpoints

All endpoints are rooted at your workspace's origin, e.g. https://your-workspace.app.lumora.live.

How graphics route to channels

Trigger endpoints don't take a channel parameter. Each graphic carries a channel lock — the set of output channels it's assigned to — and the trigger fires on those channels automatically. Set the lock from the rundown's right-click Channel ▸ submenu, or via the matching control in the control panel. Status endpoints still take a channelId because they ask a per-channel question ("what's live on cg1?").

GET Requests

EndpointDescription
/api/v1/trigger/graphicId/statusPer-graphic status. Returns: { "live": true/false, "liveOn": ["cg1", ...] }
/api/v1/status/channelIdList all graphics currently live on a channel. Returns: { "live": [...] }
/api/v1/status/channelId/graphicIdCheck if a specific graphic is currently live on a channel. Returns: { "live": true/false }
/api/v1/tally/channelId/pgmReturns true if the channel is on program, false if not
/api/v1/tally/channelId/pvwReturns true if the channel is on preview, false if not

POST Requests

EndpointDescription
/api/v1/trigger/graphicId/onTake a graphic live. Fires on every channel in its lock set that is compatible with the graphic's resolution / framerate. Returns: { "ok": true, "dispatched": ["cg1", ...], "skipped": [...] }
/api/v1/trigger/graphicId/offTake a graphic off air on every channel it's currently live on. Returns: { "ok": true, "dispatched": ["cg1", ...] }
/api/v1/trigger/graphicId/action/actionNameFire a named action on a live graphic (e.g. Advance, Reset). The graphic must be live somewhere.
/api/v1/refresh-allRefresh all data sources (Google Sheets, etc.). Equivalent to pressing F9 in the control panel.

Push live JSON

For pushing live JSON payloads (esports stats, score tickers, anything that updates faster than a polling cadence allows), see the Data API. Same authentication, sibling versioned namespace at /api/v1/data/<slug>.

Parameters

NameWhere to find it
graphicIdUnique graphic instance ID. Right-click the graphic in the rundown → Copy Graphic ID.
channelIdOutput channel ID, e.g. cg1. Found in OutputsConfigure Outputs. Used by /status/... and /tally/... only — trigger endpoints don't take a channelId.
actionNameAction name as defined in the template, e.g. Advance. Case-sensitive.

Responses

Success

POST /trigger/{graphicId}/on returns the list of channels it actually fired on (dispatched) plus anything it had to skip (incompatible channel, channel no longer exists):

json
{ "ok": true, "dispatched": ["cg1", "cg2"], "skipped": [] }

POST /trigger/{graphicId}/off returns the channels it cleared:

json
{ "ok": true, "dispatched": ["cg1", "cg2"] }

POST /refresh-all and POST /trigger/{graphicId}/action/{actionName} return:

json
{ "ok": true }

GET /trigger/{graphicId}/status returns:

json
{ "live": true, "liveOn": ["cg1", "cg2"] }

Error

json
{ "error": "No channels assigned to this graphic.", "reason": "no_channels_assigned" }

Status codes

CodeMeaning
200Success
401Missing or invalid token
403Workspace URL doesn't match API key
404Channel, graphic, action, or template not found
409Trigger conflict. reason: "no_channels_assigned" (graphic has no channels in its lock set), reason: "locked_channel_missing" (every channel in the lock set has been deleted from the workspace), or "Graphic is not live" on action endpoints.

Examples

Read channel status

bash
curl -H "Authorization: Bearer lumora_live_sk_<your key>" \
  https://your-workspace.app.lumora.live/api/v1/status/cg1

Take a graphic on-air

bash
curl -X POST \
  -H "Authorization: Bearer lumora_live_sk_<your key>" \
  https://your-workspace.app.lumora.live/api/v1/trigger/<graphicId>/on

Take a graphic off air

bash
curl -X POST \
  -H "Authorization: Bearer lumora_live_sk_<your key>" \
  https://your-workspace.app.lumora.live/api/v1/trigger/<graphicId>/off

Fire a custom action

bash
curl -X POST \
  -H "Authorization: Bearer lumora_live_sk_<your key>" \
  https://your-workspace.app.lumora.live/api/v1/trigger/<graphicId>/action/Advance

Refresh all data sources

bash
curl -X POST \
  -H "Authorization: Bearer lumora_live_sk_<your key>" \
  https://your-workspace.app.lumora.live/api/v1/refresh-all

Common integrations

There's no Lumora-specific plugin for any of these. They all drive the HTTP endpoints above directly. Configure each tool's generic HTTP action with your endpoint URL, set the method (GET or POST), and add the Authorization: Bearer ... header.

  • Stream Deck. Elgato's built-in HTTP Request action (or the Web Requests plugin on older firmware) fires a request per button press. One button per endpoint is the typical setup: a "take on" button and a "take off" button per graphic you control regularly. Each graphic carries its own channel lock, so the same button always fires it on the same channels — no per-channel button strip needed.
  • Bitfocus Companion. Drop these endpoints into Companion's generic HTTP action; the Authorization header is set per-button or globally in connection config.
  • OBS / vMix macros. Most switchers can fire an HTTP POST as part of a macro; use the trigger endpoints with the relevant graphicId.
  • Custom scripts. Any language with an HTTP client works. The endpoints are stateless, so retry logic is straightforward.

Rate limits

API requests are rate-limited per workspace across two dimensions: a 60-second burst ceiling and a rolling 24-hour daily ceiling. Both apply on every request; whichever runs out first triggers a 429 Too Many Requests with a Retry-After header.

PlanBurst (per minute)Daily
Standard10010,000
Professional30030,000
EnterpriseSet per account, contact usSet per account, contact us

Stream Deck / Companion typical usage (a handful of triggers per minute) is well within these limits. They exist to protect the workspace from runaway scripts.

Every response carries the standard RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset headers so you can pace yourself without waiting for a 429.

Troubleshooting

401 Unauthorized. The Authorization header is missing, malformed, or the key has been revoked. Re-mint a key and update your automation tool. Check for accidental whitespace before/after the key when pasting.

404 Not Found. Usually the wrong graphicId or channelId. The graphic ID is shown via the rundown right-click → Copy Graphic ID. The channel ID is the short ID (e.g. cg1), not the channel name — find it in OutputsConfigure Outputs.

409 Conflict with reason: "no_channels_assigned". The graphic has no channels in its lock set. Right-click the graphic in the rundown and pick a channel from the Channel ▸ submenu, then re-fire.

409 Conflict with reason: "locked_channel_missing". Every channel in the graphic's lock set has been deleted from the workspace. Either reassign the graphic to a current channel or restore one of the missing channels in OutputsConfigure Outputs.

409 Conflict on an action endpoint. The graphic isn't currently live anywhere. Custom actions can only fire while the graphic is on-air. Take it on first, then fire the action.

Triggers fire on some channels but skip others. The response's skipped array lists why — incompatible_channel (channel's resolution/framerate doesn't match the graphic's renderRequirements) or channel_missing (a locked channel was deleted but others survive).