Endpoint list

Here is a list of Publytics endpoints. Every path starts with https://publytics.net/api, and every request needs the token described in the introduction. All the queries resemble the structure of the queries made with the dashboard interface, and they accept the same dimensions, metrics and filters.

The names of the dimensions and metrics are listed in Dimensions and metrics; Describe a Site returns the complete list for a site, custom ones included.

The responses below show the exact format of each endpoint, with example values.

List Sites

GET /sites

Lists the sites, custom views and networks your token can read, with the identifier to use as {siteId} in the other endpoints. This call is not charged to the API / MCP calls per hour.

Response

{
  "sites": [
    {
      "domain": "example.com",
      "timezone": "Europe/Rome",
      "owned": true,
      "views": [
        { "site": "EXMPL1", "name": "Standard view" },
        { "site": "EXMPL1@12", "name": "Italy only" }
      ]
    }
  ],
  "networks": [
    { "site": "network=3", "name": "My network", "owned": true, "domains": ["example.com", "example.org"] }
  ]
}

Describe a Site

GET /site/{siteId}/meta

Describes a site: the report sections you can read, and every dimension and metric the site accepts, standard and custom, with the endpoints and datasets they work with. Call it before building a query, to know the names to use in metricsCols, customCols, sort and in the filters. Custom dimensions and metrics have "custom": true; the dimensions that the plan of the site does not include are left out.

Response

Field Content
site Identifier, type (site, custom_view or network), domain, name, timezone and data retention of the site
sections The report sections your user can read: realtime, content, acquisition, events, reports
endpoints For each endpoint, the dataset it reads, the section it needs (/content and /events also accept the reports section, in also_with_section) and its default metrics
dimensions Every dimension, with its name, label, type, whether it is custom, the filter operators it accepts and the datasets it belongs to. Dimensions with a fixed list of values also list them (or their number, when they are more than 60)
metrics Every metric, with its name, label, type, whether it is custom and the datasets it belongs to. The daily unique counts carry a caveat
filters The syntax of the filters, with an example
{
  "site": {
    "site": "EXMPL1",
    "type": "site",
    "domain": "example.com",
    "name": "Standard view",
    "timezone": "Europe/Rome",
    "data_retention_years": "unlimited",
    "sites": [{ "domain": "example.com", "view": "standard view", "uuid_view": "EXMPL1" }]
  },
  "sections": ["realtime", "content", "acquisition", "events", "reports"],
  "endpoints": {
    "/content": {
      "dataset": "content",
      "section": "content",
      "also_with_section": "reports",
      "default_metrics": ["views", "sessions", "viewers", "avg_time_on_page", "bounce_rate_sessions"],
      "primaryProp": "the primary dimension: any dimension of the content dataset (pathname by default)",
      "totals": "/content/totals"
    },
    ...
  },
  "dimensions": [
    {
      "name": "pathname",
      "label": "Pathname",
      "type": "string",
      "custom": false,
      "operators": ["=", "!=", "LIKE", "NOT LIKE", "EMPTY", "NOT EMPTY", "STARTS WITH", "ENDS WITH"],
      "datasets": ["content", "audience", "events", "realtime"]
    },
    {
      "name": "screen_size",
      "label": "Device",
      "type": "string",
      "custom": false,
      "operators": ["=", "!="],
      "values": ["Desktop", "Mobile", "Tablet", ""],
      "datasets": ["content", "audience", "events", "realtime"]
    },
    {
      "name": "article_id",
      "label": "Custom Dimension: article_id",
      "type": "number",
      "custom": true,
      "operators": ["=", "!=", ">", ">=", "<", "<="],
      "datasets": ["content", "audience", "events", "realtime"]
    },
    ...
  ],
  "metrics": [
    {
      "name": "views",
      "label": "Pageviews",
      "type": "number",
      "custom": false,
      "datasets": ["content", "audience", "events"]
    },
    {
      "name": "viewers",
      "label": "Unique Pageviews",
      "type": "number",
      "custom": false,
      "caveat": "Daily unique count: over a period of more days it is the sum of the daily values, so a returning person is counted once per day.",
      "datasets": ["content", "events"]
    },
    ...
  ],
  "filters": { "syntax": "...", "example": [...], "metric_operators": ["=", "!=", ">", ">=", "<", "<="] }
}

