Raw event export
The complete event stream, paged forward on time. This is the endpoint to build a warehouse sync on. Requires the analytics:events scope.
Request
bash
GET /v1/analytics/events?days=7&type=click,form_submit&limit=500| Name | Type | Required | Description |
|---|---|---|---|
| type | string | No | Comma-separated event types. Unknown names are ignored rather than rejected, so a client can pass a superset safely. |
| path | string | No | One page, normalised the same way ingest normalises it. |
| after | ISO instant | No | The pagination cursor. Everything strictly after this instant. |
| limit | integer | No | 1–1000, default 200. |
Pagination
Paged forward on occurred_at, not by offset. An export that runs while events are still arriving would skip and repeat rows under an offset, and this is exactly the endpoint people leave running on a schedule.
js
let after = lastSyncedAt // persist this between runs
for (;;) {
const url = new URL('https://hldgroup.org/api/v1/analytics/events')
url.searchParams.set('site', siteKey)
url.searchParams.set('days', '30')
url.searchParams.set('limit', '1000')
if (after) url.searchParams.set('after', after)
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.HLDA_KEY}` },
})
const { data, next_after } = await res.json()
await warehouse.insert(data)
// next_after is null when the page was not full — you are caught up.
if (!next_after) break
after = next_after
}Tip:
next_after is present only when the page came back full, so a null is a definitive "nothing more right now" rather than something to probe for.Event shape
| Name | Type | Required | Description |
|---|---|---|---|
| session_key | string | No | Join key back to the session. |
| event_type | string | No | One of the types in the Tracking reference. |
| path / page_title | string | No | The normalised page, and its title at the time. |
| viewport_bucket | string | No | mobile | tablet | desktop. |
| x_ratio / y_ratio | number | No | Click position as a fraction of the viewport. |
| doc_y_ratio | number | No | Click position as a fraction of the full document height. This is the one to use for anything positional. |
| scroll_pct | integer | No | 0–100. |
| target / target_text | string | No | Element description, and its visible label. Never an input value. |
| value | number | No | Conversion value, video position, or a measurement. Meaning is per event type. |
| dwell_ms / active_ms | integer | No | On page_exit: wall-clock and engaged time. |
| metadata | object | No | Free-form detail. Scalars only, up to ten keys, values capped at 300 characters. |
| occurred_at | ISO instant | No | Client-reported, but replaced with arrival time when it disagrees with the server clock by more than six hours — a machine an hour out would otherwise scatter its events across the timeline. |
Volume
A busy page produces far more events than sessions. If you only need aggregates, use Reports instead — it is one request against pre-aggregated rows rather than a walk over the stream.