Embedding the SDK

Add this to any page you want to track. It's a single, dependency-free script — no build step, no npm install.

<script src="https://your-host/sdk/tracker.js"
        data-site-id="your-site-id"
        data-api-base="https://your-host"
        data-grid="200x500"></script>

data-grid is optional; omit it to track the full window size instead. Grid resolution is always 1 real pixel = 1 cell — there's no coordinate scaling.

Script attributes

AttributeRequiredDescription
data-site-idYesIdentifies which site the events belong to.
data-api-baseNoBase URL for the API. Omit if the script is served same-origin.
data-gridNoFixed WIDTHxHEIGHT viewport to track instead of the live window size.

The SDK buffers events in memory, flushing every 5 seconds and again on beforeunload via navigator.sendBeacon (falling back to fetch). mousemove, touchmove, and scroll are throttled to once every 100ms; clicks and taps are not.

POST /api/events

SDK ingestion endpoint. Also usable directly if you want to send events from your own code.

{
  "siteId": "your-site-id",
  "page": "/pricing",
  "viewport": { "width": 1440, "height": 900 },
  "events": [
    { "type": "click", "x": 512, "y": 240, "t": 1755000000000 }
  ]
}

Returns 202 { "stored": <total events for this site+page> } on success.

FieldConstraint
siteId, pageNon-empty strings, max 512 characters
eventsArray of at most 500 events per request
events[].typeNon-empty string (click, touchend, mousemove, scroll, keydown, focus, blur, …)
events[].x, events[].yFinite number or null (coordinate-less events like keydown send null)
events[].tFinite number (client timestamp)
sessionIdOptional string, max 512 characters — see Sessions & privacy

GET /api/grid/:dims?

Validates and echoes back a grid size — useful when you want to confirm dimensions before configuring data-grid.

GET /api/grid/200x500
GET /api/grid?width=800&height=600

200 OK
{ "width": 200, "height": 500, "cellSize": 1 }

GET /api/heatmap

Returns aggregated click/tap counts per coordinate for a given site + page.

GET /api/heatmap?siteId=your-site-id&page=/pricing

200 OK
{
  "siteId": "your-site-id",
  "page": "/pricing",
  "points": [ { "x": 512, "y": 240, "count": 7 } ],
  "viewport": { "width": 1440, "height": 900 }
}

Returns 404 only if that siteId + page has never received an event. If it has events but none are clicks/taps yet, it returns 200 with an empty points array. viewport is the most frequently recorded viewport size for that page (or null if none was recorded) — use it to render the page at the width it was actually viewed at, since a responsive layout can reflow differently at other widths.

GET /api/flow

Returns a page-to-page navigation graph built from pageview events, grouped by session and ordered by time. Powers the User Flow page.

GET /api/flow?siteId=your-site-id

200 OK
{
  "siteId": "your-site-id",
  "sessionCount": 42,
  "transitions": [
    { "from": null, "to": "/", "count": 30 },
    { "from": "/", "to": "/pricing", "count": 18 }
  ],
  "pageVisits": [
    { "page": "/", "count": 30 },
    { "page": "/pricing", "count": 18 }
  ]
}

from: null means that page was the first one visited in the session (an "entry" transition). Returns 404 if no pageview events have been recorded for that siteId yet.

Sessions & privacy

To build page-to-page flow reports, the SDK generates an anonymous session id and stores it in the browser's sessionStoragenot a cookie. It is never sent to any server automatically; the SDK includes it explicitly in its own event payload, the same way it already sends siteId and page.

PropertyBehavior
ScopePer browser tab — a new tab gets a new session id
LifetimeCleared automatically when the tab is closed (sessionStorage, not persistent storage)
ContentsA random string with no personal information — not a fingerprint, not tied to an account
PurposeGrouping this visitor's own page loads into an ordered journey for GET /api/flow

Every page load also automatically records one pageview event (with the previous page in that session as its referrer) even if the visitor never clicks or scrolls — otherwise a page visited with no interaction would be invisible to flow reporting.

GET /health

Reports real database connectivity — useful for uptime checks.

200 OK   { "status": "ok", "db": "ok" }
503      { "status": "error", "db": "unreachable" }

Limits & rate limits

EndpointLimit
POST /api/events120 requests / minute / IP, max 500 events per request
GET /api/heatmap60 requests / minute / IP
GET /api/flow60 requests / minute / IP
Request body256KB max

CORS is intentionally open on /api/events and /sdk/tracker.js so the SDK can be embedded on any site.