REST · JSON or CSV · OpenAPI 3.1

The quietstats Stats API

Pull any number from your dashboard into your own tools: spreadsheets, Looker Studio, internal dashboards or scripts. Read-only, simple Bearer keys, still cookie-free.

Get a free keyOpenAPI spec

Your appSheets · script · dashboard quietstats API/api/v1 · read-only GET + Authorization: Bearer qs_… 200 · JSON or CSV aggregates No cookies · no visitor IDs · no IPs · only totals and breakdowns

What you can pull

EndpointReturnsParameters
GET /sitesYour sites (IDs and domains).format
GET /aggregateVisitors, pageviews, visits, views per visit, bounce rate and visit duration, each with previous value and % change.site, period, from, to, filters, compare, format
GET /timeseriesThe same metrics per hour, day, week or month, with no gaps.site, period, from, to, interval, metrics, filters, format
GET /breakdownEvery value of a property (the full list, not just the top 10) with visitors and pageviews (entry/exit pages: visits, bounce rate, duration). Search, sort and page through it.site, property, period, from, to, filters, search, sort, limit, page or offset, format
GET /realtimeVisitors in the last 5 minutes, the pages and countries they are on, and the latest pageviews (page, country, referrer, device; no visitor IDs).site, format

Base URL https://quietstats.co.uk/api/v1. Breakdown properties: page entry_page exit_page referrer channel ai utm_source utm_medium utm_campaign country device browser os screen. The channel property includes AI (ChatGPT, Perplexity, Gemini, Grok, Claude, Copilot).

Get a key in 30 seconds

  1. Create a free account and add your site.
  2. Open Settings → API keys and press Create read-only key. You can limit a key to one site.
  3. Copy the key straight away: we only keep a hash of it, so it is shown once. You can revoke it at any time.

Quick start

curl
curl -H "Authorization: Bearer qs_your_key_here" \
  "https://quietstats.co.uk/api/v1/aggregate?site=YOUR_SITE_ID&period=30d"
JavaScript (fetch)
const res = await fetch(
  "https://quietstats.co.uk/api/v1/timeseries?site=YOUR_SITE_ID&period=7d&interval=day",
  { headers: { Authorization: "Bearer " + process.env.QUIETSTATS_KEY } }
);
if (!res.ok) throw new Error((await res.json()).error.message);
const { results } = await res.json();
console.table(results); // [{ date, visitors, pageviews, visits, ... }]
Python (requests)
import os, requests

r = requests.get(
    "https://quietstats.co.uk/api/v1/breakdown",
    params={"site": "YOUR_SITE_ID", "property": "channel", "period": "30d"},
    headers={"Authorization": f"Bearer {os.environ['QUIETSTATS_KEY']}"},
    timeout=10,
)
r.raise_for_status()
for row in r.json()["results"]:
    print(row["value"], row["visitors"])
CSV export
curl -H "Authorization: Bearer qs_your_key_here" \
  "https://quietstats.co.uk/api/v1/breakdown?site=YOUR_SITE_ID&property=page&limit=500&format=csv" \
  -o top-pages.csv

Sample responses

GET /aggregate

json
{
  "site": "YOUR_SITE_ID",
  "period": { "from": "2026-09-21", "to": "2026-10-20", "days": 30, "timezone": "UTC" },
  "compare": { "previous_from": "2026-08-22", "previous_to": "2026-09-20" },
  "filters": [],
  "results": {
    "visitors":        { "value": 14210, "previous": 12133, "change": 0.171 },
    "pageviews":       { "value": 34870, "previous": 29512, "change": 0.182 },
    "visits":          { "value": 16304, "previous": 13811, "change": 0.181 },
    "views_per_visit": { "value": 2.139, "previous": 2.137, "change": 0.001 },
    "bounce_rate":     { "value": 0.418, "previous": 0.42,  "change": -0.005 },
    "visit_duration":  { "value": 102,   "previous": 100,   "change": 0.02 }
  }
}

GET /breakdown?property=channel

json
{
  "site": "YOUR_SITE_ID",
  "property": "channel",
  "pagination": { "page": 1, "limit": 100, "total": 6, "has_more": false },
  "results": [
    { "value": "Direct", "visitors": 5702, "pageviews": 13960 },
    { "value": "Search", "visitors": 4980, "pageviews": 12140 },
    { "value": "Social", "visitors": 1890, "pageviews": 4410 },
    { "value": "AI",     "visitors": 640,  "pageviews": 1720 }
  ]
}

