Complete API documentation for Chiba digital signage system.
Protected endpoints require authentication via one of:
Authorization: Bearer <API_KEY>headerX-API-Key: <API_KEY>header?api_key=<API_KEY>query parameter
The API key is shared between controller and all nodes, configured via API_KEY environment variable.
The node server runs on each Raspberry Pi (default port 8080).
Service information.
Response:
{
"name": "chiba-node",
"version": "0.1.0",
"friendlyName": "living-room",
"nodeId": "uuid-here",
"uptime": 3600
}Health check endpoint.
Response:
{ "status": "ok" }Full node status including playback state, cache info, and hardware metrics.
Response:
{
"success": true,
"data": {
"node": {
"id": "uuid",
"friendlyName": "living-room",
"hostname": "mars01.local",
"ip": "192.168.1.101",
"port": 8080,
"version": "0.1.0",
"uptime": 3600,
"displayRotation": 0
},
"playback": {
"mode": "video",
"playlistIndex": 0,
"loop": true,
"shuffle": false,
"paused": false,
"volume": 100,
"imageDuration": 10000,
"currentContent": { ... }
},
"controllerConnected": true,
"wsClients": 1
}
}List all cached content files.
Response:
{
"success": true,
"data": {
"files": [
{
"hash": "a1b2c3d4",
"filename": "a1b2c3d4.mp4",
"name": "My Video",
"type": "video",
"sizeBytes": 52428800,
"cachedAt": 1704067200000
}
],
"totalBytes": 52428800,
"count": 1
}
}Debug screen data for the player overlay.
Response:
{
"nodeName": "living-room",
"nodeId": "uuid",
"ipAddress": "192.168.1.101",
"networkStatus": "online",
"controllerStatus": "online",
"content": [
{ "filename": "abc123.mp4", "sizeBytes": 52428800, "type": "video", "name": "My Video" }
],
"totalCacheSize": 1073741824,
"playlists": [
{ "id": "uuid", "name": "My Playlist", "itemCount": 5, "loop": true, "createdAt": 1704067200000, "updatedAt": 1704067200000 }
],
"currentPlaylist": {
"id": "uuid",
"name": "My Playlist",
"currentIndex": 2,
"totalItems": 5
}
}Stream a cached media file. Used by the player to load content. Supports HTTP range requests for video seeking.
Response: Binary file stream with appropriate Content-Type header.
Serves the player web application (SPA).
Play content. Auto-detects source type from provided parameters. Downloads content if needed (async) or plays immediately if already cached (sync).
Request Body Options:
# Play cached file by filename (sync - plays immediately)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"filename": "a1b2c3d4.mp4"}'
# Play YouTube URL (async - returns taskId, plays when downloaded)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://youtube.com/watch?v=dQw4w9WgXcQ"}'
# Play Google Drive file (async - returns taskId)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://drive.google.com/file/d/FILE_ID/view"}'
# Play direct media URL (async - returns taskId)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://example.com/video.mp4"}'
# Play Eden collection as playlist (async - returns taskId)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"collectionId": "6526f38042a1043421aa28e8", "db": "PROD"}'
# Play Eden single creation (async - returns taskId)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"creationId": "abc123", "db": "PROD"}'
# Play non-media URL in iframe (sync - no download needed)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://example.com/dashboard"}'
# Play a playlist object directly (sync)
curl -X POST http://node:8080/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"playlist": {...}, "startIndex": 0}'Parameters:
| Parameter | Type | Description |
|---|---|---|
filename |
string | Cached file to play (sync) |
url |
string | URL to play (YouTube, Drive, direct media, or webpage) |
collectionId |
string | Eden collection ID |
creationId |
string | Eden creation ID |
db |
string | Eden database: PROD or STAGE (default: PROD) |
loop |
boolean | Loop playback (preserves current setting if not specified) |
showIntro |
boolean | Show intro screen before content |
name |
string | Optional friendly name for cached content |
playlist |
object | Playlist object to play directly |
startIndex |
number | Starting index for playlist (default: 0) |
content |
object | Content object to play directly |
Synchronous Response (cached file, iframe URL):
{
"success": true,
"data": {
"state": { ... }
}
}Asynchronous Response (downloads required):
{
"success": true,
"data": {
"taskId": "youtube_abc123...",
"status": "queued",
"message": "YouTube download queued, will play when complete"
}
}Stop playback and return to off mode.
curl -X POST http://node:8080/stop \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "data": { "state": { "mode": "off", ... } } }Pause current playback.
curl -X POST http://node:8080/pause \
-H "Authorization: Bearer $API_KEY"Resume paused playback.
curl -X POST http://node:8080/resume \
-H "Authorization: Bearer $API_KEY"Skip to next item in playlist.
curl -X POST http://node:8080/next \
-H "Authorization: Bearer $API_KEY"Go to previous item in playlist.
curl -X POST http://node:8080/previous \
-H "Authorization: Bearer $API_KEY"Set volume level.
curl -X POST http://node:8080/volume \
-H "Authorization: Bearer $API_KEY" \
-d '{"level": 75}'Parameters:
| Parameter | Type | Description |
|---|---|---|
level |
number | Volume level 0-100 |
Response:
{ "success": true, "data": { "volume": 75 } }Set loop mode.
curl -X POST http://node:8080/loop \
-H "Authorization: Bearer $API_KEY" \
-d '{"enabled": true}'Parameters:
| Parameter | Type | Description |
|---|---|---|
enabled |
boolean | Enable/disable loop (omit to toggle) |
Response:
{ "success": true, "data": { "loop": true } }Set shuffle mode for playlist playback.
curl -X POST http://node:8080/shuffle \
-H "Authorization: Bearer $API_KEY" \
-d '{"enabled": true}'Parameters:
| Parameter | Type | Description |
|---|---|---|
enabled |
boolean | Enable/disable shuffle (omit to toggle) |
Response:
{ "success": true, "data": { "shuffle": true } }Set how long images display in playlists before auto-advancing.
curl -X POST http://node:8080/image-duration \
-H "Authorization: Bearer $API_KEY" \
-d '{"duration": 5000}'Parameters:
| Parameter | Type | Description |
|---|---|---|
duration |
number | Duration in milliseconds (minimum: 1000) |
Response:
{ "success": true, "data": { "imageDuration": 5000 } }Cache content without playing. Always async - returns immediately with taskId.
# Cache YouTube video
curl -X POST http://node:8080/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://youtube.com/watch?v=dQw4w9WgXcQ"}'
# Cache Google Drive file
curl -X POST http://node:8080/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://drive.google.com/file/d/FILE_ID/view"}'
# Cache media URL
curl -X POST http://node:8080/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://example.com/video.mp4"}'
# Cache Eden collection
curl -X POST http://node:8080/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"collectionId": "6526f380...", "db": "PROD"}'
# Cache Eden creation
curl -X POST http://node:8080/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"creationId": "abc123", "db": "PROD"}'Response:
{
"success": true,
"data": {
"taskId": "youtube_abc123...",
"status": "queued",
"message": "Download queued"
}
}Append items to the current playlist, or create a new playlist if none is active.
# Append items to current playlist
curl -X POST http://node:8080/append \
-H "Authorization: Bearer $API_KEY" \
-d '{
"items": [
{"id": "item-1", "content": {"type": "file", "filename": "abc123.mp4"}, "order": 0}
]
}'
# Create new playlist (when none is playing)
curl -X POST http://node:8080/append \
-H "Authorization: Bearer $API_KEY" \
-d '{
"items": [...],
"name": "My Playlist",
"loop": true,
"showIntros": false
}'Parameters:
| Parameter | Type | Description |
|---|---|---|
items |
array | Array of PlaylistItem objects (required) |
name |
string | Playlist name (only used when creating new) |
loop |
boolean | Loop setting (only used when creating new) |
showIntros |
boolean | Show intro screens (only used when creating new) |
Response:
{
"success": true,
"data": {
"playlist": { ... },
"state": { ... }
}
}Delete all cached content.
curl -X POST http://node:8080/clear-cache \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "data": { "deletedCount": 15, "freedBytes": 1073741824 } }Exit kiosk mode on the Pi.
curl -X POST http://node:8080/exit-kiosk \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "message": "Kiosk killed" }Get the current kiosk URL override (empty string means default player).
curl http://node:8080/kiosk-urlResponse:
{ "success": true, "data": { "url": "http://controller:8787/?screenId=pi-01" } }Set the kiosk URL override and restart the kiosk.
curl -X POST http://node:8080/kiosk-url \
-H "Authorization: Bearer $API_KEY" \
-d '{"url":"http://controller:8787/?screenId=pi-01"}'Response:
{ "success": true, "data": { "url": "http://controller:8787/?screenId=pi-01", "restartQueued": true } }Restart the kiosk without changing the URL.
curl -X POST http://node:8080/kiosk-restart \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "data": { "restartQueued": true } }Rename the node's friendly name.
curl -X POST http://node:8080/rename \
-H "Authorization: Bearer $API_KEY" \
-d '{"name": "Living Room"}'Response:
{ "success": true, "data": { "oldName": "unnamed-node", "newName": "Living Room" } }Rotate the display. Applies immediately and persists across reboots.
curl -X POST http://node:8080/rotate \
-H "Authorization: Bearer $API_KEY" \
-d '{"rotation": 90}'Parameters:
| Parameter | Type | Description |
|---|---|---|
rotation |
number | Rotation in degrees: 0, 90, 180, or 270 |
Response:
{ "success": true, "data": { "oldRotation": 0, "newRotation": 90, "appliedImmediately": true } }The controller server manages multiple nodes (default port 8080).
Controller information.
Response:
{
"name": "chiba-controller",
"version": "0.1.0",
"endpoints": [
"GET /api/info",
"GET /health",
"GET /api/nodes",
...
]
}Health check endpoint.
Response:
{ "status": "ok", "uptime": 86400 }Get controller configuration (used by dashboard).
Response:
{
"apiKey": "your-api-key",
"version": "0.1.0"
}List all connected nodes.
Response:
{
"success": true,
"data": {
"nodes": [
{
"node": {
"id": "uuid",
"friendlyName": "living-room",
"hostname": "mars01.local",
"ip": "192.168.1.101",
"port": 8080,
"version": "0.1.0",
"uptime": 3600,
"displayRotation": 0
},
"connected": true,
"lastSeen": 1704067200000,
"playbackState": { ... },
"cachedContent": [...],
"diskUsage": { ... },
"hardware": { ... }
}
]
}
}Get specific node details. Accepts node ID or friendly name.
Response:
{
"success": true,
"data": {
"node": { ... }
}
}List content library.
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"hash": "a1b2c3d4",
"filename": "video.mp4",
"name": "My Video",
"originalUrl": "https://youtube.com/...",
"source": { "type": "youtube", "url": "..." },
"type": "video",
"sizeBytes": 52428800,
"duration": 180,
"metadata": { ... },
"createdAt": 1704067200000
}
]
}List all playlists.
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Morning Rotation",
"items": [...],
"loop": true,
"showIntros": false,
"introDuration": 3000,
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
]
}Get a single playlist by ID.
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Morning Rotation",
"items": [
{
"id": "item-uuid",
"sourceType": "youtube",
"sourceData": { "url": "https://..." },
"name": "Video Title",
"duration": null,
"order": 0
}
],
"loop": true,
"showIntros": false,
"introDuration": 3000,
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
}Serve uploaded media files. Supports HTTP range requests for video streaming.
Response: Binary file stream with appropriate Content-Type header.
Get Eden creation metadata.
curl "http://controller:8080/api/eden/creation/abc123?db=PROD"Get Eden collection with creations.
curl "http://controller:8080/api/eden/collection/6526f380...?db=PROD"Response:
{
"success": true,
"data": {
"collection": { ... },
"creations": [...]
}
}Parse an Eden URL to extract type and ID.
curl "http://controller:8080/api/eden/parse?url=https://app.eden.art/creations/abc123"Response:
{
"success": true,
"data": {
"valid": true,
"type": "creation",
"id": "abc123",
"db": "PROD"
}
}Upload a file directly to the controller.
curl -X POST http://controller:8080/api/upload \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: multipart/form-data" \
-F "file=@video.mp4"Response:
{
"success": true,
"data": {
"id": "uuid",
"hash": "a1b2c3d4...",
"filename": "a1b2c3d4.mp4",
"originalName": "video.mp4",
"contentType": "video",
"sizeBytes": 52428800,
"url": "http://controller:8080/uploads/a1b2c3d4.mp4"
}
}Add content to library.
# Add YouTube URL
curl -X POST http://controller:8080/api/content \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://youtube.com/watch?v=...", "name": "My Video"}'
# Add Eden creation URL
curl -X POST http://controller:8080/api/content \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://app.eden.art/creations/abc123"}'
# Add Eden collection URL (creates playlist automatically)
curl -X POST http://controller:8080/api/content \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://app.eden.art/collection/6526f380...", "name": "My Collection"}'Parameters:
| Parameter | Type | Description |
|---|---|---|
url |
string | URL to add (YouTube, Eden, or direct) |
name |
string | Optional display name |
description |
string | Optional description |
author |
string | Optional author name |
Response (single content):
{
"success": true,
"data": {
"id": "uuid",
"hash": "a1b2c3d4",
"filename": "video.mp4",
"name": "My Video",
"sourceType": "youtube",
"originalUrl": "https://youtube.com/...",
"metadata": { ... }
}
}Response (Eden collection):
{
"success": true,
"data": {
"type": "collection",
"collectionId": "6526f380...",
"collectionName": "Art Collection",
"playlistId": "uuid",
"playlistName": "My Collection",
"contentCount": 15,
"contentIds": ["uuid1", "uuid2", ...]
}
}Remove content from library.
curl -X DELETE http://controller:8080/api/content/uuid-here \
-H "Authorization: Bearer $API_KEY"Create a new playlist.
curl -X POST http://controller:8080/api/playlists \
-H "Authorization: Bearer $API_KEY" \
-d '{
"name": "Morning Rotation",
"items": [
{"url": "https://youtube.com/watch?v=..."},
{"collectionId": "6526f380...", "db": "PROD"},
{"creationId": "abc123"},
{"filename": "cached-video.mp4"}
],
"loop": true,
"showIntros": false,
"introDuration": 3000,
"targetNodes": ["node-uuid-1", "node-uuid-2"]
}'Item Types:
| Field | Description |
|---|---|
url |
Direct URL, YouTube URL, or Eden URL |
creationId |
Eden creation ID |
collectionId |
Eden collection ID |
filename |
Local cached filename |
name |
Display name for item |
duration |
Override duration (ms) |
db |
Eden database (PROD/STAGE) |
Parameters:
| Parameter | Type | Description |
|---|---|---|
name |
string | Playlist name (required) |
items |
array | Array of playlist items |
loop |
boolean | Loop playback (default: true) |
showIntros |
boolean | Show intro screens (default: true) |
introDuration |
number | Intro duration in ms (default: 3000) |
targetNodes |
array | Node IDs to pre-cache content on |
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Morning Rotation",
"items": [...],
"loop": true,
"showIntros": false,
"introDuration": 3000,
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
}Update a playlist.
curl -X PUT http://controller:8080/api/playlists/uuid-here \
-H "Authorization: Bearer $API_KEY" \
-d '{"name": "New Name", "loop": false}'Updatable Fields:
name- Playlist nameitems- Replace all itemsloop- Loop settingshowIntros- Show intro screensintroDuration- Intro screen duration (ms)
Delete a playlist.
curl -X DELETE http://controller:8080/api/playlists/uuid-here \
-H "Authorization: Bearer $API_KEY"Add items to existing playlist.
curl -X POST http://controller:8080/api/playlists/uuid-here/items \
-H "Authorization: Bearer $API_KEY" \
-d '{"items": [{"url": "https://..."}]}'Response:
{
"success": true,
"data": {
"addedItems": [...],
"totalItems": 10
}
}Remove item from playlist by index.
curl -X DELETE http://controller:8080/api/playlists/uuid-here/items/0 \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "message": "Item removed", "remainingItems": 9 }Play playlist on a specific node.
curl -X POST http://controller:8080/api/playlists/uuid-here/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"nodeId": "node-uuid", "startIndex": 0}'Parameters:
| Parameter | Type | Description |
|---|---|---|
nodeId |
string | Target node ID (required) |
startIndex |
number | Starting item index (default: 0) |
Cache playlist content on specified nodes.
curl -X POST http://controller:8080/api/playlists/uuid-here/cache \
-H "Authorization: Bearer $API_KEY" \
-d '{"nodeIds": ["node-uuid-1", "node-uuid-2"]}'Parameters:
| Parameter | Type | Description |
|---|---|---|
nodeIds |
array | Array of node IDs to cache content on (required) |
Response:
{
"success": true,
"data": {
"playlistId": "uuid",
"results": {
"node-uuid-1": { "status": "success" },
"node-uuid-2": { "status": "partial", "errors": ["..."] }
}
}
}HTTP registration endpoint for nodes (used when WebSocket isn't available).
curl -X POST http://controller:8080/api/nodes/register \
-H "Authorization: Bearer $API_KEY" \
-d '{"id": "node-uuid", "name": "living-room", "port": 8080}'Response:
{
"success": true,
"data": {
"id": "node-uuid",
"name": "living-room",
"apiKey": "shared-api-key"
}
}Proxy any command to a specific node. Node can be referenced by ID or friendly name.
# Play on node
curl -X POST http://controller:8080/api/nodes/living-room/play \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://youtube.com/..."}'
# Stop node
curl -X POST http://controller:8080/api/nodes/node-uuid/stop \
-H "Authorization: Bearer $API_KEY"
# Set volume on node
curl -X POST http://controller:8080/api/nodes/node-uuid/volume \
-H "Authorization: Bearer $API_KEY" \
-d '{"level": 50}'
# Rename node
curl -X POST http://controller:8080/api/nodes/node-uuid/rename \
-H "Authorization: Bearer $API_KEY" \
-d '{"name": "Kitchen Display"}'
# Rotate node display
curl -X POST http://controller:8080/api/nodes/node-uuid/rotate \
-H "Authorization: Bearer $API_KEY" \
-d '{"rotation": 90}'Any node endpoint can be proxied through the controller using this pattern.
The controller manages smart lights (currently Wizlights).
Get all registered lights with their current states.
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Living Room Lamp",
"ipAddress": "192.168.1.50",
"port": 38899,
"deviceType": "ESP25_SHRGB_01",
"createdAt": 1704067200000,
"updatedAt": 1704067200000,
"state": {
"lightId": "uuid",
"power": true,
"hue": 240,
"saturation": 100,
"brightness": 80,
"updatedAt": 1704067200000
},
"reachable": true
}
]
}Get all light presets (both predefined and user-created).
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"name": "Warm Evening",
"isPredefined": false,
"settings": [
{
"lightId": "*",
"power": true,
"hue": 30,
"saturation": 80,
"brightness": 60
}
],
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
]
}Control a single light.
curl -X POST http://controller:8080/api/lights/uuid/control \
-H "Authorization: Bearer $API_KEY" \
-d '{"power": true, "brightness": 80, "hue": 240, "saturation": 100}'Parameters:
| Parameter | Type | Description |
|---|---|---|
power |
boolean | Turn light on/off |
brightness |
number | Brightness 0-100 |
hue |
number | Color hue 0-360 |
saturation |
number | Color saturation 0-100 |
Response:
{
"success": true,
"data": {
"lightId": "uuid",
"state": {
"power": true,
"hue": 240,
"saturation": 100,
"brightness": 80
}
}
}Control all lights at once.
curl -X POST http://controller:8080/api/lights/all/control \
-H "Authorization: Bearer $API_KEY" \
-d '{"power": false}'Response:
{
"success": true,
"data": {
"results": [
{ "lightId": "uuid1", "success": true },
{ "lightId": "uuid2", "success": true }
]
}
}Create a new light preset.
curl -X POST http://controller:8080/api/presets \
-H "Authorization: Bearer $API_KEY" \
-d '{
"name": "Movie Mode",
"settings": [
{"lightId": "*", "power": true, "brightness": 20, "hue": 30, "saturation": 80}
]
}'Parameters:
| Parameter | Type | Description |
|---|---|---|
name |
string | Preset name (required) |
settings |
array | Array of light settings (required) |
Light Setting Object:
| Field | Type | Description |
|---|---|---|
lightId |
string | Light ID or * for all lights |
power |
boolean | Turn light on/off |
brightness |
number | Brightness 0-100 |
hue |
number | Color hue 0-360 |
saturation |
number | Color saturation 0-100 |
Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Movie Mode",
"isPredefined": false,
"settings": [...],
"createdAt": 1704067200000,
"updatedAt": 1704067200000
}
}Apply a preset to lights.
curl -X POST http://controller:8080/api/presets/uuid/apply \
-H "Authorization: Bearer $API_KEY"Response:
{
"success": true,
"data": {
"presetId": "uuid",
"results": [
{ "lightId": "uuid1", "success": true },
{ "lightId": "uuid2", "success": true }
]
}
}Delete a user-created preset. Cannot delete predefined presets.
curl -X DELETE http://controller:8080/api/presets/uuid \
-H "Authorization: Bearer $API_KEY"Response:
{ "success": true, "message": "Preset deleted" }Player connects to node's WebSocket to receive playback state.
Node → Player Messages:
{"type": "state", "playback": {...}}
{"type": "preload", "content": {...}}
{"type": "download_progress", "taskId": "...", "status": "...", "progress": 50}Player → Node Messages:
{"type": "ready"}
{"type": "ended"}
{"type": "intro_complete"}
{"type": "error", "error": "..."}Nodes connect to controller for registration and heartbeat.
Node → Controller:
{"type": "register", "config": {...}, "info": {...}}
{"type": "heartbeat", "status": {...}}
{"type": "state", "playback": {...}}
{"type": "pong", "timestamp": 1704067200000}
{"type": "download_progress", "taskId": "...", "status": "...", "progress": 50}
{"type": "content_cached", "content": {...}}Controller → Node:
{"type": "command", "command": {"action": "play", ...}}
{"type": "preload", "content": [...]}
{"type": "ping", "timestamp": 1704067200000}
{"type": "config", "config": {...}}Dashboard connects to controller for real-time updates.
Controller → Dashboard:
{"type": "nodes", "nodes": [...]}
{"type": "node_update", "nodeId": "...", "status": {...}}
{"type": "node_disconnected", "nodeId": "..."}
{"type": "task_progress", "nodeId": "...", "task": {...}}Dashboard → Controller:
{"type": "subscribe"}
{"type": "command", "nodeId": "...", "command": {"action": "...", ...}}Long-running operations (downloads, YouTube, Eden sync) use an async task queue.
- Request: Client sends
/playor/cacherequest - Response: Server returns immediately with
taskIdandstatus: "queued" - Processing: Node processes tasks sequentially in background
- Progress: Node sends
download_progressmessages via WebSocket to controller - Forwarding: Controller forwards progress to dashboards as
task_progress - Completion: On success, sends final progress with
status: "completed"andresult
{
type: 'download_progress',
taskId: 'youtube_abc123...',
nodeId: 'pi-living-room',
taskType: 'youtube' | 'cache' | 'eden' | 'gdrive',
status: 'queued' | 'started' | 'downloading' | 'processing' | 'completed' | 'error',
progress: 0-100,
message?: 'Downloading...',
result?: { filename, hash, sizeBytes, alreadyCached },
error?: { code: 'DOWNLOAD_FAILED', message: 'Connection timeout' }
}| Type | Description |
|---|---|
cache |
Generic URL download |
youtube |
YouTube video via yt-dlp |
gdrive |
Google Drive file via gdown |
eden |
Eden collection sync or creation download |
The playback state object is used throughout the system:
interface PlaybackState {
mode: 'off' | 'video' | 'image' | 'url' | 'intro';
currentContent?: Content;
currentUrl?: string;
playlist?: Playlist;
playlistIndex: number;
loop: boolean;
shuffle: boolean;
shuffledOrder?: number[]; // Shuffle order indices
paused: boolean;
volume: number; // 0-100
imageDuration: number; // milliseconds (default: 10000)
position?: number; // current position in seconds
introMetadata?: ContentMetadata;
}Content is automatically detected from the source:
| Source | Detection | Type |
|---|---|---|
youtube.com, youtu.be |
URL pattern | YouTube video |
drive.google.com |
URL pattern | Google Drive file |
app.eden.art/creations/... |
URL pattern | Eden creation |
app.eden.art/collection/... |
URL pattern | Eden collection |
.mp4, .webm, .mov, .mkv |
File extension | Video |
.jpg, .jpeg, .png, .gif, .webp |
File extension | Image |
| Other URLs | Default | Iframe/webpage |
All endpoints return errors in this format:
{
"success": false,
"error": "Error message here"
}Common HTTP status codes:
400- Bad request (missing/invalid parameters)401- Unauthorized (missing/invalid API key)403- Forbidden (e.g., cannot delete predefined presets)404- Not found (node, content, or playlist doesn't exist)409- Conflict (e.g., preset name already exists)500- Internal server error502- Bad gateway (node unreachable when proxying)