Stats API
Read a site's traffic from your own code or another tool: totals, a daily series, breakdowns by source, page, country, device and more, and AI crawler activity. The numbers match the dashboard for the same dates and filters.
GET https://api.snowanalytics.app/api/v1/stats/aggregate
GET https://api.snowanalytics.app/api/v1/stats/timeseries
GET https://api.snowanalytics.app/api/v1/stats/breakdown
GET https://api.snowanalytics.app/api/v1/stats/crawlersAuthentication
Authorization: Bearer snw_live_your_keyThe key needs the stats:read scope. Create one under
API keys and tick Read stats. The scope is off by
default, so keys you already use for events cannot read stats.
A key belongs to one site, so there is no site parameter. Every request reads the site the key was created for.
Date range
Send either a preset period or a custom from and to. Dates are days in the
site's timezone.
| Parameter | Value |
|---|---|
period | today, yesterday, 7d, 30d, 90d, 180d, 365d, 12m, this_week, last_week, this_month, last_month, this_year, all_time. Defaults to 30d. |
from, to | YYYY-MM-DD, both required together. |
Rolling periods include today: 7d is today plus the previous 6 days, and
30d is today plus the previous 29.
A range ends today at the latest: a to after today reads through today, and a
from after today returns 400.
Every response includes the range that was read. If your plan's history window
is shorter than the range you asked for, the range is shortened and marked with
"clamped": true and the earliest day you can read.
Filters
Each filter takes one exact value. Filters combine with AND.
| Parameter | Matches |
|---|---|
channel | A channel such as Organic Search or Direct, or one source such as google.com. |
country | Two-letter country code, such as US. |
city | City name. |
browser, os, device | Browser, operating system, or device type. |
page | This path. Totals count its own views. Breakdowns cover the sessions that viewed it. |
entry_page, exit_page | Sessions that started or ended on this path. |
campaign | utm_campaign value. |
utm_source, utm_medium, utm_content, utm_term | The matching UTM value. |
A parameter sent twice, or one that is not listed on this page, returns 400. A
typo never returns unfiltered numbers.
Aggregate
Totals for the range.
curl "https://api.snowanalytics.app/api/v1/stats/aggregate?period=30d&country=US" \
-H "Authorization: Bearer snw_live_your_key"{
"status": "success",
"data": {
"site": { "domain": "example.com", "name": "Example", "timezone": "UTC" },
"range": { "from": "2026-09-05", "to": "2026-10-04" },
"totals": {
"visitors": 1840,
"sessions": 2311,
"pageviews": 5120,
"bounce_rate": 41.2,
"avg_duration": 96
}
}
}bounce_rate is a percentage. avg_duration is the average session length in
seconds.
Timeseries
One point per day. The series starts on the first day with traffic inside the range and has no gaps after it.
curl "https://api.snowanalytics.app/api/v1/stats/timeseries?from=2026-10-01&to=2026-10-03" \
-H "Authorization: Bearer snw_live_your_key"{
"status": "success",
"data": {
"range": { "from": "2026-10-01", "to": "2026-10-03" },
"interval": "day",
"series": [
{ "date": "2026-10-01", "visitors": 61, "sessions": 70, "pageviews": 152, "bounce_rate": 42.9 },
{ "date": "2026-10-02", "visitors": 0, "sessions": 0, "pageviews": 0, "bounce_rate": 0 },
{ "date": "2026-10-03", "visitors": 58, "sessions": 66, "pageviews": 140, "bounce_rate": 39.4 }
]
}
}Breakdown
Rows for one dimension, largest first.
| Parameter | Value |
|---|---|
dimension | Required. One of the dimensions below. |
limit | Rows per page, 1 to 1000. Defaults to 100. |
offset | Rows to skip, for paging. Defaults to 0. |
Each dimension returns the metrics that make sense for it:
| Dimension | Metrics |
|---|---|
source, campaign, utm_source, utm_medium, utm_content, utm_term | visitors, sessions, bounce_rate, avg_duration |
page | visitors, pageviews |
entry_page | sessions, bounce_rate |
exit_page | sessions |
country, city, browser, os, device | visitors, sessions, avg_duration |
channel | sessions |
visitor_type | visitors, in two rows named new and returning |
On the UTM dimensions, traffic without that tag is one row named (none).
channel groups the site's top 200 sources, the same way the dashboard's
Channels chart does.
curl "https://api.snowanalytics.app/api/v1/stats/breakdown?dimension=page&period=7d&limit=2" \
-H "Authorization: Bearer snw_live_your_key"{
"status": "success",
"data": {
"range": { "from": "2026-09-28", "to": "2026-10-04" },
"dimension": "page",
"limit": 2,
"offset": 0,
"has_more": true,
"rows": [
{ "name": "/", "visitors": 412, "pageviews": 590 },
{ "name": "/pricing", "visitors": 138, "pageviews": 171 }
]
}
}To read every row, raise offset by limit until has_more is false.
Crawlers
AI crawler requests to the site: who fetched, what for, and which pages. This is the data behind the dashboard's AI crawlers section, so the site needs crawler tracking installed.
It takes the date range parameters only. Crawler requests carry no
country, device or source, so a filter returns 400 instead of being ignored.
curl "https://api.snowanalytics.app/api/v1/stats/crawlers?period=7d" \
-H "Authorization: Bearer snw_live_your_key"{
"status": "success",
"data": {
"range": { "from": "2026-09-28", "to": "2026-10-04" },
"installed": true,
"installed_at": "2026-09-09T10:12:44.000Z",
"totals": {
"hits": 8412,
"verified_hits": 7950,
"markdown_hits": 240,
"html_hits": 8172,
"agents": 34,
"providers": 16,
"answer_fetches": 1203
},
"previous": { "hits": 7011, "verified_hits": 6600, "markdown_hits": 180, "html_hits": 6831 },
"series": [
{ "date": "2026-09-28", "hits": 1010, "verified_hits": 960, "markdown_hits": 31, "html_hits": 979 }
],
"categories": [
{ "category": "training", "hits": 4300, "verified_hits": 4100 }
],
"providers": [
{ "provider": "OpenAI", "hits": 2140, "verified_hits": 2140 }
],
"agents": [
{ "agent": "GPTBot", "provider": "OpenAI", "category": "training", "hits": 1500, "verified_hits": 1500, "markdown_hits": 0, "html_hits": 1500 }
],
"daily_by_agent": [
{ "date": "2026-09-28", "agent": "GPTBot", "hits": 210, "verified_hits": 210, "markdown_hits": 0, "html_hits": 210 }
],
"pages": [
{ "path": "/pricing", "provider": "OpenAI", "category": "training", "hits": 96, "verified_hits": 96 }
],
"ai_referrals": { "sessions": 28, "visitors": 24, "previous_sessions": 19 }
}
}| Field | Meaning |
|---|---|
installed | Whether the site has ever reported a crawler request. false means crawler tracking is not set up, not that the range was quiet. |
totals, previous | Counts for the range and for the equal-length range before it. previous covers whole days, even while today is still running. |
series | One point per day in the range, zeros included. |
categories, providers | Requests by what the crawler was fetching for, and by operator. |
agents | One row per crawler user agent, with its operator in provider. An agent that fetches for more than one purpose is listed under the busiest. |
daily_by_agent | One row per day and agent. Days with no requests are left out. |
pages | The most requested paths, one row per path, provider and category. Capped at the 400 busiest rows. |
ai_referrals | People who arrived from an AI assistant in the same range. |
category is what the crawler was fetching for: answer_fetch, search_index,
training, ai_crawler or markdown_client. verified_hits counts requests
whose identity was verified.
Responses
| Status | Meaning |
|---|---|
200 OK | { "status": "success", "data": … } |
400 Bad Request | A parameter is unknown, repeated, or invalid. The message names it. |
401 Unauthorized | Missing or invalid API key. |
403 Forbidden | The key lacks the stats:read scope, or the site is locked until its subscription is restored. |
429 Too Many Requests | Rate limit exceeded. |
500 Internal Server Error | The numbers could not be read. Nothing was cached, so retry. |
Errors use the same envelope as the other authenticated endpoints:
{ "status": "error", "error": { "code": 400, "message": "Unknown parameter: contry" } }Results are cached for up to 30 seconds.