Developers Analytics & usage

Observe

Analytics & usage

Read Frontpage site analytics, plan meters, and the built page list from the CLI, with real response shapes.

What this covers

Three read-only observe commands share the RL-READ rate-limit class (see Rate limits):

  • frontpage analytics: traffic dashboard JSON (pageviews, visitors, top pages/sources)
  • frontpage usage: plan entitlements and AI / bandwidth / storage meters
  • frontpage pages: routes the site currently exposes (path + title)

Viewer role is enough. None of these publish or mutate the site branch.

Analytics

frontpage analytics
frontpage analytics --days 7
frontpage analytics --days 30 --site acme

# Structured ops (same cores as the editor agent tools)
frontpage analytics summary --days 30
frontpage analytics query --dimension path --measure count --filters '{"type":"pageview"}' --limit 10
frontpage analytics funnel --steps '[{"path":"/"},{"path":"/pricing"}]' --days 7
frontpage analytics funnel --steps /, /pricing, /updates --days 7
frontpage analytics timeseries --metric pageviews --interval day --days 7
frontpage analytics compare --a '{"device":"mobile"}' --b '{"device":"desktop"}'
frontpage analytics sql --sql "SELECT path, count(*)::int AS n FROM analytics_events GROUP BY 1 ORDER BY n DESC LIMIT 10"

Bare analytics / analytics dashboard returns the legacy dashboard JSON (GET). Subcommands use POST with allowlisted dimensions/measures; SQL is guard-validated and RLS site-scoped. Without --json, the CLI prints the result object directly (pretty-printed JSON).

Typical dashboard shape (fields may grow; treat unknown keys as forward-compatible):

{
  "days": 7,
  "range": {
    "from": "2026-07-26T06:54:57.611Z",
    "to": "2026-08-02T06:54:57.611Z"
  },
  "totals": {
    "events": 3389,
    "pageviews": 526,
    "visitors": 313,
    "sessions": 396,
    "conversions": 0,
    "revenue": 0,
    "totalSeconds": 1694871,
    "avgScroll": 48
  },
  "deltas": {
    "visitors": -70.2,
    "sessions": -67.5,
    "pageviews": -70.1,
    "conversions": null,
    "revenue": null
  },
  "topPages": [
    { "path": "/", "views": 315 },
    { "path": "/pricing", "views": 67 }
  ],
  "topSources": [
    { "source": "direct", "sessions": 348 },
    { "source": "search", "sessions": 23 },
    { "source": "social", "sessions": 22 },
    { "source": "referral", "sessions": 19 },
    { "source": "paid", "sessions": 1 }
  ],
  "pages": [
    {
      "path": "/",
      "pageviews": 315,
      "visitors": 220,
      "sessions": 285,
      "avgSeconds": 3301,
      "avgScroll": 50
    }
  ]
}

--days

  • The bare dashboard defaults to 7 days; structured operations default to 30.
  • Use a supported preset: 1, 7, 30 or 90. For custom bounds on structured operations use both --from and --to; the bare CLI dashboard accepts only --days. Date-only bounds cover full UTC days. SQL takes its date bounds inside the query, not through --days.
  • range.from / range.to are ISO timestamps for the window that was queried.
  • deltas compare against the previous equal-length window (null when not applicable).

Analytics reflect Frontpage first-party tracking on the live/staging site surfaces. Empty or sparse sites return zero counts and null averages or percentiles when samples are missing. That is success, not an auth failure.

Page reports and agent tools

The CLI can answer page-specific counts and trends through its existing structured operations:

frontpage analytics query --dimension path --measure count --filters '{"type":"pageview","path":"/pricing"}' --days 7
frontpage analytics query --dimension path --measure uniqueSessions --filters '{"type":"pageview","path":"/pricing"}' --days 7
frontpage analytics timeseries --metric pageviews --filters '{"path":"/pricing"}' --interval day --days 7

The internal site agent also has analytics_dashboard and analytics_page_detail. MCP exposes frontpage_analytics_dashboard and frontpage_analytics_page_detail with separate business-read consent. They call the existing UI report services. A dashboard is site-wide; its audience filters do not select a page. The page report requires an exact site pathname such as /pricing, preserving its trailing slash. Do not pass a URL, query string or source filename. The CLI has no separate full page-detail command.

The full page report includes views, visitors, sessions, average time and scroll, trends, sources, next pages, audience/technology, scroll buckets, rage/dead clicks, errors and performance. Global report filters are device, country, source, referrer, os and browser. Generic query operations have their own wider filter schema. Reports retain the UI's top-N limits; a partial list does not mean every row. The UI's paginated “View all” contract and complete labeled element-click report are not MCP tools. The internal agent retains analytics_element_clicks.

See the MCP guide for exact tool inputs, permissions and continuation fields. These are different clients of the same analytics services.

