Skip to content

Summaries Overview

Summary queries aggregate counts and statistics about objects over a time range rather than returning the individual records themselves. Reach for a summary when a dashboard, visualization, or broad report needs numbers across many records — for example, “how many customer visits per store per hour last week?” — without ever materializing the underlying data. The API exposes summaries for object types including activityChronicleSummary, tracksSummary, videosSummary, zoneIntersectionSummary, and deviceSummary.

This guide uses activityChronicleSummary as an example. Other summary queries follow the same shape, with different filters and bucket fields suited to their domain.

The running examples below use the grocery-store scenario from the Chronicles Overview: a chain where each customer visit is recorded as an activity chronicle on “Store #401”, with a metadata.entrance field identifying where the customer entered.

The standard summary query accepts a startTime and endTime and returns three top-level counts:

  • total — the number of activities overlapping the time range ([activityChronicle.startTime, activityChronicle.endTime] overlaps [bucket.startTime, nextBucket.startTime)).
  • startedCount — the number of activities that began within the time range (activityChronicle.startTime is within [bucket.startTime, nextBucket.startTime)).
  • endedCount — the number of activities that ended within the time range (activityChronicle.endTime is within [bucket.startTime, nextBucket.startTime)).
query StoreActivityTotals {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: {
and: [
{ name: { eq: "Customer Visit" } }
{ siteIds: { in: ["store-401-id"] } }
]
}
) {
total
startedCount
endedCount
}
}

Summaries can also return buckets for finer breakdowns and summaryStatistics for derived statistics from the activities.

Summaries support the same filtering paradigm used in connection queries. Queries can be filtered by multiple conditions, joined with and, or, and not. Summary filter types will be different from the underlying object’s filter, as some field filters make less sense in a summary context.

Bucketing groups the activities within the time range into keyed groups, each carrying its own counts. A bucket has a key — the dimension values that characterize the bucket — and the same total, startedCount, and endedCount fields as the top-level summary.

Buckets are configured through the activityChronicleBucket input, which accepts up to three distinct bucketing dimensions. A query may combine any mix of time, semantic fields, position, and metadata, as long as the total number of dimensions stays within that limit.

Set activityChronicleBucket.size to a SummaryBucketSize to group activities into evenly-spaced time intervals. The available sizes are MINUTES, MINUTES_10, MINUTES_12, MINUTES_15, MINUTES_20, MINUTES_30, HOURS, and DAYS. The minute-level sizes are only valid on summaries of up to one day. HOURS is only valid on summaries of up to seven days. DAYS is valid on any time range.

The time range will be rounded down to the nearest bucket size, and buckets will be aligned to the start of the time range.

Intervals are closed-open, [start, end).

query StoreActivityByHour {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: { name: { eq: "Customer Visit" } }
activityChronicleBucket: { size: HOURS }
) {
buckets {
key { time }
total
}
}
}

Set activityChronicleBucket.fields to group by one or more semantic fields on the activity. Available fields include NAME, STATUS, CHRONICLE_PRODUCER, SITE, DATA_SOURCE, and LABEL.

A single activity can belong to multiple sites, data sources, or labels, and bucketing on those fields will count that activity in each corresponding bucket. Top-level counts remain the distinct total, with each activity counted only once. Summing across all buckets will exceed the top-level total if an activity belongs to multiple buckets.

query VisitsByStore {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: { name: { eq: "Customer Visit" } }
activityChronicleBucket: { size: DAYS, fields: [SITE] }
) {
buckets {
key {
time
site { id name }
}
total
}
}
}

Set activityChronicleBucket.position to a PositionBucketPrecision to group activities into geographic cells using the H3 hierarchical index. Each bucket’s key carries the matching H3 cell. The enum exposes every H3 resolution (RESOLUTION_0 through RESOLUTION_15), plus semantic aliases for common use cases:

NameAlias ForApproximate average cell size
CONTINENTALRESOLUTION_0~4.3 million km²
REGIONALRESOLUTION_4~1,700 km²
DISTRICTRESOLUTION_7~5 km²
NEIGHBORHOODRESOLUTION_9~100,000 m²
BUILDINGRESOLUTION_12~300 m²

Activities without a recorded position are grouped into a single bucket with a null position key.

