Analytics API
Behavioural analytics for the websites and applications HLD runs — pageviews and sessions, click and attention heatmaps, scroll reach, form behaviour, Core Web Vitals, campaign attribution and intent inference. One tracker, one read API, and the same numbers HLD sees in its own dashboard.
What this is for
Request logs tell you how many times a page was served. They cannot tell you whether anyone read it. Everything worth knowing about a visit — how far down the page they got, what they clicked and what they clicked that did nothing, whether they started your enquiry form and gave up, which campaign actually brought them — only exists in the browser. This API is the record of that.
Two keys, two jobs
The distinction matters more than anything else in these docs, so it comes first.
| Name | Type | Required | Description |
|---|---|---|---|
| hlda_site_… | site key — public | No | Ships inside the tracking snippet in your page source. It names where data goes and grants nothing. It is not a secret and does not need protecting. |
| hlda_live_… | read key — secret | No | Reads your analytics through /v1/analytics/*. Only a SHA-256 hash is stored, so it is shown once at issue time and never again. Treat it like a password. |
Base URL and authentication
https://hldgroup.org/api/v1/analytics
curl https://hldgroup.org/api/v1/analytics/overview \
-H "Authorization: Bearer hlda_live_xxxx" \
-G --data-urlencode "site=hlda_site_xxxx" \
--data-urlencode "days=30"A key issued for a single site does not need site at all — it is implied. Call GET /v1/analytics/sites first if you do not know which sites your key covers.
Scopes
Every key carries analytics:read. The rest are additive, so a key can be issued for a reporting dashboard without also handing over an email list.
| Name | Type | Required | Description |
|---|---|---|---|
| analytics:read | always present | No | Overview, timeseries, pages and breakdowns. |
| analytics:sessions | optional | No | Individual visits and their event timelines. |
| analytics:events | optional | No | The raw event stream, for warehouse syncs. |
| analytics:heatmaps | optional | No | Click, attention and scroll layers. |
| analytics:visitors | optional | No | Names, emails and companies from identify() calls. Without it those fields return null rather than erroring. |
Endpoints
| Name | Type | Required | Description |
|---|---|---|---|
| GET /v1/analytics/sites | analytics:read | No | The sites this key can read. Start here. |
| GET /v1/analytics/overview | analytics:read | No | Headline numbers for one site and window. |
| GET /v1/analytics/timeseries | analytics:read | No | Visits bucketed by hour or day. |
| GET /v1/analytics/pages | analytics:read | No | Per-page reach, attention, entries and exits. |
| GET /v1/analytics/breakdown | analytics:read | No | Any dimension: channel, campaign, country, device, organisation. |
| GET /v1/analytics/sessions | analytics:sessions | No | Individual visits, and one visit replayed. |
| GET /v1/analytics/events | analytics:events | No | The raw event stream, paged forward on time. |
| GET /v1/analytics/heatmap | analytics:heatmaps | No | Click, attention and scroll layers for one page. |
Errors
Every failure returns the same envelope, so a client can branch on error.type rather than parsing prose.
{
"error": {
"type": "forbidden",
"message": "This key is missing the analytics:heatmaps scope."
}
}| Name | Type | Required | Description |
|---|---|---|---|
| unauthorized | 401 | No | Missing, unknown, revoked or expired key. The same message for all four, on purpose — the difference would let someone test whether a key ever existed. |
| forbidden | 403 | No | The key is valid but lacks the scope this endpoint needs. |
| not_found | 404 | No | No such site, page or session inside this key’s scope. A site belonging to someone else is a 404, not a 403. |
| invalid_request | 400 | No | A missing or unusable parameter. The message names it. |
| rate_limited | 429 | No | Per-key limit exceeded. Retry-After says when. |
| server_error | 500 | No | Our side. Safe to retry. |
Rate limits
120 requests per minute per key by default, counted per key rather than per IP — a client calling from a fleet of servers is one caller, and the limit should not depend on how many machines they run it from. Ask HLD if you need it raised; it is a per-key setting.