Skip to main content

Reference

Shared parameters

The window, filters, paging and response envelope every endpoint has in common.

Every read endpoint parses the same window, the same filters and the same paging. They are described once here rather than repeated on two dozen reference pages.

Each endpoint's own page lists the parameters it adds on top, and notes where it deliberately ignores one of these.

Choosing a window

Two ways, and passing both is a 400 — which one wins would be a guess you did not make.

ParameterContract
periodA named window: today, yesterday, last24h, last7d, last30d, last12m, month, year, all. Resolved in tz.
from, toISO 8601 with an offset. Must be given together — one alone is a 400.
tzIANA timezone for day boundaries. Defaults to UTC.

tz is validated as a real zone, so Mars/Olympus is a 400 naming the problem rather than a failure further in. Aliases work: Asia/Calcutta is accepted alongside Asia/Kolkata.

period=today&tz=Europe/Istanbul means your today, not the server's. Pass the zone your reader is in — a site's own timezone is on its settings, and the API does not apply it for you.

Where the buckets fall

/timeseries and /heatmap cut their buckets in tz. The others that bucket — crawler series and engagement — resolve the window in tz but cut at UTC boundaries, and say so on their own pages.

The live endpoints take no window at all. They take a lookback in minutes; see Online now.

Filters

Flat query parameters, one per dimension — 21 of them:

filter_path            filter_path_pattern    filter_hostname
filter_entry_path      filter_exit_link       filter_file_download
filter_goal            filter_browser         filter_os
filter_device          filter_country         filter_region
filter_city            filter_referrer        filter_channel
filter_utm_source      filter_utm_medium      filter_utm_campaign
filter_gad_campaign_id filter_utm_term        filter_bot_kind

Equality only. There is no is_not, no contains and no numeric comparison. The one exception is filter_path_pattern, which takes * as a wildcard:

?filter_path_pattern=/blog/*

Unknown query parameters are ignored, not refused — a query string collects analytics tags, cache-busters and proxy additions that have nothing to do with us. A misspelled filter_ name therefore does nothing silently; check the spelling against the list above if a filter seems to have no effect.

Two endpoints ignore filters entirely and explain why on their own pages: channel quality compares channels, so narrowing to one leaves nothing to compare, and engagement reads sessions rather than events.

Crawler traffic

bots=excluded is the default everywhere. A report about people that quietly counts machines is wrong.

The AI endpoints ignore the parameter and always include crawlers — there the machines are the report.

Retention clamping

If your window reaches further back than your plan retains, you get the data that exists and the response says so — not an error, and not a silent truncation:

"meta": { "range": { "from": "…", "to": "…", "clamped": true, "tz": "UTC" } }

A caller that plots the result without reading clamped draws a chart missing the years it asked for, with nothing in the response to indicate it. It is the difference between a short answer and a wrong one.

Your retention window is on usage and, for the figure actually applied to a given site's reads, on its settings.

The response envelope

One shape, on every endpoint:

{
  "data": { },
  "meta": {
    "site_id": "6fed8980-…",
    "range": { "from": "…", "to": "…", "clamped": false, "tz": "UTC" },
    "page": { "limit": 100, "offset": 0, "has_more": true }
  }
}

meta.range appears on endpoints that take a window, meta.page on those that return rows, and meta.minutes on the live ones. Nothing carries a field it cannot fill.

Adding a field to data is not a breaking change. Read the fields you need and ignore the rest.

Errors use one shape too, with a symbolic code rather than a repeated HTTP status — see Errors.

Paging

limit (default 100, maximum 1000) and offset. There is no cursor.

meta.page carries has_more and not a total. Counting every matching row costs as much as the query itself on a wide window, and the number is stale by the time you read it on a live dataset. has_more is measured rather than guessed: the query asks for one row more than you did and reports whether it came back.

One endpoint ignores paging and returns everything: traffic split. In a ratio, a row cut off at the bottom does not shorten the answer — it silently changes every percentage above it.

What no endpoint will do

Why
/visitors, /visitors/{id}Needs cross-day person identity TrueStat is built without. Not deferred — not this product.
Write a pageviewUse the tracking script. Conversions are the exception: POST /goals.
Post a paymentRevenue comes from the payment provider and reconciles against it. Connect one with the integrations endpoints.
Transfer ownershipAn account changes hands deliberately, in the dashboard.
Mint an API keyA credential that issues credentials turns one leak into permanent access. Dashboard only.
Delete a siteIrreversible, and dashboard only.
Return any secretSite keys, integration credentials and key plaintexts are never in a response.
A fields projectionRows are a handful of columns. It would buy nothing.
Was this page helpful?

Last updated September 17, 2026