lume

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

typewhat it is
lightone bulb or panel
grouped_lightthe settable handle for a room or zone
room, zonegrouping; their services array points at the grouped_light
devicethe 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.brightness is a percentage, 0–100 (v1 used 0–254).
  • color_temperature.mirek is 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: 0 is 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.