# REST API reference > The OakData read API: bearer-authed JSON endpoints for overview, sessions, visitors, journeys, live, events, and replays. Source: https://oakdata.co/docs/api/rest --- The REST API serves your analytics back as JSON. Every endpoint is `GET`, lives under `https://oakdata.co/api/v1`, and is read-only. Requests are authenticated with a **secret key** and scoped to exactly one project. ## Authentication Pass your secret key (`oak_sec_…`) as a bearer token. An `x-api-key` header is accepted as a fallback. **auth** ```bash curl https://oakdata.co/api/v1/overview \ -H "Authorization: Bearer oak_sec_xxxxxxxxxxxxxxxxxxxxxxxx" ``` > **Secret keys only** Public keys (`oak_pub_…`) are rejected with `403` - they're browser-side and can only write events. A missing or invalid key returns `401`. A key resolves to one project, so a key for project A can never read project B. ## Errors Errors return a non-2xx status with a JSON body of the shape `{ "error": "message" }`. Common cases: `400` (invalid query parameter, e.g. a bad `range`), `401` (missing/invalid key), `403` (public key used), and `404` (no such session or replay). ## Overview ```http GET /api/v1/overview ``` The headline view of traffic and conversion for a time window - aggregate stats plus the top dimensions. | Name | Type | Description | | --- | --- | --- | | `range` | string | Time window: `24h`, `7d`, `30d`, or `90d`. Defaults to `7d`. | | `exclude_bots` | boolean | Exclude traffic classified as [bots](https://oakdata.co/docs/concepts/bots). Defaults to `false`. | Response: | Name | Type | Description | | --- | --- | --- | | `range` | string | The resolved time window. | | `stats` | object | `pageviews`, `visitors`, `sessions`, `avgSessionDurationSec`, `avgPagesPerSession`, and `bounceRate`. | | `timeSeries` | array | Per-bucket points: `{ bucket, visitors, pageviews, sessions }` (hourly for `24h`, daily otherwise). | | `topPages, topReferrers, topSources, topCampaigns, topCountries, topCities, browsers, operatingSystems, deviceTypes` | array | Ranked breakdowns, each a list of `{ label, visitors, pageviews }`. | | `eventCount` | number | Total events in the window. | ## Sessions ```http GET /api/v1/sessions ``` Recent visitor sessions, newest first. | Name | Type | Description | | --- | --- | --- | | `range` | string | Time window: `24h`, `7d`, `30d`, or `90d`. Defaults to `7d`. | | `limit` | number | Max sessions to return (1–200). Defaults to `50`. | Returns `{ range, count, sessions }`, where each session carries: | Name | Type | Description | | --- | --- | --- | | `session_id` | string | The session identifier. | | `distinct_id` | string | The visitor (user id if identified, else anonymous id). | | `started_at / last_at` | string | ISO timestamps for the session's first and last event. | | `pageviews / events` | number | Counts within the session. | | `session_number / duration_ms` | number | The visitor's nth session, and the session's length in ms. | | `entry_path / exit_path` | string \| null | First and last page paths. | | `referring_domain` | string \| null | Where the session came from. | | `country / city` | string \| null | Geo, derived from IP at ingest. | | `browser / os / device_type` | string \| null | Device context. | | `is_bot / bot_name` | boolean / string \| null | Bot classification for the session. | ## Session detail ```http GET /api/v1/sessions/{id} ``` The full ordered event timeline for one session id - every event row, oldest first. Returns `404` if the session has no events. | Name | Type | Description | | --- | --- | --- | | `session_id` | string | The session id you requested. | | `event_count` | number | Number of events returned. | | `events` | array | The raw event rows in chronological order. Each row includes `event`, `timestamp`, `pathname`, `properties`, and the device/geo columns. See [events & properties](https://oakdata.co/docs/concepts/events). | ## Visitor profile ```http GET /api/v1/visitors/{distinctId} ``` A single visitor's full profile, aggregated from the last 90 days. The path segment matches either the `distinct_id` or the `anonymous_id`, so activity from before sign-up is included. | Name | Type | Description | | --- | --- | --- | | `distinct_id` | string | The id you looked up. | | `user_id` | string \| null | The identified user id, if any. | | `traits` | object | Accumulated traits (replayed from $identify / $set / $set_once). | | `groups` | object | Group memberships, keyed by group type. | | `first_seen / last_seen` | string \| null | ISO timestamps. | | `total_events` | number | Lifetime event count in the window. | | `sessions` | array | Recent sessions for this visitor (same shape as /sessions). | | `firstTouch / lastTouch` | object \| null | First- and last-touch attribution (referrer, UTM, path, timestamp). | | `identityTimeline` | array | Identity events with a human summary: { event, timestamp, summary }. | | `replays` | array | Replay references: { id, session_id, started_at, last_event_at, has_errors }. | ## Journey ```http GET /api/v1/journey ``` The aggregated path/funnel tree for a time window - the common routes visitors take and where they drop off. | Name | Type | Description | | --- | --- | --- | | `range` | string | Time window: `24h`, `7d`, `30d`, or `90d`. Defaults to `7d`. | Returns `{ range, totalSessions, depth, roots }`. `roots` is a tree of nodes; each node is `{ label, count, children[] }`, where `count` is the number of sessions whose path passes through that node. `depth` is the maximum path length analyzed (3). ## Live ```http GET /api/v1/live ``` Who is on the site right now - sessions active in the last 5 minutes. Takes no parameters. **response** ```json { "active_now": 2, "sessions": [ { "session_id": "s_a1b2c3", "last_seen": "2026-06-11T14:03:21.000Z" }, { "session_id": "s_d4e5f6", "last_seen": "2026-06-11T14:01:55.000Z" } ] } ``` ## Events ```http GET /api/v1/events ``` The raw event firehose, newest first, paginated. | Name | Type | Description | | --- | --- | --- | | `offset` | number | Row offset for pagination. Defaults to `0` (max 1,000,000). | | `limit` | number | Max rows (1–500). Defaults to `100`. | Returns `{ offset, limit, rows, total }`, where `total` is the exact count across all pages and `rows` are full event records. ## Replays ```http GET /api/v1/replays ``` Recorded session replays for the project, newest first. | Name | Type | Description | | --- | --- | --- | | `limit` | number | Max replays (1–200). Defaults to `100`. | Returns `{ count, replays }`. Each replay includes its recording `id`, `session_id`, `distinct_id`, `started_at`, `last_event_at`, `duration_ms`, `chunks_count`, `entry_url`, viewport and device fields, `country`, and `has_errors`. ## Replay detail ```http GET /api/v1/replays/{sessionId} ``` One replay by its recording `id` (the `id` from the list above - not the analytics session id). Returns the recording metadata plus short-lived signed URLs for each rrweb chunk. | Name | Type | Description | | --- | --- | --- | | `session` | object | The replay metadata (same shape as a row from /replays). | | `chunk_urls` | array | A list of `{ url }` objects - signed URLs to the gzipped NDJSON rrweb chunks, valid for ~30 minutes. A player fetches, gunzips, and merges them by timestamp. | > Returns `404` if no replay matches the id. Chunk URLs expire - fetch them fresh each time you need to play a recording. Prefer to let an agent query this for you? The same data is available as [MCP tools](https://oakdata.co/docs/api/mcp).