Skip to content

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:

json
{ "error": "API key required. Use Authorization: Bearer <key> or X-API-Key header." }
json
{ "error": "Invalid API key" }

Setting the API Key

EnvironmentMethod
DevelopmentANALYTICS_API_KEY in wrangler.toml [vars]
Productionwrangler secret put ANALYTICS_API_KEY --env production

Overview

GET /analytics/overview

High-level summary stats across the entire dataset.

Response:

json
{
  "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.

ParamTypeDefaultMaxDescription
daysinteger3090Number of days to look back

Response:

json
[
  {
    "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.

ParamTypeDefaultMaxDescription
limitinteger20100Number of pages to return

Response:

json
[
  {
    "page": "https://example.com/home",
    "views": 234,
    "unique_sessions": 180
  }
]

Geo Distribution

GET /analytics/geo

Country-level session distribution.

Response:

json
[
  {
    "country": "US",
    "sessions": 500,
    "unique_visitors": 420
  }
]

Engagement Distribution

GET /analytics/engagement

Engagement score distribution in 5 buckets for completed sessions.

Response:

json
[
  { "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:

json
[
  { "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:

json
{
  "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:

json
{
  "total_objects": 15234,
  "estimated_sessions": 1200
}

Leads List

GET /analytics/leads?limit=25&offset=0&q=search

Paginated lead list for dashboard CRM views.

ParamTypeDefaultMaxDescription
limitinteger25100Number of leads to return
offsetinteger0-Pagination offset
qstring--Search term (name, email, phone, project, visitor_id)
statusstring--Filter by pipeline stage: new, contacted, qualified, proposal, won, lost
sortstringnewest-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.

json
{
  "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:

json
{
  "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:

json
{ "status": "contacted" }

Allowed values: new, contacted, qualified, proposal, won, lost (case-insensitive).

Response (success):

json
{ "updated": true }

Errors: 400 if body is invalid or status missing; 404 if lead not found or status value invalid.


Error Responses

StatusMeaning
401Missing or invalid API key
400Invalid lead id or request body
404Unknown analytics endpoint or lead not found
405Method not allowed (only GET and PATCH for lead update)
500Analytics query failed