## Drill into report rows

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

### Header Parameters

- `"Featurebase-Version": optional "2026-01-01.nova" or "2025-12-12.clover"`

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

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

### Body Parameters

- `endDate: string`

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

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

  Time bucket size for the returned time series.

  - `"hour"`

  - `"day"`

  - `"week"`

  - `"month"`

- `metric: string`

  Metric ID the drill-in belongs to.

- `startDate: string`

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

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

- `columns: optional array of string`

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

- `dataPointFilters: optional object { dayOfWeek, flowPathId, groupValue, 5 more }`

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

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

  - `flowPathId: optional string`

    Flow path ID — for `sankey` views.

  - `groupValue: optional string`

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

  - `hourOfDay: optional number`

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

  - `segmentValue: optional string`

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

  - `sourceNodeId: optional string`

    Flow source node ID — for `sankey` views.

  - `targetNodeId: optional string`

    Flow target node ID — for `sankey` views.

  - `timeBucket: optional string`

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

- `filters: optional ReportFilterRule or ReportFilterExpression`

  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 object { fieldId, kind, operator, value }`

    - `fieldId: string`

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

    - `kind: "rule"`

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

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

      - `string`

      - `number`

      - `boolean`

      - `array of string or number or boolean`

        - `string`

        - `number`

        - `boolean`

  - `ReportFilterExpression object { children, kind, op }`

    - `children: array of ReportFilterRule 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.

      - `ReportFilterRule object { fieldId, kind, operator, value }`

      - `ReportFilterGroup object { children, kind, op }`

        - `children: array of ReportFilterRule`

          - `fieldId: string`

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

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

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

        - `kind: "group"`

          - `"group"`

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

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

          - `"and"`

          - `"or"`

    - `kind: "group"`

      - `"group"`

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

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

      - `"and"`

      - `"or"`

- `groupBy: optional string`

- `officeHoursOnly: optional boolean`

- `page: optional number`

  Page number (1-based).

- `pageSize: optional number`

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

- `segmentBy: optional string`

- `selectedMetricId: optional string`

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

  Stable metric-row ID selected within the launching chart.

- `sort: optional array of object { direction, fieldId }`

  - `direction: "asc" or "desc"`

    - `"asc"`

    - `"desc"`

  - `fieldId: string`

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

- `timezone: optional string`

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

  - `"standard"`

  - `"hourly_heatmap"`

  - `"sankey"`

### Returns

- `availableColumns: array of object { id, name, supportsSort, 2 more }`

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

- `defaultColumnIds: array of string`

- `meta: object { datasetId, metricId, rawMetricId, 6 more }`

  - `datasetId: string`

  - `metricId: string`

  - `rawMetricId: string`

  - `reconciliation: object { kind, rowCount, status, 4 more }`

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

      - `"rows"`

      - `"actions_across_conversations"`

      - `"active_hour_rate"`

      - `"positive_ratings_over_eligible_ratings"`

      - `"missed_over_evaluated"`

    - `rowCount: number`

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

      - `"not_applicable"`

      - `"available"`

      - `"denominator_unavailable"`

    - `denominatorHours: optional number`

    - `displayedRate: optional number`

    - `eligibleDenominatorTotal: optional number`

    - `rawNumeratorTotal: optional number`

  - `rowGrain: string`

  - `rowSemantics: "metric_rows" or "numerator_rows"`

    - `"metric_rows"`

    - `"numerator_rows"`

  - `sourceFact: string`

  - `emptyReason: optional "no_matching_rows" or "denominator_unavailable"`

    - `"no_matching_rows"`

    - `"denominator_unavailable"`

  - `metricRowId: optional string`

- `object: "report_drill_in_result"`

  - `"report_drill_in_result"`

- `page: number`

- `pageSize: number`

- `rows: array of map[string or number or boolean or 3 more]`

  One record per underlying row, keyed by column ID.

  - `string`

  - `number`

  - `boolean`

  - `array of string`

  - `ReportDrillInIdentityCell object { id, kind, label, 3 more }`

    - `id: string`

    - `kind: "identity"`

      - `"identity"`

    - `label: string`

    - `avatarUrl: optional string`

    - `color: optional string`

    - `provenance: optional "current" or "historical" or "fallback" or "unassigned"`

      - `"current"`

      - `"historical"`

      - `"fallback"`

      - `"unassigned"`

  - `ReportDrillInSourceDetailsCell object { items, kind }`

    - `items: array of object { label, id, type, url }`

      - `label: string`

      - `id: optional string`

      - `type: optional string`

      - `url: optional string`

    - `kind: "source_details"`

      - `"source_details"`

- `timezone: string`

- `total: number`

### Example

```http
curl https://do.featurebase.app/v2/reports/drill-in \
    -H 'Content-Type: application/json' \
    -H "Authorization: Bearer $FEATUREBASE_API_KEY" \
    -d '{
          "endDate": "2026-07-15",
          "granularity": "day",
          "metric": "new_conversations",
          "startDate": "2026-07-12",
          "view": "standard"
        }'
```

#### 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
}
```
