Run analytics queries programmatically against the same engine that powers dashboard charts, overview KPIs, and saved tiles. The Insights API is a server-side interface - it is not callable from browser clients.
Service: shared.insights.v1.InsightsService
Auth: Dual - private key (prv_...) via x-api-key or dashboard JWT via Authorization: Bearer. See Authentication.
Methods
Query
POST /shared.insights.v1.InsightsService/QueryRuns an analytics query and returns a typed result. The shape of the result depends on spec.insight_type (see Insight types).
Request: QueryRequest
| Field | Type | Required | Description |
|---|---|---|---|
spec |
InsightQuerySpec |
Yes | The query specification (insight type, events, breakdowns, filters). |
time_range |
TimeRange |
Yes | Absolute start/end (from, to) as RFC 3339 timestamps. from must be before to. |
granularity |
Granularity |
Yes | Time bucket size. See Granularities. |
Response: QueryResponse
The response carries exactly one of the following fields, selected by spec.insight_type:
| Field | Type | Populated when |
|---|---|---|
trends |
TrendsResult |
INSIGHT_TYPE_TRENDS |
segmentation |
SegmentationResult |
INSIGHT_TYPE_SEGMENTATION |
funnel |
FunnelResult |
INSIGHT_TYPE_FUNNEL |
retention |
RetentionResult |
INSIGHT_TYPE_RETENTION |
user_flow |
UserFlowResult |
INSIGHT_TYPE_USER_FLOW |
top_k |
TopKResult |
INSIGHT_TYPE_TOP_K, INSIGHT_TYPE_MAP |
SegmentUsers
POST /shared.insights.v1.InsightsService/SegmentUsersReturns a paginated list of distinct user IDs that match a set of event conditions within a time range. Useful for audience export or cross-referencing profiles.
| Field | Type | Description |
|---|---|---|
time_range |
TimeRange |
Required. |
events |
EventQuery[] |
Required, at least one. |
filter_groups |
FilterGroup[] |
Optional top-level filters. |
filter_groups_operator |
LogicalOperator |
How groups are combined (default AND). |
page_size |
int32 |
0-1000. |
page_token |
string |
Cursor from previous response. |
Response: { distinct_ids: string[], next_page_token: string }.
GetFilterSchema
POST /shared.insights.v1.InsightsService/GetFilterSchemaReturns the set of known event kinds and property keys (auto, custom, profile) for the project. Use this to populate filter pickers. The response shape is common.v1.GetFilterSchemaResponse. Optionally scoped to a single event kind or filtered to specific PropertyValueType values.
GetPropertyValues
POST /shared.insights.v1.InsightsService/GetPropertyValuesReturns the distinct observed values for a specific property key. Useful for populating FILTER_OPERATOR_IN value lists.
| Field | Type | Required | Description |
|---|---|---|---|
property_key |
string |
Yes | The property to enumerate (e.g. $country, plan). Include the $ prefix for auto-properties. |
source |
PropertySource |
Yes | Which namespace the key lives in: PROPERTY_SOURCE_AUTO, PROPERTY_SOURCE_CUSTOM, or PROPERTY_SOURCE_PROFILE. |
event_kind |
string |
No | Restrict observed values to a single event kind. Omit to scan across all events. |
Response: { "values": string[] } - the distinct observed values.
{ "propertyKey": "$country", "source": "PROPERTY_SOURCE_AUTO" }Insight types
InsightQuerySpec.insight_type selects the computation and the response field.
| Enum value | Description |
|---|---|
INSIGHT_TYPE_TRENDS |
Time-series event counts or aggregations. One TrendSeries per (event, breakdown) combination. |
INSIGHT_TYPE_SEGMENTATION |
Single scalar: total count or aggregation across the full time range, no time axis. |
INSIGHT_TYPE_FUNNEL |
Multi-step conversion funnel. Steps are the events array in order. Each series carries per-step counts; optionally includes per-step timing statistics when include_step_timing is true. |
INSIGHT_TYPE_RETENTION |
Cohort retention matrix. events[0] is the starting event; events[1] (optional) is the return event. Returns cohorts bucketed by the query granularity. |
INSIGHT_TYPE_USER_FLOW |
Sankey / path graph showing transitions between events or property values. Requires a user_flow sub-spec; no events field. |
INSIGHT_TYPE_TOP_K |
Ranking: the top K values of a dimension by an aggregate metric, plus a trailing $others bucket. Requires a top_k sub-spec; no events field, no time axis. See TopKQuery. |
INSIGHT_TYPE_MAP |
Choropleth: one metric per country. The dimension is fixed to $country, which is what makes it an insight type rather than a chart style. Requires a map sub-spec; no events field, no time axis. Results arrive in top_k. See MapQuery. |
Insight type constraints
INSIGHT_TYPE_FUNNEL: requires at least one event; supportsbreakdowns(max 5); supportsconversion_window(whole seconds, min 1 s); supportsinclude_step_timing.INSIGHT_TYPE_RETENTION: requires at least one event; accepts at most 2 (events[0]= start,events[1]= return).INSIGHT_TYPE_SEGMENTATION: does not supportbreakdowns.INSIGHT_TYPE_USER_FLOW: requires auser_flowsub-spec;eventsmust be empty;breakdownsare not supported.INSIGHT_TYPE_TOP_K: requires atop_ksub-spec;events,session,breakdownsandbreakdown_limitmust all be empty. Scope events throughtop_k.scopeinstead.INSIGHT_TYPE_MAP: requires amapsub-spec;events,session,breakdownsandbreakdown_limitmust all be empty. Scope events throughmap.scopeinstead. The country dimension is implied, so there is nothing to break down by and no top-N to bound.INSIGHT_TYPE_TRENDS/INSIGHT_TYPE_SEGMENTATION: support numeric aggregations (AGGREGATION_TYPE_SUM,AVG,MIN,MAX); these are not valid for funnel or retention.
Granularities
QueryRequest.granularity controls the time-bucket width and imposes a maximum query range.
| Enum value | Bucket width | Maximum time range |
|---|---|---|
GRANULARITY_MINUTE |
1 minute | 6 hours |
GRANULARITY_HOUR |
1 hour | 14 days |
GRANULARITY_DAY |
1 day | 365 days |
GRANULARITY_WEEK |
1 week | ~4 years (1461 days) |
GRANULARITY_MONTH |
1 month | ~10 years (3652 days) |
InsightQuerySpec fields
| Field | Type | Description |
|---|---|---|
insight_type |
InsightType |
Required. Selects the computation and response shape. |
events |
EventQuery[] |
Event series (for trends, segmentation, funnel, retention). Each entry selects one event kind with optional filters and an aggregation type. |
breakdowns |
Breakdown[] |
Group results by up to 5 properties ({ property: string }). Not supported for segmentation or user flow. |
breakdown_limit |
int32 |
Cap on number of breakdown values returned (0-100). Requires at least one breakdown. |
filter_groups |
FilterGroup[] |
Top-level property filters. Multiple groups are combined by filter_groups_operator. |
filter_groups_operator |
LogicalOperator |
How filter groups are combined. Defaults to LOGICAL_OPERATOR_AND. |
conversion_window |
Duration |
Funnel only. Maximum time from step 1 to last step per user. Whole seconds, min 1 s. Absent = no constraint. |
include_step_timing |
bool |
Funnel only. When true, each non-entry step includes StepTiming (avg, median, p95, 8-bucket histogram). |
session |
SessionQuery |
Trends/segmentation only. Query session-level metrics instead of events. events must be empty when set. |
user_flow |
UserFlowQuery |
User-flow only. Configures the Sankey graph (node kind, max hops, max nodes, max links). |
top_k |
TopKQuery |
Top-K only. Configures the ranking (dimension, metric, limit). See TopKQuery. |
map |
MapQuery |
Map only. Configures the per-country metric (scope, metric, metric property). See MapQuery. |
include_cookieless |
bool |
Count cookieless visitors in user-based results. Defaults to false - see below. |
Cookieless visitors are excluded from user counts
Cookieless IDs rotate daily, so one returning human would count as a new user every day. By default they are excluded from:
UNIQUE_USERSandPER_USER_AVG, numerator and denominator alike, wherever those metrics appear: trends, segmentation, top K, map.- Person-based insights: funnel, retention, user flow, and
DIMENSION_USERtop K. These resolve people rather than events, so they exclude regardless of which metric is selected.
Event totals, numeric aggregations and all session metrics always count every visitor, cookieless or not. Set include_cookieless: true to fold them into the metrics above as well - useful for a relative trend, misleading as an absolute user count.
EventQuery
| Field | Type | Description |
|---|---|---|
event |
EventFilter |
Event kind + optional per-event property filters. |
aggregation |
AggregationType |
Defaults to AGGREGATION_TYPE_TOTAL. |
aggregation_property |
string |
Required for SUM, AVG, MIN, MAX. Property name to aggregate (e.g. revenue). |
Aggregation types
| Enum value | Description |
|---|---|
AGGREGATION_TYPE_TOTAL |
Total event occurrences. |
AGGREGATION_TYPE_UNIQUE_USERS |
Count of distinct users. |
AGGREGATION_TYPE_PER_USER_AVG |
Average events per user. |
AGGREGATION_TYPE_SUM |
Sum of a numeric property. Requires aggregation_property. |
AGGREGATION_TYPE_AVG |
Mean of a numeric property. Requires aggregation_property. |
AGGREGATION_TYPE_MIN |
Minimum of a numeric property. Requires aggregation_property. |
AGGREGATION_TYPE_MAX |
Maximum of a numeric property. Requires aggregation_property. |
TopKQuery
Ranks the top values of one dimension by an aggregate metric. Top K has no time axis - granularity is still required on the request, but for this insight type it only selects which time-range cap applies, so send the coarsest granularity whose cap admits your window.
| Field | Type | Description |
|---|---|---|
dimension |
Dimension |
Required. What is being ranked - see below. |
property |
string |
The property to rank values of. Required when dimension is DIMENSION_PROPERTY, and rejected otherwise. Same $-prefix encoding as Breakdown.property. |
scope |
EventFilter |
Optional event scope (kind and/or per-event filters). Empty means all events participate. This replaces events, which top K may not set. |
metric |
AggregationType |
Metric to rank by. Defaults to AGGREGATION_TYPE_TOTAL. |
metric_property |
string |
Property to aggregate. Required for SUM, AVG, MIN, MAX; ignored otherwise. |
limit |
int32 |
Rows to return, 0-100. Defaults to 10. |
omit_others |
bool |
Drop the trailing synthetic $others bucket and return only the top limit rows. Defaults to false. |
Dimension |
Ranks |
|---|---|
DIMENSION_PROPERTY |
Values of an event property, auto or custom, named by property. |
DIMENSION_EVENT_KIND |
Event kinds: “top events”. |
DIMENSION_USER |
Canonical users. Rows carry profile enrichment where the key resolves to a profile. UNIQUE_USERS and PER_USER_AVG are rejected here - each group is a single user, so those metrics are degenerate. |
TopKResult holds rows, ordered metric-descending with the $others bucket last. Each TopKRow carries dimension_value, value, is_others, and, for resolved DIMENSION_USER rows only, a profile (id, external_id, properties).
Identify the overflow bucket by is_others, never by matching dimension_value against "$others" - a real property value or event kind can legitimately be that literal string. The same contract applies to UserFlowNode.
MapQuery
Measures one metric per country for a choropleth. The dimension is fixed to the $country auto-property - there is no dimension or property field to set, and no limit. Like top K it has no time axis, so granularity only selects which time-range cap applies.
| Field | Type | Description |
|---|---|---|
scope |
EventFilter |
Optional event scope (kind and/or per-event filters). Empty means all events participate. This replaces events, which map may not set. |
metric |
AggregationType |
Metric to measure per country. Defaults to AGGREGATION_TYPE_TOTAL. |
metric_property |
string |
Property to aggregate. Required for SUM, AVG, MIN, MAX; rejected for the counting metrics. |
Results arrive in QueryResponse.top_k as a TopKResult, ordered metric-descending, with dimension_value set to the country code. There is no $others bucket - every row is a country.
Filter model
Filters are expressed as FilterGroup objects inside spec.filter_groups. Each group contains one or more PropertyFilter entries combined by the group’s own operator. Groups are then combined by spec.filter_groups_operator.
FilterGroup
{
"filters": [
{ "property": "plan", "operator": "FILTER_OPERATOR_EQUALS", "value": "pro" },
{ "property": "$country", "operator": "FILTER_OPERATOR_IN", "values": ["US", "CA"] }
],
"operator": "LOGICAL_OPERATOR_AND"
}Filter operators (FilterOperator)
| Enum value | Value fields used | Description |
|---|---|---|
FILTER_OPERATOR_EQUALS |
value |
Exact match. |
FILTER_OPERATOR_NOT_EQUALS |
value |
Excludes exact match. |
FILTER_OPERATOR_CONTAINS |
value |
Substring match. |
FILTER_OPERATOR_NOT_CONTAINS |
value |
Excludes substring match. |
FILTER_OPERATOR_IS_SET |
- | Property exists. |
FILTER_OPERATOR_IS_NOT_SET |
- | Property is absent. |
FILTER_OPERATOR_LTE |
value (numeric) |
Less than or equal. |
FILTER_OPERATOR_GTE |
value (numeric) |
Greater than or equal. |
FILTER_OPERATOR_LT |
value (numeric) |
Strictly less than. |
FILTER_OPERATOR_GT |
value (numeric) |
Strictly greater than. |
FILTER_OPERATOR_IN |
values |
Match any of a list. |
FILTER_OPERATOR_NOT_IN |
values |
Exclude all of a list. |
FILTER_OPERATOR_BETWEEN |
values[0], values[1] (numeric, ordered) |
Inclusive range. |
FILTER_OPERATOR_NOT_BETWEEN |
values[0], values[1] (numeric, ordered) |
Outside range. |
Logical operators (LogicalOperator)
| Enum value | Description |
|---|---|
LOGICAL_OPERATOR_AND |
All conditions must match (default). |
LOGICAL_OPERATOR_OR |
At least one condition must match. |
Property names follow the pattern ^\\$?[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$. Auto-properties use their flat $ key (e.g. $country, $browser); a dotted name addresses a nested key inside a custom property’s JSON (e.g. metadata.tier). For filterable auto-properties see Auto-properties.
Session metrics
When spec.session is set, the query measures session-level data instead of individual events. spec.events must be empty.
| Enum value | Description | Restrictions |
|---|---|---|
SESSION_METRIC_SESSIONS |
Count of distinct sessions started in the window. | Trends or segmentation. |
SESSION_METRIC_AVG_DURATION |
Average session duration in seconds (last event - first event). | Trends or segmentation. |
SESSION_METRIC_BOUNCE_RATE |
Percentage of sessions with exactly one event (after optional scope filter). | Trends or segmentation. |
SESSION_METRIC_ENTRY |
Count sessions by their first matching event’s breakdown value (entry page). | Trends + exactly one breakdown. |
SESSION_METRIC_EXIT |
Count sessions by their last matching event’s breakdown value (exit page). | Trends + exactly one breakdown. |
SessionQuery.scope (optional EventFilter) restricts which events participate in the metric. An empty scope considers all events in the session.
Worked example: TRENDS query
A daily time series of unique users who fired page_view, broken down by $country, over the last 30 days.
POST /shared.insights.v1.InsightsService/Query
Content-Type: application/json
Connect-Protocol-Version: 1
x-api-key: prv_...{
"spec": {
"insight_type": "INSIGHT_TYPE_TRENDS",
"events": [
{
"event": { "kind": "page_view" },
"aggregation": "AGGREGATION_TYPE_UNIQUE_USERS"
}
],
"breakdowns": [
{ "property": "$country" }
],
"breakdown_limit": 10
},
"time_range": {
"from": "2026-05-06T00:00:00Z",
"to": "2026-06-05T00:00:00Z"
},
"granularity": "GRANULARITY_DAY"
}Response sketch:
{
"trends": {
"series": [
{
"event_kind": "page_view",
"breakdown": { "$country": "US" },
"points": [
{ "time": "2026-05-06T00:00:00Z", "value": 412 },
{ "time": "2026-05-07T00:00:00Z", "value": 389 }
]
},
{
"event_kind": "page_view",
"breakdown": { "$country": "GB" },
"points": [
{ "time": "2026-05-06T00:00:00Z", "value": 104 },
{ "time": "2026-05-07T00:00:00Z", "value": 97 }
]
}
]
}
}Each series entry corresponds to one (event_kind, breakdown) pair. points are in ascending time order with one entry per granularity bucket.
Worked example: FUNNEL query
POST /shared.insights.v1.InsightsService/Query
Content-Type: application/json
Connect-Protocol-Version: 1
x-api-key: prv_...{
"spec": {
"insight_type": "INSIGHT_TYPE_FUNNEL",
"events": [
{ "event": { "kind": "signup_started" } },
{ "event": { "kind": "signup_completed" } },
{ "event": { "kind": "purchase" } }
],
"conversion_window": "604800s"
},
"time_range": {
"from": "2026-05-01T00:00:00Z",
"to": "2026-06-01T00:00:00Z"
},
"granularity": "GRANULARITY_DAY"
}Response sketch:
{
"funnel": {
"series": [
{
"breakdown": {},
"steps": [
{ "event_kind": "signup_started", "total": 1000 },
{ "event_kind": "signup_completed", "total": 650 },
{ "event_kind": "purchase", "total": 120 }
]
}
]
}
}With include_step_timing: true, each non-entry step gains a timing object:
{
"event_kind": "signup_completed",
"total": 650,
"timing": {
"avg": "180s",
"median": "95s",
"p95": "1200s",
"distribution": [
{ "label": "0-30s", "upper_bound": "30s", "count": 78 },
{ "label": "30s-2m", "upper_bound": "120s", "count": 210 }
]
}
}The distribution array above is truncated for brevity - it always contains exactly 8 buckets in the canonical order, and the 8 boundaries are fixed (a full response returns all 8).
Performance notes
Insights queries run against ClickHouse. Query time scales with:
- Time range x granularity: wider ranges at fine granularities scan more partitions.
- Breakdown cardinality: avoid very high-cardinality breakdowns (e.g. user ID). Use
breakdown_limitto cap result size. - Funnel step count: funnel supports at most 20 steps.
Related
- API reference: base URL, auth, error codes
- Auto-properties: filterable
$-prefixed properties - RPC services: full method index