Live events
Stream real-time measurements from meters over Server-Sent Events
The live-events endpoint streams real-time measurements from a home's meters (Tibber Pulse, Watty) as Server-Sent Events. The connection stays open and pushes a new frame as measurements arrive.
Required scope
Streaming requires the data-api-meters-read scope. Without it the stream endpoint returns 403,
and the discovery endpoint returns an empty list. See Scopes.
Discovering streamable devices
GET /v1/homes/{homeId}/live-events/devicesReturns the devices in the home that can be streamed, each with the opaque id you pass as a
deviceId query parameter below. Not every meter is always streamable (for example, a Pulse CT is
only listed when it measures the home itself, and bridge-attached Pulses require an active Tibber
Bridge), so always use this endpoint to determine what you can stream rather than assuming. If your
token lacks data-api-meters-read, this returns an empty list.
Opening a stream
GET /v1/homes/{homeId}/live-events?deviceId={a}&deviceId={b}- Pass one or more
deviceIdquery parameters (the opaque ids from the discovery endpoint). At least one is required; the maximum is 5 per connection. - Each device must belong to the target home.
- Only one live-events connection per home is allowed at a time; opening a second connection
terminates the first (see the
evictederror below).
The response is Content-Type: text/event-stream and stays open until the client disconnects.
Frame format
Each event is one SSE event with an event: type and a data: line. measurement events carry a
LiveMeasurement JSON object; the deviceId field is always present:
event: measurement
data: {"deviceId":"...","timestamp":"2026-04-27T15:34:30+02:00","power":275.0,...}
event: measurement
data: {"deviceId":"...","timestamp":"2026-04-27T15:34:32+02:00","power":280.0,...}Fields are nullable; missing values are expected and vary by device hardware and firmware.
Event types
measurement: a liveLiveMeasurementpayload tagged withdeviceId.ping: an empty keep-alive sent every ~20s of silence to keep proxies from dropping the connection.stream_error: a terminal stream error. JSON payload of shape{"code":"...","transient":bool,"retryAfter":int?,"deviceId":string?}, wheretransientindicates whether the client should retry,retryAfter(when present) hints at a backoff in seconds, anddeviceId(when present) names the offending device. Codes:upstream_unavailable: transient backend error; may retry afterretryAfterseconds.device_not_streamable: the named device can't be streamed right now (e.g. still setting up or recently removed). Not transient; drop thatdeviceIdor re-check the discovery endpoint.evicted: a newer connection took the home's slot. Not transient; do not reconnect immediately or you will evict whoever takes the slot next, causing a reconnect loop.
EventSource.onmessage does not fire for events with explicit types, so subscribe per event type:
const es = new EventSource(url);
es.addEventListener('measurement', (e) => { /* ... */ });
es.addEventListener('stream_error', (e) => { /* ... */ });Clients and browser usage
Most clients can use any HTTP library that supports streaming responses and set the Authorization
header normally. The browser EventSource API cannot set custom headers, so browser clients should
either use a fetch-based polyfill such as
@microsoft/fetch-event-source, or
proxy the stream through their own backend.