Metric definitions

  • Events versus pageviews: count counts all matching events. Filter type=pageview for pageviews. Page scorecard visitors/sessions count distinct IDs on pageviews; summary and timeseries can include IDs from all matching events. Compare like filters.
  • Visitors: visitor IDs rotate daily. A distinct-ID count over several days is not a count of distinct people across those days.
  • Time and scroll: page time averages recorded time-on-page milliseconds and reports rounded seconds. Summary total time is a sum. Average scroll differs from scroll-depth buckets, which count distinct sessions.
  • Performance: p75 means the 75th percentile. LCP, INP, TTFB, FCP and load use milliseconds; CLS is unitless. Missing samples stay null.
  • Sessions: bounce rate counts sessions with at most one pageview. An engaged session has at least two pageviews or ten seconds of recorded activity. Duration uses the first and last matching event.
  • Journeys and funnels: next-page journeys order pageviews within a session. Funnel steps count sessions matching each step and every preceding step set; they do not enforce chronological event order.
  • Dates: UI reports primarily filter server created_at. Next-page ordering uses coalesce(occurred_at, created_at) plus event ID. Use identical fixed bounds, paths and filters when comparing tools.

Custom SQL: CLI/API and internal agent

Custom SQL is available through the existing CLI/API operation and internal analytics_sql. MCP deliberately has no raw-SQL tool. Prefer structured reports for the UI's exact calculations.

Each query must be one reporting SELECT tree, optionally with non-recursive CTEs, on analytics_events, analytics_sessions or analytics_rollups. PostgreSQL parses the query; the server checks tables, functions, types and operators, serializes the checked tree and verifies that its structure survives. The authorized site's existing row-level security and a read-only transaction still apply. You do not need a site ID filter.

Supported reporting cases include aggregates, joins, subqueries, unions, regex and LIKE filters, date binning, JSON inspection, percentiles, tested window functions and quoted labels. Unrelated/system tables, configuration or scope changes, writes, custom functions/types/operators, recursive CTEs, TABLESAMPLE and unknown syntax reject. An unlisted PostgreSQL function may reject even when it is read-only; use a structured report or request a reviewed addition.

Limits remain 4,000 input characters, five seconds per statement and 500 returned rows. These are response and execution limits, not a promise that every query scans only 500 rows. Put date bounds inside SQL. A guard rejection does not permit another credential or bypass.

frontpage analytics sql --sql "SELECT path, count(*) AS pageviews, count(DISTINCT visitor_id) AS visitors, count(DISTINCT session_id) AS sessions FROM analytics_events WHERE type = 'pageview' AND created_at >= '2026-10-01T00:00:00Z'::timestamptz AND created_at < '2026-10-02T00:00:00Z'::timestamptz GROUP BY path ORDER BY pageviews DESC LIMIT 20"
frontpage analytics sql --sql "SELECT date_bin('1 hour', created_at, '2026-01-01'::timestamptz) AS hour, count(*) AS pageviews FROM analytics_events WHERE type = 'pageview' AND path ~ '^/pricing(/|$)' AND created_at >= '2026-10-01T00:00:00Z'::timestamptz AND created_at < '2026-10-02T00:00:00Z'::timestamptz GROUP BY 1 ORDER BY 1"

Usage (plan meters)

frontpage usage
frontpage usage --site acme

Prints plan entitlements and current-cycle meters. Useful when exit code 2 might mean AI budget rather than HTTP rate limiting.

{
  "plan": {
    "id": "growth",
    "label": "Growth",
    "customDomain": true,
    "extensions": true,
    "aiBudgetUsd": 50,
    "bandwidthGb": 100,
    "storageGb": 50,
    "timeMachineDays": 30
  },
  "ai": {
    "used": 12.4,
    "limit": 50,
    "pct": 0.248,
    "warn": false,
    "atLimit": false,
    "over": false
  },
  "bandwidth": {
    "used": 3.2,
    "limit": 100,
    "pct": 0.032,
    "warn": false,
    "atLimit": false,
    "over": false
  },
  "storage": {
    "used": 1.1,
    "limit": 10,
    "pct": 0.11,
    "warn": false,
    "atLimit": false,
    "over": false
  },
  "members": {
    "used": 2,
    "limit": 5,
    "pct": 0.4,
    "warn": false,
    "atLimit": false,
    "over": false
  },
  "cycleResetsAt": "2026-09-01T00:00:00.000Z"
}

Exact plan ids and limits depend on the site’s subscription. Internal/demo plans may show very large limits. Branch on ai.over / ai.atLimit (and the same flags on other meters) rather than hardcoding plan names.

Pages list

frontpage pages
frontpage pages --json
frontpage pages --site acme --json

Human mode prints path and title columns. JSON envelope:

{
  "ok": true,
  "command": "pages",
  "data": {
    "slug": "acme",
    "pages": [
      { "path": "/", "title": "Acme Home" },
      { "path": "/pricing", "title": "Pricing" },
      { "path": "/about", "title": "About" }
    ]
  }
}

Use this before SEO edits or agent prompts so you target paths that actually exist. Titles come from the site’s page metadata / build, not from analytics.

Agent recipes

# Weekly traffic snapshot
frontpage analytics --days 7 > /tmp/fp-analytics.json

# Before prompting: confirm routes
frontpage pages --json

# Diagnose exit 2 after a failed prompt
frontpage usage
# if ai.atLimit / ai.over → budget; else see /developers/rate-limits

Related