Common Query Parameters

The Content, Entry/Exit, Acquisition, Audience Stats and Events endpoints accept these query parameters. Dates are days in the format YYYY-MM-DD, and both start and end are included. JSON values must be URL-encoded.

Parameter Description Default Value
start The first day of the period. Current date - 7 days
end The last day of the period. Current date - 1 day
metricsCols A JSON array with the metrics to include, e.g. ["views","viewers"]. If omitted or empty, the default metrics of the endpoint are used. Default metrics
customCols A JSON array with secondary dimensions: each row is split by them too, e.g. ["country_code"]. Up to 10 dimensions, the primary one included. Empty array
dimensionsFilters A JSON array of conditions on the dimensions (the equivalent of WHERE). See Filters. No filter
metricsFilters A JSON array of conditions on the metrics (the equivalent of HAVING). See Filters. No filter
sort The metric or dimension to sort the rows by. It must be one of the requested metrics or dimensions, otherwise the first metric is used. "views"
sortOrder desc (descending) or asc (ascending). "desc"
limit The number of rows per page. The maximum allowed limit is 1000. 20
offset The page number, starting from 1: with limit=20, offset=2 returns the rows from 21 to 40. 1
compareStart The first day of a period to compare with. null
compareEnd The last day of a period to compare with. null
primaryPropFilter, customFilters, customPropFilter (deprecated) Legacy filter formats, still accepted. Use dimensionsFilters and metricsFilters instead. Empty array

Response

A JSON array with one object per row, containing the dimensions of the row and the requested metrics. Shares (bounce rate, % quality pageviews, scroll depth) are fractions between 0 and 1, and times are in seconds.

[
  {
    "pathname": "/blog/how-to-start",
    "views": 6768,
    "sessions": 6201,
    "viewers": 6053,
    "avg_time_on_page": 96.08,
    "bounce_rate_sessions": 0.0565
  },
  ...
]

With customCols, each row also contains the secondary dimensions, and there is one row per combination:

[
  { "pathname": "/blog/how-to-start", "country_code": "IT", "views": 6198 },
  { "pathname": "/blog/what-is-new", "country_code": "IT", "views": 5331 },
  ...
]

When compareStart and compareEnd are set, each row also contains a compared_{metric} field for each metric, with its value in the comparison period (0 when the row has no data then). The rows are the ones of the main period:

[
  {
    "pathname": "/blog/how-to-start",
    "views": 6768,
    "viewers": 6053,
    "compared_views": 5120,
    "compared_viewers": 4610
  },
  ...
]

Unique counts (viewers, unique_entrances, unique_exits) are computed per day: over a period of more days they are the sum of the daily values, so a returning visitor is counted once per day.

Time series: to get one row per day, use the date dimension as the primary one and sort by it, e.g. GET /site/{siteId}/acquisition/date?sort=date&sortOrder=asc for the daily page views, sessions and users of the site, or GET /site/{siteId}/content?primaryProp=date&sort=date&sortOrder=asc.

[
  { "date": "2026-03-01", "views": 6016, "unique_entrances": 4545 },
  { "date": "2026-03-02", "views": 11155, "unique_entrances": 7689 },
  { "date": "2026-03-03", "views": 7611, "unique_entrances": 5626 }
]

Filters

dimensionsFilters and metricsFilters have the same syntax, but act at two different moments:

  • dimensionsFilters (the SQL WHERE) chooses which data is counted, before the rows are grouped: e.g. only the page views from Italy. It accepts any dimension of the dataset, even one that is not in the rows.
  • metricsFilters (the SQL HAVING) chooses which rows are returned, after they are counted: e.g. only the pages with more than 1,000 page views. It accepts any metric of the dataset, even one that is not in metricsCols, and tests it on each row over the whole period.

Structure

A filter always has exactly two levels: a list of groups joined by "OR", where each group is a list of conditions joined by "AND". Each condition is [name, operator, value].

[  group , "OR",  group , "OR", ... ]          the outer list: groups joined by "OR"
group = [ condition, "AND", condition, ... ]   a group: conditions joined by "AND"
condition = [name, operator, value]