query VisitsByNeighborhood {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: { name: { eq: "Customer Visit" } }
activityChronicleBucket: { position: NEIGHBORHOOD }
) {
buckets {
key {
position {
index
center { coordinates }
}
}
total
}
}
}

Set activityChronicleBucket.metadata to one or more JSONFieldStringBucket values to group by user-defined metadata. When multiple metadata entries are specified, bucket by each distinct combination of the metadata values. Each entry supplies a path into the metadata object — a list of keys or array indices — and the bucket is keyed on the string value (cast to string if not already) found at that path. Activities with no value at the path are grouped into a single bucket with a null value.

The grocery-store example uses this dimension to split customer visits by entrance:

query HourlyVisitsByEntrance {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: {
and: [
{ name: { eq: "Customer Visit" } }
{ siteIds: { in: ["store-401-id"] } }
]
}
activityChronicleBucket: {
size: HOURS
metadata: [{ path: ["entrance"] }]
}
) {
buckets {
key {
time
metadata { path string }
}
total
}
}
}

Before reaching for metadata bucketing, prefer first-class fields like SITE or LABEL if the data you need is available there. Metadata bucketing is useful for dimensions the schema does not model, but the semantic fields are the native path for this kind of grouping.

To discover which keys are available for metadata bucketing, read the metadataKeys field on the summary, which returns the distinct top-level keys present across the matching activities. For nested metadata objects, the metadataKeysPath query argument restricts the list to keys at a specific JSON path.

The query below would return "entrance" as a top-level key, as it is the only metadata key present in our sample activities.

query ActivityChronicleMetadataKeys {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
activityChronicleBucket: { size: DAYS }
) {
metadataKeys
}
}

For data occuring over a timerange, the bucketingStrategy argument controls how an activity that straddles multiple time buckets is counted. Three strategies are available:

  • ACTIVE (default) — an activity is counted in every bucket it overlaps. With an hourly bucket, a two-hour visit is counted in both hours. This is the right choice when the question is “how many activities were in progress during this bucket?”
  • STARTED — an activity is counted only in the bucket where it started. Each activity appears in exactly one bucket, so bucket totals sum cleanly to the top-level startedCount.
  • ENDED — an activity is counted only in the bucket where it ended. Each activity appears in exactly one bucket, so bucket totals sum cleanly to the top-level endedCount.

STARTED and ENDED are common when each activity should be attributed to a single bucket — for example, “new visits per hour” rather than “visits concurrently in progress per hour.”

When not bucketing by time, the bucketingStrategy is still used for determining which activities fall within the queried time range. For example, a bucketingStrategy of STARTED across a single day would only summarize activities that started during that day.

query NewVisitsByHour {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: { name: { eq: "Customer Visit" } }
bucketingStrategy: STARTED
activityChronicleBucket: { size: HOURS }
) {
buckets {
key { time }
total
}
}
}

Summaries support richer per-group statistics (min/max/avg of numeric attributes) through the summaryStatistics field.

For activity chronicles and other summaries of items over a time range (tracks and videos), summaryStatistics.duration exposes the minimum, average, and maximum duration across finished activities in the time range or bucket. Unfinished activities are excluded, and the field returns null if no activities are finished.

query VisitDurationStats {
activityChronicleSummary(
startTime: "2026-03-18T00:00:00-04:00"
endTime: "2026-03-25T00:00:00-04:00"
filter: { name: { eq: "Customer Visit" } }
activityChronicleBucket: { size: DAYS }
) {
summaryStatistics {
duration { min average max }
}
buckets {
key { time }
total
summaryStatistics {
duration { min average max }
}
}
}
}

Summaries are designed to answer broad questions efficiently, but a few guidelines keep them fast.

Bucket count cap. Each summary query is validated against an approximate bucket count before it runs. If a query is rejected or if performance is a concern, widen the bucket size, reduce the number of bucketing dimensions, or tighten the filter. The bucket limit is currently 25,000 distinct buckets.

Bucketing strategy. STARTED and ENDED are generally faster than ACTIVE because each activity contributes to only one bucket. The difference depends on the specific query, so it is worth comparing to ACTIVE when tuning a real dashboard.

Request only the fields you need. Summary response fields — such as summaryStatistics, startedCount, endedCount, and metadataKeys — can result in more complex queries which are avoided when those fields are not selected. When optimizing a query, trim any response fields the consumer is not using.