Skip to content
Dashboard

Run a report query

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).

Header ParametersExpand Collapse
"Featurebase-Version": optional "2026-08-19.orbit" or "2026-01-01.nova" or "2025-12-12.clover"
One of the following:
"2026-08-19.orbit"
"2026-01-01.nova"
"2025-12-12.clover"
Body ParametersJSONExpand Collapse
aggregation: "count" or "sum" or "avg" or 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"
endDate: string

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

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

Time bucket size for the returned time series.

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

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

minLength1
maxLength128
startDate: string

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: optional string

End of the comparison window. Must be paired with compareStartDate.

minLength10
maxLength64
compareStartDate: optional string

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

minLength10
maxLength64
filters: optional ReportFilterRule { fieldId, kind, operator, value } or ReportFilterExpression { children, kind, op }

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 object { 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" or "is_not" or "in" or 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: optional string or number or boolean or array of string or number or 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 of string or number or boolean
One of the following:
string
number
boolean
ReportFilterExpression object { children, kind, op }
children: array of ReportFilterRule { fieldId, kind, operator, value } or object { 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 object { 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" or "is_not" or "in" or 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: optional string or number or boolean or array of string or number or 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 of string or number or boolean
One of the following:
string
number
boolean
ReportFilterGroup object { children, kind, op }
children: array of 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" or "is_not" or "in" or 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: optional string or number or boolean or array of string or number or 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 of string or number or boolean
One of the following:
string
number
boolean
kind: "group"
op: "and" or "or"

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

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

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

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

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

maxLength512
officeHoursOnly: optional boolean

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

percentile: optional number

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

minimum1
maximum100
segmentBy: optional string

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

maxLength512
segmentShowOther: optional boolean

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: optional number

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: optional boolean

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: optional string

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

maxLength64
topValuesLimit: optional number

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: optional "standard" or "hourly_heatmap" or "sankey"

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"
ReturnsExpand Collapse
meta: object { aggregation, datasetId, granularity, 5 more }
aggregation: "count" or "sum" or "avg" or 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" or "percentage" or "duration_ms" or "number"
One of the following:
"count"
"percentage"
"duration_ms"
"number"
groupBy: optional string
segmentBy: optional string
object: "report_query_result"
value: number

Aggregated value across the whole reporting window.

deltaPercent: optional number

Percentage change vs the comparison window.

flowData: optional array of object { metricId, pathId, source, 7 more }
metricId: string
pathId: string
source: string
sourceLabel: string
target: string
targetLabel: string
value: number
colorKey: optional string
percentage: optional number
sortOrder: optional number
groupedData: optional array of ReportGroupedDatum { group, value, groupLabel, 3 more }
group: string

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

value: number
groupLabel: optional string
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
previousValue: optional number
segments: optional array of ReportSegmentDatum { segment, value, identity, 2 more }
segment: string
value: number
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
previousValue: optional number
segmentLabel: optional string
previousValue: optional number

Aggregated value for the comparison window, when requested.

segmentData: optional array of ReportSegmentDatum { segment, value, identity, 2 more }
segment: string
value: number
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
previousValue: optional number
segmentLabel: optional string
table: optional object { columns, key, mode, 6 more }
columns: array of object { id, kind, label, 6 more }
id: string
kind: "dimension" or "metric" or "record_attribute"
One of the following:
"dimension"
"metric"
"record_attribute"
label: string
sortable: boolean
unit: "count" or "percentage" or "duration_ms" or 3 more
One of the following:
"count"
"percentage"
"duration_ms"
"number"
"text"
"datetime"
align: optional "left" or "right"
One of the following:
"left"
"right"
sticky: optional boolean
summary: optional "sum" or "weighted_rate" or "none"
One of the following:
"sum"
"weighted_rate"
"none"
valueType: optional string
key: string
mode: "aggregate" or "records"
One of the following:
"aggregate"
"records"
page: number
pageSize: number
rows: array of object { id, cells }
id: string
cells: map[object { display, deltaPercent, denominator, 6 more } ]
display: string
deltaPercent: optional number
denominator: optional number
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
numerator: optional number
previousValue: optional number
raw: optional unknown
sortValue: optional string or number
One of the following:
string
number
value: optional unknown
totalRows: number
sort: optional object { columnId, direction }
columnId: string
direction: "asc" or "desc"
One of the following:
"asc"
"desc"
summaryRows: optional array of object { id, cells }
id: string
cells: map[object { display, deltaPercent, denominator, 6 more } ]
display: string
deltaPercent: optional number
denominator: optional number
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
numerator: optional number
previousValue: optional number
raw: optional unknown
sortValue: optional string or number
One of the following:
string
number
value: optional unknown
timeSeries: optional array of ReportTimeSeriesDatum { date, value, previousValue, segments }
date: string

Time bucket start (ISO 8601).

value: number
previousValue: optional number

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

segments: optional array of ReportSegmentDatum { segment, value, identity, 2 more }
segment: string
value: number
identity: optional object { id, kind, label, 3 more }
id: string
kind: "teammate" or "team" or "user" or 2 more
One of the following:
"teammate"
"team"
"user"
"company"
"fibi"
label: string
avatarUrl: optional string
color: optional string
website: optional string
previousValue: optional number
segmentLabel: optional string

Run a report query

curl https://do.featurebase.app/v2/reports/query \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $FEATUREBASE_API_KEY" \
    -d '{
          "aggregation": "count",
          "endDate": "2026-07-15",
          "granularity": "day",
          "metric": "new_conversations",
          "startDate": "2026-07-14",
          "groupBy": "conversation.channel",
          "percentile": 95,
          "segmentTopValuesLimit": 5,
          "timezone": "Europe/Tallinn",
          "topValuesLimit": 10,
          "view": "standard"
        }'
{
  "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"
        }
      ]
    }
  ]
}