The joining words are strings written between the items, and each one belongs to its own level: "OR" only between groups, "AND" only between the conditions of a group. A row matches when all the conditions of at least one group are true: in SQL terms, (a AND b) OR (c) OR (d AND e).

Dimension filters (WHERE)

One condition: the data from Italy. A single condition still needs both levels of brackets.

[[["country_code", "=", "IT"]]]

→ country_code = 'IT'

AND: the blog pages read on a mobile phone. The two conditions are in the same group.

[[["pathname", "STARTS WITH", "/blog"], "AND", ["screen_size", "=", "Mobile"]]]

→ pathname LIKE '/blog%' AND screen_size = 'Mobile'

OR: the visits from Google or from Bing. Each condition is a group of its own.

[[["referrer_source", "=", "Google"]], "OR", [["referrer_source", "=", "Bing"]]]

→ referrer_source = 'Google' OR referrer_source = 'Bing'

AND inside OR: the blog pages read from Italy, plus any page reached from Google.

[[["pathname", "STARTS WITH", "/blog"], "AND", ["country_code", "=", "IT"]], "OR", [["referrer_source", "=", "Google"]]]

→ (pathname LIKE '/blog%' AND country_code = 'IT') OR referrer_source = 'Google'

OR inside AND: there is no third level, so a filter like (A OR B) AND C is written by repeating C in each group, as (A AND C) OR (B AND C). The visits from Google or Bing, only from Italy:

[[["referrer_source", "=", "Google"], "AND", ["country_code", "=", "IT"]], "OR", [["referrer_source", "=", "Bing"], "AND", ["country_code", "=", "IT"]]]

→ (referrer_source = 'Google' OR referrer_source = 'Bing') AND country_code = 'IT'

In the same way, (A OR B) AND (C OR D) becomes four groups: (A AND C) OR (A AND D) OR (B AND C) OR (B AND D).

Exclusions: != and NOT LIKE in the same group. All the pages except the home page and the tag pages:

[[["pathname", "!=", "/"], "AND", ["pathname", "NOT LIKE", "/tag/"]]]

→ pathname != '/' AND pathname NOT LIKE '%/tag/%'

Metric filters (HAVING)

The same syntax, with the names of the metrics and the operators =, !=, >, >=, <, <=. Shares are fractions between 0 and 1: a 10% bounce rate is 0.1.

One condition: only the rows with more than 1,000 page views.

[[["views", ">", 1000]]]

AND: the rows with between 100 and 1,000 page views.

[[["views", ">=", 100], "AND", ["views", "<=", 1000]]]

AND inside OR: the pages with more than 1,000 page views and a bounce rate under 10%, plus the pages with more than 6,000 page views whatever their bounce rate.

[[["views", ">", 1000], "AND", ["bounce_rate_sessions", "<", 0.1]], "OR", [["views", ">", 6000]]]

→ HAVING (views > 1000 AND bounce_rate_sessions < 0.1) OR views > 6000

Both together

The blog pages read from Italy in September, only those with at least 100 page views. With curl, -G and --data-urlencode take care of the encoding:

curl -G 'https://publytics.net/api/site/{siteId}/content' \
  --header 'Authorization: Bearer {token}' \
  --data-urlencode 'start=2026-09-01' \
  --data-urlencode 'end=2026-09-30' \
  --data-urlencode 'metricsCols=["views","viewers"]' \
  --data-urlencode 'dimensionsFilters=[[["pathname","STARTS WITH","/blog"],"AND",["country_code","=","IT"]]]' \
  --data-urlencode 'metricsFilters=[[["views",">=",100]]]'

Operators

Type Operators
Text dimensions =, !=, LIKE, NOT LIKE, STARTS WITH, ENDS WITH, EMPTY, NOT EMPTY
Dimensions with a fixed list of values =, !=
Numbers, dates and metrics =, !=, >, >=, <, <=

LIKE and NOT LIKE match anywhere in the value (no % needed), EMPTY and NOT EMPTY take an empty string as value, and dates are written as YYYY-MM-DD. The Describe a Site endpoint lists the operators accepted by each dimension.

Limits and errors

