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.read for site:{siteUuid} and site:{siteUuid}@{customViewId}
  • networks.read for network:{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 range
  • compare_start, compare_end: optional comparison range, both required together
  • granularity: minute, hour, day, week, month, or year. Defaults to day
  • metrics: comma-separated metric names, for example views,allowed_requests
  • where_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 example pathname. Defaults to pathname
  • dimensions: comma-separated secondary dimensions, for example hostname,country_code
  • metrics: comma-separated metrics, for example views,entrances
  • sort, sort_order: sorting field and asc or desc. Defaults to views and desc
  • limit, offset: pagination controls. limit accepts 1 to 1000 and defaults to 20
  • where_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_level1pagepath_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