Embed the SDK and query the API
Everything you need to instrument a page and read back its heatmap.
- Embedding the SDK
- Script attributes
- POST /api/events
- GET /api/grid
- GET /api/heatmap
- GET /api/flow
- Sessions & privacy
- GET /health
- Limits & rate limits
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
| Attribute | Required | Description |
|---|---|---|
data-site-id | Yes | Identifies which site the events belong to. |
data-api-base | No | Base URL for the API. Omit if the script is served same-origin. |
data-grid | No | Fixed 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.
| Field | Constraint |
|---|---|
siteId, page | Non-empty strings, max 512 characters |
events | Array of at most 500 events per request |
events[].type | Non-empty string (click, touchend, mousemove, scroll, keydown, focus, blur, …) |
events[].x, events[].y | Finite number or null (coordinate-less events like keydown send null) |
events[].t | Finite number (client timestamp) |
sessionId | Optional 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
sessionStorage — not 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.
| Property | Behavior |
|---|---|
| Scope | Per browser tab — a new tab gets a new session id |
| Lifetime | Cleared automatically when the tab is closed (sessionStorage, not persistent storage) |
| Contents | A random string with no personal information — not a fingerprint, not tied to an account |
| Purpose | Grouping 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
| Endpoint | Limit |
|---|---|
POST /api/events | 120 requests / minute / IP, max 500 events per request |
GET /api/heatmap | 60 requests / minute / IP |
GET /api/flow | 60 requests / minute / IP |
| Request body | 256KB max |
CORS is intentionally open on /api/events and /sdk/tracker.js so the SDK can be embedded on any site.