---
source: "https://docs.lumora.live/advanced/client-api"
url: /advanced/client-api.md
description: >-
  The Lumora Controller's local Client API: trigger hotkey-equivalent actions
  and query app state from local tools over HTTP, with no API key required.
---

# 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.

::: tip Server API
Looking for the cloud-side API for triggering specific graphics by ID from anywhere on the internet? See the [Server API](/advanced/server-api).
:::

## When to use which

| Use this | If you want to… |
|---|---|
| **Server API** | Trigger a specific graphic by ID from anywhere on the internet, regardless of what the operator has selected. Authenticated, internet-exposed. |
| **Client API** | Drive 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](#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](#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.

::: warning 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](#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](https://www.rfc-editor.org/rfc/rfc9745). 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

| Endpoint | Description |
|---|---|
| `/v1/graphic/name` | Name of the currently selected graphic as a JSON string, or `null` if nothing is selected |
| `/v1/channel/number` | Active output channel number, which is the number in its channel ID (`cg2` gives `2`) |
| `/v1/channel/name` | Active output channel name as a JSON string |

### `POST` Requests

| Endpoint | Description | Hotkey equivalent |
|---|---|---|
| `/v1/take/on` | Take the selected graphic **on**. Fires on every channel in its lock set | Numpad Enter |
| `/v1/take/off` | Take the selected graphic **off**. Clears it from every channel it's live on (no advance) | Numpad − (without advance) |
| `/v1/take/on/next` | Take **on**, then advance selection to the next graphic | Numpad + |
| `/v1/take/off/next` | Take **off**, then advance selection to the next graphic | Numpad − |
| `/v1/clear` | Clear every graphic from the active output channel | (none) |
| `/v1/panic` | Instantly clear all graphics from **all** channels | Esc Esc |
| `/v1/refresh-preview` | Reload the graphic preview iframe | F8 |

See [Hotkeys](/operating/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.&#x20;

### Status codes

| Code | Meaning |
|---|---|
| **200** | Success |
| **401** | [Enhanced security](#enhanced-security) is on and the request didn't present the token |
| **403** | The request came from a browser (`Origin` header) or used a host name other than `127.0.0.1` / `localhost`. See [Network access](#network-access) |
| **404** | Endpoint not found (typo in path) |
| **500** | Controller 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).&#x20;

## 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](#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](#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.&#x20;

**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.&#x20;

\*\[CG]: Character Generator. AKA graphics overlay engine.
\*\[OBS]: Open Broadcaster Software. Open-source streaming and recording tool.
\*\[SDK]: Software Development Kit.
\*\[API]: Application Programming Interface.
\*\[HTTP]: HyperText Transfer Protocol.
\*\[HTML]: HyperText Markup Language.
\*\[CSS]: Cascading Style Sheets.
\*\[JS]: JavaScript.
\*\[OGraf]: An EBU backed specification for HTML graphics.
\*\[EBU]: European Broadcasting Union.
\*\[NDI]: Network Device Interface. IP-based video transport protocol.
