Skip to content
Dashboard

Run a report query

client.reports.query(ReportQueryParams { aggregation, endDate, granularity, 16 more } params, RequestOptionsoptions?): ReportQueryResponse { meta, object, value, 7 more }
POST/v2/reports/query

Executes an ad-hoc reporting query: one metric + aggregation over a date range, with optional filters, grouping, segmentation and period-over-period comparison.

Building a query

  1. Pick a metric and aggregation from the datasets catalog (GET /v2/reports/datasets). Counts use count; duration metrics support sum, avg, median, min, max, range and percentile (pass percentile: 95 for p95); rate metrics use value.
  2. Set the reporting window with startDate / endDate (ISO 8601) and a granularity (hour, day, week, month) for the returned time series. Reporting data is available from July 14, 2026. Windows ending earlier are rejected; crossing windows are clamped to that boundary in the requested timezone.
  3. Optionally narrow with filters — a rule ({ "kind": "rule", "fieldId": "...", "operator": "in", "value": [...] }) or an and/or group of rules. Attribute IDs and allowed operators come from the catalog.
  4. Optionally break results down with groupBy (primary dimension) and segmentBy (secondary dimension).
  5. Optionally pass compareStartDate / compareEndDate to get previousValue / deltaPercent alongside every data point.

Top-N and Show Other

For high-cardinality breakdowns, cap the number of returned series:

  • topValuesLimit keeps only the top N groupBy values; segmentTopValuesLimit does the same for segmentBy. Both accept integers from 1 to 100; the editor offers common presets plus a Custom value. topValuesLimit requires groupBy; segmentTopValuesLimit requires segmentBy.
  • For time-series segment breakdowns, members are ranked inside each time bucket. The response keeps the union of those bucket winners in its series catalog, while a member outside a bucket’s Top N has a zero value in that bucket. This matches the chart’s per-period ranking semantics. A comparison period is folded into the primary period’s union of visible members so current and previous series keep the same identities.
  • For non-time-series grouped results, members are ranked over the full filtered primary range. A comparison period reuses the primary period’s ranked set rather than independently changing the legend.
  • showOther / segmentShowOther append a single synthetic bucket with id __other__ and label Other that sums every value outside the top set. Because the values are summed, Show Other is only supported for additive aggregations (count, sum, value); it is rejected for avg, median, min, max, range, percentile, and for percentage metrics (whose ratios cannot be summed). showOther also requires topValuesLimit (and segmentShowOther requires segmentTopValuesLimit). Plain topValuesLimit (without Show Other) works with any aggregation.
  • The __other__ group/segment aggregates rows outside the selected top groups and cannot be drilled into (POST /v2/reports/drill-in rejects dataPointFilters.groupValue / segmentValue equal to __other__).

Example

{
  "metric": "new_conversations",
  "aggregation": "count",
  "startDate": "2026-07-14",
  "endDate": "2026-07-15",
  "granularity": "day",
  "groupBy": "conversation.channel",
  "filters": {
    "kind": "rule",
    "fieldId": "conversation.state",
    "operator": "is",
    "value": "closed"
  }
}

Response shape

  • value — the aggregate across the whole window
  • timeSeries — one datum per granularity bucket
  • groupedData / segmentData — present when groupBy / segmentBy were requested
  • flowData — present for view: "sankey" (Overview conversation-flow metrics)
  • meta — echo of the resolved metric, dataset, unit and aggregation

Special views

  • view: "hourly_heatmap" buckets by day-of-week × hour-of-day (use with volume metrics)
  • view: "sankey" returns conversation flow edges (Overview metrics only)

Version Availability

This endpoint is only available in API version 2026-01-01.nova and newer, and only for workspaces with the Reports product enabled (404 otherwise).

ParametersExpand Collapse
params: ReportQueryParams { aggregation, endDate, granularity, 16 more }
aggregation: "count" | "sum" | "avg" | 6 more

Body param: Aggregation function applied to the metric. Each metric supports a subset of aggregations — see the allowedAggregations field in the GET /v2/reports/datasets catalog.

One of the following:
"count"
"sum"
"avg"
"median"
"min"
"max"
"range"
"percentile"
"value"
endDate: string

Body param: End of the reporting window (ISO 8601 date or datetime, inclusive). Windows ending before 2026-07-14 are rejected.

minLength10
maxLength64
granularity: "hour" | "day" | "week" | "month"

