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

# Read analytics

> Learn how to read Cactal website analytics over the API: engagement totals, dimension breakdowns, pageview and event time series, and realtime activity, with exact query parameters.

Website analytics is served by three endpoints: `query` for engagement totals and dimension breakdowns, `timeseries` for values over time, and `realtime` for who is on the site right now.

<Note>
  Analytics requires the Pro plan or higher. On lower plans these endpoints return `403` with kind `forbidden`. See [Plans and limits](/docs/platform/plans-and-limits).
</Note>

## Prerequisites

* An API key with any access role on the website, see [Create an API key](/docs/create-an-api-key)
* A published website receiving traffic, see the [Quickstart](/docs/quickstart)

## Time ranges and filters

The `query` and `timeseries` endpoints take `startAt` and `endAt` as Unix epoch timestamps in milliseconds, and accept these optional filters as query parameters: `path`, `referrer`, `title`, `query`, `browser`, `os`, `device`, `country`, `region`, `city`, `language`, `hostname`, `tag`, `event`, `distinctId`, `utmSource`, `utmMedium`, `utmCampaign`, `utmContent`, `utmTerm`, `segment`, `cohort`.

<Steps>
  <Step title="Read engagement totals">
    `GET /v1/websites/{websiteId}/analytics/query` returns totals for the range.

    ```bash Last 7 days of totals theme={null}
    curl -G 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/analytics/query' \
      -H "Authorization: Bearer $CACTAL_API_KEY" \
      --data-urlencode 'startAt=1751328000000' \
      --data-urlencode 'endAt=1751932800000'
    ```

    ```json Response theme={null}
    {
      "pageviews": 4821,
      "visitors": 1930,
      "visits": 2214,
      "bounces": 871,
      "totaltime": 391244
    }
    ```

    Add a filter to scope the totals, for example `--data-urlencode 'country=US'`. A website with no recorded traffic returns the same shape with zeros.
  </Step>

  <Step title="Break down by dimension">
    Add `dimension` to the same endpoint to rank values of one dimension alongside the totals. `dimension` is one of: `path`, `entry`, `exit`, `title`, `query`, `referrer`, `channel`, `domain`, `country`, `region`, `city`, `browser`, `os`, `device`, `language`, `screen`, `event`, `hostname`, `tag`, `distinctId`. `limit` caps the rows (default 25, max 1000).

    ```bash Top pages theme={null}
    curl -G 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/analytics/query' \
      -H "Authorization: Bearer $CACTAL_API_KEY" \
      --data-urlencode 'startAt=1751328000000' \
      --data-urlencode 'endAt=1751932800000' \
      --data-urlencode 'dimension=path' \
      --data-urlencode 'limit=10'
    ```

    ```json Response (truncated) theme={null}
    {
      "pageviews": 4821,
      "visitors": 1930,
      "visits": 2214,
      "bounces": 871,
      "totaltime": 391244,
      "rows": [
        { "name": "/", "pageviews": 1811, "visitors": 902, "visits": 1004, "bounces": 388, "totaltime": 140212 },
        { "name": "/pricing", "pageviews": 640, "visitors": 411, "visits": 452, "bounces": 130, "totaltime": 48120 }
      ]
    }
    ```

    `rows` is present only when `dimension` is set, ranked by page views with full engagement totals per value.
  </Step>

  <Step title="Read a time series">
    `GET /v1/websites/{websiteId}/analytics/timeseries` returns one series bucketed by `unit` (`year`, `month`, `day`, `hour`, or `minute`). `series` selects which:

    * `series=pageviews` returns parallel `pageviews` and `sessions` arrays, one point per bucket. Optional `compare` (`prev` for the previous period, `yoy` for a year earlier) requests comparison data.
    * `series=events` returns tracked custom-event counts in `events`, one point per event name per bucket, shaped `{ "x": "<event>", "t": "<period>", "y": <count> }`. `compare` is not valid here.

    Optional `timezone` (an IANA name) controls bucket boundaries, and `maxPoints` (default 30, max 500) caps how many of the most recent time buckets are returned.

    ```bash Daily pageviews theme={null}
    curl -G 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/analytics/timeseries' \
      -H "Authorization: Bearer $CACTAL_API_KEY" \
      --data-urlencode 'startAt=1751328000000' \
      --data-urlencode 'endAt=1751932800000' \
      --data-urlencode 'series=pageviews' \
      --data-urlencode 'unit=day' \
      --data-urlencode 'timezone=America/New_York'
    ```

    ```json Response (truncated) theme={null}
    {
      "pageviews": [ { "x": "2026-07-01", "y": 612 }, { "x": "2026-07-02", "y": 748 } ],
      "sessions": [ { "x": "2026-07-01", "y": 251 }, { "x": "2026-07-02", "y": 300 } ]
    }
    ```

    Only the requested series' fields are present. When a query produces more buckets than `maxPoints`, only the most recent buckets are returned and the response carries a `truncated` field with `shown`, `total`, and a `hint`. Request a coarser `unit`, a narrower range, or a larger `maxPoints` for full coverage.
  </Step>

  <Step title="Watch realtime activity">
    `GET /v1/websites/{websiteId}/analytics/realtime` takes no range and returns a compact snapshot of Umami's trailing 30-minute realtime window:

    ```bash Realtime snapshot theme={null}
    curl 'https://api.cactal.ai/v1/websites/V1StGXR8_Z5jdHi6B-myT/analytics/realtime' \
      -H "Authorization: Bearer $CACTAL_API_KEY"
    ```

    ```json Response theme={null}
    {
      "views": 44,
      "visitors": 12,
      "events": 3,
      "countries": 5,
      "timestamp": "2026-07-08T15:04:05.000Z"
    }
    ```

    `visitors` is the distinct visitor count in that 30-minute window; `timestamp` is the snapshot time as ISO 8601. This is not directly comparable to the removed `active` endpoint, which counted distinct visitors over five minutes.
  </Step>
</Steps>

## Troubleshooting

| Symptom                     | Cause                                                           | Fix                                                    |
| --------------------------- | --------------------------------------------------------------- | ------------------------------------------------------ |
| `403` forbidden             | The organization's plan does not include analytics.             | Upgrade to Pro or higher.                              |
| All-zero responses          | No traffic recorded yet, or the website was published recently. | Confirm the website is published and receiving visits. |
| `400` on `compare`          | `compare` sent with `series=events`.                            | Drop `compare`, or set `series=pageviews`.             |
| Series buckets look shifted | Range timestamps are interpreted in UTC without a `timezone`.   | Pass `timezone`, for example `America/New_York`.       |

## Next steps

* [Plans and limits](/docs/platform/plans-and-limits) for what each plan includes
* [How Cactal works](/docs/concepts/how-cactal-works) for where analytics sits in the platform
