# Reports

## List report datasets

`client.reports.listDatasets(ReportListDatasetsParamsparams?, RequestOptionsoptions?): ReportListDatasetsResponse`

**get** `/v2/reports/datasets`

Returns the reporting catalog: every dataset available to your workspace with its metrics and attributes.

This is the discovery endpoint for the reporting API — read it before building query payloads:

- **Metrics** carry the `metric` IDs accepted by `POST /v2/reports/query`, plus each metric's `allowedAggregations`, unit, and whether it supports office-hours restriction and period comparison.
- **Attributes** carry the `fieldId`s accepted in filter rules and the attribute IDs accepted as `groupBy` / `segmentBy`, plus each attribute's `allowedOperators` and value type. Attributes with `staticOptions` list their full value set inline; other filterable attributes resolve values via `POST /v2/reports/filter-values`.

The catalog includes your workspace's custom conversation/ticket attributes where applicable, so it can differ between workspaces.

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

### Parameters

- `params: ReportListDatasetsParams`

  - `featurebaseVersion?: "2026-01-01.nova" | "2025-12-12.clover"`

    API version for this request. Defaults to your organization's configured API version if not specified.

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `ReportListDatasetsResponse`

  - `data: Array<ReportDataset>`

    - `id: string`

    - `attributes: Array<ReportAttribute>`

      - `id: string`

        Attribute ID — use as `fieldId` in filter rules and as `groupBy` / `segmentBy` in query requests.

      - `allowedOperators: Array<"is" | "is_not" | "in" | 10 more>`

        - `"is"`

        - `"is_not"`

        - `"in"`

        - `"not_in"`

        - `"contains"`

        - `"not_contains"`

        - `"gte"`

        - `"lte"`

        - `"between"`

        - `"exists"`

        - `"not_exists"`

        - `"is_member_of"`

        - `"is_not_member_of"`

      - `category: string`

      - `description: string`

      - `name: string`

      - `semantics: "current_state" | "historical_action" | "historical_snapshot" | "dynamic_metric_mapped"`

        Whether the filter uses the latest mutable state, an immutable historical action/snapshot, or a metric-dependent mapping.

        - `"current_state"`

        - `"historical_action"`

        - `"historical_snapshot"`

        - `"dynamic_metric_mapped"`

      - `supportsFilter: boolean`

        Whether the attribute can be used in `filters` rules.

      - `supportsGroupBy: boolean`

        Whether the attribute can be used as `groupBy` / `segmentBy`.

      - `supportsValueLookup: boolean`

        Whether `POST /v2/reports/filter-values` can list this attribute's values.

      - `valueType: "string" | "number" | "boolean" | 3 more`

        - `"string"`

        - `"number"`

        - `"boolean"`

        - `"date"`

        - `"enum"`

        - `"id"`

      - `multiValue?: boolean`

      - `staticOptions?: Array<ReportAttributeOption>`

        Fixed value set for enum-like attributes. Attributes without static options resolve values via `POST /v2/reports/filter-values`.

        - `label: string`

        - `value: string`

    - `description: string`

    - `metrics: Array<ReportMetric>`

      - `id: string`

        Metric ID — use as `metric` in query requests.

      - `allowedAggregations: Array<"count" | "sum" | "avg" | 6 more>`

        - `"count"`

        - `"sum"`

        - `"avg"`

        - `"median"`

        - `"min"`

        - `"max"`

        - `"range"`

        - `"percentile"`

        - `"value"`

      - `description: string`

      - `name: string`

      - `supportedFilterAttributeIds: Array<string>`

        Attribute IDs this specific metric accepts in filter expressions.

      - `supportedGroupByDimensions: Array<string>`

        Attribute IDs this specific metric accepts as `groupBy` or `segmentBy`.

      - `supportedViews: Array<"standard" | "hourly_heatmap" | "sankey">`

        Which `view` values `POST /v2/reports/query` accepts for this metric. Most metrics support `standard` and `hourly_heatmap`; conversation-flow metrics are `sankey`-only.

        - `"standard"`

        - `"hourly_heatmap"`

        - `"sankey"`

      - `supportsOfficeHours: boolean`

      - `supportsPeriodComparison: boolean`

      - `type: "count" | "percentage" | "duration" | "number"`

        - `"count"`

        - `"percentage"`

        - `"duration"`

        - `"number"`

      - `unit: "count" | "percentage" | "duration_ms" | "number"`

        - `"count"`

        - `"percentage"`

        - `"duration_ms"`

        - `"number"`

      - `dataAvailableFromIso?: string`

        Local calendar date (ISO 8601) from which this specific metric has data, when it differs from the global reporting floor. Windows ending before this date are rejected for this metric.

      - `isDefaultVariant?: boolean`

        Whether this variant is the default selection for its `variantGroupId`.

      - `variantGroupId?: string`

        Metrics sharing a `variantGroupId` are timestamp variants of one logical metric (same display `name`); each measures the same population at a different timestamp. Query the concrete metric `id` to pick a variant.

      - `variantId?: string`

        Stable identifier for this variant within its group (e.g. `created_at`, `started_at`).

      - `variantLabel?: string`

        Human-readable variant label for pickers (e.g. `Created at`, `Started at`).

    - `name: string`

  - `object: "list"`

    - `"list"`

### Example

```typescript
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.listDatasets();

console.log(response.data);
```

#### Response

