Analytics API
Dashboard analytics endpoints for querying tracking data. All endpoints require API key authentication. Most endpoints accept GET only; PATCH is supported for updating a lead's pipeline status (see Update lead status).
Authentication
Every request must include an API key in one of these headers:
Authorization: Bearer <your-api-key>X-API-Key: <your-api-key>Missing or invalid key returns 401 Unauthorized:
{ "error": "API key required. Use Authorization: Bearer <key> or X-API-Key header." }{ "error": "Invalid API key" }Setting the API Key
| Environment | Method |
|---|---|
| Development | ANALYTICS_API_KEY in wrangler.toml [vars] |
| Production | wrangler secret put ANALYTICS_API_KEY --env production |
Overview
GET /analytics/overview
High-level summary stats across the entire dataset.
Response:
{
"total_sessions": 1234,
"total_events": 56789,
"unique_visitors": 890,
"unique_fingerprints": 750,
"avg_engagement_score": 42.5,
"avg_session_duration_s": 185,
"vpn_suspected_count": 12
}Daily Sessions
GET /analytics/sessions/daily?days=30
Sessions aggregated by day.
| Param | Type | Default | Max | Description |
|---|---|---|---|---|
days | integer | 30 | 90 | Number of days to look back |
Response:
[
{
"date": "2026-02-22",
"total_sessions": 45,
"unique_visitors": 38,
"avg_duration": 120.5,
"avg_engagement": 55.2
}
]Top Pages
GET /analytics/pages/top?limit=20
Most viewed pages ranked by page_view event count.
| Param | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 20 | 100 | Number of pages to return |
Response:
[
{
"page": "https://example.com/home",
"views": 234,
"unique_sessions": 180
}
]Geo Distribution
GET /analytics/geo
Country-level session distribution.
Response:
[
{
"country": "US",
"sessions": 500,
"unique_visitors": 420
}
]Engagement Distribution
GET /analytics/engagement
Engagement score distribution in 5 buckets for completed sessions.
Response:
[
{ "bucket": "0-19", "count": 120 },
{ "bucket": "20-39", "count": 85 },
{ "bucket": "40-59", "count": 200 },
{ "bucket": "60-79", "count": 150 },
{ "bucket": "80-100", "count": 45 }
]Traffic Sources
GET /analytics/sources
Top referrer domains ranked by session count. Direct traffic appears as (direct).
Response:
[
{ "referrer_domain": "google.com", "sessions": 300 },
{ "referrer_domain": "(direct)", "sessions": 200 },
{ "referrer_domain": "facebook.com", "sessions": 50 }
]Realtime
GET /analytics/realtime
Approximate live stats: active sessions (not ended in last hour), events in last hour, and today's visitor/session counts.
Response:
{
"active_sessions": 12,
"events_last_hour": 456,
"unique_visitors_today": 89,
"total_sessions_today": 95
}Storage
GET /analytics/storage
R2 storage stats: total object count and estimated session count.
Response:
{
"total_objects": 15234,
"estimated_sessions": 1200
}Leads List
GET /analytics/leads?limit=25&offset=0&q=search
Paginated lead list for dashboard CRM views.
| Param | Type | Default | Max | Description |
|---|---|---|---|---|
limit | integer | 25 | 100 | Number of leads to return |
offset | integer | 0 | - | Pagination offset |
q | string | - | - | Search term (name, email, phone, project, visitor_id) |
status | string | - | - | Filter by pipeline stage: new, contacted, qualified, proposal, won, lost |
sort | string | newest | - | newest, oldest, last_follow_up, score_desc, score_asc, risk_desc, risk_asc, name_asc |
Response:
Each lead includes follow_ups_count, last_follow_up_at, and status (pipeline stage for kanban). Use sort=last_follow_up to put leads with recent follow-ups first.
{
"leads": [
{
"lead_id": 42,
"enquiry_datetime": "2026-02-23 11:15:00",
"contact_name": "Rahul Sharma",
"contact_email": "rahul@example.com",
"contact_phone": "9876543210",
"project_name": "Sky Towers",
"project_location": "Pune",
"project_source": "WebSite",
"contact_country_name": "IN",
"contact_city": "Pune",
"visitor_id": "abc123...",
"session_id": "session_123",
"engagement_score": 74,
"engagement_label": "Very Hot",
"form_submit_count": 2,
"suspicion_score": 5,
"follow_ups_count": 1,
"last_follow_up_at": "2026-02-24 09:30:00",
"status": "contacted"
}
],
"total": 187
}Lead Detail
GET /analytics/leads/:id
Returns complete lead fields plus linked session activity and submission history.
Response:
{
"lead": {
"lead_id": 42,
"contact_name": "Rahul Sharma",
"contact_email": "rahul@example.com",
"session_id": "session_123",
"engagement_score": 74,
"engagement_label": "Very Hot"
},
"sessions": [
{
"session_id": "session_123",
"visitor_id": "abc123...",
"entry_page": "https://example.com/sky-towers",
"exit_page": "https://example.com/thank-you",
"country": "IN",
"started_at": 1740280000000,
"ended_at": 1740280080000,
"duration": 80000,
"engagement_score": 74,
"event_count": 19
}
],
"submissions": [
{
"submission_id": 501,
"lead_id": 42,
"submitted_at": "2026-02-23 11:15:00",
"project_name": "Sky Towers",
"visitor_id": "abc123...",
"session_id": "session_123",
"contact_email": "rahul@example.com",
"contact_phone": "9876543210",
"contact_ip": "198.51.100.20",
"source": "WebSite",
"referer": "https://example.com/sky-towers",
"cta_name": "hero-form",
"is_primary_lead": 1,
"is_duplicate_window": 0,
"payload_json": "{\"FIRSTNAME\":\"Rahul Sharma\",\"EMAIL\":\"rahul@example.com\",\"PHONE\":\"9876543210\",\"PROJECT\":\"Sky Towers\"}"
}
]
}Each submission may include payload_json: the raw form payload at submit time (e.g. FIRSTNAME, EMAIL, PHONE, PROJECT). The dashboard uses this to show follow-up history — the exact data submitted in each form submit so admins can see changes over time (e.g. name or phone updates). The lead object also includes status (pipeline stage: new, contacted, qualified, proposal, won, lost).
Update lead status
PATCH /analytics/leads/:id
Update a lead's pipeline status (for kanban). Requires API key.
Request body:
{ "status": "contacted" }Allowed values: new, contacted, qualified, proposal, won, lost (case-insensitive).
Response (success):
{ "updated": true }Errors: 400 if body is invalid or status missing; 404 if lead not found or status value invalid.
Error Responses
| Status | Meaning |
|---|---|
401 | Missing or invalid API key |
400 | Invalid lead id or request body |
404 | Unknown analytics endpoint or lead not found |
405 | Method not allowed (only GET and PATCH for lead update) |
500 | Analytics query failed |