Dashboard API
File: app/api/analytics/dashboard/route.ts
GET /api/analytics/dashboard
Returns aggregated analytics data from PostHog via HogQL queries.
Parameters
| Parameter | Required | Description |
|---|---|---|
password | Yes | Must match ANALYTICS_PASSWORD env var |
period | No | Time range: all (default), year, 90d, month, week, day |
Response
{
"routes": [
{
"id": "bassegoda-fau",
"name": "Falda Bassegoda i La Fau",
"gpxUrl": "http://...",
"views": 42,
"downloads": 12,
"shares": 3,
"articleUrl": "https://www.bttpirineus.org/..."
}
],
"core": {
"views": 100,
"downloads": 50,
"shares": 20,
"shareModalOpens": 15,
"openRouteStartClicks": 8
},
"shareChannels": [
{ "channel": "facebook", "count": 10 }
],
"timeSeries": [
{ "date": "2026-02-20", "views": 10, "downloads": 5, "shares": 2, "actions": 3 }
],
"granularity": "day",
"comparison": {
"views": 80,
"downloads": 40,
"shares": 15,
"shareModalOpens": 12,
"openRouteStartClicks": 6
},
"topPages": [
{ "page": "https://www.bttpirineus.org/...", "views": 10 }
],
"source": "posthog"
}HogQL queries
The endpoint runs 6 parallel queries against PostHog:
- Core totals:
countIf(event = 'track_viewed'), etc. - Per-route breakdown: Group by
properties.gpx_url - Time-series: Group by
toDate(timestamp)ortoStartOfWeek(timestamp) - Share channels: Group by
properties.share_channelwhere event =track_shared - Top pages: Group by
properties.page_url, limit 20 - Comparison: Same queries for the previous equivalent period
All queries exclude events where properties.is_internal = true.
Error codes
| Status | Condition |
|---|---|
| 401 | Invalid or missing password |
| 500 | PostHog not configured (POSTHOG_PERSONAL_API_KEY missing) |