```json
{
  "data": [
    {
      "id": "conversations",
      "attributes": [
        {
          "id": "conversation.channel",
          "allowedOperators": [
            "in"
          ],
          "category": "conversation",
          "description": "description",
          "name": "Channel",
          "semantics": "current_state",
          "supportsFilter": true,
          "supportsGroupBy": true,
          "supportsValueLookup": true,
          "valueType": "string",
          "multiValue": true,
          "staticOptions": [
            {
              "label": "label",
              "value": "value"
            }
          ]
        }
      ],
      "description": "description",
      "metrics": [
        {
          "id": "new_conversations",
          "allowedAggregations": [
            "count"
          ],
          "description": "description",
          "name": "New conversations",
          "supportedFilterAttributeIds": [
            "string"
          ],
          "supportedGroupByDimensions": [
            "string"
          ],
          "supportedViews": [
            "standard"
          ],
          "supportsOfficeHours": true,
          "supportsPeriodComparison": true,
          "type": "count",
          "unit": "count",
          "dataAvailableFromIso": "2026-07-12",
          "isDefaultVariant": true,
          "variantGroupId": "new_conversations",
          "variantId": "started_at",
          "variantLabel": "Started at"
        }
      ],
      "name": "Conversations"
    }
  ],
  "object": "list"
}
```

## Run a report query

`client.reports.query(ReportQueryParamsparams, RequestOptionsoptions?): ReportQueryResponse`

