Visitor statistics
Page views counted on the server per website (without cookies; method described in the manual).
GET /stats?root_id={id}&from=JJJJ-MM-TT&to=JJJJ-MM-TT 🔑
Without from/to the last 30 days, without root_id the website for the domain of the request; root_id=0 or all combines all websites (pages is then separated per website, with root_id). Freshly counted views are written first.
{
"views": 2475, "visitors": 825, "first_day": "2026-09-07", "last_day": "2026-10-06", "has_data": true,
"days": [{ "day": "2026-10-06", "views": 36, "visitors": 12 }],
"pages": [{ "root_id": 1, "name": "/", "views": 1642, "visitors": 825 }],
"dims": {
"referrer": [{ "name": "google.com", "visitors": 6 }, { "name": "(direkt)", "visitors": 3 }],
"browser": [{ "name": "Chrome", "visitors": 6 }], "os": [{ "name": "Windows", "visitors": 3 }], "device": [{ "name": "Desktop", "visitors": 6 }],
"country": [{ "name": "DE", "visitors": 5 }, { "name": "(unbekannt)", "visitors": 1 }]
},
"downloads": [{ "root_id": 1, "name": "/media/downloads/preisliste.pdf", "views": 28, "visitors": 25 }],
"downloads_total": 44
}
visitors is the sum of unique visitors per day; pages and each list in dims have at most 50 entries – except country (complete, for the map). country contains ISO 3166 country codes (country of registration of the IP, see Country data), (unbekannt) e.g. for private addresses; empty as long as no country data is loaded or it is disabled.
downloads: file downloads per path, most frequent first (at most 50; views = downloads, visitors = unique visitors per file and day, summed up), downloads_total the sum of all. Counted are successful requests (200, 206, 304) under /media/… except images, fonts, CSS/JS/JSON, embedded requests (Sec-Fetch-Dest image, font, style, script, audio, video, embed, object, iframe …) and follow-up partial requests (Range not starting at byte 0); bots, prefetch and logged-in admins as for pages. Website = domain of the request. Downloads don’t count towards views/visitors/days.
GET /stats/export?root_id={id}&from=JJJJ-MM-TT&to=JJJJ-MM-TT 🔑
All daily values of the period as CSV (UTF-8 with BOM, semicolon delimiter) – parameters as for GET /stats. Columns Datum;Website;Art;Name;Aufrufe;Besucher, one row per day, website and
| Art | Name | Aufrufe |
|---|---|---|
Gesamt |
empty | yes |
Seite |
path | yes |
Herkunft, Browser, Betriebssystem, Gerät, Land |
value (country as ISO code) | empty – dimensions only count visitors |
Download |
path of the file (/media/…) |
number of downloads |
Sorted by date, website, type and views or visitors. Besucher are unique visitors of the day – summed over several days, a returning visitor counts several times (like visitors in GET /stats).
DELETE /stats?root_id={id} ✏️
Deletes the statistics of the website.
Country data
For the country dimension, the server maps the IP to a country when counting – based on the allocation lists of the five RIRs (delegated-*-extended-latest from AFRINIC, APNIC, ARIN, LACNIC, RIPE NCC), stored locally in the database, not in bundles or backups. The nightly job syncs them weekly (from 3 a.m., with ETag/If-Modified-Since; after an error, the following night); without data, right after startup and then hourly. If a list fails, its previous data is kept.
GET /geo 🔑
{
"enabled": true, "running": false, "ranges": 212243, "updated_at": 1791354517, "checked_at": 1791354548,
"sources": [{ "rir": "ripencc", "ranges": 106330, "updated_at": 1791354517, "checked_at": 1791354548, "error": "" }]
}
ranges = merged address ranges in memory; updated_at/checked_at (Unix seconds) = data last replaced or last checked.
PUT /geo ✏️
{ "enabled": false } disables countries (no recording, no sync; saved), true enables them again. Response as for GET /geo.
POST /geo/update ✏️
Starts the sync in the background – 202 with the state (running: true), 409 if one is already running. Query progress via GET /geo.
POST /geo/upload ✏️
For servers without internet access: multipart/form-data with one or more file fields (the allocation lists, gzip-compressed is fine too). The registry is taken from the header line of the file. Response as for GET /geo, 400 for an unknown file.