Base URL and authentication
Authorization: Bearer <YOUR_API_KEY> (details) and the stats:read permission.
site_id is the site’s snippet key, the ttid_ value your install snippet carries as data-site. You can read it from GET /v1/sites.
The breakdowns
They all take the same parameters and return the same envelope. Only the row shape and a few notes differ — including that two of them,
hostnames and exit-links, also take optional filters (see Filtering hostnames and exit links).
Shared request
Send
from and to together or send neither; with neither, the window is the last 7 days. Rows come back ordered by session count, highest first.
Filters are not supported on most of these endpoints. They read pre-aggregated rollups that carry no per-row conditions, so filter_country=is:US returns a 422 rather than a full unfiltered list under a 200. Two endpoints are the exception: /v1/breakdowns/hostnames and /v1/breakdowns/exit-links take dimension/value filters, described in Filtering hostnames and exit links below.
Shared response
suppressed_cities and suppressed_sessions tell you how much was withheld so your totals still reconcile. They never identify which cities.
The floor exists because plotting a named city off the back of a handful of sessions can identify a person in a small town. See GDPR and Geography.
Countries and cities do not reconcile
The two lists come from different places, and both are correct./v1/breakdowns/countriesreads a daily rollup that Traceten keeps indefinitely. Itssessionsis a distinct-count estimate, so merge these figures rather than adding them.pageviewson the same row is exact and does add up./v1/breakdowns/citiesreads individual session records, which are kept for at least 730 days and at most 760 days (Data retention). Its counts are exact, and it has the privacy floor above.
/v1/breakdowns/cities, /devices, /browsers, /operating-systems and /campaigns return 422 for a window whose from falls outside the last 730 days (today counts as day 1), or spanning more than 730 days. They refuse rather than return a partial answer, because past the boundary the query does not come back empty, it comes back confidently low. Ask /v1/breakdowns/countries, /pages, /hostnames or /referrers for a longer window, all of which read rollups kept indefinitely.
The two sessions figures will not match even inside the retention window. meta.source names which store answered each request.
An empty country_code is a real row: geolocation resolved no country. It is returned rather than dropped for the same reason unknown is.
Pages count AI traffic only; hostnames count all traffic
/v1/breakdowns/pages carries "ai_only": true in meta. It counts only sessions referred by an AI assistant, which is what the dashboard’s Top Pages card shows. Sessions from non-AI channels (direct, organic search, social, email, paid, referral) and sessions whose source could not be placed are excluded. It is not a total of all traffic to a page, and you should not present it as one.
/v1/breakdowns/hostnames carries "ai_only": false. It counts every session that viewed a page on the host, whatever its source, so its sessions field is a site total and not an AI figure. Do not compare a host’s sessions with a page’s ai_sessions: they count different populations.
Each hostname row also carries visitors, the distinct visitors behind those sessions. It is absent, rather than 0, when your window’s from is more than 730 days ago (today counts as day 1), because it is counted from raw events, which are kept for 730 days. The rest of the row still answers that window.
visitors and revenue_usd_cents are absent from a row, rather than 0, when your window reaches past the session-record retention boundary (at least 730 days, at most 760). Unlike the five session-grain breakdowns above, this endpoint does not refuse such a window: its other columns come from a rollup that is kept indefinitely and stay correct, so it drops only the two fields it cannot answer.
avg_confidence is a 0.0 to 1.0 mean over the page’s exit events, the only events the classifier scores, and confidence_samples is how many of those events are behind it. avg_confidence is null, never 0, when nothing was measured, and confidence_samples is 0 on exactly those rows. null is routine rather than an error: a pageview sent from a server SDK produces no exit event, and neither does a page load whose unload beacon is lost. 0.0 means measured and not confident, which is a different statement from not measured, so render an absent value as unknown. Use confidence_samples as the weight if you roll several pages or windows into one average.
On hostnames, ports are dropped and case is normalised, so SHOP.example.com:8443 and shop.example.com are one row. A host’s sessions is a distinct count: a session that viewed three pages on one host counts once for that host. visitors is distinct in the same way, so neither field adds up across hosts.
Exit links count outbound clicks, across all traffic
/v1/breakdowns/exit-links ranks the external domains visitors left your site for, by distinct sessions. It carries "ai_only": false in meta: every session counts, whatever its source.
exit_domain is the destination’s hostname as the browser reported it, in lowercase, with no path and no query string. It is not shortened to a registrable domain, so www.stripe.com and stripe.com are separate rows.
A session that left for two different domains counts once under each, so the rows do not add up to a number of sessions, and this breakdown returns no meta.total_sessions. visitors is the distinct visitors behind a domain’s sessions, counted by browser cookie; a visitor who left for the same domain in two visits counts once, and visitors do not add up across rows either.
Hostnames have no visitors count: the rollup behind that breakdown holds session counts only.
Exit links are kept for 730 days. A from older than that returns 422 rather than a silently low count. Counting starts when this breakdown became available; outbound clicks from before then are not included.
Filtering hostnames and exit links
/v1/breakdowns/hostnames and /v1/breakdowns/exit-links accept dimension/value query parameters to scope rows to sessions matching one or more conditions. No other breakdown endpoint accepts them; sending dimension to, say, /v1/breakdowns/countries returns 422.
dimension and value are paired by position: the example above is country=US AND device=mobile, not country=US OR device=mobile, and every pair must match for a session to count. You can stack up to 8 distinct dimensions, each with up to 20 values.
Referrers means source name
/v1/breakdowns/referrers returns the source Traceten resolved, not the raw HTTP Referer header. The header is missing for most AI-referred traffic, which is the reason this product exists. For a channel the row names the referring host; for an AI assistant it names the assistant.
meta.other_sessions, meta.other_visitors and meta.other_bounces rather than returned as a row, so the list stays bounded without losing traffic. They are page-relative: paging deeper fetches more named sources, so the remainder shrinks, and on the last page it is the complete tail. They are null when nothing was cut off. sessions is a distinct-count estimate here too: merge, do not sum.
visitors counts distinct visitors, by browser cookie, whose visit started from that source. sessions counts sessions under every source any of their events came from, so for a small share of visits the two are measured against slightly different sources. Visitors do not add up across rows; other_visitors is recounted over the tail, not summed.
Campaigns are an open set
/v1/breakdowns/campaigns carries "open_set": true in meta, because utm_campaign is free text off a visitor’s URL rather than a fixed list. Two consequences:
- The list is a top-N. It is never “all campaigns”, however large a
limityou send. - Its
unknownrow is normally the largest, because most visitors arrive with no campaign at all. That is correct and it is not filtered out.
There is no regions breakdown
/v1/breakdowns/regions does not exist and returns 404.
Traceten collects geography at city granularity and nothing between city and country. There is no region, state or postcode field to report, by design rather than by omission. Use /v1/breakdowns/countries or /v1/breakdowns/cities.
Pagination
has_more tells you whether another page exists.
pagination.total is the exact number of rows available, but it is only present on the breakdowns that hold the whole list already: devices, browsers, operating systems, campaigns, cities and countries. It is absent on pages, hostnames and referrers, whose rows come from a database-side top-N where a total would need a second pass over the same data. Branch on whether the field is there; do not treat a missing total as zero.
These lists are top-N results computed inside the database, so they have a ceiling: paging past roughly 100 rows on the device, browser, operating system, campaign and city breakdowns will return has_more: false even though a longer tail exists in principle. Use meta.total_sessions to account for that tail rather than paging for it.
The two AI breakdowns
/v1/breakdowns/ai-sources and /v1/breakdowns/source-kinds live on the same path prefix but are not shaped like the nine above. They take their own parameters, cap the window at 730 days, and return their own response bodies. Everything from here down describes those two.
ai_source and source_kind are different questions
These two get confused more than anything else in the API, so before the endpoints:
They are separate axes over the same sessions, not two levels of one hierarchy. A number from
/ai-sources and a number from /source-kinds describe overlapping populations, so adding them together double counts.
There is a fourth source_kind value, unmatched, that you will only ever see on GET /v1/detection/coverage. It means no browsing session could be matched to a conversion at all. It cannot appear on either endpoint here, because both are derived from sessions and unmatched is the absence of one.
GET /v1/breakdowns/ai-sources
One row per AI assistant over the window, with the detection method and confidence behind it.
Parameters
Same window rules as
/ai-sources: 730 day maximum span, from no more than 730 days ago, no timezone.
limit bounds each kind’s list separately, and it is pushed into a separate query per kind rather than applied to one shared list afterwards. That distinction is the point: a site with hundreds of named non-AI sources would otherwise fill a single ranking before any AI source appeared, and slicing that list per kind afterwards would hand you an empty AI list on the endpoint family built to surface AI traffic.
Each kind carries its own truncated, telling you whether that kind’s list was cut.
Response fields
Each
kinds row:
totals carries visitors, new_visitors, sessions, conversions and revenue_usd_cents.
A visitor is a browser, not a person: the same human on a phone and a laptop is two.
new_visitors counts browsers whose session was their first ever page load, so label it
“new browsers” or “new visitors”, never “new users” or “new people”.
unknown is a real bucket
unknown is a session whose entry source we could not place, either because the row predates the field or because no signal identified it. It is never dropped, because dropping it would make the shares stop adding up to your traffic.
Do not confuse it with unmatched on the coverage endpoint. unknown means “we saw the session and could not name its source”. unmatched means “we never found a session at all”.
Revenue here is US dollars
revenue_usd_cents on this endpoint is US cents and there is no revenue_display field, even if your reporting currency is not USD.
This is not an oversight. Our rule is to convert money at the exchange rate of the day it was earned, and this endpoint reports one total for the whole window rather than a figure per day. There is no honest rate to apply to that total, and applying today’s rate would silently reprice history. /ai-sources keeps its per-day rows and is converted.
Do not sum the inner list to get the outer number
kinds[].sessions is an exact count of sessions in that category. kinds[].sources[].sessions is a top N list, bounded by limit, and it is measured slightly differently: the kind total counts sessions that started in the window, the inner list counts sessions active in it. The two will not add up, and the outer number is the one to report.
retention_floor is null on almost every request. It becomes non-null only when your from lands on the oldest day still covered by the 730 day session retention, which is the one date where part of the window may already have expired. When it is set, treat it as the oldest date this response is trustworthy for.
Errors
The dimension breakdowns
Beyond the standard errors:ai-sources and source-kinds
An unknown query parameter is a
422 rather than being ignored. A silently dropped filter returns the wrong number with a 200 beside it, and nothing looks broken.
Next
- Stats API for the totals these breakdowns split up.
- Sources API for the per-day source series.
- Detection for the bot filter and attribution coverage behind the AI figures.
- AI crawlers for what AI crawlers read on your site.

