Skip to content
PixDrive
All articles

External Control API (REST & OSC)

Control PixDrive Studio via REST API and OSC – from Stream Deck, Companion, Home Assistant, Node-RED, lighting consoles and QLab.

Updated

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.

  1. Open Settings → External Control.
  2. Toggle HTTP/REST and/or OSC on.
  3. 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/go Requests without a valid token get 401.
  • 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.