Browse the guide

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 works

Authentication

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.

  1. Sign in to the device’s web page once with your PIN.
  2. Fetch the key: GET /api/auth/device-key {"key": "…"}.
  3. 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-off

From 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

EndpointWhat it returns
GET/api/pingLiveness: {"pong": true, "isMain": …}, plus uptime and schedule count when authenticated.
GET/api/device/statusThe 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-statusWhat’s lit: current pattern, brightness, and the queue of active reminders.
GET/api/timeThe 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/diagBoot history, restart reasons, and memory stats — the local diagnostics described in Privacy.

Reminders

EndpointBehaviour
GET/api/schedulesThe reminder list. Each entry carries id, name, time, days (seven booleans), pattern, recurrence, target, and state.
POST/api/schedulesCreate: hour, minute, days required; name, pattern, recurrence, monthlyDay, deviceTarget optional. Caps at ten reminders.
PUT/api/schedulesUpdate by id— send only the fields you’re changing.
DELETE/api/schedules?id=NDelete. 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=NFlip a reminder on or off.

Light control

EndpointBehaviour
POST/api/trigger-patternThe 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-offDismiss everything currently glowing, system-wide.
POST/api/identifyFive 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

EndpointBehaviour
GET/api/hydrationHydration config; POST the same shape to change it (fields:isOn, startHour, endHour, intervalHours 1–3, schedule, deviceTarget).
GET/api/pomodoroTimer config and live state — including remaining minutes while one runs. POST to configure (enabled, mode, maxMinutes 20/40/60).
GET/api/vacationVacation state; POST {"isOn": true, "days": 7} to schedule quiet time (days ≤ 0 means until further notice).
POST/api/timezoneSet 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-gradientRead 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).