A filter can have up to 100 conditions in dimensionsFilters and 50 in metricsFilters; a query can group by up to 10 dimensions. Above them the API answers 422 with the reason. A filter that is not well formed (a joining word at the wrong level, a dimension or metric that does not exist, an operator the dimension does not accept, a value outside its fixed list) answers 500.

Get Content Pages

GET /site/{siteId}/content

Retrieves the most read articles of a site, or its content grouped by any other dimension. Requires the Content or the Reports section.

Query parameters

All the common query parameters, plus:

Parameter Description Default Value
primaryProp The dimension each row represents. Any dimension of the content dataset works, custom ones included (e.g. author). "pathname"

Default metrics: views, sessions, viewers, avg_time_on_page, bounce_rate_sessions.

Response

[
  {
    "pathname": "/blog/how-to-start",
    "views": 8157,
    "sessions": 7518,
    "viewers": 7346,
    "avg_time_on_page": 96.09,
    "bounce_rate_sessions": 0.2322
  },
  ...
]

Get Entry/Exit Pages

GET /site/{siteId}/acquisition/entry & GET /site/{siteId}/acquisition/exit

Retrieves the entry/exit pages of a site, one row per page (pathname). Requires the Acquisition section.

Query parameters

All the common query parameters, except sort: the rows are always sorted by entrances (entry) or exits (exit), or by the first metric of metricsCols when that one is not requested. sortOrder can still reverse the order.

Default metrics: entrances, unique_entrances, bounce_rate (entry); exits, unique_exits (exit).

Response (entry)

[
  { "pathname": "/blog/how-to-start", "entrances": 5847, "unique_entrances": 5333, "bounce_rate": 0.0614 },
  { "pathname": "/blog/what-is-new", "entrances": 5313, "unique_entrances": 4940, "bounce_rate": 0.2136 },
  ...
]

Get Acquisition Pages (group by a specific dimension)

GET /site/{siteId}/acquisition/{dimension}

Retrieves the acquisition stats of a site grouped by a specific dimension. Requires the Acquisition section.

Parameter Description

dimension (required) The dimension each row represents: any dimension of the audience dataset, custom ones included. The most used are:

  • UTM Medium: "utm_medium"
  • UTM Source: "utm_source"
  • UTM Campaign: "utm_campaign"
  • UTM Content: "utm_content"
  • UTM Term: "utm_term"
  • Referrer: "referrer"
  • Referrer + Source: "referrer_source"
  • Source / Medium: "source_medium"
  • Entry page: "entry_pathname"
  • Date: "date", for a daily series of the whole site

A dimension that is not in the audience dataset answers 400.

Query parameters

All the common query parameters.

Default metrics: views, entrances, unique_entrances, avg_time, bounce_rate.

Response

[
  {
    "referrer_source": "Facebook",
    "views": 41209,
    "entrances": 33258,
    "unique_entrances": 30546,
    "avg_time": 59.04,
    "bounce_rate": 0.1657
  },
  {
    "referrer_source": "Google",
    "views": 4794,
    "entrances": 3374,
    "unique_entrances": 3136,
    "avg_time": 86.32,
    "bounce_rate": 0.2217
  },
  ...
]

In the audience dataset entrances are the sessions and unique_entrances the users, as labelled in the dashboard.

Get Audience Overview: Users, Sessions, Pageviews and Bounce Rate

GET /site/{siteId}/visitors

Retrieves the totals of the site in a period: users, sessions, page views and bounce rate.

Query parameters

Parameter Description Default Value
start The first day of the period. Current date - 7 days
end The last day of the period. Current date - 1 day

No other parameter is supported: filters and comparison periods are not available on this endpoint. For totals with filters, use Get Totals.

Response

{ "viewers": 38352, "views": 53000, "sessions": 41834, "bounceRate": 0.1812 }

viewers is the sum of the daily unique visitors, and bounceRate is the share of single-page sessions, between 0 and 1.

Get Audience Stats (group by a specific dimension)

GET /site/{siteId}/visitors/{dimension}

Retrieves the Audience stats of a site grouped by a specific dimension. Requires the Acquisition section.

Parameter Description

dimension (required) The dimension each row represents. The following dimensions are supported:

  • Device: "device" (rows contain the screen_size field)
  • Country: "country" (rows contain the country_code field)
  • Operating system: "operating_system"
  • Browser: "browser"
  • Hostname: "hostname"