Parameters

ParameterMeaning
siteSite ID from your Sites page (required except /sites).
periodtoday · 7d · 30d (default) · 90d · 12mo · all (all time) · custom
from, toYYYY-MM-DD (UTC, inclusive) with period=custom. Up to 400 days; use period=all for everything.
filterskey==value or key!=value, joined with ;. Use | for OR. Keys: page, referrer, country, device, browser, os, screen, utm_source, utm_medium, utm_campaign. referrer==(direct) = no referrer.
intervalhour (up to 31 days) · day · week (Mon start) · month
metricsvisitors, pageviews, visits, views_per_visit, bounce_rate, visit_duration
propertypage, entry_page, exit_page, referrer, channel, ai, utm_source, utm_medium, utm_campaign, country, device, browser, os, screen
limit, page, offsetPagination for /breakdown. limit 1–1000 (default 100); page from 1, or offset from 0. The response has pagination.total and has_more, so you can walk the full list.
searchCase-insensitive text match on the value for /breakdown (country also matches names, e.g. united → GB, US).
sortFor /breakdown: visitors (default) or pageviews; for entry_page/exit_page: visits (default) or visitors.
comparetrue (default) or false
formatjson (default) or csv

Fields

FieldMeaning
visitorsUnique visitors. Counted with a daily-rotating hash, so multi-day totals are the sum of daily uniques.
pageviewsPage loads and single-page-app route changes.
visitsPageviews from one visitor no more than 30 minutes apart.
views_per_visitpageviews ÷ visits
bounce_rateShare (0–1) of visits with one pageview.
visit_durationAverage seconds from first to last pageview in a visit.
changeFractional change against the previous period of the same length (0.171 = +17.1%).

All dates and hours are UTC. Rows with no data are returned as zeros, so charts have no gaps.

Rate limits

PlanRequests per hour, per key
Free300
Starter2,000
Growth6,000
Business20,000

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Up to 10 active keys per account.

Errors

StatusCodeWhat it means
400invalid_param / missing_paramA parameter is missing or wrong. The message says which.
401unauthorized / invalid_keyNo key, a malformed key, or a revoked key.
403forbiddenThe key is limited to a different site.
404site_not_found / not_foundThe site is not on your account, or the endpoint does not exist.
405method_not_allowedThe API is read-only: use GET.
429rate_limitedHourly limit reached. Wait for Retry-After seconds.
json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit of 300 requests per hour reached for your plan.",
    "status": 429,
    "docs": "/api#errors"
  }
}

Ideas: what people build

📊 Google Sheets

Paste the Apps Script below, add your key under Project Settings → Script properties as QUIETSTATS_KEY, and set a daily trigger.

📈 Looker Studio

Point Looker Studio’s Google Sheets connector at that sheet for scheduled reports and client dashboards.

🖥️ Your own dashboard

Call /aggregate and /timeseries from your server and draw your own charts. CORS is open, but keep keys server-side.

🤖 Scripts and bots

Post yesterday’s numbers to Slack or Discord, or check /realtime during a launch.

Google Apps Script
// Google Sheets → Extensions → Apps Script. Run daily with a time-driven trigger.
function quietstatsDaily() {
  const key = PropertiesService.getScriptProperties().getProperty('QUIETSTATS_KEY');
  const url = 'https://quietstats.co.uk/api/v1/timeseries?site=YOUR_SITE_ID&period=90d&interval=day&format=csv';
  const res = UrlFetchApp.fetch(url, { headers: { Authorization: 'Bearer ' + key } });
  const rows = Utilities.parseCsv(res.getContentText());
  const sheet = SpreadsheetApp.getActive().getSheetByName('quietstats') || SpreadsheetApp.getActive().insertSheet('quietstats');
  sheet.clearContents().getRange(1, 1, rows.length, rows[0].length).setValues(rows);
}

Privacy stays the same

The API only returns aggregate numbers: totals, time buckets and top values. It never returns visitor hashes, IP addresses, user agents or single visits, because we don’t store the first three and don’t expose the last. Keys are read-only and can be limited to one site.

Your numbers, wherever you need them

The API is included on every plan, including Free.

Start free