# Get analytics across pages

`GET https://evohub.io/api/v1/pages/analytics`

Part of the [Status API](https://docs-dev.evohub.io/status.md) reference · operationId `getStatusAnalytics`.

Every page the caller can see, together: views, visitors, feed and AI reads,
subscriber growth (signed up, confirmed, unsubscribed, removed) and incidents
published with time to first publication, as totals, a series, per page and
top lists.

`from` and `to` go together: dates (`to` inclusive) or RFC 3339 instants,
widened to whole UTC days; default the last 30 days, at most 366. Two days or
less gives an hourly series. `compare` adds the previous period's or last
year's headline numbers.

**Permission (API-key scope):** `status:page:read`.

## Authorization

Any one of:

- `bearerAuth` (status:page:read)
- `apiKeyHeader` (status:page:read)

Where:

- `bearerAuth`: HTTP Bearer — An EvoHub API key (`evohub_…`) as a bearer token.
- `apiKeyHeader`: API key in the header `X-API-Key` — An EvoHub API key (`evohub_…`).

## Query parameters

- `from` (string, example `2026-09-10`): `YYYY-MM-DD` or RFC 3339.
- `to` (string, example `2026-10-09`): `YYYY-MM-DD` (inclusive) or RFC 3339.
- `team_id` (string): Only this team's pages. A team the caller cannot see gives an empty list.
- `page_id` (string (uuid)): Only this page (404 if the caller cannot see it).
- `compare` (string, one of `previous`, `year`)

## Responses

### 200 — The analytics.

Content type: `application/json`

Type: `object`

- `data` (OrgAnalytics, required)
  - `range` (AnalyticsRange)
    - `from` (string (date-time))
    - `to` (string (date-time))
    - `granularity` (string, one of `hour`, `day`)
    - `tz` (string, value `UTC`)
  - `totals` (object)
    - `pages` (integer)
    - `views` (integer)
    - `visitors` (integer)
    - `feed_reads` (integer)
    - `ai_requests` (integer)
    - `subscribers_confirmed` (integer)
    - `subscribers` (SubscriberEvents)
      - `signed_up` (integer)
      - `confirmed` (integer)
      - `unsubscribed` (integer)
      - `removed` (integer)
      - `net` (integer)
    - `incidents` (IncidentStats)
      - `published` (integer)
      - `by_impact` (array of object)
        - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`)
        - `count` (integer)
      - `time_to_first_publication` (DurationStats)
  - `series` (array of object): Hourly for a range of two days or less (then the per-day counters are `null`), daily otherwise.
    - `bucket` (string (date-time))
    - `views` (integer)
    - `visitors` (integer | null)
    - `feed_reads` (integer | null)
    - `ai_requests` (integer | null)
    - `subscribers` (SubscriberEvents | null)
      - One of:
        - SubscriberEvents
          - `signed_up` (integer)
          - `confirmed` (integer)
          - `unsubscribed` (integer)
          - `removed` (integer)
          - `net` (integer)
        - null
    - `incidents_published` (integer)
  - `pages` (array of object)
    - `page_id` (string)
    - `name` (string)
    - `slug` (string)
    - `team_id` (string)
    - `custom_domain` (string | null)
    - `analytics_enabled` (boolean)
    - `views` (integer)
    - `visitors` (integer)
    - `feed_reads` (integer)
    - `ai_requests` (integer)
    - `subscribers_confirmed` (integer)
    - `subscribers` (SubscriberEvents)
      - `signed_up` (integer)
      - `confirmed` (integer)
      - `unsubscribed` (integer)
      - `removed` (integer)
      - `net` (integer)
    - `incidents` (IncidentStats)
      - `published` (integer)
      - `by_impact` (array of object)
        - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`)
        - `count` (integer)
      - `time_to_first_publication` (DurationStats)
  - `routes` (array of object)
    - `route` (string)
    - `views` (integer)
  - `referrers` (array of object)
    - `host` (string)
    - `views` (integer)
  - `countries` (array of object)
    - `code` (string)
    - `views` (integer)
  - `devices` (array of object)
    - `device` (string)
    - `views` (integer)
  - `feeds` (array of object)
    - `kind` (string)
    - `count` (integer)
  - `ai` (array of object)
    - `kind` (string)
    - `count` (integer)
  - `ai_agents` (array of object)
    - `agent` (string)
    - `count` (integer)
  - `compare` (object | null)
    - One of:
      - object
        - `mode` (string, one of `previous`, `year`)
        - `from` (string (date-time))
        - `to` (string (date-time))
        - `views` (integer)
        - `visitors` (integer)
        - `feed_reads` (integer)
        - `ai_requests` (integer)
        - `subscribers` (SubscriberEvents)
          - `signed_up` (integer)
          - `confirmed` (integer)
          - `unsubscribed` (integer)
          - `removed` (integer)
          - `net` (integer)
        - `incidents` (IncidentStats)
          - `published` (integer)
          - `by_impact` (array of object)
            - `impact` (IncidentImpact, one of `none`, `minor`, `major`, `critical`)
            - `count` (integer)
          - `time_to_first_publication` (DurationStats)
      - null
- `success` (boolean, required, value `true`)

### 400 — `from`/`to` or `compare` is not valid (`VALIDATION_FAILED`, each field in `details`).

Content type: `application/json`

Type: `ValidationError`

- `error` (object, required)
  - `code` (string, required, value `VALIDATION_FAILED`)
  - `message` (string, required)
  - `request_id` (string)
  - `details` (array of object, required)
    - `field` (string)
    - `message` (string)

### 401 — No API key was sent, or it is unknown, revoked or expired (`UNAUTHORIZED`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): This request's id, also in `X-Request-ID`.

### 403 — The key lacks the scope this endpoint needs (`FORBIDDEN`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): This request's id, also in `X-Request-ID`.

### 404 — `page_id` is not a page the caller can see (`NOT_FOUND`).

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): This request's id, also in `X-Request-ID`.

### 429 — Too many requests (`RATE_LIMITED`). Wait `Retry-After` seconds.

Headers:

- `Retry-After` (integer): Seconds to wait.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): This request's id, also in `X-Request-ID`.

### 500 — Something went wrong on EvoHub's side (`INTERNAL_ERROR`). Retry later.

Content type: `application/json`

Type: `Error`

- `error` (object, required)
  - `code` (string, required): Machine-readable code. Branch on this.
  - `message` (string, required): Human-readable explanation.
  - `request_id` (string): This request's id, also in `X-Request-ID`.

## Example request

```bash
curl -X GET 'https://evohub.io/api/v1/pages/analytics' \
  -H 'Authorization: Bearer <TOKEN>'
```
