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.