Body param: Time bucket size for the returned time series.

One of the following:
"hour"
"day"
"week"
"month"
metric: string

Body param: Metric ID to query (e.g. new_conversations). Discover metric IDs via GET /v2/reports/datasets.

minLength1
maxLength128
startDate: string

Body param: Start of the reporting window (ISO 8601 date or datetime, inclusive). Reporting data is available from 2026-07-14; crossing windows are clamped to that boundary.

minLength10
maxLength64
compareEndDate?: string

Body param: End of the comparison window. Must be paired with compareStartDate.

minLength10
maxLength64
compareStartDate?: string

Body param: Start of the comparison window for period-over-period deltas. Must be paired with compareEndDate.

minLength10
maxLength64
filters?: ReportFilterRule { fieldId, kind, operator, value } | ReportFilterExpression { children, kind, op }

Body param: Filter expression: a single rule, or an and/or group combining rules and nested groups. Attribute IDs and their allowed operators come from GET /v2/reports/datasets.

One of the following:
ReportFilterRule { fieldId, kind, operator, value }
fieldId: string

Attribute ID to filter on (e.g. conversation.channel). Discover attribute IDs via GET /v2/reports/datasets.

minLength1
maxLength512
kind: "rule"
operator: "is" | "is_not" | "in" | 10 more

Comparison operator. Each attribute supports a subset of operators — see the allowedOperators field in the GET /v2/reports/datasets catalog.

One of the following:
"is"
"is_not"
"in"
"not_in"
"contains"
"not_contains"
"gte"
"lte"
"between"
"exists"
"not_exists"
"is_member_of"
"is_not_member_of"
value?: string | number | boolean | Array<string | number | boolean>

Value to compare against. Scalar for is, is_not, contains, not_contains, gte, lte (ISO date strings for date attributes); non-empty array for in, not_in, is_member_of, is_not_member_of; 2-element array for between; omit for exists / not_exists.

One of the following:
string
number
boolean
Array<string | number | boolean>
string
number
boolean
ReportFilterExpression { children, kind, op }
children: Array<ReportFilterRule { fieldId, kind, operator, value } | ReportFilterGroup { children, kind, op } >

Rules and/or nested groups of rules. Groups may nest up to 6 levels at runtime; 100 rules max per expression.

One of the following:
ReportFilterRule { fieldId, kind, operator, value }
fieldId: string

Attribute ID to filter on (e.g. conversation.channel). Discover attribute IDs via GET /v2/reports/datasets.

minLength1
maxLength512
kind: "rule"
operator: "is" | "is_not" | "in" | 10 more

Comparison operator. Each attribute supports a subset of operators — see the allowedOperators field in the GET /v2/reports/datasets catalog.

One of the following:
"is"
"is_not"
"in"
"not_in"
"contains"
"not_contains"
"gte"
"lte"
"between"
"exists"
"not_exists"
"is_member_of"
"is_not_member_of"
value?: string | number | boolean | Array<string | number | boolean>

Value to compare against. Scalar for is, is_not, contains, not_contains, gte, lte (ISO date strings for date attributes); non-empty array for in, not_in, is_member_of, is_not_member_of; 2-element array for between; omit for exists / not_exists.

One of the following:
string
number
boolean
Array<string | number | boolean>
string
number
boolean
ReportFilterGroup { children, kind, op }
children: Array<ReportFilterRule { fieldId, kind, operator, value } >
fieldId: string

Attribute ID to filter on (e.g. conversation.channel). Discover attribute IDs via GET /v2/reports/datasets.

minLength1
maxLength512
kind: "rule"
operator: "is" | "is_not" | "in" | 10 more

Comparison operator. Each attribute supports a subset of operators — see the allowedOperators field in the GET /v2/reports/datasets catalog.

One of the following:
"is"
"is_not"
"in"
"not_in"
"contains"
"not_contains"
"gte"
"lte"
"between"
"exists"
"not_exists"
"is_member_of"
"is_not_member_of"
value?: string | number | boolean | Array<string | number | boolean>

Value to compare against. Scalar for is, is_not, contains, not_contains, gte, lte (ISO date strings for date attributes); non-empty array for in, not_in, is_member_of, is_not_member_of; 2-element array for between; omit for exists / not_exists.

One of the following:
string
number
boolean
Array<string | number | boolean>
string
number
boolean
kind: "group"
op: "and" | "or"

