PixDrive Studio accepts commands from external systems over two network interfaces:
- REST, a plain HTTP API. Use it from Stream Deck, Bitfocus Companion, Home Assistant, Node-RED, shell scripts, or anything that can send an HTTP request.
- OSC, an Open Sound Control receiver over UDP. Use it from lighting consoles, TouchOSC, QLab, Ableton, and other show-control tools.
Both interfaces control cues, playback, and the master brightness. They use the same engine path as the app’s own UI, so an external trigger behaves like a click in the app.
For AI/agent automation, PixDrive also ships an MCP server for LLM-driven control. The REST/OSC APIs on this page target show-control hardware and home-automation systems.
Cues
Every preset is a cue. There is nothing to configure. A cue is addressable two ways:
- by name: the preset’s name (stable, recommended), or
- by number: its 1-based position in the preset list.
A cue is one of two kinds:
- Look: a snapshot of one or more effects, created on the Control page.
- Show: a full sequencer timeline, created on the Sequencer page. Triggering a Show restarts it from the beginning and starts playback.
Enabling external control
External control is off by default and requires the full (licensed) version.
- Open Settings → External Control.
- Toggle HTTP/REST and/or OSC on.
- Optionally change the bind address, port, and (REST only) a bearer token.
Changes to enabled/port/bind take effect after you restart the app; the token applies without a restart.
Default endpoints
| Interface | Default bind | Default port | Base path |
|---|---|---|---|
| REST | 127.0.0.1 | 9848 | /api |
| OSC (UDP) | 127.0.0.1 | 9000 | — |
Both servers bind to 127.0.0.1 by default, so only the local machine can reach them. To trigger from another device on the network, set the bind address to 0.0.0.0 or a specific interface IP. See Security.
REST API
Base URL: http://<host>:9848
All responses are JSON. Most mutating endpoints accept both GET and POST so you can trigger them from tools that only send GET requests (e.g. a browser or a simple button). The exception is /api/brightness?value=<f>, which is POST-only (a GET to /api/brightness always reads the current value).
Endpoints
| Method | Path | Description |
|---|---|---|
GET | /api/cues | List all cues (number, label, kind). |
GET/POST | /api/cue/{id}/go | Trigger a cue by number or name. |
GET/POST | /api/playback/play | Start sequence playback. |
GET/POST | /api/playback/pause | Pause playback. |
GET/POST | /api/playback/stop | Stop and reset to the start. |
GET/POST | /api/playback/position?ms=<n> | Seek to a position (milliseconds). |
GET | /api/brightness | Read the master brightness (0.0–1.0). |
POST | /api/brightness?value=<f> | Set the master brightness (0.0–1.0). |
GET | /api/status | Current playback + brightness state. |
List cues
curl http://127.0.0.1:9848/api/cues
{
"cues": [
{ "number": "1", "label": "Colors", "kind": "look" },
{ "number": "2", "label": "Rainbow 2D", "kind": "look" },
{ "number": "3", "label": "Mexico", "kind": "look" },
{ "number": "4", "label": "Show", "kind": "show" }
]
}
To build a controller, read this list and trigger each cue by its number or label.
Trigger a cue
By number:
curl -X POST http://127.0.0.1:9848/api/cue/4/go
By name (URL-encode spaces):
curl -X POST "http://127.0.0.1:9848/api/cue/Show/go"
curl -X POST "http://127.0.0.1:9848/api/cue/Rainbow%202D/go"
{ "ok": true, "cue": "Show" }
Unknown cue → 404:
{ "ok": false, "error": "Cue '99' not found" }
Playback
curl -X POST http://127.0.0.1:9848/api/playback/play
curl -X POST http://127.0.0.1:9848/api/playback/pause
curl -X POST http://127.0.0.1:9848/api/playback/stop
curl -X POST "http://127.0.0.1:9848/api/playback/position?ms=5000"
Each returns { "ok": true } on success.
Master brightness
The global dimmer scales the entire output (all effects and the sequencer), 0.0–1.0. PixDrive clamps values to that range.
# read
curl http://127.0.0.1:9848/api/brightness
# → { "value": 1.0 }
# set to 50 %
curl -X POST "http://127.0.0.1:9848/api/brightness?value=0.5"
# → { "ok": true, "value": 0.5 }
Setting brightness over the API also moves the toolbar slider in the app.
Status
curl http://127.0.0.1:9848/api/status
{
"has_sequence": true,
"state": "Running",
"position_ms": 3430,
"duration_ms": 36000,
"active_segment_ids": ["10d55fa9-…"],
"loop_active": false,
"master_brightness": 1.0
}
state is one of Stopped, Running, Paused.
Security
- Local by default. Both servers bind to
127.0.0.1; only the local machine can reach them until you change the bind address. - Optional bearer token (REST). Set a token in Settings → External Control. Once set, every request must include it:
curl -H "Authorization: Bearer <your-token>" \ -X POST http://127.0.0.1:9848/api/cue/1/goRequests without a valid token get401. - Demo mode. The trial/demo version rejects all requests with
403; the servers run on the full version only. - OSC has no authentication (the protocol defines none). Restrict it via the bind address and run it on a trusted network/LAN only.
If you expose REST on the LAN (
bind = 0.0.0.0), set a bearer token.
OSC API
Send OSC messages over UDP to <host>:9000. OSC is fire-and-forget: no reply, no listing endpoint. If you need the cue list, use the REST GET /api/cues.
| Address | Argument | Description |
|---|---|---|
/pixdrive/cue/<id>/go | — | Trigger a cue by number/name. |
/pixdrive/playback/play | — | Start playback. |
/pixdrive/playback/pause | — | Pause playback. |
/pixdrive/playback/stop | — | Stop and reset. |
/pixdrive/playback/position | int/float (ms) | Seek to a position. |
/pixdrive/brightness | float (0.0–1.0) | Set master brightness. |
The cue <id> is part of the OSC address, e.g. /pixdrive/cue/4/go or /pixdrive/cue/Show/go.
Quick test from a terminal (Python, no dependencies):
import socket, struct
def osc(addr, *args):
# address, null-terminated + padded to 4 bytes
a = addr.encode() + b"\x00"
a += b"\x00" * ((4 - len(a) % 4) % 4)
if not args:
return a + b",\x00\x00\x00"
# single float argument
return a + b",f\x00\x00" + struct.pack(">f", float(args[0]))
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(osc("/pixdrive/cue/Show/go"), ("127.0.0.1", 9000))
s.sendto(osc("/pixdrive/brightness", 0.5), ("127.0.0.1", 9000))
Integration examples
Bitfocus Companion / Stream Deck: add a Generic HTTP action:
POST http://<pixdrive-host>:9848/api/cue/1/go
Add the Authorization: Bearer <token> header if you set a token. Because the endpoints also accept GET, an “Open URL” button works too.
Home Assistant: a REST command:
rest_command:
pixdrive_cue:
url: "http://<pixdrive-host>:9848/api/cue/{{ cue }}/go"
method: POST
headers:
Authorization: "Bearer !secret pixdrive_token"
# call it
service: rest_command.pixdrive_cue
data:
cue: "Show"
TouchOSC / lighting console: map a button to /pixdrive/cue/1/go and a fader (0.0–1.0) to /pixdrive/brightness.
Notes & limits
- PixDrive derives cues from presets and updates the cue list whenever presets change; there is no hand-edited cue list.
- Trigger a Show by name; positions shift when you add, remove, or reorder presets.
- Playback and seeking act on the loaded sequence. Triggering a Show cue loads that show and starts it from
0. - REST and OSC are independent; enable either or both.