- Dashboards and reporting: Power real-time dashboards with summary statistics, distributions, and time-histogram charts from a single API call.
- Trend analysis: Track how metrics change over time using time-based histograms and moving window functions.
- Distribution analysis: Understand how values are spread across categories, severity levels, or numeric ranges.
- Threshold monitoring: Count records that fall into specific value ranges to detect anomalies or trigger alerts.
| Category | Aggregates | Description |
|---|---|---|
| Metric | count, avg, sum, min, max | Compute a single numeric value from a set of records |
| Bucket | uniqueValues, numberHistogram, timeHistogram, filters | Group records into buckets based on property values, numeric ranges, time intervals, or filter conditions |
| Pipeline | movingFunction | Compute derived values from the output of bucket aggregations, such as moving averages over time |
Example schema
The examples on this page use records stored in a stream with the following container schema. The containerequipment_events in space factory-data tracks equipment monitoring events across an industrial facility.
| Property | Type | Description |
|---|---|---|
equipment_id | text | Equipment identifier (e.g., PUMP-001, MOTOR-042) |
location | text | Facility location (e.g., Building-A, Building-B) |
temperature | float64 | Measured temperature in degrees Celsius |
severity | text | Event severity level (LOW, MEDIUM, HIGH, CRITICAL) |
priority | int64 | Priority score from 1 (lowest) to 100 (highest) |
is_critical | boolean | Whether the event requires immediate attention |
recorded_at | timestamp | When the event was recorded |
Request structure
Send aggregation requests to the aggregate endpoint:aggregates object that maps your chosen names to aggregate definitions. You can optionally include a filter to narrow the dataset and a lastUpdatedTime range.
Example request structure
Time-based filtering with lastUpdatedTimeThe
lastUpdatedTime filter is mandatory for aggregate queries on immutable streams. Immutable streams are optimized for high-volume, append-only data and are internally organized by time. The service uses lastUpdatedTime to efficiently target only the relevant data partitions, avoiding costly full-stream scans across potentially billions of records. Without this constraint, aggregate queries on large immutable streams would be prohibitively slow.The filter defines the time range for the aggregation:gt(greater than) /gte(greater than or equal to): The start of the time range (required for immutable streams).lt(less than) /lte(less than or equal to): The end of the time range (optional, defaults to current time).
maxFilteringInterval setting (an ISO 8601 duration, for example P1Y for one year). This limit is defined by the stream template selected when the stream was created, and it applies to the span between gt and lt, not to how far back in time you can reach — you can query any historical period as long as each request stays within the interval. If the difference between gt and lt exceeds this interval, the API returns a validation error.To query data spanning more than this interval, split your requests into adjacent time windows. See Query time range limits for details and examples.For mutable streams, lastUpdatedTime is optional, but using it improves query performance.Property paths
Aggregations reference container properties using an array of three strings: the space, the container external ID, and the property name.["createdTime"]— when the record was created["lastUpdatedTime"]— when the record was last updated["space"]— the space the record belongs to (only foruniqueValues)
Naming rules
You choose the name for each aggregate in theaggregates map. Names must be 1-255 characters and cannot contain [, ], or >, since these characters are reserved for referencing aggregate paths in pipeline aggregations.
Metric aggregates
Metric aggregates compute a single numeric value from all records that match your filter. You can combine multiple metric aggregates in a single request, similar to runningSELECT COUNT(*), AVG(temperature), SUM(priority), MIN(temperature), MAX(temperature) FROM equipment_events WHERE ... in SQL.
Null value handlingFor all metric aggregates except
count without a property, records with a null or missing value for the specified property are excluded from the calculation. They do not affect the result. For example, avg computes the average only over records that have a non-null value, so 10 records where 3 have null temperatures would compute the average from the remaining 7 records.The count aggregate without a property counts all matching records regardless of null values. With a property specified, count only counts records where that property has a non-null value.count
Returns the number of records. Without aproperty, it counts all matching records. With a property, it counts only records that have a non-null value for that property.
curl
Example response
avg
Computes the arithmetic mean of a numeric property across all matching records. Records with null values for the specified property are excluded from the calculation. Supported property types:int32, int64, float32, float64
curl
Example response
sum
Computes the total sum of values for a numeric property. Useful for calculating totals like cumulative priority scores or aggregated measurements. Supported property types:int32, int64, float32, float64
curl
Example response
min
Returns the lowest value for a property. Works with numeric and timestamp properties, including the top-levelcreatedTime and lastUpdatedTime fields.
Supported property types: int32, int64, float32, float64, timestamp, date
curl
Example response
max
Returns the highest value for a property. Works with the same property types asmin.
Supported property types: int32, int64, float32, float64, timestamp, date
curl
Example response
Combined metrics example
You can request multiple metric aggregates in a single call to get a complete statistical summary without making separate requests. This example also demonstrates how to combinehasData with a range condition using and to compute metrics only for records where the temperature exceeds 85.5 degrees Celsius.
curl
Example response
and filter narrows the dataset to only records from the equipment_events container where temperature is above 85.5 degrees Celsius, so all returned metrics reflect that subset. You can use any filter expression here, including or, not, equals, prefix, and nested combinations.
Bucket aggregates
Bucket aggregates group records into categories (called buckets) based on property values, numeric ranges, time intervals, or filter conditions. Each bucket includes a count of matching records and can contain nested sub-aggregates that compute additional metrics per bucket.uniqueValues
Groups records by the distinct values of a property and returns one bucket per unique value, ordered by count (highest first). This is the equivalent ofGROUP BY in SQL, .groupby() in pandas, or .group_by() in Polars, useful for understanding distributions, for example, how many events exist for each severity level or which equipment generates the most events.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
property | array | Yes | The property to group by. Supports text, numeric, boolean properties, and the top-level ["space"] path. |
size | integer | No | Maximum number of buckets to return. Range: 1-2000. Default: 10. |
aggregates | object | No | Nested sub-aggregates to compute within each bucket. |
Ordering and size behaviorBuckets are returned in descending order by record count (most common values first). When your data contains more unique values than the requested
size, the response returns only the top buckets by count. The remaining values are not included in the response. If you need to see all unique values, increase size up to the maximum of 2000.Approximate countsCounts may be approximate when the dataset is very large. Because records are distributed across multiple partitions for performance, each partition independently identifies its top values and these are merged to produce the final result. This means a value that is moderately common across many partitions might not appear in every partition’s top list, leading to a slight undercount. For most use cases, the approximation is negligible and does not affect the relative ranking of top values.Missing valuesRecords that do not have the specified property (or have a null value) are excluded from the aggregation. They are not counted and do not produce a bucket. Only records with a non-null value for the property contribute to the unique value buckets.Example: severity distribution with nested average
curl
Example response
numberHistogram
Divides numeric values into fixed-width intervals (buckets) and counts how many records fall into each interval. Each bucket represents a range starting at the bucket key and extending to the next interval boundary. For example, with an interval of 10, a bucket with key60.0 contains records with values in the range [60, 70).
The bucket key for a value is calculated as:
73.2 and an interval of 20 would be assigned to the bucket with key 60.0 (since floor(73.2 / 20) * 20 = 60), which covers the range [60, 80).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
property | array | Yes | The numeric property to bucket. Supports int32, int64, float32, float64. |
interval | number | Yes | The width of each bucket. Must be a positive number. |
hardBounds | object | No | Limits the range of buckets. Has optional min and max fields. Buckets outside these bounds are excluded from the response, even if matching records exist. |
aggregates | object | No | Nested sub-aggregates to compute within each bucket. |
Empty bucketsBy default, the response includes empty buckets (buckets with a count of 0) between the lowest and highest values in the data. This provides a continuous, gap-free view of the data distribution. For example, if your data has values at 20 and 80 with an interval of 20, you will get buckets for 20, 40, 60, and 80, even if the 40 and 60 buckets have a count of 0. The first bucket is determined by the smallest value in the data, and the last bucket by the largest value.Hard bounds vs. filtersUse
hardBounds to limit which buckets appear in the response. This is purely a display-level control, it does not affect which records are aggregated, only which buckets are included in the output. Records outside the bounds are still counted if they fall into a bucket that starts within the bounds.This is different from using a filter in the request, which restricts which records are considered for the entire aggregation. For example, if you set hardBounds: { "min": 40, "max": 120 }, records with temperatures below 40 are still counted (in the bucket whose range starts below 40 if it overlaps with the bounds), but the response only includes buckets whose keys fall within [40, 120).If you only need results for a specific range, prefer a filter over hardBounds. Filters reduce the number of records the service needs to aggregate, which improves performance. Reserve hardBounds for cases where you need multiple views in a single request, for example, a full-range metric aggregate alongside bucketed results for a narrower range.Missing valuesRecords that do not have the specified property (or have a null value) are excluded from the histogram. They do not contribute to any bucket.Example: temperature distribution
curl
Example response
timeHistogram
Divides timestamp values into time-based intervals and counts records per interval. This is the primary aggregation for building time-series charts, trend lines, and temporal analysis. Each bucket represents a time window starting at theintervalStart timestamp.
You must specify exactly one of calendarInterval or fixedInterval:
- calendarInterval: Uses calendar-aware intervals that respect natural time boundaries. For example,
1Mproduces monthly buckets where each month has its actual number of days (28, 29, 30, or 31), and1drespects daylight saving time transitions. Only single-unit values are allowed because calendar units vary in length (a “2-month” interval would be ambiguous since months have different numbers of days). - fixedInterval: Uses fixed-duration intervals measured in consistent time units. Any multiplier is allowed (e.g.,
30m,6h,2d). Each bucket has exactly the same duration in absolute time. Use fixed intervals when you need precise, uniform bucket sizes. For example,6halways means exactly 21,600,000 milliseconds, regardless of daylight saving time or calendar boundaries.
calendarInterval when you want human-readable time boundaries (start of each day, month, or year). Choose fixedInterval when you need precise, uniform durations (every 30 minutes, every 6 hours).
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
property | array | Yes | The timestamp property to bucket. Also supports ["createdTime"] and ["lastUpdatedTime"]. |
calendarInterval | string | One of | Calendar-aware interval. Allowed values: 1s (second), 1m (minute), 1h (hour), 1d (day), 1w (week), 1M (month), 1q (quarter), 1y (year). |
fixedInterval | string | One of | Fixed-duration interval as a duration string. Supported units: ms (milliseconds), s (seconds), m (minutes), h (hours), d (days). Any positive multiplier is allowed (e.g., 30m, 6h, 2d). |
hardBounds | object | No | Limits the range of time buckets. Has optional min and max fields as ISO 8601 timestamps. |
aggregates | object | No | Nested sub-aggregates to compute within each bucket. |
timeHistogram handles edge cases such as empty intervals, bounded time ranges, and missing data.
Empty bucketsLike
numberHistogram, the timeHistogram returns empty buckets (count of 0) between the earliest and latest data points. This ensures a continuous time series with no gaps, which is important for charting and trend analysis. For example, if your data has records at 08:00 and 14:00 with a calendarInterval of "1h", you will get buckets for every hour from 08:00 through 14:00, including hours with no records.Hard bounds for time rangesUse hardBounds with min and max (ISO 8601 timestamps) to limit which time buckets appear in the response. This is useful when you want a fixed time window regardless of where your data starts and ends. Like numberHistogram, hardBounds controls only which buckets are returned, it does not filter which records are aggregated.Missing valuesRecords that do not have the specified timestamp property (or have a null value) are excluded from the time histogram. They do not contribute to any time bucket.Example: hourly event counts
curl
Example response
filters
Creates one bucket per filter expression, where each bucket contains the count of records matching that filter. Use this aggregation to segment records into categories defined by arbitrary conditions. For example, splitting events into severity bands based on numeric ranges, or comparing events across different equipment types. Each filter uses the same filter syntax available on the filter and sync endpoints.Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
filters | array | Yes | An array of 1-10 filter expressions. Each filter defines one bucket. |
aggregates | object | No | Nested sub-aggregates to compute within each bucket. |
Bucket orderingBuckets are returned in the same order as the filters in your request. Since the filters are provided as an array (not a named map), you identify each bucket by its position. For example, the first bucket in the
filterBuckets response array corresponds to the first filter in your filters array.Overlapping filtersA record can match multiple filters and appear in more than one bucket. The filters are evaluated independently, they do not partition the data into mutually exclusive groups. If you need non-overlapping segments, design your filter conditions so they don’t overlap (for example, use lt and gte to create adjacent ranges without gaps or overlaps).LimitsA single request can have at most 30 filter buckets across all filters aggregates combined. For example, if you have three filters aggregates with 10 filters each, that uses all 30.Example: temperature severity bands
curl
Example response
Pipeline aggregates
Pipeline aggregates compute derived values from the output of bucket aggregations. Unlike metric and bucket aggregates that operate on records directly, pipeline aggregates process the results of other aggregates, making them ideal for trend smoothing, rate calculations, and comparative analysis.movingFunction
Slides a window across the buckets of a histogram and applies a function to the values within that window. This is commonly used to smooth noisy data by computing a moving average over time, or to calculate rolling sums and extremes. ThemovingFunction aggregate must be nested inside a numberHistogram or timeHistogram. It cannot be used as a standalone aggregate.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bucketsPath | string | Yes | Path to the metric to use as input. Use _count for the bucket record count, or the name of a sibling metric aggregate. |
window | integer | Yes | Size of the sliding window in number of buckets. Must be at least 1. |
function | string | Yes | The function to apply over the window. |
Available functions
| Function | Description |
|---|---|
MovingFunctions.unweightedAvg | Simple average of all values in the window. The most common choice for trend smoothing. |
MovingFunctions.linearWeightedAvg | Weighted average where more recent values have higher weight. Gives a trend line that responds faster to recent changes. |
MovingFunctions.sum | Sum of all values in the window. Useful for calculating rolling totals. |
MovingFunctions.min | Minimum value in the window. |
MovingFunctions.max | Maximum value in the window. |
Window warm-upThe first few buckets in the response will have a window smaller than the specified
window size because there aren’t enough preceding buckets yet. For example, with window: 3:- Bucket 1: the
movingFunctionreceives only one value (the current bucket), soMovingFunctions.unweightedAvgreturns 0 because it requires at least 2 values. - Bucket 2: the window contains 2 values (buckets 1 and 2), so the average is computed from those two.
- Bucket 3 onward: the full 3-bucket window is used.
MovingFunctions.unweightedAvg, the first bucket always returns 0 as the fnValue.Handling gaps in dataWhen a bucket in the window has no data (a gap), the function skips that bucket and computes the result from the remaining values in the window. This prevents empty time periods from distorting the smoothed trend line. For example, if a 3-bucket window covers hours 10:00, 11:00, and 12:00, but 11:00 has no records, the moving average is computed from only the 10:00 and 12:00 values.Pipeline vs. sub-aggregatePipeline aggregates like movingFunction are fundamentally different from sub-aggregates. Sub-aggregates (like nesting avg inside timeHistogram) operate on the records within each bucket. Pipeline aggregates operate on the computed results of other aggregates across buckets — they process bucket-level outputs, not individual records.bucketsPath syntax
ThebucketsPath specifies which value from the parent histogram buckets to feed into the function. Paths are relative to the parent histogram aggregate, not absolute paths from the root of the request.
The formal syntax is:
| Path | Description |
|---|---|
_count | The record count of each bucket. Use this when you want to apply the moving function to the number of records per bucket. |
my_metric | The result of a sibling metric aggregate named my_metric. For example, if you have a sibling avg aggregate named avg_temp, use "bucketsPath": "avg_temp". |
my_agg>nested_metric | A metric inside a nested aggregate. Use > to traverse levels. For example, "by_severity>avg_temp" accesses the avg_temp metric inside the by_severity bucket aggregate. |
my_agg[bucket_key]>metric | A metric inside a specific bucket of a multi-bucket aggregate. For example, "by_severity[HIGH]>avg_temp" accesses the average temperature for the “HIGH” severity bucket only. |
Example: 3-hour moving average of event counts
curl
Example response
Nesting aggregates
Bucket aggregates (uniqueValues, numberHistogram, timeHistogram, and filters) can contain nested sub-aggregates of any type — including other bucket aggregates. This lets you build multi-dimensional analyses in a single API call.
How nesting works
When you add sub-aggregates to a bucket aggregate, the sub-aggregates are computed independently within each bucket. For example, nesting anavg inside a uniqueValues aggregate computes a separate average for each unique value group.
You can nest aggregates up to 5 levels deep, with up to 5 aggregates per level and 16 total aggregates in the request.
While nesting is powerful, deep nesting with high-cardinality data can be expensive. Keep these performance considerations in mind.
Example: equipment breakdown by location and severity
This example uses two levels ofuniqueValues with a nested avg to answer: “For each location, what are the top severity levels and their average temperatures?”
curl
Example response
Example: time-series with severity bands and trend line
This example combinestimeHistogram, filters, and movingFunction to create a stacked severity breakdown over time with a smoothed trend line — all in a single request. This is a common pattern for alarm monitoring dashboards.
curl
Example response
filterBuckets are ordered to match the filter array: index 0 is LOW, index 1 is MEDIUM, and index 2 is HIGH+CRITICAL.
Limits and constraints
The aggregate endpoint enforces limits on request complexity to ensure consistent performance. These limits apply to the aggregate tree structure, individual aggregate parameters, and the overall request.Aggregate tree limits
| Constraint | Value |
|---|---|
| Max aggregates per level | 5 |
| Max aggregate tree depth | 5 |
| Max total aggregates in request | 16 |
| Max total buckets per request | 10,000 |
| Aggregate name length | 1-255 characters |
| Aggregate name pattern | Cannot contain [, ], or > |
Aggregate-specific limits
| Aggregate | Constraint | Value |
|---|---|---|
uniqueValues | size range | 1-2000 (default: 10) |
filters | Filters per aggregate | 1-10 |
filters | Total filter buckets across all filters aggregates | 30 |
movingFunction | Minimum window | 1 |
movingFunction | bucketsPath length | 1-1280 characters |
timeHistogram | calendarInterval values | 1s (second), 1m (minute), 1h (hour), 1d (day), 1w (week), 1M (month), 1q (quarter), 1y (year) |
timeHistogram | fixedInterval units | ms, s, m, h, d (any positive multiplier, e.g., 30m, 6h) |
timeHistogram | fixedInterval length | 1-100 characters |
General request limits
| Constraint | Value |
|---|---|
lastUpdatedTime | Required for immutable streams, optional for mutable |
maxFilteringInterval | Maximum time range between gt and lt in lastUpdatedTime. Defined by the stream template (e.g., P1Y for one year). Retrieve from stream settings. |
| Rate limits | Aggregate requests have stricter rate limits than filter or sync requests. See resource throttling. |
Common patterns
Trend analysis
CombinetimeHistogram with movingFunction to smooth noisy time-series data and identify trends:
Distribution analysis
UseuniqueValues with nested metrics to understand how a categorical dimension relates to numeric measures:
Threshold monitoring
Usefilters to count records in specific value ranges for threshold-based alerting:
Further reading
- About records and streams — Core concepts for records, streams, and containers
- Get started with Records — Hands-on tutorial for setting up schemas, streams, and queries
- Filter syntax — Filter expressions used with the
filterparameter and thefiltersaggregate - Limits and restrictions — Full list of Records API limits