Query the API
GET /v1/sites/:id/overview everything the dashboard needs, in one call
GET /v1/sites/:id/summary totals + named conversion counts
GET /v1/sites/:id/timeseries visitors/pageviews per hour, day, week, month, quarter or year
GET /v1/sites/:id/realtime visitors active in the last 30 minutes
GET /v1/sites/:id/pages top pages
GET /v1/sites/:id/sources acquisition sources
GET /v1/sites/:id/direct what the Direct source is made of, in named groups
GET /v1/sites/:id/returning new browsers by source, and how many came back within 7 and 30 days
GET /v1/sites/:id/breakdown/:dimension country|device|browser|os|medium|campaign|entryPath|exitPath
GET /v1/sites/:id/funnels/:funnelId ordered funnel
GET /v1/sites/:id/clickstream every event, newest first, paged by cursor (Pro)
GET /v1/event-detail?event=:id:signup who did an event: country, page, source, device, network,
and each occurrence with its IP; repeat event= to combine
GET /v1/monitors every site's uptime status, in one call
GET /v1/sites/:id/monitor one site's monitor
GET /v1/sites/:id/monitor/checks individual checks, most recent first
GET /v1/sites/:id/monitor/incidents outages, with cause and resolution
GET /v1/sites/:id/monitor/history uptime and response time per bucket, plus outages
PUT /v1/sites/:id/monitor create or update (admin)
DELETE /v1/sites/:id/monitor turn off, and keep it off (admin)
POST /v1/sites/:id/monitor/check run one check now (admin)
Each API key may make 120 requests a minute, REST and MCP together.
Past that, requests get a 429 with the error rate_limited and a Retry-After header saying how many seconds to wait.
Ingest keys, which only send agent telemetry, are not held to it.
Uptime
Every site is watched without being asked. The first real browser visit from the site's own
domain starts a monitor on the address that browser loaded, so a site served from
www or plain http is checked where it actually lives. A visit from
localhost, a subdomain, a server-side call or a bot does not count.
DELETE turns a monitor off, and later visits leave it off; PUT turns
it back on. A workspace or site can opt out entirely with the autoMonitor: false
setting.
A monitor requests the page a visitor would load - the site's home page by default - and counts anything under 400, returned inside the timeout, as up. Not a health endpoint: an application asked whether it feels well can answer yes while every real request fails.
Status changes only after two consecutive results in the new direction, in both directions,
so a single dropped packet does not raise an incident and a flapping site does not send a
stream of email. Until the first check completes the status is unknown, which is
deliberately not up.
GET /v1/monitors returns every site in the org, including ones with no monitor
(monitor: null), so “not set up” is distinguishable from “set
up and healthy”. It takes the same period values as the stats API and
reports uptime as the share of checks that passed inside that window - or
null when no check ran, never a fabricated 100%.
GET /v1/sites/:id/monitor/history is what the dashboard's uptime graph is drawn from.
It takes period and an optional bucket, the same values as the timeseries endpoint.
Every bucket in the window is returned, with checks: 0 where nothing ran, so a paused stretch
is visibly unchecked rather than drawn as up.
Response times are the median and 95th percentile of the checks that passed, because a failed check's
duration is how long it took to fail.
Downtime is the part of each confirmed outage that falls inside the window.
Clickstream
On Pro and Scale, GET /v1/sites/:id/clickstream returns the events themselves: one row per
pageview or custom event, with the visit and visitor it belongs to, its path, referrer, country, device and
properties, newest first. Use sessionId or visitorId to follow one thread,
name or path to narrow it, limit up to 500, and pass
nextCursor back as cursor for older rows. The default window is
period=24h; every period the rest of the API accepts works here.
The segment parameters work here and in the get_clickstream MCP tool, and
select whole visits exactly as they do in the overview. Because they match the visit, an individual event's
referrer can differ from its visit's acquisition source. A filtered request checks a bounded number of events,
so a rare segment can return a short or empty page with scanLimited: true. Keep passing
nextCursor back: the stream has ended only when it is null.
It is rows, not totals. Visitors and visits are counted in the overview, and the stream will not give you a
second number to reconcile against it. Its status block says how many events are stored for the
site and how many are still being shipped from the primary store, so a row that is queued and not yet visible
is reported rather than looking like it never happened. Those counts describe the whole site, whatever segment
the request carries. A plan without clickstream gets
403 clickstream_not_in_plan, never an empty page. The same rows are available to an agent as the
get_clickstream MCP tool.
What is inside Direct
Direct is every visit that arrived with no referrer and no campaign tag. That includes links opened inside
Instagram, Facebook, TikTok and other apps (they hide the referrer), your own mobile app, typed addresses and
bookmarks, links from email and chat, and automation that was not caught. /direct splits those
visits into named groups that add up exactly to the Direct row of /sources for the same period
and filters: in-app browsers, mobile apps, rental server networks, people who came straight to the home page,
people who came straight to another page, and unverified.
Each visit lands in the first group it matches, in that order, using only what was recorded about it: its
user agent, its network, whether someone scrolled or tapped, and the page it landed on. Rental server networks
are visits from the networks in the site's botFanoutHostingAsns setting that showed no sign of a
person. They are probably automated but not proven, so they are labelled rather than removed. Direct visits
that were proven to be bots are listed separately under bots, by the rule that caught them, and
are not in the total. The same answer is available to an agent as the get_direct_breakdown MCP
tool, and on the dashboard under Top sources.
Segments and ranges
Any read endpoint accepts country, device, browser,
os, source, entryPath, clickNetwork and
engaged=yes|no, and they compose.
Ranges come in two families. Rolling windows end now and are always the same length:
period=1h|3h|8h|24h|3d|7d|30d|90d|12mo. Calendar windows are aligned to the calendar, which is
what a monthly or quarterly report is made of:
period=today|yesterday|this_week|last_week|this_month|last_month|this_quarter|last_quarter|this_year|last_year.
Or give an explicit from and to ISO pair.
An unrecognised period returns a 400 rather than falling back to a default window.
Add includeBots=true to see what the bot filter removed.
Time zones
Add tz with an IANA time zone name, such as tz=Asia/Singapore, and every
calendar period and every bucket is drawn in that zone: today starts at midnight there, and
each day bar runs from one midnight there to the next. Without it, everything is in UTC. The dashboard
sends the reader's own zone, which can be changed in its header. Every response says which zone it used
in range.timeZone, and a zone the server does not know is a 400 naming it, never a quiet
switch to UTC. The MCP tools take the same thing as timeZone.
One number the zone does not move: visitor ids reset at midnight UTC. Outside UTC that moment falls inside the day, so someone active across it counts as two visitors that day. Visits, pageviews and events are exact in every zone.
Period-on-period comparisons
compare=previous (the default) measures change against the period before this one;
compare=year measures against the same window twelve months back. Every overview
returns a comparison block carrying the deltas, the exact window they were measured
against, and a label naming the comparison - DoD, WoW,
MoM, QoQ, YoY. A rolling window gets no label, because
“the 30 days before the last 30 days” is not a month and will not be presented as one.
When a calendar period has not finished, comparison.partial is true and the earlier
window was cut at the same elapsed offset. On the 12th of the month, this month is compared with
the first 12 days of last month - comparing it with the whole of last month would report a
collapse in traffic that never happened, every month, for the first three weeks of it.
Buckets
bucket=5min|hour|day|week|month|quarter|year, defaulting to whatever keeps a chart between
a dozen and a hundred bars. Buckets with no traffic come back as zeroes rather than being omitted:
a series that drops its empty buckets does not leave a gap when plotted, it closes one, and every
label after it is wrong. A bucket that would produce more than 1,500 points is a 400 rather than a
series that quietly stops short.
GET /v1/overview also returns timeseriesBySite - the same buckets split
by site, in a stable order, which is what the dashboard stacks and colours. The per-site visitor
counts sum to the total exactly, because visitor ids are salted per site and the id space is
therefore partitioned; that is also why one person on two sites counts as two here.