Skip to main content

Telemetry

Stream live telemetry from devices, query historical data over time ranges, and fetch the latest reading for any metric.

Access via app.telemetry.

API

stream()

Subscribe to live telemetry from a device.

app.telemetry.stream(params)
ParameterTypeDescription
device_identstringThe device identifier. Must match [a-zA-Z0-9_-]+
metricstring[] or "*"Metrics to subscribe to. Use "*" for all metrics. A non-"*" string is rejected, pass an array for specific metrics
callbackfunctionCalled with each incoming reading. Python accepts both sync and async callbacks

The callback receives an object with metric and data:

FieldTypeDescription
metricstringThe metric name
data.valueanyThe reading value (matches the device schema type)
data.timestampnumberUnix milliseconds when the device published the reading
// subscribe to specific metrics
app.telemetry.stream({
device_ident: "sensor_01",
metric: ["temperature", "humidity"],
callback: (data) => {
console.log(`${data.metric}:`, data.data.value, "at", data.data.timestamp);
},
});

// subscribe to all metrics
app.telemetry.stream({
device_ident: "sensor_01",
metric: "*",
callback: (data) => {
console.log(`${data.metric}:`, data.data.value);
},
});

You can call stream() multiple times for the same device with different metric sets and callbacks. Each call creates an independent subscription.

Validation errors

The SDK throws if device_ident is empty or contains characters outside [a-zA-Z0-9_-], if metric is anything other than "*" or a non-empty array, if callback is not a function, or if the app is not connected.

off()

Unsubscribe from live telemetry.

app.telemetry.off(params)
ParameterTypeDescription
device_identstringThe device identifier
metricstring[](optional) Specific metrics to unsubscribe. Omit to unsubscribe all
// unsubscribe from specific metrics
app.telemetry.off({
device_ident: "sensor_01",
metric: ["temperature"],
});

// unsubscribe from all metrics for a device
app.telemetry.off({
device_ident: "sensor_01",
});
Wildcard subscriptions

A metric-specific off() does not tear down a wildcard ("*") subscription on the same device. If you started a stream with metric: "*", you have to call off({ device_ident }) without a metric filter to remove it. Calling off() on a device with no active subscriptions is a no-op, not an error.

history()

Query historical telemetry data over a time range.

await app.telemetry.history(params)
ParameterTypeDescription
device_identstringThe device identifier
fieldsstring[]Metrics to query
startstringStart time (ISO 8601, UTC)
endstringEnd time (ISO 8601, UTC)
intervalstringOptional. Bucket size as a Flux duration ("30s", "5m", "1h"). Must be paired with aggregate_fn.
aggregate_fnstringOptional. One of "mean", "min", "max", "sum", "count", "first", "last", "median", "stddev". Must be paired with interval.

Returns an object keyed by metric name, each containing an array of data points

const history = await app.telemetry.history({
device_ident: "sensor_01",
fields: ["temperature", "humidity"],
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-02T00:00:00.000Z",
});

// history.temperature = [{ value: 22.5, timestamp: ... }, ...]
// history.humidity = [{ value: 61.3, timestamp: ... }, ...]
history.temperature.forEach((point) => {
console.log(`${point.value} at ${point.timestamp}`);
});

Example response:

{
"temperature": [
{ "value": 22.5, "timestamp": 1774690200000 },
{ "value": 23.1, "timestamp": 1774690205000 },
{ "value": 22.8, "timestamp": 1774690210000 }
],
"humidity": [
{ "value": 61.3, "timestamp": 1774690200000 },
{ "value": 60.8, "timestamp": 1774690205000 }
]
}

If a metric has no data in the queried range, the array will be empty:

{
"temperature": [],
"humidity": []
}

Aggregating over time

Pass interval and aggregate_fn together to bucket readings into time intervals and aggregate within each bucket. This is useful for charting trends over long ranges where the raw data would be too dense to plot.

// hourly mean temperature for the last day
const hourly = await app.telemetry.history({
device_ident: "sensor_01",
fields: ["temperature"],
start: "2026-03-01T00:00:00.000Z",
end: "2026-03-02T00:00:00.000Z",
interval: "1h",
aggregate_fn: "mean",
});

The response shape is unchanged: each metric is an array of { value, timestamp } points, one per bucket boundary, aligned across all requested metrics.

interval and aggregate_fn must be paired

Passing one without the other has no effect. Both are required to switch from raw points to aggregated buckets.

History retention

History retention depends on your plan. For example, the free plan retains 3 days of history. Queries outside your retention window will return empty arrays.

latest()

Get the most recent reading for each metric.

await app.telemetry.latest(params)
ParameterTypeDescription
device_identstringThe device identifier
fieldsstring[]Metrics to query
startstringStart of lookup window (ISO 8601, UTC)
endstringEnd of lookup window (ISO 8601, UTC)

Returns an object keyed by metric name, each containing the latest value and timestamp (Unix milliseconds).

const latest = await app.telemetry.latest({
device_ident: "sensor_01",
fields: ["temperature", "humidity"],
start: "2026-03-28T00:00:00.000Z",
end: "2026-03-29T00:00:00.000Z",
});

// latest.temperature = { value: 25.3, timestamp: 1774742340000 }
console.log("Temperature:", latest.temperature.value);
console.log("Humidity:", latest.humidity.value);

Example response:

{
"temperature": { "value": 25.3, "timestamp": 1774742340000 },
"humidity": { "value": 60.1, "timestamp": 1774742325000 }
}