> ## Documentation Index
> Fetch the complete documentation index at: https://www.plain.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reporting

> Query Plain Insights metrics from the API, with filters, group-by, percentiles, and the thread IDs behind each number.

The Reporting API returns the same metrics as [Insights](/docs/product/platform/insights): thread volume, response and resolution times, SLA compliance, CSAT, and per-user activity. Use it to pull metrics into a BI tool, an internal dashboard, or a scheduled report.

<Snippet file="graphql/sdk-note.mdx" />

## Queries

The API has three queries, one per chart shape:

* [`threadTimeSeriesMetric`](/docs/graphql/reporting/time-series): one value per time bucket, such as threads created per day
* [`threadSingleValueMetric`](/docs/graphql/reporting/single-value): one value for the whole period, such as median first response time this month
* [`threadHeatmapMetric`](/docs/graphql/reporting/heatmap): a 7 x 24 grid of counts by day of week and hour

Each query takes an `input` with a `metricName`, a date range, and a `mode`. All three accept the same [filters](/docs/graphql/reporting/filters), and the time series and single value queries accept the same group-by. The [metrics reference](/docs/graphql/reporting/metrics) lists every metric name and which query supports it.

## Permissions

The Reporting API needs these permissions:

* `metrics:read`: every metric whose name doesn't start with `agent_`
* `metricsAgent:read`: every metric whose name starts with `agent_`, which report per user. Also needed to group by `ASSIGNEE`, or to filter `assignedToUser` to anyone other than the caller

`agent_` metrics also need the Team reporting feature, which is on the Horizon and Frontier plans. Every query is limited to your plan's [reporting history](/docs/product/platform/insights#reporting-history).

## Return a value or the threads behind it

`mode` is required and picks what the query returns:

* **`METRIC`**: the reporting metric's values. `threadIds` is `null`
* **`THREAD_IDS`**: the distinct IDs of every thread behind the value, for the whole date range. The value fields are `null`

To get the threads behind one bucket, narrow `from` and `to` to that bucket. To get the threads behind one group, turn the group into a [filter](/docs/graphql/reporting/filters). `groupBy` is rejected in `THREAD_IDS` mode.

## Dates and time zones

`from` and `to` are ISO 8601 timestamps in UTC, such as `2026-09-01T00:00:00Z`. Timestamps with an offset such as `+01:00` are rejected. These rules apply to every query:

* Both ends are inclusive.
* `to` can't be in the future. Plain allows up to 5 minutes of clock skew and treats that as now.
* Time series widen the range to whole intervals. `from` moves back to the start of its interval and `to` forward to the end of its interval. Weeks start on Monday.

Insights has no time zone setting, so every bucket, day, and heatmap hour is in UTC.

## Units

Every duration is in minutes. Counts are numbers of threads unless the metric says otherwise. SLA compliance is a fraction from 0 to 1, and CSAT percentage is a number from 0 to 100. `null` means there was no data for that bucket or group. `0` means a real zero.

## Data freshness and exclusions

Reporting data is rebuilt every hour, so values lag by up to an hour. Polling more than once an hour returns the same numbers. Every metric leaves out deleted threads, spam, **Ignored** threads, test threads, and threads from Plain-provisioned demo domains. See [Which threads count](/docs/product/platform/insights#which-threads-count).

## Errors and limits

Reporting queries return these errors in `extensions.code`:

| Code | When |
| - | - |
| `input_validation` | The input is invalid. For example: a bad date, a range over 1,095 days, `percentile` on a metric without percentiles, an `agent_` metric without `groupBy`, a CSAT metric without `surveyResponse.rating`, filters nested too deep, or a filter the metric doesn't support |
| `thread_metric_filter_no_match` | A company, tenant, tier, or customer group identifier in `filters` matched nothing. The whole query fails, even when the identifier sits inside `or` |
| `thread_metric_query_timed_out` | The query took longer than 10 seconds. Narrow the range, use a larger interval, or set `topN` |
| `tinybird_response_too_large` | The response was larger than 5 MB. Narrow the range, use a larger interval, or set `topN` |
| `FORBIDDEN` | The API key is missing a permission, your plan doesn't include Team reporting, or `from` is earlier than your plan's reporting history |

An error returns `data: null` for the whole query, including any other fields in the same request.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.