Group operator: and (all match) or or (any match).

One of the following:
"and"
"or"
kind: "group"
op: "and" | "or"

Group operator: and (all match) or or (any match).

One of the following:
"and"
"or"
groupBy?: string

Body param: Attribute ID to group results by (e.g. conversation.channel). See supportsGroupBy in the catalog.

maxLength512
officeHoursOnly?: boolean

Body param: Restrict time-based metrics to configured office hours. Only supported by some metrics (see supportsOfficeHours in the catalog).

percentile?: number

Body param: Percentile (1-100) — required when aggregation is percentile.

minimum1
maximum100
segmentBy?: string

Body param: Attribute ID for secondary segmentation within each group or time bucket.

maxLength512
segmentShowOther?: boolean

Body param: When true, fold segmentBy values outside the top set into a synthetic __other__ / Other segment. Only supported for additive aggregations (count, sum, value) and not for percentage metrics. The __other__ segment cannot be drilled into. Requires segmentBy and segmentTopValuesLimit.

segmentTopValuesLimit?: number

Body param: Keep only the top N segmentBy values per series. Under a time View-by, segments are ranked independently inside each time bucket and the response series catalog contains the union of bucket winners. Under a dimension View-by, ranking is applied within each retained parent group. Allowed range: 1 to 100; the editor offers common presets plus a Custom value. Requires segmentBy.

minimum1
maximum100
showOther?: boolean

Body param: When true, append a single synthetic group with id __other__ and label Other that sums the values of every groupBy value outside the top set. Only supported for additive aggregations (count, sum, value) and not for percentage metrics. The __other__ group cannot be drilled into. Requires groupBy and topValuesLimit.

timezone?: string

Body param: IANA timezone for date bucketing (e.g. America/New_York). Defaults to UTC.

maxLength64
topValuesLimit?: number

Body param: Keep only the top N groupBy (View-by) values, ranked by the selected metric/aggregation over the full filtered primary range. Allowed range: 1 to 100; the editor offers common presets plus a Custom value. A comparison period reuses the primary period’s ranked set. Requires groupBy.

minimum1
maximum100
view?: "standard" | "hourly_heatmap" | "sankey"

Body param: Result shape. standard returns a time series (plus grouped/segment data when requested), hourly_heatmap buckets by day-of-week × hour-of-day, sankey returns conversation flow data. Each metric lists its supportedViews in the GET /v2/reports/datasets catalog.

One of the following:
"standard"
"hourly_heatmap"
"sankey"
featurebaseVersion?: "2026-08-19.orbit" | "2026-01-01.nova" | "2025-12-12.clover"

Header param: API version for this request. Defaults to your organization’s configured API version if not specified.

One of the following:
"2026-08-19.orbit"
"2026-01-01.nova"
"2025-12-12.clover"
ReturnsExpand Collapse
ReportQueryResponse { meta, object, value, 7 more }
meta: Meta { aggregation, datasetId, granularity, 5 more }
aggregation: "count" | "sum" | "avg" | 6 more

Aggregation function applied to the metric. Each metric supports a subset of aggregations — see the allowedAggregations field in the GET /v2/reports/datasets catalog.

One of the following:
"count"
"sum"
"avg"
"median"
"min"
"max"
"range"
"percentile"
"value"
datasetId: string
granularity: string
metric: string
officeHoursOnly: boolean
unit: "count" | "percentage" | "duration_ms" | "number"
One of the following:
"count"
"percentage"
"duration_ms"
"number"
groupBy?: string
segmentBy?: string
object: "report_query_result"
value: number

Aggregated value across the whole reporting window.

deltaPercent?: number

Percentage change vs the comparison window.

flowData?: Array<FlowData>
metricId: string
pathId: string
source: string
sourceLabel: string
target: string
targetLabel: string
value: number
colorKey?: string
percentage?: number
sortOrder?: number
groupedData?: Array<ReportGroupedDatum { group, value, groupLabel, 3 more } >
group: string

Raw group value (e.g. an ID). Use groupLabel for display.

value: number
groupLabel?: string
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
previousValue?: number
segments?: Array<ReportSegmentDatum { segment, value, identity, 2 more } >
segment: string
value: number
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
previousValue?: number
segmentLabel?: string
previousValue?: number

Aggregated value for the comparison window, when requested.

