REST API
Sessions & journeys
Read a site's visitor sessions and their timelines, explore the paths visitors take (Sankey flow, journey funnel, path exploration), and read cohort retention. A site is addressed by {siteKey} (UUID or domain). All endpoints require Authorization: Bearer {token}.
These are reporting reads: they accept the same date-range inputs (range/from/to) and dashboard dimension filters (country, path, browser, …) documented in Conventions — read that page for the preset range values, the filter key allowlist, the response envelope, and the shared error codes. Only the pieces unique to sessions, journeys, and retention are documented below.
List sessions
GET /api/sites/{siteKey}/sessions
One row per visitor session — the HTTP twin of the web Sessions report. Paginated: keeps the standard data/meta/links envelope at a fixed page size of 25 (?page= only, no ?per_page=).
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteKey |
string | Yes | Site UUID or domain. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
range |
string | No | Preset range. See Conventions. Default today. |
from / to |
string | No | Custom range, Y-m-d. Takes precedence over range when both are valid. |
country, path, … |
string / string[] | No | Dashboard dimension filters. See Conventions. |
identified_only |
bool | No | Only include sessions with a known user_id (an identified visitor). |
min_pageviews |
int | No | Only include sessions with at least this many pageviews. Default 0 (no floor). |
min_events |
int | No | Only include sessions with at least this many custom events (pageviews and the automatic engagement beacon never count). Default 0. |
min_duration |
int | No | Only include sessions lasting at least this many seconds. Default 0. |
page |
int | No | Page number. |
Each row is aggregated over the resolved range and filters — unlike the session timeline below, whose aggregate covers the whole session regardless of range/filters. A session whose only in-range activity is the automatic engagement beacon is excluded (not a real session).
Response — each item in data:
| Field | Type | Description |
|---|---|---|
session_id |
string (uuid) | Session id. |
visitor_id |
string (uuid) | The visitor who owns this session. |
user_id |
string | Identified user id; empty string when anonymous. |
site_user_id |
string (uuid) | null | The resolved site user profile id, if any. |
display_name |
string | Same display name the People/Users list shows for this visitor. |
avatar_url |
string | Same avatar URL the People/Users list shows for this visitor. |
is_identified |
bool | Whether user_id is set. |
started_at |
string (ISO 8601) | Timestamp of the session's first in-range event. |
ended_at |
string (ISO 8601) | Timestamp of the session's last in-range event. |
duration |
int | Seconds between started_at and ended_at. |
pageviews |
int | Pageview count. |
events |
int | Custom event count (excludes pageviews and the engagement beacon). |
entry_path |
string | Path of the first pageview; empty string if the session has no pageview. |
exit_path |
string | Path of the last pageview; empty string if the session has no pageview. |
country |
string | Last-seen country. |
region |
string | Last-seen region. |
city |
string | Last-seen city. |
language |
string | Last-seen browser language. |
browser |
string | Last-seen browser name. |
browser_version |
string | Last-seen browser version. |
os |
string | Last-seen OS name. |
os_version |
string | Last-seen OS version. |
device |
string | Last-seen device type. |
screen_width |
int | Last-seen screen width in pixels. |
screen_height |
int | Last-seen screen height in pixels. |
source |
string | Attributed source at session entry. |
channel |
string | Attributed channel at session entry. |
referrer_domain |
string | Referring domain at session entry. |
Status: 200 OK.
curl "https://clickbase.so/api/sites/example.com/sessions?range=last_7_days" \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/sessions?range=last_7_days', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
requests.get(
'https://clickbase.so/api/sites/example.com/sessions',
params={'range': 'last_7_days'},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Get a session timeline
GET /api/sites/{siteKey}/sessions/{sessionId}
One session's aggregate plus its paged chronological event timeline — the HTTP twin of the web Sessions expand-detail.
By design, the session aggregate here covers the WHOLE session, not just the resolved range/filters (there is no range/filters input on this endpoint) — different from the list row above, which is range/filter-scoped. The two can therefore report different numbers for the same session.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteKey |
string | Yes | Site UUID or domain. |
sessionId |
string (uuid) | Yes | Session id. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit |
int | No | Events page size, 1–100. Default 100. |
offset |
int | No | Events page offset. Default 0. |
Response
| Field | Type | Description |
|---|---|---|
session |
object | null | The session's aggregate — same fields as a list row plus utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer, and ip (always an empty string — not stored past ingest). null when the session id is unknown on this site. |
events |
array | This page of the session's chronological events (see below). Empty when session is null. |
pagination |
object | total (int, events in the whole session, excluding the engagement beacon), limit, offset, has_more (bool). |
Each entry in events:
| Field | Type | Description |
|---|---|---|
timestamp |
string (ISO 8601) | When the event happened. |
event_type |
string | pageview or event. |
event_name |
string | Custom event name; empty for a pageview. |
path |
string | Page path. |
title |
string | Page title. |
url |
string | Full page URL. |
referrer |
string | Referring URL for this event. |
hostname |
string | Hostname the event was recorded on. |
properties |
object (string => string) |
Custom event property map. |
Status: 200 OK (a null session still returns 200, not 404, unless the site itself is foreign to the team).
curl "https://clickbase.so/api/sites/example.com/sessions/{sessionId}?limit=50" \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch(`https://clickbase.so/api/sites/example.com/sessions/${sessionId}?limit=50`, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
requests.get(
f'https://clickbase.so/api/sites/example.com/sessions/{session_id}',
params={'limit': 50},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Journey flow (Sankey)
POST /api/sites/{siteKey}/journey/flow
The top most-followed FULL visitor sequences, steps columns deep — feeds the dashboard's Sankey diagram. POST because the request carries a stepFilters payload, not because it writes anything.
IMPORTANT — filter transport. Dashboard dimension filters (country, path, …) ride the query string here, exactly like every other reporting read — never the request body. Sending a query, filters, or filter key in the JSON body is rejected with 422 Unprocessable Entity (a validation error naming that field) rather than being silently ignored, so a client mistake fails loudly instead of quietly returning unfiltered data.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteKey |
string | Yes | Site UUID or domain. |
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
steps |
int | No | Number of columns in the flow, 2–10. Default 4. (Not an array of step objects — see the funnel/next-steps endpoints below for that shape.) |
limit |
int | No | Max number of journeys returned, 1–500. Default 50. |
range / from / to |
string | No | Date range. See Conventions. |
stepFilters |
object (string => string) |
No | Per-column filter, keyed by 0-based step index (as a string, e.g. "0") to a pattern. A pattern containing * is a glob (* matches one path segment, ** matches many); otherwise it's a case-insensitive substring match. Each pattern max 2048 chars. |
query / filters / filter |
— | Forbidden | Rejected with 422 — see above. Use query-string filters instead. |
Response
{
"journeys": [
{
"path": [
{ "type": "pageview", "value": "/" },
{ "type": "pageview", "value": "/pricing" }
],
"visitors": 42,
"percentage": 12.5
}
]
}
| Field | Type | Description |
|---|---|---|
journeys[].path |
array | Ordered list of {type, value} steps, one per column. type is pageview or event; value is the path or event name. |
journeys[].visitors |
int | Visitors who followed this exact truncated sequence. |
journeys[].percentage |
float | visitors as a percentage of all visitors in the filtered/ranged population, rounded to 1 decimal. |
Status: 200 OK (422 when a body filter key is sent, or steps/limit are out of bounds).
curl -X POST https://clickbase.so/api/sites/example.com/journey/flow \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"steps": 3, "range": "last_7_days"}'
await fetch('https://clickbase.so/api/sites/example.com/journey/flow', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ steps: 3, range: 'last_7_days' }),
});
requests.post(
'https://clickbase.so/api/sites/example.com/journey/flow',
json={'steps': 3, 'range': 'last_7_days'},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Journey funnel
POST /api/sites/{siteKey}/stats/journey/funnel
Cumulative per-step visitor counts across an ad hoc sequence of steps — the same funnel chart component the saved Funnels report uses, run over free-form steps instead of a saved funnel's goal list. Same query-string-only filter rule as Journey flow above: a query/filters/filter body key is rejected with 422.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
steps |
array | Yes | At least 2 entries: {type: "pageview" | "event", value: string}. |
direction |
string | Yes | forward or backward. |
range/from/to |
string | No | Date range. See Conventions. |
Response
{
"steps": [
{
"label": "/",
"visitors": 100,
"dropoff": 0,
"dropoff_percentage": 0.0,
"conversion_rate": 100.0,
"conversion_rate_step": 100.0
},
{
"label": "/pricing",
"visitors": 40,
"dropoff": 60,
"dropoff_percentage": 60.0,
"conversion_rate": 40.0,
"conversion_rate_step": 40.0
}
]
}
| Field | Type | Description |
|---|---|---|
steps[].label |
string | The step's own input value. |
steps[].visitors |
int | Visitors who reached this step (cumulative — a subset of every prior step). |
steps[].dropoff |
int | Visitors lost versus the previous step (step 1 is always 0). |
steps[].dropoff_percentage |
float | dropoff as a percentage of the previous step's visitors. |
steps[].conversion_rate |
float | visitors as a percentage of step 1's visitors. |
steps[].conversion_rate_step |
float | visitors as a percentage of the previous step's visitors. |
Status: 200 OK (422 when fewer than 2 steps, an unrecognized direction, or a body filter key).
curl -X POST https://clickbase.so/api/sites/example.com/stats/journey/funnel \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"direction": "forward", "steps": [{"type": "pageview", "value": "/"}, {"type": "pageview", "value": "/pricing"}]}'
await fetch('https://clickbase.so/api/sites/example.com/stats/journey/funnel', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
direction: 'forward',
steps: [
{ type: 'pageview', value: '/' },
{ type: 'pageview', value: '/pricing' },
],
}),
});
requests.post(
'https://clickbase.so/api/sites/example.com/stats/journey/funnel',
json={
'direction': 'forward',
'steps': [
{'type': 'pageview', 'value': '/'},
{'type': 'pageview', 'value': '/pricing'},
],
},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Journey path exploration
POST /api/sites/{siteKey}/stats/journey/next-steps
GA4-style "Path Exploration": the events that came immediately after (forward) or before (backward) a given step prefix, ranked by visitor count. Omit steps (or send an empty array) for the entry column — each visitor's first (forward) or last (backward) event in the range. Same query-string-only filter rule as the endpoints above: a query/filters/filter body key is rejected with 422.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
steps |
array | No | {type: "pageview" | "event", value: string} entries forming the prefix. Omit/empty for the entry column. |
direction |
string | Yes | forward or backward. |
search |
string | No | Case-insensitive substring match against the candidate label. |
limit |
int | No | Max candidates returned, 1–200. Default 50. |
range/from/to |
string | No | Date range. See Conventions. |
Response — a flat JSON array (not wrapped in an object) of:
| Field | Type | Description |
|---|---|---|
type |
string | pageview or event; end for the synthetic terminal bucket (see is_end). |
value |
string | The path or event name; empty for the terminal bucket. |
label |
string | Same as value (kept for parity with the request step shape); empty for the terminal bucket. |
visitors |
int | Visitors matching this candidate. |
is_goal |
bool | Whether this candidate exactly matches one of the site's goals (event goals by name, page goals by exact path — a scroll goal never flags a candidate). |
is_end |
bool | true for the synthetic bucket counting prefix-matching visitors whose journey stopped (forward) or started (backward) there, with no further neighbor in that direction. |
Status: 200 OK (422 on an unrecognized direction or a body filter key).
curl -X POST https://clickbase.so/api/sites/example.com/stats/journey/next-steps \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"direction": "forward", "steps": [{"type": "pageview", "value": "/"}]}'
await fetch('https://clickbase.so/api/sites/example.com/stats/journey/next-steps', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({ direction: 'forward', steps: [{ type: 'pageview', value: '/' }] }),
});
requests.post(
'https://clickbase.so/api/sites/example.com/stats/journey/next-steps',
json={'direction': 'forward', 'steps': [{'type': 'pageview', 'value': '/'}]},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)
Retention cohorts
GET /api/sites/{siteKey}/retention
Cohort retention: visitors are grouped by the period (day or week) of their first-ever event, then each cohort's activity is tracked forward, period by period. Retention has no dimension filters — only the date range and mode.
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siteKey |
string | Yes | Site UUID or domain. |
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
range/from/to |
string | No | Date range. See Conventions. Defaults to last_30_days on this endpoint (cohorts need a multi-period window). |
mode |
string | No | day or week. Any other or missing value falls back to week. |
Response
{
"cohorts": {
"2026-06-29": {
"size": 120,
"percentages": [100.0, 42.5, 30.0, null]
}
},
"maxPeriods": 3,
"mode": "week"
}
| Field | Type | Description |
|---|---|---|
cohorts |
object | Keyed by the cohort's period start date (Y-m-d — the Monday of the ISO week in week mode, the calendar date in day mode). |
cohorts.{period}.size |
int | Visitors whose first-ever event fell in this cohort's period. |
cohorts.{period}.percentages |
array (float | null) |
0-indexed by periods since the cohort started (percentages[0] is always 100). null when there's no data for that many periods out for this cohort — either the period hasn't been reached yet, or nobody in the cohort returned. Every cohort's array is padded to the same length, maxPeriods + 1. |
maxPeriods |
int | The highest period offset with any data, across every cohort. |
mode |
string | The resolved day/week mode. |
Status: 200 OK.
curl "https://clickbase.so/api/sites/example.com/retention?mode=day&range=last_30_days" \
-H "Authorization: Bearer {token}" \
-H "Accept: application/json"
await fetch('https://clickbase.so/api/sites/example.com/retention?mode=day&range=last_30_days', {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
});
requests.get(
'https://clickbase.so/api/sites/example.com/retention',
params={'mode': 'day', 'range': 'last_30_days'},
headers={'Authorization': f'Bearer {token}', 'Accept': 'application/json'},
)