All JSON endpoints return Content-Type: application/json and a Content-Length header.
All HTML responses include Cache-Control: no-cache, no-store, must-revalidate.
Returns the 7-tab Single Page Application.
Response: 200 OK, text/html
Unknown URL paths also return the SPA (client-side routing).
Response: 200 OK, application/json
{"role": "parent", "hostname": "WIN-HOSTNAME", "version": "6.0.0"}Response: 404 Not Found
List all known children.
Response: 200 OK, array of child objects.
[
{
"id": 0,
"ip": "192.168.1.100",
"hostname": "SLYC-A1B2",
"altName": "Front strip",
"desc": "Living room",
"xMm": 1000, "yMm": 500, "zMm": 0,
"stringCount": 2,
"strings": [
{"ledCount": 60, "lengthMm": 1000, "ledType": 0,
"cableDir": 0, "cableMm": 200, "stripDir": 0}
]
}
]Add a child by IP (parent sends CMD_PING and waits for CMD_PONG). The ping runs outside the request lock so concurrent requests are not blocked.
Request body: {"ip": "192.168.1.100"}
Response: 200 OK
{"ok": true, "id": 0}Remove a child from the registry.
Response: 200 OK
{"ok": true}Error if id not found: {"ok": false, "err": "not found"}
Send CMD_STATUS_REQ to the child and wait up to 300 ms for CMD_STATUS_RESP.
Response: 200 OK
{"ok": true, "action": 0, "runner": false, "step": 0, "rssi": -65, "uptime": 3721}On timeout: {"ok": false, "err": "timeout"}
On bad id: {"ok": false, "err": "not found"}
Re-send CMD_PING to a specific child IP to refresh its PONG data.
Response: 200 OK, {"ok": true}
Broadcast a CMD_PING to 255.255.255.255:4210 and collect CMD_PONG responses for 1.5 seconds. Returns children found on the network that are not already in the known-children table (filtered by IP address).
Response: 200 OK, array of child objects (same schema as GET /api/children, no id field — children are not added automatically).
[
{
"ip": "192.168.1.105",
"hostname": "SLYC-C3D4",
"name": "SLYC-C3D4",
"desc": "",
"sc": 2,
"strings": [...],
"status": 1,
"seen": 1710000000
}
]Returns an empty array if no new children respond within 1.5 seconds.
Export all children as a JSON array (for backup / transfer).
Response: 200 OK, array (same schema as GET /api/children)
Import a JSON array of children; deduplicates by hostname (existing entry is updated, not duplicated).
Request body: array of child objects (only ip is required; other fields optional)
Response: 200 OK
{"ok": true, "added": 2, "updated": 1}Returns canvas dimensions and the position of every registered child. Children without a saved position default to x=0, y=0 with positioned=false.
Response: 200 OK
{
"canvasW": 10000,
"canvasH": 5000,
"children": [
{
"id": 0, "hostname": "SLYC-A1B2", "name": "Front strip",
"x": 2000, "y": 1500,
"positioned": true
},
{
"id": 1, "hostname": "SLYC-C3D4", "name": "SLYC-C3D4",
"x": 0, "y": 0,
"positioned": false
}
]
}positioned: false means no layout position has been explicitly saved for that child yet. The SPA shows unpositioned children in a sidebar list; drag them onto the canvas to place them. Each child object also includes full strings data (LED count, length, direction) which the canvas uses to render detailed string visualizations.
Save canvas positions for all children. Only id, x, and y are read from each element; other fields are ignored.
Request body:
{
"children": [
{"id": 0, "x": 2000, "y": 1500},
{"id": 1, "x": 7000, "y": 3000}
]
}Response: 200 OK, {"ok": true}
Response: 200 OK
{
"name": "SlyLED",
"units": 0,
"canvasW": 10000,
"canvasH": 5000,
"darkMode": true,
"runnerRunning": false,
"activeRunner": -1,
"runnerElapsed": 0,
"runnerLoop": true
}| Field | Type | Description |
|---|---|---|
name |
string | Parent display name |
units |
int | 0 = metric (mm), 1 = imperial (inches) |
canvasW / canvasH |
int | Canvas dimensions in mm |
darkMode |
bool | UI dark mode |
runnerRunning |
bool | Whether a runner is currently executing |
activeRunner |
int | ID of the active runner, or -1 |
runnerElapsed |
int | Seconds elapsed in current runner (computed dynamically, wraps on loop) |
runnerLoop |
bool | Whether runners loop continuously (true by default) |
Save settings (partial updates accepted — omitted fields are unchanged).
Request body: any subset of the GET response fields
Response: 200 OK, {"ok": true}
Actions are reusable presets (type, colour, timing, direction) that are referenced by runner steps. Creating or editing actions does NOT send anything to hardware.
List all actions.
Response: 200 OK, array of action objects.
[
{
"id": 0, "name": "Red Wipe", "type": 3,
"r": 255, "g": 0, "b": 0,
"onMs": 500, "offMs": 500,
"wipeDir": 0, "wipeSpeedPct": 75
}
]| Field | Description |
|---|---|
type |
0=Blackout, 1=Solid, 2=Fade, 3=Breathe, 4=Chase, 5=Rainbow, 6=Fire, 7=Comet, 8=Twinkle |
r/g/b |
Primary colour (0–255) |
r2/g2/b2 |
Second colour (Fade) |
speedMs |
Speed/period in ms (Fade, Chase, Rainbow, Fire, Comet) |
periodMs |
Breathe period in ms |
spawnMs |
Twinkle spawn interval in ms |
minBri |
Minimum brightness % (Breathe) |
spacing |
Pixel spacing (Chase) |
paletteId |
Rainbow palette: 0=Classic, 1=Ocean, 2=Lava, 3=Forest, 4=Party, 5=Heat, 6=Cool, 7=Pastel |
cooling/sparking |
Fire params |
direction |
Direction: 0=E, 1=N, 2=W, 3=S (Chase, Rainbow, Comet) |
tailLen |
Comet tail length |
density |
Twinkle density |
decay/fadeSpeed |
Comet decay % / Twinkle fade speed |
scope |
"canvas" for canvas-scoped actions (per-child delay computation) |
onMs/offMs |
Legacy compatibility fields |
wipeDir/wipeSpeedPct |
Legacy compatibility fields |
Create a new action. Maximum 32 actions.
Request body: { "name": "Red Wipe", "type": 3, "r": 255, "g": 0, "b": 0, "wipeDir": 0, "wipeSpeedPct": 75 }
Response: 200 OK, {"ok": true, "id": 0}
Get a single action. Error if not found: 404.
Update an action (partial updates accepted).
Response: 200 OK, {"ok": true}
Delete an action. Error if not found: 404.
Response: 200 OK, {"ok": true}
List all runners. Includes step count and total duration.
Response: 200 OK, array of runner summary objects.
[
{"id": 0, "name": "Sunrise", "steps": 3, "totalDurationS": 15, "computed": true}
][
{"id": 0, "name": "Sunrise", "computed": true, "steps": [...]}
]Create a new runner. Maximum 4 runners.
Request body: {"name": "My Runner"}
Response: 200 OK
{"ok": true, "id": 0}Error if at capacity: {"ok": false, "err": "max runners reached"} (HTTP 400)
Get a single runner by ID.
Response: 200 OK, runner object. Error if not found: {"ok": false, "err": "not found"}
Replace a runner's steps. Resets computed to false.
Request body:
{
"name": "Sunrise",
"steps": [
{
"actionId": 0,
"x0": 0, "y0": 0, "x1": 10000, "y1": 10000,
"durationS": 5
}
]
}Each step references an action from the library by actionId. Area-of-effect (x0/y0/x1/y1) is in units of 0–10000 (percentage × 100 of canvas dimensions).
Response: 200 OK, {"ok": true, "steps": 1}
Delete a runner.
Response: 200 OK, {"ok": true}. Error if not found.
Pre-compute LED ranges for every step × child × string based on current canvas layout. Sets computed = true.
Response: 200 OK, {"ok": true}
Send all pre-computed steps to all children via CMD_LOAD_STEP and wait for CMD_LOAD_ACK.
Response: 200 OK, {"ok": true}
Broadcast CMD_RUNNER_GO with epoch + 2 seconds so all children start simultaneously.
Response: 200 OK, {"ok": true}
Broadcast CMD_RUNNER_STOP to all children. Updates runnerRunning = false.
Response: 200 OK, {"ok": true}
List all flights.
Response: 200 OK, JSON array of flights.
Create a new flight.
Request body:
{"name": "Main Flight", "performerIds": [0, 1], "runnerId": 0, "priority": 1}Response: 200 OK, {"ok": true, "id": 0}
Response: 200 OK, flight object.
Update flight fields (name, performerIds, runnerId, priority).
Response: 200 OK, {"ok": true}
List all shows.
Create a new show.
Request body:
{"name": "Evening Show", "flightIds": [0, 1], "loop": true}Response: 200 OK, {"ok": true, "id": 0}
Response: 200 OK, show object.
Update show fields (name, flightIds, loop).
Response: 200 OK, {"ok": true}
Start show execution — syncs and starts all flights simultaneously.
Response: 200 OK, {"ok": true, "flights": 2}
Stop all running shows, runners, and WLED threads.
Response: 200 OK, {"ok": true}
Bundle children + layout as a portable config file.
Response: 200 OK
{"type": "slyled-config", "version": 1, "children": [...], "layout": {...}}Merge children by hostname and replace layout with ID remapping.
Request body: A slyled-config JSON bundle (as returned by export).
Response: 200 OK, {"ok": true, "added": 1, "updated": 0}
Bundle actions + runners + flights + shows as a portable show file.
Response: 200 OK
{"type": "slyled-show", "version": 1, "actions": [...], "runners": [...], "flights": [...], "shows": [...]}Replace all show data with the imported bundle. Reassigns all IDs and remaps cross-references (runner steps → actionId, flights → runnerId, shows → flightIds). Warns if any flight.performerIds reference children not in the current configuration.
Request body: A slyled-show JSON bundle.
Response: 200 OK
{"ok": true, "actions": 8, "runners": 1, "flights": 1, "shows": 1, "warning": "Some flights reference performers not in this configuration."}Generate and install a demo show from current children/layout.
Request body: {"mood": "default"} (mood parameter reserved for future smart show presets)
Response: 200 OK, {"ok": true, "actions": 8, "runners": 1, "flights": 1, "shows": 1}
Error (no performers): 400, {"ok": false, "err": "No performers registered"}
Clear all children, runners, actions, and layout data, and restore default settings. This is equivalent to a full wipe — the operation is not reversible.
Request body: {} (empty)
Response: 200 OK
{"ok": true}Default settings restored: name="SlyLED", units=0, canvasW=10000, canvasH=5000, darkMode=1, runnerRunning=false, activeRunner=-1, runnerElapsed=0, runnerLoop=true.
Send an immediate action to one child or all children.
Request body:
{
"target": "all",
"type": 1,
"r": 255, "g": 0, "b": 0,
"onMs": 500, "offMs": 500,
"wDir": 0, "wSpd": 20,
"ledStart": [0, 0, 255, 255, 255, 255, 255, 255],
"ledEnd": [59, 255, 255, 255, 255, 255, 255, 255]
}| Field | Description |
|---|---|
target |
"all" or child ID as string (e.g. "0") |
type |
0=Blackout, 1=Solid, 2=Fade, 3=Breathe, 4=Chase, 5=Rainbow, 6=Fire, 7=Comet, 8=Twinkle |
r/g/b |
Primary colour (0–255) |
| Various params | See Actions Library API for per-type fields |
ledStart/ledEnd |
8-byte arrays: per-string start/end LED index; 0xFF = string not included |
Response: 200 OK, {"ok": true}
Error if specific target not found: {"ok": false, "err": "target not found"} (HTTP 404)
Send CMD_ACTION_STOP to one child or all children.
Request body: {"target": "all"} (or specific child ID string)
Response: 200 OK, {"ok": true}
Error if specific target not found: {"ok": false, "err": "target not found"} (HTTP 404)
Returns WiFi SSID and whether a password is stored (never returns the plaintext password).
Response: 200 OK
{"ssid": "MyNetwork", "hasPassword": true}Save WiFi credentials. The password is encrypted at rest using a machine-derived key.
Request body: {"ssid": "MyNetwork", "password": "secret"}
Response: 200 OK, {"ok": true}
List connected COM ports with detected board type (by USB VID:PID). Fast operation (no serial I/O).
Response: 200 OK, array of port objects.
[
{
"port": "COM8",
"description": "Silicon Labs CP210x",
"vid_pid": "10C4:EA60",
"candidates": [{"board": "esp32", "chip": "CP2102", "name": "ESP32 (CP2102)"}],
"board": "esp32",
"boardName": "ESP32 (CP2102)"
}
]Query a single port via serial for firmware version, board type, and WiFi hash. Slow (~2 s).
Request body: {"port": "COM8"}
Response: 200 OK
{"ok": true, "fwVersion": "4.11", "fwBoard": "esp32", "board": "esp32", "wifiHash": "A1B2C3D4", "wifiMatch": true}wifiMatch compares the firmware's WiFi hash against the parent's stored credentials.
List available firmware binaries from the firmware directory.
Response: 200 OK
{"firmware": [{"id": "esp32-child-4.11", "board": "esp32", "version": "4.11", "file": "esp32-child-4.11.bin"}]}Detect chip type on an ambiguous port using esptool.
Request body: {"port": "COM8"}
Response: 200 OK, {"ok": true, "board": "esp32"}
Start flashing firmware in a background thread.
Request body: {"port": "COM8", "firmwareId": "esp32-child-4.11", "board": "esp32"}
Response: 200 OK, {"ok": true, "message": "Flashing started"}
Poll flash progress.
Response: 200 OK
{"running": true, "progress": 45, "message": "Writing... 45%", "error": null}Terminate the parent process. Response is sent before exit.
Request body: {} (empty)
Response: 200 OK, {"ok": true}
Children (Performers) expose a minimal HTTP interface on port 80.
| Method | Path | Description |
|---|---|---|
| GET | / |
302 redirect → /config |
| GET | /status |
{"role":"child","hostname":"SLYC-XXXX","action":N} |
| GET | /config |
3-tab self-config SPA (Dashboard / Settings / Config) |
| POST | /config |
Save config to EEPROM, broadcast CMD_PONG, 303 redirect |
| POST | /config/reset |
Factory reset to defaults, 303 redirect |
| GET | /favicon.ico |
404 |
POST /config form fields: an (altName), desc (description), sc (stringCount 1–CHILD_MAX_STRINGS); per-string: lc (ledCount), lm (lengthMm), lt (ledType), sd (stripDir).
# List children
curl http://localhost:8080/api/children
# Send all LEDs solid red
curl -X POST http://localhost:8080/api/action \
-H "Content-Type: application/json" \
-d '{"target":"all","type":1,"r":255,"g":0,"b":0,"onMs":0,"offMs":0,"wDir":0,"wSpd":0,"ledStart":[0,255,255,255,255,255,255,255],"ledEnd":[255,255,255,255,255,255,255,255]}'
# Stop everything
curl -X POST http://localhost:8080/api/action/stop -H "Content-Type: application/json" -d '{"target":"all"}'