Query syntax reference
The full reporting query grammar – metrics, aggregations, filters, grouping, comparison windows and drill-ins.
Every report query is a JSON body sent to POST /v2/reports/query. This page documents the complete grammar. The datasets catalog (GET /v2/reports/datasets) is the authoritative source for the metric and attribute IDs your workspace accepts.
Anatomy of a query
Section titled “Anatomy of a query”{ "metric": "new_conversations", "aggregation": "count", "startDate": "2026-06-01", "endDate": "2026-06-30", "compareStartDate": "2026-05-01", "compareEndDate": "2026-05-31", "granularity": "day", "timezone": "Europe/Tallinn", "officeHoursOnly": false, "filters": { "kind": "rule", "fieldId": "conversation.channel", "operator": "is", "value": "email" }, "groupBy": "teammate.currently_assigned", "segmentBy": "conversation.priority", "view": "standard"}| Field | Required | Description |
|---|---|---|
metric | yes | Metric ID from the catalog |
aggregation | yes | One of the metric’s allowedAggregations |
percentile | with percentile aggregation | 1–100, e.g. 95 for p95 |
startDate / endDate | yes | ISO 8601 date or datetime, inclusive window |
compareStartDate / compareEndDate | no | Comparison window – adds previousValue and deltaPercent to every datum |
granularity | yes | hour, day, week or month – the time-series bucket size |
timezone | no | IANA timezone for bucketing (defaults to UTC) |
officeHoursOnly | no | Restrict time metrics to configured office hours (only metrics with supportsOfficeHours) |
filters | no | Filter expression (see below) |
groupBy | no | Attribute ID for the primary breakdown |
segmentBy | no | Attribute ID for a secondary breakdown within each group/bucket |
view | no | standard (default), hourly_heatmap or sankey |
Aggregations
Section titled “Aggregations”| Aggregation | Use with |
|---|---|
count | Count metrics (new_conversations, replies_sent, …) |
avg, median, min, max | Duration and number metrics |
percentile | Duration metrics – pass percentile: 95 alongside |
sum | Summable number metrics |
value | Rate/percentage metrics (csat_score, reopen_rate, …) |
Each metric lists its own allowedAggregations in the catalog; anything else returns 400 invalid_parameter.
Filter expressions
Section titled “Filter expressions”A filter is either a single rule or a group combining child expressions with and / or. Groups nest up to 6 levels; 100 rules max per expression.
FilterExpression := Rule | GroupRule := { "kind": "rule", "fieldId": string, "operator": Operator, "value"?: Value }Group := { "kind": "group", "op": "and" | "or", "children": FilterExpression[] }Value := string | number | boolean | Array<string | number | boolean>Example – email or web-chat conversations that are closed:
{ "kind": "group", "op": "and", "children": [ { "kind": "rule", "fieldId": "conversation.channel", "operator": "in", "value": ["email", "desktop"] }, { "kind": "rule", "fieldId": "conversation.state", "operator": "is", "value": "closed" } ]}Operators
Section titled “Operators”| Operator | Meaning | Value shape |
|---|---|---|
is / is_not | Equality | scalar |
in / not_in | Set membership | non-empty array |
contains / not_contains | Substring / multi-value containment | scalar |
gte / lte | Greater/less than or equal | scalar (number, or ISO date string for date attributes) |
between | Inclusive range | 2-element array |
exists / not_exists | Attribute has any value | omit value |
is_member_of / is_not_member_of | Membership in a referenced set (e.g. team) | non-empty array |
Wrong value shape for an operator returns 400 with a message naming the operator and field.
Which attributes can I use?
Section titled “Which attributes can I use?”Attributes are scoped to datasets: filters, groupBy and segmentBy must use attributes from the same dataset as the metric. Find the metric’s dataset in GET /v2/reports/datasets (the dataset whose metrics array contains it — also echoed as meta.datasetId in query responses) and pick from that dataset’s attributes. Each attribute lists its allowedOperators, valueType and supportsFilter / supportsGroupBy flags.
For valid values: attributes with staticOptions carry their full value set inline; for the rest use POST /v2/reports/filter-values:
{ "fieldId": "conversation.tags", "query": "bill", "limit": 20 }Grouping and segmentation
Section titled “Grouping and segmentation”groupBybreaks the aggregate down by one attribute →groupedDatain the responsesegmentByadds a second dimension →segmentsarrays nested inside each group and time-series datum
Attributes must have supportsGroupBy: true in the catalog. Group values come back as raw values (group) plus display labels (groupLabel) where the raw value is an ID.
Period comparison
Section titled “Period comparison”Pass compareStartDate / compareEndDate (both or neither). Every datum then carries previousValue, and the top level adds deltaPercent. Typical usage is the same-length window immediately before startDate.
Each metric lists its supportedViews in the catalog.
standard– time series + optional groups/segments (default)hourly_heatmap– buckets by day-of-week × hour-of-day; drill in withdayOfWeek(1 = Monday) +hourOfDay(0–23)sankey– conversation flow edges inflowData(overview_conversation_flowmetric only); drill in withflowPathIdorsourceNodeId+targetNodeId
Response shape
Section titled “Response shape”{ "object": "report_query_result", "value": 1284, "previousValue": 1120, "deltaPercent": 14.6, "timeSeries": [ { "date": "2026-06-01", "value": 43, "previousValue": 39 } ], "groupedData": [ { "group": "email", "groupLabel": "Email", "value": 612 } ], "meta": { "metric": "new_conversations", "datasetId": "conversations", "unit": "count", "granularity": "day", "aggregation": "count", "officeHoursOnly": false }}Duration values are milliseconds (unit: "duration_ms"); percentages are 0–100 (unit: "percentage").
Drill-ins
Section titled “Drill-ins”POST /v2/reports/drill-in returns the paginated rows behind a metric. Send the same query context (metric, dates, granularity, filters, groupBy, segmentBy, view) plus dataPointFilters selecting the point:
| Data point | dataPointFilters |
|---|---|
| Whole window | {} |
| One time bucket | { "timeBucket": "2026-06-15" } |
| One group | { "groupValue": "email" } |
| Heatmap cell | { "dayOfWeek": 2, "hourOfDay": 14 } |
| Flow edge | { "sourceNodeId": "...", "targetNodeId": "..." } |
Pagination is page (1-based) + pageSize (max 200). The response lists availableColumns (all columns the dataset offers) and defaultColumnIds; rows are keyed by column ID. Cells holding people (teammates, contacts) are identity objects: { "kind": "identity", "id": "...", "label": "..." }. Sort with sort: [{ "fieldId": "metric.value", "direction": "desc" }].
Errors
Section titled “Errors”| Status | Code | Cause |
|---|---|---|
| 400 | invalid_parameter | Unknown metric/attribute, disallowed aggregation or operator, malformed filter |
| 404 | resource_not_found | Reports not enabled for the workspace |
| 400 | validation message | Body fails schema validation (unknown keys are rejected) |