Local API
Everything the app and web page can do, your scripts can do too. The API is plain HTTP on your own network — no cloud, no API keys to register, no rate limits beyond what the little chip can serve.
Base URL
Talk to your Main at its local address (see Finding your Luma):
http://luma-<system>.local # friendly address of the Main
http://Luma-XXXXXXXX.local # permanent hardware address
http://192.168.x.x # raw IP always worksAuthentication
If your Luma has no PIN, the API is open on your network — start calling it. With a PIN set, requests need a credential; for automation that’s the device key: a stable 32-character token sent as the X-Luma-Key header.
- Sign in to the device’s web page once with your PIN.
- Fetch the key:
GET /api/auth/device-key→{"key": "…"}. - Send it with every request:
X-Luma-Key: <key>. It doesn’t expire.
Sixty-second start
# Is it alive?
curl http://luma-hilltop.local/api/ping
# What's glowing right now?
curl -H "X-Luma-Key: $KEY" http://luma-hilltop.local/api/led-status
# Glow pattern 2 on every device (an ad-hoc nudge, dismissed like any reminder)
curl -X POST -H "X-Luma-Key: $KEY" -H "Content-Type: application/json" \
-d '{"pattern": 2, "target_device": 0}' \
http://luma-hilltop.local/api/trigger-pattern
# All clear
curl -X POST -H "X-Luma-Key: $KEY" http://luma-hilltop.local/api/turn-offFrom Apple Shortcuts, the same calls work with the “Get Contents of URL” action: method POST, header X-Luma-Key, JSON body. That’s the entire integration story — a shortcut that glows red when you leave a task undone is four actions long.
Reading state
| Endpoint | What it returns |
|---|---|
GET/api/ping | Liveness: {"pong": true, "isMain": …}, plus uptime and schedule count when authenticated. |
GET/api/device/status | The full picture: name, system, role, IP, firmware version, update availability, vacation state, and (on a Main) the satellite roster with online flags. |
GET/api/led-status | What’s lit: current pattern, brightness, and the queue of active reminders. |
GET/api/time | The device’s clock and whether it considers itself synchronised. |
GET/api/devices/list | (Main) Every device in the system with type, room, online state, and firmware version. |
GET/api/diag | Boot history, restart reasons, and memory stats — the local diagnostics described in Privacy. |
Reminders
| Endpoint | Behaviour |
|---|---|
GET/api/schedules | The reminder list. Each entry carries id, name, time, days (seven booleans), pattern, recurrence, target, and state. |
POST/api/schedules | Create: hour, minute, days required; name, pattern, recurrence, monthlyDay, deviceTarget optional. Caps at ten reminders. |
PUT/api/schedules | Update by id— send only the fields you’re changing. |
DELETE/api/schedules?id=N | Delete. Careful: ids are list positions, so deleting one shifts the ids above it — re-read the list after any delete. |
POST/api/schedules/toggle?id=N | Flip a reminder on or off. |
Light control
| Endpoint | Behaviour |
|---|---|
POST/api/trigger-pattern | The automation workhorse. JSON pattern (0–11), optional brightness and target_device. Glows like a reminder until dismissed — tap or timeout, same as anything else. |
POST/api/turn-off | Dismiss everything currently glowing, system-wide. |
POST/api/identify | Five seconds of cyan flashing — “which one is this?” |
Patterns 0–7 are the built-in gradients in the order shown in Reminders, 8 is Rainbow, and 9–11 are your custom slots.
Settings
| Endpoint | Behaviour |
|---|---|
GET/api/hydration | Hydration config; POST the same shape to change it (fields:isOn, startHour, endHour, intervalHours 1–3, schedule, deviceTarget). |
GET/api/pomodoro | Timer config and live state — including remaining minutes while one runs. POST to configure (enabled, mode, maxMinutes 20/40/60). |
GET/api/vacation | Vacation state; POST {"isOn": true, "days": 7} to schedule quiet time (days ≤ 0 means until further notice). |
POST/api/timezone | Set the zone — send {"iana": "Europe/London"} and DST handles itself. |
POST/api/auto-dismiss | {"hours": 4|8|12}. |
POST/api/max-brightness | {"percent": 10|25|50|75|100}. |
GET/api/custom-gradient | Read the three custom slots; POST {"slot": 0, "color1": {...}, "color2": {...}} to save, or /api/custom-gradient/test to preview without saving. |
Live events
There isn’t one. Luma has no push feed — no WebSocket, no SSE. To follow what the light is doing, poll /api/led-status, which is exactly what the iOS app does (roughly every 30 seconds).
Ground rules
- Success responses are usually plain text; errors vary between text and JSON by endpoint. Check the HTTP status first.
- Schedule ids shift on delete (they’re positions, not stable handles).
- Setup, update, and device-management endpoints exist beyond this page but are the app’s domain — they change more freely between firmware versions and aren’t documented as stable.
- It’s one small chip: keep polling gentle (the app itself polls at most a couple of times per second, and only during updates).