19 KiB
analytics-api Specification
Purpose
Usage analytics of the deployment read from the rollup collections: dense time series, breakdowns, KPI
summary and CSV export, with the source collection chosen by granularity and every query backed by the
{ scope, bucket } index.
Requirements
Requirement: Series endpoint
Edition: CE. GET /api/v1/analytics/series SHALL accept metrics (comma-separated, at least one), from, to,
tz, granularity and scope (default global) and answer the time-series envelope extended with scope, one
series per requested metric named as the metric, where egressEnded expands into one series per stored end status
named egressEnded.<status> (egressEnded.complete). Unknown metrics, duplicated metrics and a malformed scope
SHALL be rejected with 400.
Scenario: Default request
- GIVEN the current time is 2026-10-13T12:00:00Z
- WHEN the client requests
GET /api/v1/analytics/series?metrics=roomsCreated,bytesUp - THEN the response is
200withscope: "global",granularity: "5m",from: "2026-10-12T12:00:00.000Z",to: "2026-10-13T12:00:00.000Z"and two seriesroomsCreatedandbytesUpof 288 points each
Scenario: Egress end statuses
- GIVEN egresses that ended as
completeandfailedin the range (EGRESS_COMPLETEandEGRESS_FAILEDin the LiveKit events) - WHEN the client requests
metrics=egressEnded - THEN the response has the series
egressEnded.completeandegressEnded.failedand no series for statuses absent from the range
Scenario: Unknown metric
- WHEN the client requests
metrics=roomsCreated,roomsDeleted - THEN the response is
400with codeVALIDATION_ERRORanddetails.issues[0].pathequal to["metrics"]
Requirement: Metric catalogue and fill values
Edition: CE. The CE metrics SHALL be the eighteen of the "CE metrics" scenario; counters and sums fill empty buckets with
0, rates and averages with null. Participant figures count STANDARD sessions only, except the kind breakdown.
ingressSessions SHALL sum the rollups' ingress.sessionsStarted and ingressDurationMs their ingress.durationMs.
Scenario: Connection success rate
- GIVEN a bucket with
participants.joined: 40andparticipants.active: 38and an empty bucket - WHEN
metrics=connectionSuccessRateis requested - THEN the first point is
95.0(percentage with one decimal) and the empty bucket holdsnull
Scenario: Participant minutes
- GIVEN a bucket with
participants.minutes: 1234 - WHEN
metrics=participantMinutesis requested - THEN the point is
1234and empty buckets hold0
Scenario: Services are not participants
- GIVEN an hour in which 40 STANDARD and 6 EGRESS participants joined
- WHEN
metrics=participantsJoinedand the breakdowndimensions=kindare requested - THEN the series point is
40and the breakdown hasSTANDARD: 40andEGRESS: 6
Scenario: Ingress metrics on CE
- GIVEN a valid token on the CE dashboard and one hour of the last 24 h whose hourly document has
ingress.sessionsStarted: 3andingress.durationMs: 5400000 - WHEN the client requests
metrics=ingressSessions,ingressDurationMs&granularity=1h - THEN the response is
200with the seriesingressSessionsandingressDurationMsof 24 points, that hour holds3and5400000and the other hours hold0
Scenario: CE metrics
- WHEN a CE client requests analytics series
- THEN the metrics it can request are
roomsCreated,roomsEnded,participantsJoined,participantsActive,participantsAborted,connectionSuccessRate,uniqueIdentities,participantMinutes,tracksPublished,egressStarted,egressEnded,egressDurationMs,ingressSessions,ingressDurationMs,bytesUp,bytesDown,apiCallsandapiErrors
Requirement: Granularity and source collection
Edition: CE. Analytics series SHALL accept granularity auto, 5m, 15m, 1h, 6h and 1d, reject 1m,
read analytics_5min for 5m and 15m, analytics_hourly for 1h and 6h and analytics_daily for 1d, and
SHALL never read entity or stats collections. Sub-hour values SHALL require a range of at most 24 h whose from is
inside the 5-minute rollup retention. auto SHALL resolve to 5m (≤ 24 h inside that retention), 1h (≤ 7 d), 6h
(≤ 90 d), otherwise 1d.
Scenario: Automatic resolution by range
- WHEN series are requested with
granularity=autofor ranges of 24 h, 7 d, 30 d and 100 d - THEN the responses use
5m,1h,6hand1dand the integration test observes reads ofanalytics_5min,analytics_hourly,analytics_hourlyandanalytics_dailyrespectively
Scenario: Sub-hour granularity on a long range
- WHEN the client requests
granularity=5mwithfrom=now-48h - THEN the response is
400with codeVALIDATION_ERRORand a message stating that5mand15mrequire a range of at most 24 hours
Scenario: Day-old range beyond the stats retention
- GIVEN the stats and 5-minute rollup retentions are 7 d
- WHEN the client requests
granularity=autofor a 24 h range that ended 30 days ago - THEN the response uses
granularity: "1h"read fromanalytics_hourly, and an explicitgranularity=5mfor the same range is rejected with400stating "5m and 15m require a range inside the 5-minute rollup retention (7 days)"
Scenario: 5-minute rollups kept apart from the stats
- GIVEN a stats retention of 7 d and a 5-minute rollup retention of 2 d
- WHEN the client requests
granularity=autofor a 24 h range that ended 3 days ago - THEN the response uses
granularity: "1h"
Scenario: Retention named in hours
- GIVEN a 5-minute rollup retention of 36 h
- WHEN the client requests
granularity=5mfor a 24 h range that ended 2 days ago - THEN the response is
400stating "5m and 15m require a range inside the 5-minute rollup retention (36 hours)"
Scenario: Minute granularity and the bucket cap
- WHEN the client requests
granularity=1mfor any range, orgranularity=1hfor a 60-day range - THEN both responses are
400, the first stating that the finest analytics granularity is5mand the second stating the 1 000-bucket limit
Requirement: Hourly-only metrics
Edition: CE. participantMinutes, tracksPublished, egressEnded, egressDurationMs, ingressSessions,
ingressDurationMs, apiCalls and apiErrors SHALL be served only at 1h or coarser; a request for any of them
with an explicit or resolved granularity below 1h SHALL be rejected with 400 naming the metric and the
granularity.
Scenario: Hourly-only metric at five minutes
- WHEN the client requests
metrics=egressStarted,egressEndedwithgranularity=autoover 24 h - THEN the response is
400with codeVALIDATION_ERRORand a message stating thategressEndedrequires a granularity of at least1h, and the same metrics withgranularity=1hanswer200with 24 points per series
Requirement: Timezone-aware buckets
Edition: CE. 6h and 1d buckets SHALL start at local midnight boundaries of tz; in a zone other than UTC,
1d buckets SHALL be built by summing hourly documents per local day while the range is inside the hourly
retention and SHALL otherwise fall back to UTC-day documents. uniqueIdentities SHALL always be reported per UTC
day, marked basis: "utcDay" on the series.
Scenario: Daily buckets in New York
- GIVEN
tz=America/New_Yorkandgranularity=1dover 7 days - WHEN the series is requested
- THEN every
tsis a local midnight serialised in UTC (…T04:00:00.000Zor…T05:00:00.000Z) and each point equals the sum of the 24 hourly documents of that local day (23 or 25 on a DST change day)
Scenario: Unique identities in Madrid
- GIVEN
tz=Europe/Madridandgranularity=1d - WHEN
metrics=uniqueIdentities,roomsCreatedis requested - THEN the
uniqueIdentitiesseries carriesbasis: "utcDay"withtsat…T00:00:00.000ZandroomsCreatedhas nobasisandtsat local midnight
Requirement: Dense series and bucket aggregation
Edition: CE. Every series SHALL contain exactly one point per bucket from from floored to the bucket start up to
and including the bucket containing to; when a bucket spans several rollup documents, counters SHALL be summed and
connectionSuccessRate SHALL be computed from the summed numerators and denominators.
Scenario: Sparse documents in a day
- GIVEN
analytics_5minholds documents for only three 5-minute buckets of the last 24 h - WHEN
metrics=roomsCreated,connectionSuccessRateis requested withgranularity=5m - THEN both series have 288 points,
roomsCreatedholds0outside the three buckets andconnectionSuccessRateholdsnull
Scenario: Fifteen-minute rate from three documents
- GIVEN three consecutive 5-minute documents with
participants.active1, 2 and 0 andparticipants.joined2, 2 and 1 - WHEN
granularity=15mis requested withmetrics=connectionSuccessRate,participantsJoined - THEN the bucket holds
connectionSuccessRate: 60(3 / 5, one decimal) andparticipantsJoined: 5
Requirement: Unique identities only at daily granularity
Edition: CE. uniqueIdentities SHALL be served only with granularity=1d; requesting it with any other explicit
or resolved granularity SHALL be rejected with 400 naming the metric.
Scenario: Unique identities on a 24 h range
- WHEN the client requests
metrics=uniqueIdentitieswithgranularity=autoover 24 h - THEN the response is
400with codeVALIDATION_ERRORand a message stating thatuniqueIdentitiesrequiresgranularity=1d
Requirement: Breakdowns endpoint
Edition: CE. GET /api/v1/analytics/breakdowns SHALL accept dimensions (comma-separated, at least one), from,
to, tz and scope and answer { from, to, scope, breakdowns: [{ dimension, total, items: [{ key, count, percent }] }] } with items sorted by count descending, percent as one decimal of total, keys as stored
(protocol enums in UPPER_SNAKE, dashboard vocabularies in lower case) and an unknown dimension rejected with 400.
Scenario: SDK breakdown
- GIVEN rollups in range with
participants.bySdksumming tojs: 600,swift: 300,android: 100 - WHEN
dimensions=sdkis requested - THEN the breakdown has
total: 1000and itemsjs600 / 60.0,swift300 / 30.0,android100 / 10.0
Scenario: Dimension without data
- GIVEN no egress in the range
- WHEN
dimensions=egressTypeis requested - THEN the breakdown has
total: 0anditems: []
Requirement: Breakdown and summary source and range snapping
Edition: CE. Breakdowns and the summary SHALL read analytics_hourly for ranges up to 90 days, snapping from down
and to up to whole hours, and analytics_daily beyond, snapping to whole UTC days; the response from/to SHALL
report the snapped range. Entity and stats collections SHALL never be read by these endpoints.
Scenario: One-hour preset
- GIVEN the current time is 12:40:00Z
- WHEN breakdowns are requested with
from=now-1h&to=now - THEN the response has
from: "…T11:00:00.000Z",to: "…T13:00:00.000Z"and the integration test observes two hourly documents read per scope
Scenario: Long range reads daily documents
- WHEN the summary is requested for the last 120 days
- THEN
analytics_dailyis read,fromandtoare UTC midnights and no hourly document is read
Requirement: Summary endpoint
Edition: CE. GET /api/v1/analytics/summary SHALL accept from, to, tz and scope and answer the fifteen KPI
values of the "KPI keys" scenario, omitting any value the stored documents cannot derive. ingressSessions and
ingressDurationMs SHALL be the sums of ingress.sessionsStarted and ingress.durationMs over the snapped range, 0
without ingress activity.
Scenario: Summary values
- GIVEN hourly documents in range summing to
rooms.created: 120,rooms.ended: 110,participants.joined: 900,participants.active: 880,apiCalls.total: 5000,apiErrors.total: 50 - WHEN the summary is requested
- THEN the body has
roomsCreated: 120,roomsEnded: 110,participantSessions: 900,connectionSuccessRate: 97.8,apiCalls: 5000andapiErrorRate: 1.0
Scenario: Average room size and duration
- GIVEN hourly documents summing to
rooms.ended: 110,rooms.participantsSum: 440androoms.durationMsSum: 39600000 - WHEN the summary is requested
- THEN the body has
avgRoomSize: 4.0andavgRoomDurationMs: 360000
Scenario: Writer without the room sums
- GIVEN hourly documents written by a writer that stores no
rooms.participantsSum - WHEN the summary is requested
- THEN
avgRoomSizeandavgRoomDurationMsare absent from the body and the other values are present
Scenario: Unique identities over several days
- GIVEN
analytics_dailydocuments for the three UTC days overlapping the range withuniqueIdentities100, 120 and 90 - WHEN the summary is requested
- THEN the body has
uniqueIdentities: { value: 310, basis: "utcDays", days: 3 }
Scenario: Ingress totals
- GIVEN a valid token on the CE dashboard and hourly documents in range summing to
ingress.sessionsStarted: 12andingress.durationMs: 7200000 - WHEN the summary is requested
- THEN the response is
200withingressSessions: 12andingressDurationMs: 7200000
Scenario: No ingress in the range
- GIVEN hourly documents in range without
ingresscounters - WHEN the summary is requested
- THEN the body has
ingressSessions: 0andingressDurationMs: 0
Scenario: KPI keys
- WHEN the client requests
GET /api/v1/analytics/summaryover a range whose stored documents derive every value - THEN the response carries the KPI values
roomsCreated,roomsEnded,participantSessions,uniqueIdentities,connectionSuccessRate,avgRoomSize,avgRoomDurationMs,bytesUp,bytesDown,egressStarted,egressDurationMs,ingressSessions,ingressDurationMs,apiCallsandapiErrorRate
Requirement: PRO metrics, dimensions and scopes
Edition: CE. scoreAvg, poorSamples and lostSamples SHALL require analytics-quality;
tracksSubscribeFailed, disconnectReason, roomEndReason and apiMethod SHALL require analytics-diagnostics;
scope=node:<id> SHALL require per-node-breakdown. ingressSessions and ingressDurationMs are CE metrics and
require no feature. On CE a request containing any of the gated items SHALL be rejected whole with 403
PRO_FEATURE.
Scenario: Mixed request on CE
- GIVEN a valid token on the CE dashboard
- WHEN the client requests
metrics=roomsCreated,scoreAvg - THEN the response is
403with codePRO_FEATURE,details.feature: "analytics-quality"and no series
Scenario: Node scope on CE
- WHEN the CE client requests the summary with
scope=node:n1 - THEN the response is
403withdetails.feature: "per-node-breakdown"
Scenario: Diagnostics dimension on CE
- GIVEN a valid token on the CE dashboard
- WHEN the client requests
GET /api/v1/analytics/breakdowns?dimensions=sdk,disconnectReason - THEN the response is
403with codePRO_FEATURE,details.feature: "analytics-diagnostics"and no breakdowns
Requirement: CSV export
Edition: CE. GET /api/v1/analytics/export.csv SHALL accept kind (series or breakdowns) with the parameters
of the matching endpoint and answer 200 text/csv; charset=utf-8 as an attachment named
analytics-<kind>-<from>-<to>.csv (colons removed), RFC 4180 with CRLF line ends and a header row, with the same
validation and PRO gating as the JSON endpoints.
Scenario: Breakdown CSV
- WHEN the client requests
export.csv?kind=breakdowns&dimensions=sdk,os - THEN the body has the header
dimension,key,count,percentfollowed by thesdkrows and then theosrows, each sorted by count descending
Scenario: PRO metric in a CE export
- WHEN the CE client requests
export.csv?kind=series&metrics=scoreAvg - THEN the response is
403JSON with codePRO_FEATURE, not a CSV body
Scenario: Series CSV on CE
- WHEN the client requests
export.csv?kind=series&metrics=bytesUp,roomsCreated&granularity=1hover 24 h - THEN the body has the header
bucket,bytesUp,roomsCreatedand 24 data rows whose first cell is the bucket start in ISO 8601 UTC
Requirement: Index-backed analytics queries
Edition: CE. Every rollup read SHALL be a bounded index scan on { scope, bucket } of the chosen collection;
integration tests SHALL assert the plans with explain().
Scenario: Explain plans on the generated database
- GIVEN a database generated by the data generator at scenario M
- WHEN the integration suite explains every analytics query
- THEN no plan contains
COLLSCANand rollup plans report the{ scope, bucket }index withtotalKeysExaminedat most the number of buckets in range plus one
Requirement: Ingress input type breakdown
Edition: CE. The breakdowns endpoint SHALL accept the dimension ingressInputType, which sums ingress.byInputType
of the hourly and daily rollups in range under the keys rtmp, whip and url: the ingress sessions started in the
range by input type. It is not gated by any feature.
Scenario: Sessions by input type
- GIVEN rollups in range with
ingress.byInputTypesumming tortmp: 8,whip: 3andurl: 1 - WHEN
dimensions=ingressInputTypeis requested on CE - THEN the breakdown has
total: 12and itemsrtmp8 / 66.7,whip3 / 25.0 andurl1 / 8.3
Scenario: No ingress in the range
- GIVEN no ingress session started in the range
- WHEN
dimensions=ingressInputTypeis requested - THEN the breakdown has
total: 0anditems: []
Requirement: Media statistics coverage
Edition: CE. The summary SHALL carry mediaStats: "none" when every nodes document of its scope whose heartbeat span
(firstSeenAt to lastSeenAt) overlaps the range has mediaStats: false, mediaStats: "partial" when only some do,
and no mediaStats property when none does or no node document overlaps the range. The global scope covers every
node, a node:<id> scope that node only.
Scenario: No server reports media statistics
- GIVEN two nodes seen during the range, both with
mediaStats: false - WHEN the summary is requested
- THEN the body has
mediaStats: "none"and still carriesbytesUpandbytesDown
Scenario: Some servers report media statistics
- GIVEN a node with
mediaStats: falseand a node withmediaStats: trueseen during the range - WHEN the summary is requested with the
globalscope - THEN the body has
mediaStats: "partial"
Scenario: Node scope
- GIVEN the same two nodes and
scope=node:<id>of the node withmediaStats: true - WHEN the summary is requested
- THEN the body has no
mediaStatsproperty
Scenario: Node seen outside the range
- GIVEN a node with
mediaStats: falsewhoselastSeenAtis earlier thanfrom, and a node withmediaStats: trueseen during the range - WHEN the summary is requested
- THEN the body has no
mediaStatsproperty