Public read API

Read-only endpoints for browsing the ESPGeiger station registry and its time-series data.

Base URL
https://stations.espgeiger.com
Formats
JSON (default). Some endpoints also support ?format=csv.
Station IDs. A station id is a stable integer up to 8 digits long (e.g. 1234567). Friendly names (fermi-counts-gamma) appear in search results and on station pages, but these endpoints only accept the numeric id.

Endpoints: stations search station history metrics feed last noise census outliers

Stations

GET /api/1/stations

List recently-active stations, optionally filtered by bounding box. Keyset pagination via cursor.

ParamDefaultDescription
bbox-minLat,minLon,maxLat,maxLon, bounding-box filter
since3600Seconds since last post. Min 60, max 86400.
limit5000Max 10000 per page.
cursor-Pass next_cursor from a prior response.
curl 'https://stations.espgeiger.com/api/1/stations?bbox=49,-8,61,2&since=3600&limit=200'
{
  "count": 14,
  "since": 3600,
  "bbox": {"minLat":49,"minLon":-8,"maxLat":61,"maxLon":2},
  "next_cursor": null,
  "stations": [
    {"id":1234567,"espid":"abcd1234","lat":51.521,"lon":-0.085,"mode":2,
     "last_post_at":1717000000,"board":"d1mini","model":"GMC-320","country_iso":"GB"}
  ]
}

Search by id (#42), espid substring, or friendly name (full or substring).

ParamDefaultDescription
q-Required. Max 64 chars.
limit10Max 50.
include_retired0Set 1 to include retired stations.
curl 'https://stations.espgeiger.com/api/1/stations/search?q=fermi'
[
  {"id":1234567,"name":"fermi-counts-gamma","espid":"abcd1234","active":true,"last_post_at":1717000000}
]

GET /api/1/station/{id}

Station metadata, live readings (most recent value per metric, averaged over 5 minutes), and a 24-hour reachability fraction.

curl 'https://stations.espgeiger.com/api/1/station/1234567'
{
  "id": 1234567,
  "espid": "abcd1234",
  "lat": 51.521, "lon": -0.085,
  "country_iso": "GB", "country_name": "United Kingdom",
  "board": "d1mini", "model": "GMC-320", "version": "1.7.6",
  "mode": 2,
  "first_seen": 1690000000,
  "last_handshake_at": 1716999990,
  "last_post_at": 1717000000,
  "post_count": 142819,
  "reset_reason": {"code": 6, "label": "External pin"},
  "tube": {"reported":"M4011","known":true,"canonical":"M4011 (J321)"},
  "live": {"cpm": 18.4, "usv": 0.12, "hv": 405},
  "reachable_24h": 0.9994
}

GET /api/1/station/{id}/history

Metadata changes over time: firmware version, board, reset reason, etc. Most recent first, capped at 50 rows.

curl 'https://stations.espgeiger.com/api/1/station/1234567/history'
{
  "id": 1234567,
  "count": 6,
  "history": [
    {"dt":1716950000,"first_dt":1716940000,"key":"version","value":"1.7.6","display":"1.7.6"},
    {"dt":1716700000,"first_dt":1716700000,"key":"reset_reason","value":"6","display":"External pin"}
  ]
}

Metrics

GET /api/1/station/{id}/metrics/{name(s)}

Bucketed time-series. Multiple metrics per call via colon separator. The server picks raw posts or 15-minute averages based on the window and bucket size; the source field on each metric tells you which.

ParamDefaultDescription
fromnow-3600Unix seconds.
tonowUnix seconds. Range capped at 5 years.
buckets200Max 2000.
formatjsonjson or csv.
curl 'https://stations.espgeiger.com/api/1/station/1234567/metrics/cpm:usv?from=1717000000&to=1717003600&buckets=60'
{
  "station_id": 1234567,
  "from": 1717000000, "to": 1717003600, "buckets": 60,
  "metrics": {
    "cpm": {
      "source": "raw", "bucket_seconds": 60, "count": 60,
      "columns": ["dt","count","avg","min","max"],
      "data": [[1717000000, ...], [1, ...], [18.4, ...], [16, ...], [22, ...]],
      "latest": 19.0
    }
  }
}

CSV is long-format: dt,metric,count,avg,min,max, one row per bucket per metric.

GET /api/1/station/{id}/feed

The last N samples per metric, newest first.

ParamDefaultDescription
metricscpmColon-separated names, e.g. cpm:usv:hv.
results100Rows per metric. Max 1000.
sourcerawraw for individual posts; lt for 15-minute averages.
formatjsonjson or csv.
curl 'https://stations.espgeiger.com/api/1/station/1234567/feed?metrics=cpm&results=10'
{
  "station_id": 1234567,
  "results": 10,
  "source": "raw",
  "metrics": {
    "cpm": {
      "source": "raw", "count": 10,
      "columns": ["dt","value"],
      "data": [[1717003540, 1717003480, ...], [19, 17, ...]]
    }
  }
}

GET /api/1/station/{id}/last

Most-recent value per metric, with its timestamp. Returned regardless of age; check dt to judge staleness. Metrics never recorded are omitted.

ParamDefaultDescription
formatjsonjson or csv.
curl 'https://stations.espgeiger.com/api/1/station/1234567/last'
{
  "station_id": 1234567,
  "metrics": {
    "cpm": {"dt": 1717003540, "value": 18.4},
    "usv": {"dt": 1717003540, "value": 0.12},
    "hv":  {"dt": 1717003480, "value": 405}
  }
}

GET /api/1/station/{id}/noise

Median absolute deviation of cpm, binned over the requested window. Highlights persistent electronic noise (HV ripple, EMI, ageing tube) while ignoring sustained natural shifts (radon washouts, weather).

ParamDefaultDescription
days71–30.
window14400Bin width in seconds. Min 900, max 21600.
curl 'https://stations.espgeiger.com/api/1/station/1234567/noise?days=7'
{
  "station_id": 1234567, "metric": "cpm",
  "days": 7, "window_seconds": 14400, "count": 42,
  "columns": ["dt","mad"],
  "data": [[1716950000, ...], [0.92, ...]]
}

Census

GET /api/1/census

Fleet distributions: recently-active stations grouped by country, board, model, firmware version, and feature flags. Plus live, 28-day, and all-time totals.

curl 'https://stations.espgeiger.com/api/1/census'
{
  "window_seconds": 2419200, "live_window_seconds": 86400,
  "total_live": 487, "total_active": 612, "total_with_geo": 598,
  "all_time": {"stations_ever": 1083, "stations_retired": 471, "handshakes_ever": 18432005, "posts_ever": 921846003},
  "by_country": [{"iso":"DE","name":"Germany","count":92}, ...],
  "by_board":   [{"name":"d1mini","count":318}, ...],
  "by_model":   [...],
  "by_firmware":[...],
  "by_feature": [...]
}

GET /api/1/census/outliers

Stations whose departure score from baseline is above threshold, newest score first.

ParamDefaultDescription
thresholdconfigured warning bandFloat ≥ 0.
limit20Max 100.
curl 'https://stations.espgeiger.com/api/1/census/outliers?threshold=10&limit=5'
{
  "threshold": 10,
  "computed_at": 1717003500,
  "stations": [
    {"id":1234567,"name":"fermi-counts-gamma","baseline":18.2,"observed":42.7,
     "direction":"up","score":13.4,"board":"d1mini","model":"GMC-320","last_post_at":1717003480}
  ]
}

Errors

Errors return a JSON body with error: true and a short message. Status codes used:

Notes