segmentData?: Array<ReportSegmentDatum { segment, value, identity, 2 more } >
segment: string
value: number
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
previousValue?: number
segmentLabel?: string
table?: Table { columns, key, mode, 6 more }
columns: Array<Column>
id: string
kind: "dimension" | "metric" | "record_attribute"
One of the following:
"dimension"
"metric"
"record_attribute"
label: string
sortable: boolean
unit: "count" | "percentage" | "duration_ms" | 3 more
One of the following:
"count"
"percentage"
"duration_ms"
"number"
"text"
"datetime"
align?: "left" | "right"
One of the following:
"left"
"right"
sticky?: boolean
summary?: "sum" | "weighted_rate" | "none"
One of the following:
"sum"
"weighted_rate"
"none"
valueType?: string
key: string
mode: "aggregate" | "records"
One of the following:
"aggregate"
"records"
page: number
pageSize: number
rows: Array<Row>
id: string
cells: Record<string, Cells>
display: string
deltaPercent?: number
denominator?: number
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
numerator?: number
previousValue?: number
raw?: unknown
sortValue?: string | number | null
One of the following:
string
number
value?: unknown
totalRows: number
sort?: Sort { columnId, direction }
columnId: string
direction: "asc" | "desc"
One of the following:
"asc"
"desc"
summaryRows?: Array<SummaryRow>
id: string
cells: Record<string, Cells>
display: string
deltaPercent?: number
denominator?: number
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
numerator?: number
previousValue?: number
raw?: unknown
sortValue?: string | number | null
One of the following:
string
number
value?: unknown
timeSeries?: Array<ReportTimeSeriesDatum { date, value, previousValue, segments } >
date: string

Time bucket start (ISO 8601).

value: number
previousValue?: number

Value for the same bucket in the comparison window, when requested.

segments?: Array<ReportSegmentDatum { segment, value, identity, 2 more } >
segment: string
value: number
identity?: Identity { id, kind, label, 3 more }
id: string
kind: "teammate" | "team" | "user" | 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl?: string
color?: string
website?: string
previousValue?: number
segmentLabel?: string

Run a report query

import Featurebase from 'featurebase-node';

const client = new Featurebase({
  apiKey: process.env['FEATUREBASE_API_KEY'], // This is the default and can be omitted
});

const response = await client.reports.query({
  aggregation: 'count',
  endDate: '2026-07-15',
  granularity: 'day',
  metric: 'new_conversations',
  startDate: '2026-07-14',
});