**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`.
1. 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 12, 2026**. Windows ending earlier are rejected; crossing windows are clamped to that boundary in the requested timezone.
1. 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.
1. Optionally break results down with `groupBy` (primary dimension) and `segmentBy` (secondary dimension).
1. 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`. Allowed values: **5, 7, 10, 15, 20**. `topValuesLimit` requires `groupBy`; `segmentTopValuesLimit` requires `segmentBy`.
- Members are ranked by the selected metric/aggregation over the **full filtered primary range**, so the ranked set is **stable across every time bucket** (a value that spikes in one bucket but is small overall does not enter the set). A comparison period reuses the **primary period's** ranked set — it is never re-ranked, so the legend stays identical period-over-period.
- `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

```json
{
  "metric": "new_conversations",
  "aggregation": "count",
  "startDate": "2026-07-12",
  "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).

### Parameters

- `params: ReportQueryParams`

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

    - `"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-12 are rejected.

  - `granularity: "hour" | "day" | "week" | "month"`

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

    - `"hour"`

    - `"day"`

    - `"week"`

    - `"month"`

  - `metric: string`

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

  - `startDate: string`

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

  - `compareEndDate?: string`

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

  - `compareStartDate?: string`

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

  - `filters?: ReportFilterRule | ReportFilterExpression`

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

    - `ReportFilterRule`

      - `fieldId: string`

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

      - `kind: "rule"`

        - `"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.

        - `"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`.

        - `string`

        - `number`

        - `boolean`

        - `Array<string | number | boolean>`

          - `string`

          - `number`

          - `boolean`

    - `ReportFilterExpression`

      - `children: Array<ReportFilterRule | ReportFilterGroup>`

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

        - `ReportFilterRule`

        - `ReportFilterGroup`

          - `children: Array<ReportFilterRule>`

            - `fieldId: string`

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

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

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

          - `kind: "group"`

            - `"group"`

          - `op: "and" | "or"`

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

            - `"and"`

            - `"or"`

      - `kind: "group"`

        - `"group"`

      - `op: "and" | "or"`

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

        - `"and"`

        - `"or"`

  - `groupBy?: string`

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

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

  - `segmentBy?: string`

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

  - `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?: 5 | 7 | 10 | 2 more`

    Body param: Keep only the top N `segmentBy` values per series, ranked by the selected metric over the full filtered primary range. Under a time View-by the ranked segment set is identical across every bucket; under a dimension View-by the ranking is applied within each retained parent group. Allowed values: `5`, `7`, `10`, `15`, `20`. Requires `segmentBy`.

    - `5`

    - `7`

    - `10`

    - `15`

    - `20`

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

  - `topValuesLimit?: 5 | 7 | 10 | 2 more`

    Body param: Keep only the top N `groupBy` (View-by) values, ranked by the selected metric/aggregation over the full filtered primary range. Allowed values: `5`, `7`, `10`, `15`, `20`. The ranked set is stable across every time bucket, and a comparison period reuses the primary period's ranked set (never re-ranked). Requires `groupBy`.

    - `5`

    - `7`

    - `10`

    - `15`

    - `20`

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

    - `"standard"`

    - `"hourly_heatmap"`

    - `"sankey"`

  - `featurebaseVersion?: "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.

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `ReportQueryResponse`

  - `meta: Meta`

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

      - `"count"`

      - `"sum"`

      - `"avg"`

      - `"median"`

      - `"min"`

      - `"max"`

      - `"range"`

      - `"percentile"`

      - `"value"`

    - `datasetId: string`

    - `granularity: string`

    - `metric: string`

    - `officeHoursOnly: boolean`

    - `unit: "count" | "percentage" | "duration_ms" | "number"`

      - `"count"`

      - `"percentage"`

      - `"duration_ms"`

      - `"number"`

    - `groupBy?: string`

    - `segmentBy?: string`

  - `object: "report_query_result"`

    - `"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: string`

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

    - `value: number`

    - `groupLabel?: string`

    - `previousValue?: number`

    - `segments?: Array<ReportSegmentDatum>`

      - `segment: string`

      - `value: number`

      - `previousValue?: number`

      - `segmentLabel?: string`

  - `previousValue?: number`

    Aggregated value for the comparison window, when requested.

  - `segmentData?: Array<ReportSegmentDatum>`

    - `segment: string`

    - `value: number`

    - `previousValue?: number`

    - `segmentLabel?: string`

  - `table?: Table`

    - `columns: Array<Column>`

      - `id: string`

      - `kind: "dimension" | "metric" | "record_attribute"`

        - `"dimension"`

        - `"metric"`

        - `"record_attribute"`

      - `label: string`

      - `sortable: boolean`

      - `unit: "count" | "percentage" | "duration_ms" | 3 more`

        - `"count"`

        - `"percentage"`

        - `"duration_ms"`

        - `"number"`

        - `"text"`

        - `"datetime"`

      - `align?: "left" | "right"`

        - `"left"`

        - `"right"`

      - `sticky?: boolean`

      - `summary?: "sum" | "weighted_rate" | "none"`

        - `"sum"`

        - `"weighted_rate"`

        - `"none"`

      - `valueType?: string`

    - `key: string`

    - `mode: "aggregate" | "records"`

      - `"aggregate"`

      - `"records"`

    - `page: number`

    - `pageSize: number`

    - `rows: Array<Row>`

      - `id: string`

      - `cells: Record<string, Cells>`

        - `display: string`

        - `denominator?: number`

        - `numerator?: number`

        - `raw?: unknown`

        - `sortValue?: string | number | null`

          - `string`

          - `number`

        - `value?: unknown`

    - `totalRows: number`

    - `sort?: Sort`

      - `columnId: string`

      - `direction: "asc" | "desc"`

        - `"asc"`

        - `"desc"`

    - `summaryRows?: Array<SummaryRow>`

      - `id: string`

      - `cells: Record<string, Cells>`

        - `display: string`

        - `denominator?: number`

        - `numerator?: number`

        - `raw?: unknown`

        - `sortValue?: string | number | null`

          - `string`

          - `number`

        - `value?: unknown`

  - `timeSeries?: Array<ReportTimeSeriesDatum>`

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

      - `value: number`

      - `previousValue?: number`

      - `segmentLabel?: string`

### Example

```typescript
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-12',
});

console.log(response.meta);
```

#### Response

```json
{
  "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",
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ],
  "previousValue": 0,
  "segmentData": [
    {
      "segment": "segment",
      "value": 0,
      "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",
            "denominator": 0,
            "numerator": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ],
    "totalRows": 0,
    "sort": {
      "columnId": "columnId",
      "direction": "asc"
    },
    "summaryRows": [
      {
        "id": "id",
        "cells": {
          "foo": {
            "display": "display",
            "denominator": 0,
            "numerator": 0,
            "raw": {},
            "sortValue": "string",
            "value": {}
          }
        }
      }
    ]
  },
  "timeSeries": [
    {
      "date": "2026-07-15",
      "value": 0,
      "previousValue": 0,
      "segments": [
        {
          "segment": "segment",
          "value": 0,
          "previousValue": 0,
          "segmentLabel": "segmentLabel"
        }
      ]
    }
  ]
}
```

## Look up report filter values

`client.reports.lookupFilterValues(ReportLookupFilterValuesParamsparams, RequestOptionsoptions?): ReportLookupFilterValuesResponse`

**post** `/v2/reports/filter-values`

Returns the available values for a filterable attribute — use it to build valid `filters` rules for `POST /v2/reports/query`.

Works for attributes whose values live in your workspace data (tags, teammates, teams, companies, plans, countries, ...). Attributes with `staticOptions` in the catalog don't need this endpoint — their value set is already inline.

- `query` narrows results by search string
- `selectedValues` resolves labels for values you already hold (returned alongside search results)

### Example

```json
{ "fieldId": "conversation.tags", "query": "bill", "limit": 20 }
```

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

### Parameters

- `params: ReportLookupFilterValuesParams`

  - `fieldId: string`

    Body param: Attribute ID to look up values for (must have `valueSource: "remote_search"` or static options in the catalog).

  - `limit?: number`

    Body param: Maximum number of options to return (1-50, default 20).

  - `query?: string`

    Body param: Optional search string to narrow the returned options.

  - `selectedValues?: Array<string>`

    Body param: Already-selected values to resolve labels for (returned alongside search results).

  - `featurebaseVersion?: "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.

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `ReportLookupFilterValuesResponse`

  - `data: Array<ReportAttributeOption>`

    - `label: string`

    - `value: string`

  - `object: "list"`

    - `"list"`

### Example

```typescript
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.lookupFilterValues({ fieldId: 'conversation.tags' });

console.log(response.data);
```

#### Response

```json
{
  "data": [
    {
      "label": "label",
      "value": "value"
    }
  ],
  "object": "list"
}
```

## Drill into report rows

`client.reports.drillIn(ReportDrillInParamsparams, RequestOptionsoptions?): ReportDrillInResponse`

**post** `/v2/reports/drill-in`

Returns the paginated row-level records behind a metric — either the whole reporting window, or one specific data point from a previous `POST /v2/reports/query` response.

Send the **same** `metric`, date range, `granularity`, `filters`, `groupBy` / `segmentBy` and `view` as the query you're drilling into, plus `dataPointFilters` selecting the data point:

- `timeBucket` — a `date` from the time series
- `groupValue` / `segmentValue` — a `group` / `segment` value from grouped data
- `dayOfWeek` + `hourOfDay` — a heatmap cell (`hourly_heatmap` view)
- `flowPathId` or `sourceNodeId` + `targetNodeId` — a flow edge (`sankey` view)

Pass `dataPointFilters: {}` to list all rows behind the metric for the window.

Results are paginated with `page` / `pageSize` (max 200 per page); `availableColumns` describes every column the dataset can return and `defaultColumnIds` the recommended subset. Rows are keyed by column ID; identity cells (teammates, contacts) are objects with `id` + `label`.

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

### Parameters

- `params: ReportDrillInParams`

  - `endDate: string`

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

  - `granularity: "hour" | "day" | "week" | "month"`

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

    - `"hour"`

    - `"day"`

    - `"week"`

    - `"month"`

  - `metric: string`

    Body param: Metric ID the drill-in belongs to.

  - `startDate: string`

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

  - `chartEditorId?: string`

    Body param: Stable editor ID of the saved chart that launched this drill-in.

  - `columns?: Array<string>`

    Body param: Column IDs to include in each returned row. Omit for legacy full-width static rows; pass an empty array to use `defaultColumnIds`.

  - `dataPointFilters?: DataPointFilters`

    Body param: Narrows the drill-in to one data point from a previous query (time bucket, group value, heatmap cell, or flow edge). Pass `{}` to list all underlying rows.

    - `dayOfWeek?: number`

      Day of week (1 = Monday … 7 = Sunday) — for `hourly_heatmap` views.

    - `flowPathId?: string`

      Flow path ID — for `sankey` views.

    - `groupValue?: string`

      Group value to drill into (a `group` value from `groupedData`).

    - `hourOfDay?: number`

      Hour of day (0-23) — for `hourly_heatmap` views.

    - `segmentValue?: string`

      Segment value to drill into (a `segment` value from segment data).

    - `sourceNodeId?: string`

      Flow source node ID — for `sankey` views.

    - `targetNodeId?: string`

      Flow target node ID — for `sankey` views.

    - `timeBucket?: string`

      Time bucket to drill into (a `date` value from the query time series).

  - `filters?: ReportFilterRule | ReportFilterExpression`

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

    - `ReportFilterRule`

      - `fieldId: string`

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

      - `kind: "rule"`

        - `"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.

        - `"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`.

        - `string`

        - `number`

        - `boolean`

        - `Array<string | number | boolean>`

          - `string`

          - `number`

          - `boolean`

    - `ReportFilterExpression`

      - `children: Array<ReportFilterRule | ReportFilterGroup>`

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

        - `ReportFilterRule`

        - `ReportFilterGroup`

          - `children: Array<ReportFilterRule>`

            - `fieldId: string`

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

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

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

          - `kind: "group"`

            - `"group"`

          - `op: "and" | "or"`

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

            - `"and"`

            - `"or"`

      - `kind: "group"`

        - `"group"`

      - `op: "and" | "or"`

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

        - `"and"`

        - `"or"`

  - `groupBy?: string`

    Body param

  - `officeHoursOnly?: boolean`

    Body param

  - `page?: number`

    Body param: Page number (1-based).

  - `pageSize?: number`

    Body param: Rows per page (1-200, default 25).

  - `segmentBy?: string`

    Body param

  - `selectedMetricId?: string`

    Body param: A metric ID from the datasets catalog. Use when the originating chart aggregates several metrics to pick which one the drill-in follows. Defaults to `metric`.

  - `selectedMetricRowId?: string`

    Body param: Stable metric-row ID selected within the launching chart.

  - `sort?: Array<Sort>`

    Body param

    - `direction: "asc" | "desc"`

      - `"asc"`

      - `"desc"`

    - `fieldId: string`

      Column ID to sort by (an attribute ID or `metric.value`).

  - `timezone?: string`

    Body param

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

    - `"standard"`

    - `"hourly_heatmap"`

    - `"sankey"`

  - `featurebaseVersion?: "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.

    - `"2026-01-01.nova"`

    - `"2025-12-12.clover"`

### Returns

- `ReportDrillInResponse`

  - `availableColumns: Array<AvailableColumn>`

    - `id: string`

      Column ID (an attribute ID or `metric.value`).

    - `name: string`

    - `supportsSort: boolean`

      Whether this column may be used in the drill-in sort request.

    - `valueType: string`

    - `category?: string`

  - `defaultColumnIds: Array<string>`

  - `meta: Meta`

    - `datasetId: string`

    - `metricId: string`

    - `rawMetricId: string`

    - `reconciliation: Reconciliation`

      - `kind: "rows" | "actions_across_conversations" | "active_hour_rate" | 2 more`

        - `"rows"`

        - `"actions_across_conversations"`

        - `"active_hour_rate"`

        - `"positive_ratings_over_eligible_ratings"`

        - `"missed_over_evaluated"`

      - `rowCount: number`

      - `status: "not_applicable" | "available" | "denominator_unavailable"`

        - `"not_applicable"`

        - `"available"`

        - `"denominator_unavailable"`

      - `denominatorHours?: number`

      - `displayedRate?: number`

      - `eligibleDenominatorTotal?: number`

      - `rawNumeratorTotal?: number`

    - `rowGrain: string`

    - `rowSemantics: "metric_rows" | "numerator_rows"`

      - `"metric_rows"`

      - `"numerator_rows"`

    - `sourceFact: string`

    - `emptyReason?: "no_matching_rows" | "denominator_unavailable"`

      - `"no_matching_rows"`

      - `"denominator_unavailable"`

    - `metricRowId?: string`

  - `object: "report_drill_in_result"`

    - `"report_drill_in_result"`

  - `page: number`

  - `pageSize: number`

  - `rows: Array<Record<string, string | number | boolean | 3 more | null>>`

    One record per underlying row, keyed by column ID.

    - `string`

    - `number`

    - `boolean`

    - `Array<string>`

    - `ReportDrillInIdentityCell`

      - `id: string`

      - `kind: "identity"`

        - `"identity"`

      - `label: string`

      - `avatarUrl?: string`

      - `color?: string`

      - `provenance?: "current" | "historical" | "fallback" | "unassigned"`

        - `"current"`

        - `"historical"`

        - `"fallback"`

        - `"unassigned"`

    - `ReportDrillInSourceDetailsCell`

      - `items: Array<Item>`

        - `label: string`

        - `id?: string`

        - `type?: string`

        - `url?: string`

      - `kind: "source_details"`

        - `"source_details"`

  - `timezone: string`

  - `total: number`

### Example

```typescript
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.drillIn({
  endDate: '2026-07-15',
  granularity: 'day',
  metric: 'new_conversations',
  startDate: '2026-07-12',
});

console.log(response.availableColumns);
```

#### Response

```json
{
  "availableColumns": [
    {
      "id": "id",
      "name": "name",
      "supportsSort": true,
      "valueType": "valueType",
      "category": "category"
    }
  ],
  "defaultColumnIds": [
    "string"
  ],
  "meta": {
    "datasetId": "datasetId",
    "metricId": "metricId",
    "rawMetricId": "rawMetricId",
    "reconciliation": {
      "kind": "rows",
      "rowCount": 0,
      "status": "not_applicable",
      "denominatorHours": 0,
      "displayedRate": 0,
      "eligibleDenominatorTotal": 0,
      "rawNumeratorTotal": 0
    },
    "rowGrain": "rowGrain",
    "rowSemantics": "metric_rows",
    "sourceFact": "sourceFact",
    "emptyReason": "no_matching_rows",
    "metricRowId": "metricRowId"
  },
  "object": "report_drill_in_result",
  "page": 0,
  "pageSize": 0,
  "rows": [
    {
      "foo": "string"
    }
  ],
  "timezone": "timezone",
  "total": 0
}
```

## Domain Types

### Report Attribute

- `ReportAttribute`

  - `id: string`

    Attribute ID — use as `fieldId` in filter rules and as `groupBy` / `segmentBy` in query requests.

  - `allowedOperators: Array<"is" | "is_not" | "in" | 10 more>`

    - `"is"`

    - `"is_not"`

    - `"in"`

    - `"not_in"`

    - `"contains"`

    - `"not_contains"`

    - `"gte"`

    - `"lte"`

    - `"between"`

    - `"exists"`

    - `"not_exists"`

    - `"is_member_of"`

    - `"is_not_member_of"`

  - `category: string`

  - `description: string`

  - `name: string`

  - `semantics: "current_state" | "historical_action" | "historical_snapshot" | "dynamic_metric_mapped"`

    Whether the filter uses the latest mutable state, an immutable historical action/snapshot, or a metric-dependent mapping.

    - `"current_state"`

    - `"historical_action"`

    - `"historical_snapshot"`

    - `"dynamic_metric_mapped"`

  - `supportsFilter: boolean`

    Whether the attribute can be used in `filters` rules.

  - `supportsGroupBy: boolean`

    Whether the attribute can be used as `groupBy` / `segmentBy`.

  - `supportsValueLookup: boolean`

    Whether `POST /v2/reports/filter-values` can list this attribute's values.

  - `valueType: "string" | "number" | "boolean" | 3 more`

    - `"string"`

    - `"number"`

    - `"boolean"`

    - `"date"`

    - `"enum"`

    - `"id"`

  - `multiValue?: boolean`

  - `staticOptions?: Array<ReportAttributeOption>`

    Fixed value set for enum-like attributes. Attributes without static options resolve values via `POST /v2/reports/filter-values`.

    - `label: string`

    - `value: string`

### Report Attribute Option

- `ReportAttributeOption`

  - `label: string`

  - `value: string`

### Report Dataset

- `ReportDataset`

  - `id: string`

  - `attributes: Array<ReportAttribute>`

    - `id: string`

      Attribute ID — use as `fieldId` in filter rules and as `groupBy` / `segmentBy` in query requests.

    - `allowedOperators: Array<"is" | "is_not" | "in" | 10 more>`

      - `"is"`

      - `"is_not"`

      - `"in"`

      - `"not_in"`

      - `"contains"`

      - `"not_contains"`

      - `"gte"`

      - `"lte"`

      - `"between"`

      - `"exists"`

      - `"not_exists"`

      - `"is_member_of"`

      - `"is_not_member_of"`

    - `category: string`

    - `description: string`

    - `name: string`

    - `semantics: "current_state" | "historical_action" | "historical_snapshot" | "dynamic_metric_mapped"`

      Whether the filter uses the latest mutable state, an immutable historical action/snapshot, or a metric-dependent mapping.

      - `"current_state"`

      - `"historical_action"`

      - `"historical_snapshot"`

      - `"dynamic_metric_mapped"`

    - `supportsFilter: boolean`

      Whether the attribute can be used in `filters` rules.

    - `supportsGroupBy: boolean`

      Whether the attribute can be used as `groupBy` / `segmentBy`.

    - `supportsValueLookup: boolean`

      Whether `POST /v2/reports/filter-values` can list this attribute's values.

    - `valueType: "string" | "number" | "boolean" | 3 more`

      - `"string"`

      - `"number"`

      - `"boolean"`

      - `"date"`

      - `"enum"`

      - `"id"`

    - `multiValue?: boolean`

    - `staticOptions?: Array<ReportAttributeOption>`

      Fixed value set for enum-like attributes. Attributes without static options resolve values via `POST /v2/reports/filter-values`.

      - `label: string`

      - `value: string`

  - `description: string`

  - `metrics: Array<ReportMetric>`

    - `id: string`

      Metric ID — use as `metric` in query requests.

    - `allowedAggregations: Array<"count" | "sum" | "avg" | 6 more>`

      - `"count"`

      - `"sum"`

      - `"avg"`

      - `"median"`

      - `"min"`

      - `"max"`

      - `"range"`

      - `"percentile"`

      - `"value"`

    - `description: string`

    - `name: string`

    - `supportedFilterAttributeIds: Array<string>`

      Attribute IDs this specific metric accepts in filter expressions.

    - `supportedGroupByDimensions: Array<string>`

      Attribute IDs this specific metric accepts as `groupBy` or `segmentBy`.

    - `supportedViews: Array<"standard" | "hourly_heatmap" | "sankey">`

      Which `view` values `POST /v2/reports/query` accepts for this metric. Most metrics support `standard` and `hourly_heatmap`; conversation-flow metrics are `sankey`-only.

      - `"standard"`

      - `"hourly_heatmap"`

      - `"sankey"`

    - `supportsOfficeHours: boolean`

    - `supportsPeriodComparison: boolean`

    - `type: "count" | "percentage" | "duration" | "number"`

      - `"count"`

      - `"percentage"`

      - `"duration"`

      - `"number"`

    - `unit: "count" | "percentage" | "duration_ms" | "number"`

      - `"count"`

      - `"percentage"`

      - `"duration_ms"`

      - `"number"`

    - `dataAvailableFromIso?: string`

      Local calendar date (ISO 8601) from which this specific metric has data, when it differs from the global reporting floor. Windows ending before this date are rejected for this metric.

    - `isDefaultVariant?: boolean`

      Whether this variant is the default selection for its `variantGroupId`.

    - `variantGroupId?: string`

      Metrics sharing a `variantGroupId` are timestamp variants of one logical metric (same display `name`); each measures the same population at a different timestamp. Query the concrete metric `id` to pick a variant.

    - `variantId?: string`

      Stable identifier for this variant within its group (e.g. `created_at`, `started_at`).

    - `variantLabel?: string`

      Human-readable variant label for pickers (e.g. `Created at`, `Started at`).

  - `name: string`

### Report Filter Expression

- `ReportFilterExpression`

  - `children: Array<ReportFilterRule | ReportFilterGroup>`

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

    - `ReportFilterRule`

      - `fieldId: string`

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

      - `kind: "rule"`

        - `"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.

        - `"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`.

        - `string`

        - `number`

        - `boolean`

        - `Array<string | number | boolean>`

          - `string`

          - `number`

          - `boolean`

    - `ReportFilterGroup`

      - `children: Array<ReportFilterRule>`

        - `fieldId: string`

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

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

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

      - `kind: "group"`

        - `"group"`

      - `op: "and" | "or"`

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

        - `"and"`

        - `"or"`

  - `kind: "group"`

    - `"group"`

  - `op: "and" | "or"`

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

    - `"and"`

    - `"or"`

### Report Filter Rule

- `ReportFilterRule`

  - `fieldId: string`

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

  - `kind: "rule"`

    - `"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.

    - `"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`.

    - `string`

    - `number`

    - `boolean`

    - `Array<string | number | boolean>`

      - `string`

      - `number`

      - `boolean`

### Report Grouped Datum

- `ReportGroupedDatum`

  - `group: string`

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

  - `value: number`

  - `groupLabel?: string`

  - `previousValue?: number`

  - `segments?: Array<ReportSegmentDatum>`

    - `segment: string`

    - `value: number`

    - `previousValue?: number`

    - `segmentLabel?: string`

### Report Metric

- `ReportMetric`

  - `id: string`

    Metric ID — use as `metric` in query requests.

  - `allowedAggregations: Array<"count" | "sum" | "avg" | 6 more>`

    - `"count"`

    - `"sum"`

    - `"avg"`

    - `"median"`

    - `"min"`

    - `"max"`

    - `"range"`

    - `"percentile"`

    - `"value"`

  - `description: string`

  - `name: string`

  - `supportedFilterAttributeIds: Array<string>`

    Attribute IDs this specific metric accepts in filter expressions.

  - `supportedGroupByDimensions: Array<string>`

    Attribute IDs this specific metric accepts as `groupBy` or `segmentBy`.

  - `supportedViews: Array<"standard" | "hourly_heatmap" | "sankey">`

    Which `view` values `POST /v2/reports/query` accepts for this metric. Most metrics support `standard` and `hourly_heatmap`; conversation-flow metrics are `sankey`-only.

    - `"standard"`

    - `"hourly_heatmap"`

    - `"sankey"`

  - `supportsOfficeHours: boolean`

  - `supportsPeriodComparison: boolean`

  - `type: "count" | "percentage" | "duration" | "number"`

    - `"count"`

    - `"percentage"`

    - `"duration"`

    - `"number"`

  - `unit: "count" | "percentage" | "duration_ms" | "number"`

    - `"count"`

    - `"percentage"`

    - `"duration_ms"`

    - `"number"`

  - `dataAvailableFromIso?: string`

    Local calendar date (ISO 8601) from which this specific metric has data, when it differs from the global reporting floor. Windows ending before this date are rejected for this metric.

  - `isDefaultVariant?: boolean`

    Whether this variant is the default selection for its `variantGroupId`.

  - `variantGroupId?: string`

    Metrics sharing a `variantGroupId` are timestamp variants of one logical metric (same display `name`); each measures the same population at a different timestamp. Query the concrete metric `id` to pick a variant.

  - `variantId?: string`

    Stable identifier for this variant within its group (e.g. `created_at`, `started_at`).

  - `variantLabel?: string`

    Human-readable variant label for pickers (e.g. `Created at`, `Started at`).

### Report Segment Datum

- `ReportSegmentDatum`

  - `segment: string`

  - `value: number`

  - `previousValue?: number`

  - `segmentLabel?: string`

### Report Time Series Datum

- `ReportTimeSeriesDatum`

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

    - `value: number`

    - `previousValue?: number`

    - `segmentLabel?: string`

### Report List Datasets Response

- `ReportListDatasetsResponse`

  - `data: Array<ReportDataset>`

    - `id: string`

    - `attributes: Array<ReportAttribute>`

      - `id: string`

        Attribute ID — use as `fieldId` in filter rules and as `groupBy` / `segmentBy` in query requests.

      - `allowedOperators: Array<"is" | "is_not" | "in" | 10 more>`

        - `"is"`

        - `"is_not"`

        - `"in"`

        - `"not_in"`

        - `"contains"`

        - `"not_contains"`

        - `"gte"`

        - `"lte"`

        - `"between"`

        - `"exists"`

        - `"not_exists"`

        - `"is_member_of"`

        - `"is_not_member_of"`

      - `category: string`

      - `description: string`

      - `name: string`

      - `semantics: "current_state" | "historical_action" | "historical_snapshot" | "dynamic_metric_mapped"`

        Whether the filter uses the latest mutable state, an immutable historical action/snapshot, or a metric-dependent mapping.

        - `"current_state"`

        - `"historical_action"`

        - `"historical_snapshot"`

        - `"dynamic_metric_mapped"`

      - `supportsFilter: boolean`

        Whether the attribute can be used in `filters` rules.

      - `supportsGroupBy: boolean`

        Whether the attribute can be used as `groupBy` / `segmentBy`.

      - `supportsValueLookup: boolean`

        Whether `POST /v2/reports/filter-values` can list this attribute's values.

      - `valueType: "string" | "number" | "boolean" | 3 more`

        - `"string"`

        - `"number"`

        - `"boolean"`

        - `"date"`

        - `"enum"`

        - `"id"`

      - `multiValue?: boolean`

      - `staticOptions?: Array<ReportAttributeOption>`

        Fixed value set for enum-like attributes. Attributes without static options resolve values via `POST /v2/reports/filter-values`.

        - `label: string`

        - `value: string`

    - `description: string`

    - `metrics: Array<ReportMetric>`

      - `id: string`

        Metric ID — use as `metric` in query requests.

      - `allowedAggregations: Array<"count" | "sum" | "avg" | 6 more>`

        - `"count"`

        - `"sum"`

        - `"avg"`

        - `"median"`

        - `"min"`

        - `"max"`

        - `"range"`

        - `"percentile"`

        - `"value"`

      - `description: string`

      - `name: string`

      - `supportedFilterAttributeIds: Array<string>`

        Attribute IDs this specific metric accepts in filter expressions.

      - `supportedGroupByDimensions: Array<string>`

        Attribute IDs this specific metric accepts as `groupBy` or `segmentBy`.

      - `supportedViews: Array<"standard" | "hourly_heatmap" | "sankey">`

        Which `view` values `POST /v2/reports/query` accepts for this metric. Most metrics support `standard` and `hourly_heatmap`; conversation-flow metrics are `sankey`-only.

        - `"standard"`

        - `"hourly_heatmap"`

        - `"sankey"`

      - `supportsOfficeHours: boolean`

      - `supportsPeriodComparison: boolean`

      - `type: "count" | "percentage" | "duration" | "number"`

        - `"count"`

        - `"percentage"`

        - `"duration"`

        - `"number"`

      - `unit: "count" | "percentage" | "duration_ms" | "number"`

        - `"count"`

        - `"percentage"`

        - `"duration_ms"`

        - `"number"`

      - `dataAvailableFromIso?: string`

        Local calendar date (ISO 8601) from which this specific metric has data, when it differs from the global reporting floor. Windows ending before this date are rejected for this metric.

      - `isDefaultVariant?: boolean`

        Whether this variant is the default selection for its `variantGroupId`.

      - `variantGroupId?: string`

        Metrics sharing a `variantGroupId` are timestamp variants of one logical metric (same display `name`); each measures the same population at a different timestamp. Query the concrete metric `id` to pick a variant.

      - `variantId?: string`

        Stable identifier for this variant within its group (e.g. `created_at`, `started_at`).

      - `variantLabel?: string`

        Human-readable variant label for pickers (e.g. `Created at`, `Started at`).

    - `name: string`

  - `object: "list"`

    - `"list"`

### Report Query Response

- `ReportQueryResponse`

  - `meta: Meta`

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

      - `"count"`

      - `"sum"`

      - `"avg"`

      - `"median"`

      - `"min"`

      - `"max"`

      - `"range"`

      - `"percentile"`

      - `"value"`

    - `datasetId: string`

    - `granularity: string`

    - `metric: string`

    - `officeHoursOnly: boolean`

    - `unit: "count" | "percentage" | "duration_ms" | "number"`

      - `"count"`

      - `"percentage"`

      - `"duration_ms"`

      - `"number"`

    - `groupBy?: string`

    - `segmentBy?: string`

  - `object: "report_query_result"`

    - `"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: string`

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

    - `value: number`

    - `groupLabel?: string`

    - `previousValue?: number`

    - `segments?: Array<ReportSegmentDatum>`

      - `segment: string`

      - `value: number`

      - `previousValue?: number`

      - `segmentLabel?: string`

  - `previousValue?: number`

    Aggregated value for the comparison window, when requested.

  - `segmentData?: Array<ReportSegmentDatum>`

    - `segment: string`

    - `value: number`

    - `previousValue?: number`

    - `segmentLabel?: string`

  - `table?: Table`

    - `columns: Array<Column>`

      - `id: string`

      - `kind: "dimension" | "metric" | "record_attribute"`

        - `"dimension"`

        - `"metric"`

        - `"record_attribute"`

      - `label: string`

      - `sortable: boolean`

      - `unit: "count" | "percentage" | "duration_ms" | 3 more`

        - `"count"`

        - `"percentage"`

        - `"duration_ms"`

        - `"number"`

        - `"text"`

        - `"datetime"`

      - `align?: "left" | "right"`

        - `"left"`

        - `"right"`

      - `sticky?: boolean`

      - `summary?: "sum" | "weighted_rate" | "none"`

        - `"sum"`

        - `"weighted_rate"`

        - `"none"`

      - `valueType?: string`

    - `key: string`

    - `mode: "aggregate" | "records"`

      - `"aggregate"`

      - `"records"`

    - `page: number`

    - `pageSize: number`

    - `rows: Array<Row>`

      - `id: string`

      - `cells: Record<string, Cells>`

        - `display: string`

        - `denominator?: number`

        - `numerator?: number`

        - `raw?: unknown`

        - `sortValue?: string | number | null`

          - `string`

          - `number`

        - `value?: unknown`

    - `totalRows: number`

    - `sort?: Sort`

      - `columnId: string`

      - `direction: "asc" | "desc"`

        - `"asc"`

        - `"desc"`

    - `summaryRows?: Array<SummaryRow>`

      - `id: string`

      - `cells: Record<string, Cells>`

        - `display: string`

        - `denominator?: number`

        - `numerator?: number`

        - `raw?: unknown`

        - `sortValue?: string | number | null`

          - `string`

          - `number`

        - `value?: unknown`

  - `timeSeries?: Array<ReportTimeSeriesDatum>`

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

      - `value: number`

      - `previousValue?: number`

      - `segmentLabel?: string`

### Report Lookup Filter Values Response

- `ReportLookupFilterValuesResponse`

  - `data: Array<ReportAttributeOption>`

    - `label: string`

    - `value: string`

  - `object: "list"`

    - `"list"`

### Report Drill In Response

- `ReportDrillInResponse`

  - `availableColumns: Array<AvailableColumn>`

    - `id: string`

      Column ID (an attribute ID or `metric.value`).

    - `name: string`

    - `supportsSort: boolean`

      Whether this column may be used in the drill-in sort request.

    - `valueType: string`

    - `category?: string`

  - `defaultColumnIds: Array<string>`

  - `meta: Meta`

    - `datasetId: string`

    - `metricId: string`

    - `rawMetricId: string`

    - `reconciliation: Reconciliation`

      - `kind: "rows" | "actions_across_conversations" | "active_hour_rate" | 2 more`

        - `"rows"`

        - `"actions_across_conversations"`

        - `"active_hour_rate"`

        - `"positive_ratings_over_eligible_ratings"`

        - `"missed_over_evaluated"`

      - `rowCount: number`

      - `status: "not_applicable" | "available" | "denominator_unavailable"`

        - `"not_applicable"`

        - `"available"`

        - `"denominator_unavailable"`

      - `denominatorHours?: number`

      - `displayedRate?: number`

      - `eligibleDenominatorTotal?: number`

      - `rawNumeratorTotal?: number`

    - `rowGrain: string`

    - `rowSemantics: "metric_rows" | "numerator_rows"`

      - `"metric_rows"`

      - `"numerator_rows"`

    - `sourceFact: string`

    - `emptyReason?: "no_matching_rows" | "denominator_unavailable"`

      - `"no_matching_rows"`

      - `"denominator_unavailable"`

    - `metricRowId?: string`

  - `object: "report_drill_in_result"`

    - `"report_drill_in_result"`

  - `page: number`

  - `pageSize: number`

  - `rows: Array<Record<string, string | number | boolean | 3 more | null>>`

    One record per underlying row, keyed by column ID.

    - `string`

    - `number`

    - `boolean`

    - `Array<string>`

    - `ReportDrillInIdentityCell`

      - `id: string`

      - `kind: "identity"`

        - `"identity"`

      - `label: string`

      - `avatarUrl?: string`

      - `color?: string`

      - `provenance?: "current" | "historical" | "fallback" | "unassigned"`

        - `"current"`

        - `"historical"`

        - `"fallback"`

        - `"unassigned"`

    - `ReportDrillInSourceDetailsCell`

      - `items: Array<Item>`

        - `label: string`

        - `id?: string`

        - `type?: string`

        - `url?: string`

      - `kind: "source_details"`

        - `"source_details"`

  - `timezone: string`

  - `total: number`
