HTTP API
REST endpoints for activating content, controlling outputs, and firing actions from external systems.
REST endpoints for controlling Lightpath from external systems. All paths are relative to your install's base URL. Pick whichever of these matches how the caller reaches your install:
| Setup | Base URL |
|---|---|
| Caller runs on the same machine as Lightpath | http://localhost:3001/api |
| Caller is another machine on the same LAN | http://<install-ip>:3001/api (e.g. http://192.168.1.50:3001/api) |
| Caller is remote / over the internet | https://<your-tunnel-hostname>/api (Cloudflare tunnel, no port) |
The Cloudflare tunnel surface exposes the same API as LAN — every endpoint documented here is reachable through it. For remote callers, integration API keys (covered below) are the recommended credential.
# Pick one and reuse below
BASE_URL="http://localhost:3001/api"
# BASE_URL="http://192.168.1.50:3001/api"
# BASE_URL="https://your-tunnel-hostname/api"Authentication
Every endpoint except /api/health and /api/login requires a bearer
token. Send it as Authorization: Bearer <token> on every request.
Lightpath accepts two kinds of bearer tokens on the same endpoints:
- Integration API keys — long-lived, scoped, revocable tokens you mint in the UI. The recommended path for machine-to-machine integrations (Crestron, lighting boards, show-control software).
- User session JWTs — short-lived tokens you get from
POST /api/login. Appropriate for tooling operated by a specific person, or for one-off scripts where the caller's identity matters.
The server auto-detects which kind you sent by the lpath_ prefix on
integration keys.
Integration API keys
Go to Settings → API Keys (admin-only) and click + New Key.
- Give the key a descriptive name — the integration's name plus where it lives, e.g. Crestron — Main Hall. The name shows up in the list view and audit logs; you can't see the key value again after creation, so make the name useful.
- Tick the scopes the integration needs (table below). Grant the minimum that gets the job done.
- Optionally set an expiry date. Default is "never expires."
- Click Create key.
The full key value is shown once, with a copy button. Paste it directly into the integration's configuration — Lightpath only stores a hash and can't surface the plaintext again later.
KEY="lpath_live_xxxxxxxxxxxxxxxxxxxxxxxxx"
curl -X POST "$BASE_URL/scenes/Showtime/activate" \
-H "Authorization: Bearer $KEY"Keys begin with the lpath_ prefix. Treat them like passwords —
never commit them to source control, paste them into chat, or
email them in plaintext.
Keys work identically over LAN and through the Cloudflare tunnel — remote integrations are a primary use case.
Scopes
Each key declares the scopes it's allowed to use. A request to a route
that requires a scope the key doesn't carry returns 403.
| Scope | What it allows |
|---|---|
content.activate | Fire looks, scenes, and playlists |
action.execute | Run actions, set toggle states |
output.control | Set output brightness, enable/disable outputs |
playback.control | Pause / resume / next / prev / stop playlists |
state.read | Read content, outputs, and catalog (all GET endpoints) |
A "fire one scene from a button" integration typically only needs
content.activate. Keys deliberately can't reach admin endpoints
(user management, output config, calendar editing) — if your
integration needs those, it's an admin tool, not a machine
integration, and should use a user login instead.
Listing and revoking
The same Settings → API Keys page lists every key with name, scopes, prefix, created date, last-used timestamp, and status.
- Revoke stops the key from authenticating, immediately. Future
requests with it return
401 Invalid or expired integration key. The record is kept so audit logs can still resolve "who was that key?". - Delete removes the record entirely. Prefer Revoke unless the key was created in error.
User logins
Use POST /api/login to exchange credentials for a JWT. Appropriate
for admin dashboards, internal scripts, or anything that should
attribute its actions to a named user.
TOKEN=$(curl -s -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d '{ "username": "you@example.com", "password": "..." }' \
| jq -r .token)
curl "$BASE_URL/looks" -H "Authorization: Bearer $TOKEN"POST /api/login accepts either email or username plus password.
Tokens expire after 30 days by default — refresh by re-logging in on
401.
Catalog
Read what's available in the loaded project. Authenticated.
| Method | Path | Description |
|---|---|---|
| GET | /sources | Available sources published by TouchDesigner |
| GET | /effects | Available effects published by TouchDesigner |
Content queries
Read content definitions. All require Authorization: Bearer <token>.
| Method | Path | Description |
|---|---|---|
| GET | /looks | All looks |
| GET | /looks/:name | A specific look |
| GET | /scenes | All scenes |
| GET | /scenes/:name | A specific scene |
| GET | /playlists | All playlists (both scene-rotation and per-output look-rotation) |
| GET | /playlists/:name | A specific playlist |
| GET | /palettes | All palettes |
curl "$BASE_URL/looks" -H "Authorization: Bearer $TOKEN"Content activation
Activate looks, scenes, and playlists. Authenticated.
| Method | Path | Description |
|---|---|---|
| POST | /looks/:name/activate | Activate a look by name |
| POST | /scenes/:name/activate | Activate a scene across all outputs |
| POST | /playlists/:name/activate | Start a playlist (per-output or global, auto-detected) |
curl -X POST "$BASE_URL/looks/Sunset/activate" \
-H "Authorization: Bearer $TOKEN"Each look is bound to a single output at design time — the look activates on that output automatically; no body is required.
Playlist control
Add ?output=Name for per-output, omit for global. Authenticated.
| Method | Path | Description |
|---|---|---|
| POST | /playlists/pause | Pause playlist |
| POST | /playlists/resume | Resume playlist |
| POST | /playlists/stop | Stop playlist |
| POST | /playlists/next | Advance to next track (requires ?output=Name) |
| POST | /playlists/prev | Go to previous track (requires ?output=Name) |
| POST | /playlists/track | Jump to a specific track |
| POST | /playlists/loop | Toggle loop mode |
curl -X POST "$BASE_URL/playlists/track" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "index": 2, "output": "Output 1" }'Actions
Execute actions and set toggle states. Authenticated.
| Method | Path | Description |
|---|---|---|
| GET | /actions | Get all actions |
| POST | /actions/:name/execute | Execute an action by name |
| POST | /actions/:name/state | Set a toggle action to a specific state |
curl -X POST "$BASE_URL/actions/All%20Lights%20On/execute" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "targetState": true }'Output control
Brightness and power state per output. Authenticated.
| Method | Path | Description |
|---|---|---|
| GET | /outputs | Get all outputs |
| POST | /outputs/:name/dimmer | Set output brightness (0.0 – 1.0) |
| POST | /outputs/:name/enabled | Enable or disable an output |
curl -X POST "$BASE_URL/outputs/Facade/dimmer" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "value": 0.75 }'Playback
Global playback control and status. Authenticated.
| Method | Path | Description |
|---|---|---|
| POST | /playback/stop | Stop all playback across all outputs |
| GET | /playback/status | Get playback status for all playlists |
Health
The only public endpoint besides /api/login. Useful for liveness probes.
| Method | Path | Description |
|---|---|---|
| GET | /health | Status, uptime, version |
Errors
The API uses standard HTTP status codes.
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request — missing/invalid body or query parameters |
| 401 | Missing or expired bearer token |
| 403 | Authenticated but not authorized (e.g., wrong org for the loaded project, or local login attempted over a tunnel) |
| 404 | Resource not found (e.g., /looks/Unknown) |
| 500 | Server error |
Error responses are JSON:
{ "error": "Invalid or expired token" }
