API & MCP
API usage
Read traffic reports from your sites and networks with the available API endpoints.
The API provides read-only traffic reports for sites, custom views, and networks. Every request uses a team API token and returns a JSON response with a data and meta object.
If you want an AI assistant to read these reports instead of writing the calls yourself, see the MCP server. It is a thin wrapper over these very endpoints: every one of its tools calls one of them, and the two share one rate limit.
#Authentication
Create a team token from Settings → API tokens and grant it the read permission for the resource you want to query:
sites.readforsite:{siteUuid}andsite:{siteUuid}@{customViewId}networks.readfornetwork:{networkId}
Send it as a bearer token:
Authorization: Bearer YOUR_TEAM_TOKEN
Accept: application/json
The token belongs to the team, so the addressed site or network must belong to that team. The team must also have API access enabled by its plan.
Permissions cascade: networks.read also grants sites.read, and the edit permissions include their read counterpart. A token that can read a network can read the sites inside it.
#Available endpoints
Every endpoint below a site group is addressed by a wireable: site:{siteUuid}, site:{siteUuid}@{customViewId} or network:{networkId}. The first endpoint is the one that hands you those addresses, so start there.
#Your sites
GET /api/v1/sites
The only endpoint without a wireable, because it is the one that produces them. It takes no parameters and lists every site, custom view and network the token can read, each with the address the other endpoints expect and the timezone its dates are reported in:
{
"data": {
"sites": [
{
"wireable": "site:ab12cd",
"domain": "example.com",
"timezone": "Europe/Rome",
"custom_views": [
{ "wireable": "site:ab12cd@7", "name": "Blog only" }
]
}
],
"networks": [
{ "wireable": "network:3", "name": "All properties" }
]
},
"meta": { "sites": 1, "networks": 1 }
}
networks is listed only for a token carrying networks.read; with sites.read alone it comes back empty rather than as an error. The endpoint itself requires at least sites.read.
#Field dictionary
GET /api/v1/site:SITE_UUID/fields
The metric and dimension names that site group accepts, each with its display name, type and meaning, plus the allowed values of the fields that have a closed set:
{
"data": {
"default_metrics": ["views", "sessions", "viewers", "avg_time_on_page", "bounce_rate_sessions"],
"metrics": [
{ "name": "views", "display_name": "Pageviews", "type": "number", "description": "…" }
],
"dimensions": [
{ "name": "pathname", "display_name": "Pathname", "type": "string" },
{
"name": "verified_state",
"display_name": "Macro Category",
"type": "select",
"allowed_values": [
{ "value": "-1", "label": "Human" },
{ "value": "2", "label": "Malicious Bot" }
]
}
]
},
"meta": { "wireable": "site:ab12cd", "timezone": "Europe/Rome" }
}
This is the same list the stats endpoints validate against, so anything it returns is accepted and anything it omits is rejected with 422. It is also worth reading because the list depends on the address: site_id, for instance, exists only when the address covers more than one site. allowed_values appears only on closed-set fields, and carries the value the filter expects next to the label that explains it — on verified_state, month and day_name that value is an internal code.
The Metrics and Dimensions tables further down are the same vocabulary written out for a reader; this endpoint is the machine-readable copy, and it cannot fall out of date.
#Time series
GET /api/v1/site:SITE_UUID/stats/views
Useful query parameters:
start,end: optional date or Unix timestamp rangecompare_start,compare_end: optional comparison range, both required togethergranularity:minute,hour,day,week,month, oryear. Defaults todaymetrics: comma-separated metric names, for exampleviews,allowed_requestswhere_expression,having_expression: filter expressions
The response gives one label per time bucket and one series per metric. Buckets with no traffic are filled with zeros, so every series has exactly one value per label:
{
"data": {
"labels": ["2026-08-01", "2026-08-02"],
"series": [
{ "label": "Pageviews", "data": [120, 98], "compared": false }
]
},
"meta": { "start": 1785542400, "end": 1785715200, "timezone": "Europe/Rome", "granularity": "day" }
}
Note that label is the metric's display name, not its key: views comes back as Pageviews. The table below maps the two. When a comparison range is requested the response also carries compare_labels, and the extra series are marked "compared": true.
#Dimension report
GET /api/v1/site:SITE_UUID/stats/pages
Useful query parameters:
dimension: primary dimension, for examplepathname. Defaults topathnamedimensions: comma-separated secondary dimensions, for examplehostname,country_codemetrics: comma-separated metrics, for exampleviews,entrancessort,sort_order: sorting field andascordesc. Defaults toviewsanddesclimit,offset: pagination controls.limitaccepts 1 to 1000 and defaults to 20where_expression,having_expression: filter expressions
Each row carries the grouped dimensions followed by the requested metrics:
{
"data": [
{ "pathname": "/pricing", "views": 412, "sessions": 380 },
{ "pathname": "/", "views": 388, "sessions": 351 }
],
"meta": {
"start": 1785542400, "end": 1785715200, "timezone": "Europe/Rome",
"dimension": "pathname", "dimensions": [], "limit": 20, "offset": 0
}
}
With a comparison range, each row also carries compared_{metric} keys. There is no total row count, so to page through a report keep raising offset until a page comes back shorter than limit.
sort is the one parameter that is not checked against the dictionary: a name that is neither one of the requested metrics nor the dimension is ignored, and the report is sorted by the first metric instead.
The same endpoints accept a network or custom-view address:
GET /api/v1/network:NETWORK_ID/stats/pages?dimension=pathname&metrics=views
GET /api/v1/site:SITE_UUID@VIEW_ID/stats/views?granularity=day&metrics=views
All parameters belong in the URL. Lists can be written as comma-separated values, so a GET request does not need a request body. Repeated array parameters such as metrics[]=views&metrics[]=entrances remain supported as well.
#Dates and defaults
start, end, compare_start and compare_end accept either Unix timestamps or any ordinary date string, such as 2026-08-01 or 2026-08-01T10:00:00Z. They are interpreted in the site's timezone, which every response repeats in meta.timezone.
When start and end are omitted the window is the last 7 days. These two endpoints report by whole days, so granularity=hour and granularity=minute only add detail on very recent windows. Requests reaching further back than your plan's data retention are answered with the data that is still available.
#Metrics
Omitting metrics returns views, sessions, viewers, avg_time_on_page and bounce_rate_sessions. GET /api/v1/{wireable}/fields returns this same table for a given address, including the descriptions.
| Metric | Display name | Type |
|---|---|---|
views |
Pageviews | number |
viewers |
Unique Pageviews | number |
sessions |
Involved Sessions | number |
entrances |
Entrances | number |
unique_entrances |
Unique Entrances | number |
exits |
Exits | number |
unique_exits |
Unique Exits | number |
views_per_session |
Pageviews per Sessions | number |
sessions_per_user |
Sessions per Users | number |
views_per_entrance |
Pageviews per Entrances | number |
entrances_per_user |
Entrances per Unique Entrances | number |
avg_time_on_page |
Avg. Time on Page | seconds |
avg_time |
Avg. Session Duration (Entrances) | seconds |
avg_time_sessions |
Avg. Session Duration | seconds |
bounce_rate |
Bounce Rate (Entrances) | percentage |
bounce_rate_sessions |
Bounce Rate | percentage |
avg_response_time |
Avg. Response Time | seconds |
avg_bytes_sent |
Average Bytes Sent | number |
allowed_requests |
2xx Requests | number |
redirect_requests |
3xx Requests | number |
denied_requests |
4xx Requests | number |
error_requests |
5xx Requests | number |
last_visit |
Last Visit | date |
#Dimensions
Usable as dimension, in dimensions, and on the left-hand side of where_expression. GET /api/v1/{wireable}/fields returns this same table for a given address, with the allowed values of every closed set spelled out.
| Dimension | Display name | Type |
|---|---|---|
pathname |
Pathname | string |
full_url |
Full URL | string |
hostname |
Hostname | string |
entry_pathname |
Entry Page | string |
pagepath_level1 … pagepath_level5 |
Pagepath Level 1 … 5 | string |
query_string |
Query String | string |
referrer |
Referrer | string |
referrer_source |
Referrer + Source | string |
source_medium |
Source / Medium | string |
utm_source, utm_medium, utm_campaign, utm_content, utm_term |
UTM parameters | string |
country_code |
Country | closed set: ISO country codes, plus Unknown |
browser |
Browser | string |
operating_system |
Operating System | string |
accept_language |
Accept Language | string |
bot_name |
Bot Name | string |
bot_type |
Bot Type | string |
verified_state |
Macro Category | closed set: -1 Human, 0 Suspicious Bot, 1 Verified Bot, 2 Malicious Bot |
asn_verified |
ASN Verified | closed set: true, false |
is_datacenter |
Is Datacenter | closed set: true, false |
is_vpn |
Is VPN | closed set: true, false |
user_agent_group |
User Agent Group | string |
full_user_agent |
Full User Agent | string |
request_method |
Request Method | string |
status_code |
Status Code | number |
date |
Date | date |
datetime |
Datetime | datetime |
hourminute |
Hour + Minute | hourminute |
year |
Year | number |
month |
Month | closed set: 1 to 12 |
day_name |
Day Name | closed set: 1 (Monday) to 7 (Sunday) |
When the address covers more than one site, a site_id dimension is also available, filtered by domain name; in the report it comes back as site_domain.
#Filter expressions
where_expression filters on dimensions, having_expression on metric values. Both take the same shape: an OR of AND-groups, with the joining words written in between.
[[["pathname", "=", "/pricing"], "AND", ["country_code", "=", "IT"]], "OR", [["pathname", "=", "/about"]]]
A single condition is still a group inside a list: [[["verified_state", "=", "2"]]].
The parameter accepts that JSON directly, or the URL-safe encoded form that dashboard links use. The Copy API call button on the chart and report table includes the filters currently applied to that component. The copied URL intentionally omits dashboard dates, pagination limits, and offsets. Add those values manually when a fixed reporting window or page is required.
Which operators are allowed depends on the field:
| Field type | Operators |
|---|---|
| String | =, !=, IN, NOT IN, LIKE, NOT LIKE, STARTS WITH, ENDS WITH, EMPTY, NOT EMPTY |
| Number, percentage, seconds, date | =, !=, >, >=, <, <=, IN, NOT IN |
| Closed set | =, !=, IN, NOT IN |
| Boolean | =, != |
IN and NOT IN take an array as the third element, with at most 50 values. LIKE and NOT LIKE surround the value with wildcards for you, and STARTS WITH and ENDS WITH add one on the correct side, so do not write % yourself. EMPTY and NOT EMPTY ignore the value: pass an empty string.
Percentages are given as they are displayed, so ["bounce_rate", ">", 50] means more than 50%. Durations accept either a number of seconds or a HH:MM:SS string.
#Rate limits
The API limit is measured in requests per minute for the team and for the caller IP. When the plan limit is reached, the API returns 429 Too Many Requests with a retry interval in the message. A plan without API access returns 403.
Every endpoint counts the same, sites and fields included. Neither of those changes between calls, though, so read them once and keep the answer rather than asking again before each report.
Calls made through the MCP server are counted here too: it is one shared allowance, not two.
#Errors
Every error returns a JSON body with a message, and validation errors add an errors object listing the offending fields.
| Status | Meaning |
|---|---|
401 |
The token is missing, malformed or has been deleted |
403 |
A user token was used instead of a team token, the token lacks the required permission, the site belongs to another team, the plan has no API access, or the subscription is inactive or in overage |
404 |
No such site, custom view or network |
422 |
A parameter failed validation, for example an unknown metric or dimension name, or a malformed filter expression |
429 |
The per-minute rate limit was reached |