API reference

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.