APIs
Stats API
Query any metric, dimension and filter from your dashboard with a single JSON endpoint.
The Stats API gives you programmatic access to the same query engine that powers the dashboard — build internal reports, feed a data warehouse or show numbers in your own app.
Authentication
Create a key in Account settings → API keys → New API key and choose the Stats API type. The key is shown once, so store it safely. Send it as a bearer token:
Authorization: Bearer YOUR_KEY
A key can read every site its owner can access. Each key allows 600 requests per hour by default; responses include X-RateLimit-Limit and X-RateLimit-Remaining headers, and you'll get 429 with a Retry-After header when the limit is reached.
Query endpoint
curl -X POST https://plainwebanalytics.com/api/v2/query \
-H 'Authorization: Bearer YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{
"site_id": "example.com",
"date_range": "7d",
"metrics": ["visitors", "pageviews", "bounce_rate"],
"dimensions": ["visit:source"],
"filters": [["is", "visit:country", ["GB"]]],
"order_by": [["visitors", "desc"]],
"pagination": { "limit": 10 }
}'
{
"results": [
{ "metrics": [812, 1904, 41], "dimensions": ["Google"] },
{ "metrics": [233, 501, 52], "dimensions": ["Direct / None"] }
],
"meta": { "imports_included": false },
"query": { "site_id": "example.com", "date_range": ["2026-10-19T00:00:00Z", "2026-10-25T23:59:59Z"], "...": "..." }
}
Each result lists metric values in the order you asked for them, followed by the dimension values.
Request fields
| Field | Description |
|---|---|
site_id |
The site's domain. Required. |
date_range |
"day", "24h", "7d", "28d", "30d", "91d", "month", "year", "12mo", "all", "realtime" (last 30 minutes), any "<n>d", or a custom range ["2026-01-01", "2026-01-31"]. Required. |
relative_date |
Anchor date (YYYY-MM-DD) for relative ranges. Defaults to today in the site's timezone. "7d" means the seven full days before it. |
metrics |
One or more metrics (below). Required. |
dimensions |
Group results by up to several dimensions (below). |
filters |
A list of filters (below). |
order_by |
e.g. [["visitors", "desc"], ["visit:source", "asc"]]. Defaults to the first metric, descending. |
include |
Options: imports (merge imported data), time_labels, total_rows, and compare ("previous_period", "year_over_year" or a date range). |
pagination |
{ "limit": 100, "offset": 0 }. |
Metrics
| Metric | Meaning |
|---|---|
visitors |
Unique visitors |
visits |
Visits (sessions) |
pageviews |
Pageviews |
events |
All events except engagement pings |
views_per_visit |
Pageviews per visit |
bounce_rate |
Percentage of single-page visits |
visit_duration |
Average visit length, in seconds |
percentage |
Share of total visitors (with a dimension) |
conversion_rate |
Share of visitors who converted (with a goal filter or event:goal dimension) |
group_conversion_rate |
Conversion rate within each group |
exit_rate |
Exits as a share of pageviews (with visit:exit_page) |
time_on_page |
Average engaged time on a page, in seconds |
scroll_depth |
Average maximum scroll depth, in percent |
total_revenue, average_revenue |
Revenue for revenue goals |
Dimensions
- Event:
event:page,event:hostname,event:name,event:goal,event:props:<name> - Visit:
visit:source,visit:referrer,visit:channel,visit:utm_medium,visit:utm_source,visit:utm_campaign,visit:utm_content,visit:utm_term,visit:entry_page,visit:exit_page,visit:entry_page_hostname,visit:exit_page_hostname - Device:
visit:device,visit:browser,visit:browser_version,visit:os,visit:os_version - Location:
visit:country,visit:region,visit:city, plusvisit:country_name,visit:region_name,visit:city_name - Time:
time(interval chosen automatically),time:minute,time:hour,time:day,time:week,time:month
Filters
A filter is [operator, dimension, values]:
[
["is", "visit:country", ["GB", "IE"]],
["contains", "event:page", ["/blog"]],
["is_not", "visit:source", ["Direct / None"]]
]
Operators: is, is_not, contains, contains_not, matches and matches_not (regular expressions), and matches_wildcard / matches_wildcard_not (* and **). Add {"case_sensitive": false} as a fourth element to ignore case. Filters in the list are combined with AND; you can also nest ["and", [...]], ["or", [...]] and ["not", filter], and use ["has_done", filter] / ["has_not_done", filter] to select visitors by events they did or didn't perform.
To filter by a goal, use ["is", "event:goal", ["Signup"]].
Errors
Invalid queries return 400 with {"error": "…"} describing the problem. A missing or wrong key, or a site the key can't access, returns 401.
Version 1 endpoints
For existing integrations, the older GET endpoints are also available with the same keys: /api/v1/stats/realtime/visitors, /api/v1/stats/aggregate, /api/v1/stats/timeseries and /api/v1/stats/breakdown. They take site_id, period, date, metrics, filters (e.g. event:page==/blog;visit:country==GB), and for breakdowns property, limit and page. New integrations should use /api/v2/query.
Sites API
Keys of the Sites API type can manage sites, goals, shared links, guests and custom properties under /api/v1/sites — for example, PUT /api/v1/sites/goals creates a goal if it doesn't already exist.
Something unclear or missing? Tell us.