Any other audience dimension (e.g. city) can be read with Get Acquisition Pages.

Query parameters

All the common query parameters.

Default metrics: views, entrances, unique_entrances, avg_time, bounce_rate.

Response (/visitors/country)

[
  {
    "country_code": "IT",
    "views": 48712,
    "entrances": 38499,
    "unique_entrances": 35347,
    "avg_time": 61.41,
    "bounce_rate": 0.1816
  },
  ...
]

Get Events Report

GET /site/{siteId}/events

Retrieves the events of a site. Requires the Events or the Reports section.

Query parameters

All the common query parameters, plus:

Parameter Description Default Value
primaryProp The dimension each row represents: name for one row per event, pathname for one row per page, or any other dimension of the events dataset, custom ones included (e.g. country_code). "pathname"

To get the events by name, set primaryProp=name.

Default metrics: views, sessions, viewers. In the events dataset views is the number of times the event happened (Hits).

Response (primaryProp=name)

[
  { "name": "newsletter_signup", "views": 7212, "sessions": 4208, "viewers": 4109 },
  { "name": "share_click", "views": 4301, "sessions": 4184, "viewers": 4092 },
  ...
]

Get AI Traffic

GET /site/{siteId}/ai-traffic

Retrieves the visits brought by each AI assistant: the table of the AI Traffic overview of the dashboard, with the same sources, filters and numbers. Requires the Content section.

Query parameters

Parameter Description Default Value
start The first day of the period. Current date - 7 days
end The last day of the period. Current date - 1 day
compareStart The first day of a period to compare with. null
compareEnd The last day of a period to compare with. null

Response

{
  "sources": [
    {
      "name": "ChatGPT",
      "views": 305,
      "entrances": 281,
      "unique_entrances": 270,
      "dimensionsFilters": [[["referrer_source", "=", "chatgpt.com"]]]
    },
    {
      "name": "Perplexity",
      "views": 478,
      "entrances": 402,
      "unique_entrances": 390,
      "dimensionsFilters": [[["referrer_source", "=", "perplexity"]], "OR", [["referrer_source", "=", "perplexity.ai"]]]
    },
    ...
  ],
  "totals": { "views": 2831, "entrances": 2390, "unique_entrances": 2301 },
  "dimensionsFilters": [[["referrer_source", "=", "Google AI Overview"]], "OR", [["referrer_source", "=", "chatgpt.com"]], "OR", ...]
}
Field Content
sources Every AI source (Google AI Overview, ChatGPT, DeepSeek, Mistral, Claude, Perplexity, Microsoft Copilot, Google Gemini), sorted by views, also the ones with no visit. views are the page views, entrances the sessions and unique_entrances the users
totals The sum of all the sources
dimensionsFilters The filter that recognises all the AI traffic; each source also has its own
compared_metric With compareStart and compareEnd, in every source and in totals

The filters can be passed as they are to the other endpoints, to analyse the AI traffic any way you like. For example the pages most visited from an AI assistant, with /content, or the AI visits day by day:

curl -G 'https://publytics.net/api/site/{siteId}/acquisition/date' \
  --header 'Authorization: Bearer {token}' \
  --data-urlencode 'sort=date' \
  --data-urlencode 'sortOrder=asc' \
  --data-urlencode 'dimensionsFilters=[[["referrer_source","=","chatgpt.com"]]]'

To combine an AI filter with another condition, add the condition to each of its groups, as explained in Filters: ChatGPT or Claude visits from Italy are [[["referrer_source","=","chatgpt.com"],"AND",["country_code","=","IT"]],"OR",[["referrer_source","=","claude.ai"],"AND",["country_code","=","IT"]]].

Get Totals

GET /site/{siteId}/content/totals, GET /site/{siteId}/acquisition/entry/totals, GET /site/{siteId}/acquisition/exit/totals, GET /site/{siteId}/acquisition/{dimension}/totals, GET /site/{siteId}/visitors/{dimension}/totals & GET /site/{siteId}/events/totals

