Philips Hue CLIP v2
Pairing, mirek, per-light gamuts, grouped lights and the event stream.
lume speaks the bridge’s CLIP v2 API. The older v1 API still answers on most bridges, but Signify has been retiring it, and v2 is the one worth building on.
Transport
- HTTPS on the bridge, port 443.
https://<bridge>/clip/v2/resource/<type> - Authentication is the header
hue-application-key: <key>— no OAuth, no cloud round trip. - The bridge serves a certificate signed by Signify’s own CA and issued to the bridge id, not to its address. Verifying it against an IP therefore cannot succeed, so lume skips verification. On a LAN that is still strictly better than v1, which is plain HTTP with the key in the URL.
Getting a key
There is no v2 way to mint one — you cannot authenticate before you have a key. The single legacy call remains:
POST https://<bridge>/api {"devicetype":"lume#hostname","generateclientkey":true}
The bridge answers [{"error":{"type":101,"description":"link button not pressed"}}]
until someone presses the round button, then
[{"success":{"username":"…","clientkey":"…"}}]. username is the application
key. lume pair polls this for 45 seconds.
Resources lume uses
| type | what it is |
|---|---|
light | one bulb or panel |
grouped_light | the settable handle for a room or zone |
room, zone | grouping; their services array points at the grouped_light |
device | the physical product behind a light |
A grouped_light takes exactly the same body as a light, which is why lume
treats “one lamp” and “a whole room” as the same kind of target.
Names are not unique across kinds — a room and a bulb inside it are commonly
both called “Schlafzimmer” — so lume accepts a room: / zone: / light: /
group: prefix on a reference. Without one, a light wins, because that is the
narrower thing to have asked for.
Every resource carries id_v1 (/lights/9, /groups/3). That field is what
lets lume accept the small numbers people know from the old API and from most
existing Hue tooling, and resolve them to v2 uuids.
Setting state
PUT /clip/v2/resource/light/<uuid>
{"on":{"on":true},
"dimming":{"brightness":80},
"color_temperature":{"mirek":222}}
dimming.brightnessis a percentage, 0–100 (v1 used 0–254).color_temperature.mirekis reciprocal megakelvin — the same unit Elgato calls mired.mirek = 1000000 / kelvin.- Colour is
{"color":{"xy":{"x":…,"y":…}}}in CIE 1931. The bridge clamps xy into each bulb’s gamut itself, so no gamut mapping is needed client-side. - Colour and colour temperature are one mode, not two. Send one or the other; whichever you send last wins, and the other reads back as invalid.
brightness: 0is accepted but does not switch the light off — it goes to its minimum and stays on. lume passes the value through and reports the light as on at 0 %, rather than quietly turning it off on the user’s behalf.
Capabilities differ per light
color_temperature.mirek_schema gives that light’s usable range — commonly
153–500, but 153–555 on some models. Lights without a color object are white
only. lume reads these on connect and clamps against the device rather than
against a constant. A grouped_light reports neither, because it spans bulbs
that disagree; there lume sends the request as asked and lets the bridge sort
out which bulb can do what.
Watching changes
GET /eventstream/clip/v2 with Accept: text/event-stream is a server-sent
event feed of everything on the bridge. Unlike the Elgato lamps, it reports your
own changes too, so it is genuinely useful for watching what a wall switch or
the Hue app does.
Rate limits
Roughly 10 commands/second for lights and about 1/second for groups. Fanning a profile out across a handful of lamps is fine; a loop that animates them is not.