Audience: integrators wiring DjMirror Pulse into show control, companion apps, installers and scripts. End users driving the UI do not need this guide.
Your first integration in five steps — each is explained below:
- Check the server answers:
getHealth(§1). - Add your API key header if one is configured (§2).
- Read the live state:
getConfig, and remembermeta.revision(§3). - Change only what you need with
PATCH(§3, §4). - Open the WebSocket so you hear about changes made by the desktop UI or other clients (§5).
Chapter 1: Connect And Make A First Call
The server runs by default on port 8080; start it explicitly with DjMirrorPulse.exe --server --port 8080 (or
"server": true, "port": 8080 in config.json). The WebSocket event server always listens on HTTP port + 1
(default 8081). All current routes live under /api/v1; the unversioned legacy routes still exist but new
integrations should not use them.
curl http://127.0.0.1:8080/api/v1/health
{
"ok": true,
"data": { "status": "ok", "realtimeWsPort": 8081 },
"error": null,
"meta": { "apiVersion": "v1", "requestId": "3f9e2b1a-...", "timestamp": "2026-10-13T10:15:30.123Z", "revision": 42 }
}
Every /api/v1 response has this envelope: ok, then data on success or error on failure, plus meta.
meta.revision is a counter of the live configuration (see §3). The only exceptions are the long-poll
subscription and the binary downloads (recordings, snapshots), which send raw bytes.
Then ask what this build can do — getCapabilities
returns flags such as supportsWebSocket and, per feature (spout, ndi, link...), whether it is available
and why not. Branch on capabilities, not on version numbers. Some features also depend on the license edition:
getLicense tells you which are included, and a call that
needs a higher edition answers 403 EDITION_REQUIRED. The reference marks those operations with their minimum
edition.
Chapter 2: Authentication
- No API key (the default): authentication is off — every route and the WebSocket are open to anyone on the LAN who can reach the port.
- API key set (
DJMIRROR_API_TOKEN,"apiKey"inconfig.json, or the Pairing panel): every route, legacy ones included, and the WebSocket need it, in either header:X-API-Key: <key>orAuthorization: Bearer <key>. The token can also be thedeviceTokenof a phone paired by QR. - WebSocket with a key: connect to
ws://<host>:<httpPort+1>/?token=<key or deviceToken>. - Routes that need no key because they authenticate themselves: the phone pairing flow
(
postPairingClaim,getPairingInfoand the mobile link routes).
curl -H "X-API-Key: $KEY" http://127.0.0.1:8080/api/v1/capabilities
In PowerShell use curl.exe (the plain curl is an alias of Invoke-WebRequest) or Invoke-RestMethod:
Invoke-RestMethod -Uri http://127.0.0.1:8080/api/v1/capabilities -Headers @{ "X-API-Key" = $env:KEY }
Chapter 3: How Reads And Writes Work
GETnever changes anything.PUTreplaces a whole value;PATCHdeep-merges only the keys you send.getConfigreturns the full live configuration;patchConfigmerges a partial object into it. Focused variants exist for the global settings, layers, the audio rules, artists and playlists — each is listed under its tag in the reference.- Validate, then store. A
422means the body was understood but wrong and nothing was stored; readerror.hint, fix the payload and resend. meta.revisionincreases every time the live configuration changes in memory. Two reads with the same revision saw the same state. Writes that only touch files on disk (editing a saved style, DMX files) do not change it.
A tiny helper that unwraps the envelope and turns ok: false into an exception (JavaScript, browser or Node 18+):
async function api(path, options = {}) {
const res = await fetch(`http://127.0.0.1:8080/api/v1${path}`, {
...options,
headers: { "Content-Type": "application/json", "X-API-Key": KEY, ...options.headers },
});
const envelope = await res.json();
if (!envelope.ok) throw new Error(`${envelope.error.code}: ${envelope.error.message}`);
return envelope.data;
}
Chapter 4: Recipes
Each recipe lists the operations it uses; click one for the exact fields and every possible answer. The examples
use curl; add -H "X-API-Key: $KEY" when a key is set.
Change a layer
Find the layer ids, then patch only the fields you want.
getConfigLayerIdslists every layer and sub-layer id (videoBackgroundView,waveFormView,lightingView...).patchConfigLayersByLayerIdmerges your fields into that layer.patchConfigLayersVisibilityis the quick show/hide switch.
# Brightness up on the video layer, nothing else touched
curl -X PATCH http://127.0.0.1:8080/api/v1/config/layers/videoBackgroundView \
-H "Content-Type: application/json" -d '{"brightness": 1.2}'
# Hide the waveform
curl -X PATCH http://127.0.0.1:8080/api/v1/config/layers/visibility \
-H "Content-Type: application/json" -d '{"layerId": "waveFormView", "enabled": false}'
Apply a saved style
A style is a saved look (colours, media, layers). Applying one is a single, atomic server-side load.
getVisualsStyleslists the style names (listStylesMetadataadds whether each is installed).postVisualsStylesByNameApplyloads it onto the live configuration. An optionaloverridesobject is deep-merged on top, handy for "this style, but brighter".
curl -X POST "http://127.0.0.1:8080/api/v1/visuals/styles/03-Rave-Neon-v1/apply" \
-H "Content-Type: application/json" -d '{"overrides": {"elements": {"videoBackgroundView": {"brightness": 0.8}}}}'
Changing currentStyle through patchConfig only moves the
"active style" pointer; apply is the call that actually loads the look.
Run an action from a script
The same actions the schedule, the timecode cues and the MIDI/OSC/HID controllers fire — switch style, artist or
playlist, trigger a DMX scene, mark the downbeat... — can be run on demand with
executeAction.
curl -X POST http://127.0.0.1:8080/api/v1/actions/execute \
-H "Content-Type: application/json" -d '{"action": "style", "target": "03-Rave-Neon-v1"}'
To run the action of something you already stored, use
executeScheduleEntry or
postTimecodeCuesEntriesByEntryIdExecute.
Record a clip and take a snapshot
postRecordingStart(optionalfps,maxDurationSec,fileName), thenpostRecordingStop, which answers once the file is fully written.getRecordingStatustells whether it is running and whether the clip has audio.getRecordingListandgetRecordingDownloadByFileNamefetch the file.- A still image:
postRecordingSnapshot, thengetRecordingSnapshotDownloadByFileName.
curl -X POST http://127.0.0.1:8080/api/v1/recording/start -H "Content-Type: application/json" -d '{"fps": 30}'
# ... later
curl -X POST http://127.0.0.1:8080/api/v1/recording/stop
curl -o clip.mp4 http://127.0.0.1:8080/api/v1/recording/download/<fileName>
The Recording & Snapshots guide covers the same workflow from the UI.
Send the picture to OBS or Resolume
Spout shares the output as a texture on the same computer; NDI publishes it on the network. Both are checked and controlled the same way: look at the status, set the options, start, stop.
- Spout:
getSpoutStatus,patchSpoutConfig,postSpoutStart,postSpoutStop. - NDI:
getNdiStatus,patchNdiSettings,startNdiBridge,stopNdiBridge. - Several cropped outputs from one file: the Spout layout and NDI layout operations are under their tags in the reference; the NDI & Spout guide explains the layouts themselves.
curl http://127.0.0.1:8080/api/v1/spout/status
curl -X POST http://127.0.0.1:8080/api/v1/spout/start
Starting a Spout or NDI output replaces what a running OBS or Resolume receives: do it deliberately.
Light the rig
DMX fixtures, scenes and patterns are read and written with GET/PUT/PATCH under the DMX tag (every write
is validated and stores nothing on a 422). For live control during a show, use the runtime operations:
postDmxRuntimeBlackout for a blackout,
postDmxRuntimeSilence for the fade and for forcing
silence while you rehearse, and
getDmxRuntimeStatus to read the live state.
curl -X POST http://127.0.0.1:8080/api/v1/dmx/runtime/blackout -H "Content-Type: application/json" -d '{"active": true}'
Automate with the schedule or timecode
Time-of-day automation is the Schedule tag (entries, import/export and a simulated clock to rehearse it);
timecode cues are the Timecode tag. Both store the same kind of action as
executeAction. See the
Programming Schedule and Timecode Shows guides for how they behave.
Pair a phone
The phone-pairing flow (QR session, claim, approval, device list) is the Pairing tag, and
Mobile Remote explains it from the user's side. A paired phone gets a one-time
deviceToken that works like an API key.
Chapter 5: Live Updates: Websocket And Long-Poll
| Channel | Endpoint | Latency | Notes |
|---|---|---|---|
| WebSocket events | ws://<host>:<httpPort+1>/ |
< 1 s | Everything below; needs port HTTP+1 open |
| HTTP long-poll | getConfigSubscribe |
seconds | Config changes only; works through any firewall that allows HTTP |
Both announce changes made from the desktop UI as well as by other API clients. The long-poll answers one event and closes, so you re-subscribe each time; it never carries the DMX, recording, Spout or NDI events, which are WebSocket-only. The full list of events is in the reference, under WebSocket events in its introduction.
Events arrive in two shapes: configuration changes, and named events.
{ "type": "event", "eventType": "config.changed", "resource": "config", "action": "updated",
"data": { "...": "only the changed keys" }, "requestId": "...", "timestamp": "..." }
{ "type": "event", "eventType": "custom.event", "resource": "event", "action": "triggered",
"name": "spout.started", "data": {}, "requestId": "...", "timestamp": "..." }
A minimal listener. Omit ?token= when no API key is set; the port is realtimeWsPort from
getHealth.
function connect() {
const ws = new WebSocket(`ws://127.0.0.1:8081/?token=${KEY}`);
ws.onmessage = (m) => {
const ev = JSON.parse(m.data);
if (ev.eventType === "config.changed") console.log("config changed:", ev.data);
else if (ev.eventType === "custom.event") console.log(ev.name, ev.data);
};
ws.onclose = () => setTimeout(connect, 5000); // reconnect every ~5 s
}
connect();
Take the getConfig snapshot after the socket is open, so a
change between the two cannot be missed. The server sends a ping event every 30 seconds to keep idle connections
alive.
Chapter 6: Errors
Every failure has the same shape, with the HTTP status to match; hint and details appear only when the endpoint
has something to add (a parser message, for example).
{ "ok": false, "data": null, "error": { "code": "INVALID_PAYLOAD", "message": "...", "hint": "..." }, "meta": { } }
| HTTP | Meaning | Typical error.code |
What to do |
|---|---|---|---|
400 |
Body is not valid JSON, or a parameter is missing | INVALID_JSON, INVALID_PARAMS |
Fix the request |
401 |
Missing or wrong API key | UNAUTHORIZED, DEVICE_REVOKED |
Send X-API-Key or Authorization: Bearer |
403 |
License or phone limit | EDITION_REQUIRED, MOBILE_LIMIT_REACHED |
Activate or upgrade; unpair stale phones |
404 |
Unknown id, name or file | NOT_FOUND |
List again and use an id that exists |
409 |
Wrong state for the call | ALREADY_RUNNING, NOT_RUNNING |
Read the status first |
422 |
Validation failed — nothing was stored | INVALID_PAYLOAD, EMPTY_PATCH |
Read error.hint, fix, resend |
503 |
Feature not available on this build or not running | NOT_AVAILABLE |
Check capabilities |
Each operation in the reference lists the codes it can really answer.
Good practice: prefer config.json over command-line key flags on shared machines (process lists leak arguments);
treat 422 as "my request was wrong", not as a transient fault; and never poll faster than you need — subscribe
instead.
Chapter 7: Where To Go Next
- Every route, field and status code: the API reference, generated from
openapi-api-v1.yaml. Use that file to generate a client in your language. - The same control for AI assistants: MCP Server for Users.
- Something not working: Troubleshooting.