Every list endpoint above has a /totals version that takes the same parameters and returns, in a single object, how many rows match them and the metrics over all those rows: the same numbers as the totals row of the dashboard tables. Use it instead of reading every page of the list to add up the rows. limit, offset, sort and sortOrder are ignored. Each call counts as one call, like the list.

For example, the views of all the pages under /blog read from Italy last week:

curl 'https://publytics.net/api/site/{siteId}/content/totals?start=2026-11-02&end=2026-11-08&metricsCols=%5B%22views%22%5D&dimensionsFilters=%5B%5B%5B%22pathname%22%2C%22STARTS%20WITH%22%2C%22%2Fblog%22%5D%2C%22AND%22%2C%5B%22country_code%22%2C%22%3D%22%2C%22IT%22%5D%5D%5D' \
  --header 'Authorization: Bearer {token}'

Response

{
  "rows": 348,
  "views": 12840
}

With the default metrics and a comparison period (/content/totals?compareStart=...&compareEnd=...):

{
  "rows": 633,
  "views": 53000,
  "sessions": 48778,
  "viewers": 47136,
  "avg_time_on_page": 108.8,
  "bounce_rate_sessions": 0.1554,
  "compared_rows": 618,
  "compared_views": 52855,
  "compared_sessions": 48414,
  "compared_viewers": 46830,
  "compared_avg_time_on_page": 107.01,
  "compared_bounce_rate_sessions": 0.1635
}
Field Content
rows The number of rows the list endpoint returns with the same parameters, across all its pages. With customCols a row is a combination of the primary and secondary dimensions.
metrics Each requested metric (the default ones of the list when metricsCols is omitted) over all the rows: counts are summed, while rates and averages (bounce rate, average time) are weighted as in the dashboard. A ratio is null when no row matches.
compared_rows, compared_metric With compareStart and compareEnd, the same totals for the comparison period.

Unique counts (viewers, unique_entrances, unique_exits) are summed over the rows too: a person who read three pages counts three times in the viewers of /content/totals, on top of being counted once per day. For the visitors of the whole site use Get Audience Overview.

Get Realtime Overview

GET /site/{siteId}/realtime/overview

Retrieves what is happening on the site right now: the active users or the page views of the last minutes, split by one or more dimensions. It is the data of the Real Time section of the dashboard. Requires the Realtime section.

Query parameters

Parameter Description Default Value
select A JSON array with the dimensions each row is split by, e.g. ["referrer_source"] or ["pathname","country_code"]. Up to 10 dimensions of the realtime dataset (see Dimensions and metrics: every standard dimension except exit_pathname, plus the custom ones). ["pathname"]
realtimeMetric What to count: activeUsers (distinct visitors) or pageViews. "activeUsers"
realtimeRange How far back to look, in seconds. The maximum allowed range is 1800 (30 minutes). 300
dimensionsFilters A JSON array of conditions on the dimensions. See Filters. No filter
limit The number of rows to return. The maximum allowed limit is 1000. 20

No other parameter is supported: there are no dates, metric filters, sorting or paging.

Response

A JSON array with one object per row, sorted by count, containing the dimensions in select and:

  • views: the active users or the page views, according to realtimeMetric
  • percentage: the share of the total, from 0 to 100
  • avg_engagement_time: the average engagement time in seconds, or 0 when engagement time is not tracked
curl -G 'https://publytics.net/api/site/{siteId}/realtime/overview' \
  --header 'Authorization: Bearer {token}' \
  --data-urlencode 'select=["pathname"]' \
  --data-urlencode 'realtimeRange=1800' \
  --data-urlencode 'limit=3'
[
  { "pathname": "/blog/how-to-start", "views": 42, "percentage": 21.5, "avg_engagement_time": 48.2 },
  { "pathname": "/", "views": 31, "percentage": 15.9, "avg_engagement_time": 12.7 },
  { "pathname": "/blog/what-is-new", "views": 18, "percentage": 9.2, "avg_engagement_time": 0 }
]

The array is empty when nobody is on the site. With activeUsers, a visitor who viewed two pages is counted on both rows, so the rows can add up to more than the active users of the site: for the total, split by a dimension that has a single value, e.g. select=["hostname"] on a site with one domain. Traffic sources follow the Real-Time Attribution Model of your preferences (event-based by default).