console.log(response.meta);
{
  "meta": {
    "aggregation": "count",
    "datasetId": "datasetId",
    "granularity": "granularity",
    "metric": "metric",
    "officeHoursOnly": true,
    "unit": "count",
    "groupBy": "groupBy",
    "segmentBy": "segmentBy"
  },
  "object": "report_query_result",
  "value": 0,
  "deltaPercent": 0,
  "flowData": [
    {
      "metricId": "metricId",
      "pathId": "pathId",
      "source": "source",
      "sourceLabel": "sourceLabel",
      "target": "target",
      "targetLabel": "targetLabel",
      "value": 0,
      "colorKey": "colorKey",
      "percentage": 0,
      "sortOrder": 0
    }
  ],
  "groupedData": [
    {
      "group": "group",
      "value": 0,
      "groupLabel": "groupLabel",
      "identity": {
        "id": "id",
        "kind": "teammate",
        "label": "label",
        "avatarUrl": "avatarUrl",
        "color": "color",
        "website": "website"
      },
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "identity": {
            "id": "id",
            "kind": "teammate",
            "label": "label",
            "avatarUrl": "avatarUrl",
            "color": "color",
            "website": "website"
          },
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ],
  "previousValue": 0,
  "segmentData": [
    {
      "segment": "segment",
      "value": 0,
      "identity": {
        "id": "id",
        "kind": "teammate",
        "label": "label",
        "avatarUrl": "avatarUrl",
        "color": "color",
        "website": "website"
      },
      "previousValue": 0,
      "segmentLabel": "segmentLabel"
    }
  ],
  "table": {
    "columns": [
      {
        "id": "id",
        "kind": "dimension",
        "label": "label",
        "sortable": true,
        "unit": "count",
        "align": "left",
        "sticky": true,
        "summary": "sum",
        "valueType": "valueType"
      }
    ],
    "key": "key",
    "mode": "aggregate",
    "page": 0,
    "pageSize": 0,
    "rows": [
      {
        "id": "id",
        "cells": {
          "foo": {
            "display": "display",
            "deltaPercent": 0,
            "denominator": 0,
            "identity": {
              "id": "id",
              "kind": "teammate",
              "label": "label",
              "avatarUrl": "avatarUrl",
              "color": "color",
              "website": "website"
            },
            "numerator": 0,
            "previousValue": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ],
    "totalRows": 0,
    "sort": {
      "columnId": "columnId",
      "direction": "asc"
    },
    "summaryRows": [
      {
        "id": "id",
        "cells": {
          "foo": {
            "display": "display",
            "deltaPercent": 0,
            "denominator": 0,
            "identity": {
              "id": "id",
              "kind": "teammate",
              "label": "label",
              "avatarUrl": "avatarUrl",
              "color": "color",
              "website": "website"
            },
            "numerator": 0,
            "previousValue": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ]
  },
  "timeSeries": [
    {
      "date": "2026-07-15",
      "value": 0,
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "identity": {
            "id": "id",
            "kind": "teammate",
            "label": "label",
            "avatarUrl": "avatarUrl",
            "color": "color",
            "website": "website"
          },
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ]
}
Returns Examples
{
  "meta": {
    "aggregation": "count",
    "datasetId": "datasetId",
    "granularity": "granularity",
    "metric": "metric",
    "officeHoursOnly": true,
    "unit": "count",
    "groupBy": "groupBy",
    "segmentBy": "segmentBy"
  },
  "object": "report_query_result",
  "value": 0,
  "deltaPercent": 0,
  "flowData": [
    {
      "metricId": "metricId",
      "pathId": "pathId",
      "source": "source",
      "sourceLabel": "sourceLabel",
      "target": "target",
      "targetLabel": "targetLabel",
      "value": 0,
      "colorKey": "colorKey",
      "percentage": 0,
      "sortOrder": 0
    }
  ],
  "groupedData": [
    {
      "group": "group",
      "value": 0,
      "groupLabel": "groupLabel",
      "identity": {
        "id": "id",
        "kind": "teammate",
        "label": "label",
        "avatarUrl": "avatarUrl",
        "color": "color",
        "website": "website"
      },
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "identity": {
            "id": "id",
            "kind": "teammate",
            "label": "label",
            "avatarUrl": "avatarUrl",
            "color": "color",
            "website": "website"
          },
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ],
  "previousValue": 0,
  "segmentData": [
    {
      "segment": "segment",
      "value": 0,
      "identity": {
        "id": "id",
        "kind": "teammate",
        "label": "label",
        "avatarUrl": "avatarUrl",
        "color": "color",
        "website": "website"
      },
      "previousValue": 0,
      "segmentLabel": "segmentLabel"
    }
  ],
  "table": {
    "columns": [
      {
        "id": "id",
        "kind": "dimension",
        "label": "label",
        "sortable": true,
        "unit": "count",
        "align": "left",
        "sticky": true,
        "summary": "sum",
        "valueType": "valueType"
      }
    ],
    "key": "key",
    "mode": "aggregate",
    "page": 0,
    "pageSize": 0,
    "rows": [
      {
        "id": "id",
        "cells": {
          "foo": {
            "display": "display",
            "deltaPercent": 0,
            "denominator": 0,
            "identity": {
              "id": "id",
              "kind": "teammate",
              "label": "label",
              "avatarUrl": "avatarUrl",
              "color": "color",
              "website": "website"
            },
            "numerator": 0,
            "previousValue": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ],
    "totalRows": 0,
    "sort": {
      "columnId": "columnId",
      "direction": "asc"
    },
    "summaryRows": [
      {
        "id": "id",
        "cells": {
          "foo": {
            "display": "display",
            "deltaPercent": 0,
            "denominator": 0,
            "identity": {
              "id": "id",
              "kind": "teammate",
              "label": "label",
              "avatarUrl": "avatarUrl",
              "color": "color",
              "website": "website"
            },
            "numerator": 0,
            "previousValue": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ]
  },
  "timeSeries": [
    {
      "date": "2026-07-15",
      "value": 0,
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "identity": {
            "id": "id",
            "kind": "teammate",
            "label": "label",
            "avatarUrl": "avatarUrl",
            "color": "color",
            "website": "website"
          },
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ]
}