# Query reports

> Query playback reports from your application.

Use the [Analytics Server SDKs](/docs/integration/analytics/server/sdk) to read playback reports from your backend. This guide helps you choose a report, understand its results, and export data as CSV.

## Choose a report

| Report | What it shows |
| --- | --- |
| Summary and videos | Views, unique viewers, and watch time, overall or per video. |
| Trends | How summary, engagement, and quality metrics change by hour or day. |
| Engagement | How long people watch and how far they progress. |
| Watched sections | Which parts of a video people actually played. Requires a `video_id`. |
| Playback quality | Startup time, buffering, fatal errors, and playback scores. |
| Renditions | Playing time per rendition and measured average bitrate. |
| Subtitles | Watch time per subtitle language, including time without subtitles. |
| Playback rates | Watch time per playback speed. |
| Sessions and events | Individual playback attempts and their event history. |
| Errors | Error groups ranked by the number of affected attempts. |

See the [OpenAPI specification](https://github.com/chunkifydev/chunkify-analytics/blob/v0.18.0/openapi.json) for each report's parameters and response fields.

## Understand the metrics

| Metric | Meaning |
| --- | --- |
| Views | Playback attempts, including those that fail before playback starts. |
| Unique viewers | Distinct anonymous viewer IDs, which the tracker generates for each browser. The same person on another browser or device counts again. |
| Watch time | Recorded time spent playing, using the final or latest measurement for each attempt. |
| Average watch time | Total watch time divided by the number of playback attempts. |
| Startup time | Time from the playback attempt to the first playing event. |
| Buffering ratio | Buffering time divided by playing plus buffering time. |
| Fatal-error ratio | The share of attempts with a recorded fatal error. |
| Watched sections | Sections actually played, excluding skips. Replays count once per attempt. |

Reaching the end can include seeking. Use watched sections to see which parts were played. Attempts without usable section measurements are excluded from that report's sample count.

For an overall viewer count, use an ungrouped summary. Adding viewer counts across videos or audience groups can count the same viewer more than once.

Ratios are fractions, so `0.05` means 5%. Show the returned sample counts alongside measurements, and display `null` as unavailable rather than zero.

## Filter and group reports

Combine date, video, video title, viewer, device, location, page, subtitle language, or playback speed filters to narrow a report. Use the dimensions report to find available filter values. Supported filters vary by report.

Dates use RFC3339 timestamps with a timezone. The `from` boundary is included; `to` is excluded. Reports select attempts by their start time, while raw event queries use each event's timestamp.

Use `group_by` on summary, quality, or engagement reports to compare audiences or pages. For example, group by `device_type` to compare desktop and mobile playback, `video_title` to compare videos by name, or `user_id` to compare signed-in users. Groups are ranked by views.

Video title, subtitle language, and playback speed filters and groups use the values at the start of each attempt, so each attempt counts once. A renamed video counts each attempt under the title it started with, and attempts without a title never match a title filter. The subtitles and playback rates reports instead split watch time across the values used during each attempt.

Filter by a [custom dimension](/docs/integration/analytics#custom-dimensions) with `custom.<key>=<value>`, such as `custom.plan=premium`, and group by one with `group_by=custom.plan`. Attempts without the key form a separate group and never match a filter. List a dimension's values with the dimensions report, using `custom.<key>`, or the keys in use with `custom_keys`.

Audience and page filters match exact values. Pair city with country, or page path with page origin, to avoid combining places or pages with the same name.

### Trends

Choose a date range and an `interval` of `hour` or `day`. Trends use UTC. An attempt's watch time belongs to the interval in which it started, even if playback continues into the next interval.

### Read more results

Pass `next_cursor` as `cursor` in the next request, keeping the same filters and sorting. Continue until `next_cursor` is `null`. Start a new query without a cursor to include new arrivals.

## Export reports

Create an export with the report, filters, sorting, and columns you need. The SDK can list the available columns for each report.

Open the returned `download_url` to download all matching rows as CSV. Keep this link private, since anyone with it can download the report. Links expire after 30 minutes. Create a new export if a link expires or no longer works.

## Limits and freshness

API and MCP queries share the project's read limits. If a request returns `429`, wait for the duration in `Retry-After` before retrying.

Summary, engagement, quality, time-series, and video reports can lag incoming events by about a minute while their hourly rollups refresh. An optional report cache can add up to 60 seconds. Raw events, sessions, and CSV downloads bypass that cache.

Retention limits the available history. Attempts whose start event is no longer retained are excluded from metric totals.
