# query_site_analytics

Source: https://modulify.ai/docs/api/analytics/query-site-analytics

Asks an arbitrary question of a site's visitor analytics as a Plausible Stats v2 query, such as its top pages, sources or countries.

- Title: Query visitor analytics freely
- Scope: `analytics:read`
- Access: Read only
- Endpoint: `POST /v1/query_site_analytics`

It answers what the fixed shape of `get_site_analytics` cannot, and it is the tool for top pages, traffic sources, countries, browsers, operating systems and devices. The site is filled in for you, so there is no `site_id` to send, and anything passed for it is overwritten.

Pick `metrics` from `visitors`, `visits`, `pageviews`, `views_per_visit`, `bounce_rate` and `visit_duration`. Set `date_range` to a named period such as `7d` or `30d`, or to a two element list of ISO dates, where a custom range longer than 3,660 days is refused. Add `dimensions` to break the numbers down: `event:page` for top pages, `visit:source` for traffic sources, `visit:country`, `visit:browser`, `visit:os`, `visit:device`, or `time:day`, `time:hour` or `time:month` for a series over time.

`pagination.limit` caps the rows at 1,000, a larger value is lowered to 1,000, and leaving it out returns up to that many. Pagination only applies when you break down by a dimension, and the product itself uses 8 for its breakdown cards. `time:minute` is not available, and `time:hour` needs `day`, `7d`, `30d`, `month` or a custom range of at most 31 days.

The response carries `provisioned`, the analytics `domain`, the `results` rows and the query `meta`. A query the analytics backend rejects fails with its reason, so an empty `results` list really means no matching traffic.

Each call costs 1 of the 1,200 analytics lookups access tokens share per site per hour, as [Rate limits and result size](https://modulify.ai/docs/mcp/tokens-and-scopes#rate-limits-and-result-size) explains, and nothing when the answer is already cached. Unlike the tools that read fixed numbers, it spends that lookup even on a site that was never published. Over that limit it fails with `Access tokens, through MCP clients or the API, used their 1,200 analytics lookups for this site this hour. Wait and try again!` See [Breakdowns](https://modulify.ai/docs/grow/analytics#breakdowns).

## Request

Call it with a `POST` to `https://api.modulify.ai/v1/query_site_analytics`, sending the inputs below as a JSON object. The token needs the `analytics:read` scope.

It only reads and changes nothing, so retrying it is safe.

```bash
curl -X POST https://api.modulify.ai/v1/query_site_analytics \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"PROJECT_ID","query":{}}'
```

Over MCP, the same method is the [query_site_analytics tool](https://modulify.ai/docs/mcp/analytics/query-site-analytics).

## Inputs

| Input | Type | Required | Description |
| --- | --- | --- | --- |
| `projectId` | string | Yes | The site id. |
| `query` | object | Yes | The Plausible Stats v2 query, with the fields `metrics`, `date_range`, `dimensions`, `filters` (for example `[["is", "visit:country", ["DE"]]]`), `order_by`, `pagination` with its `limit` and `offset`, and `include`, such as `time_labels` true for a labeled series. `site_id` is set for you, and anything passed for it is overwritten. |

## Response

Every call answers with the [JSON envelope](https://modulify.ai/docs/api/requests-and-responses#the-response) of `success`, `message`, `data`, `code` and `version`. `data` holds the result described above, and on a method that returns a total, `count` carries it. The [response headers](https://modulify.ai/docs/api/requests-and-responses#headers-on-every-method-call) carry the call's `X-Request-Id` and what is left of your per-minute budget in `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`. [Errors](https://modulify.ai/docs/api/errors) explains every status code a call can answer with.