Public read API
Read-only endpoints for browsing the ESPGeiger station registry and its time-series data.
https://stations.espgeiger.com
JSON (default). Some endpoints also support
?format=csv.
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.
| Param | Default | Description |
|---|---|---|
| bbox | - | minLat,minLon,maxLat,maxLon, bounding-box filter |
| since | 3600 | Seconds since last post. Min 60, max 86400. |
| limit | 5000 | Max 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"}
]
}
GET /api/1/stations/search
Search by id (#42), espid substring, or friendly name
(full or substring).
| Param | Default | Description |
|---|---|---|
| q | - | Required. Max 64 chars. |
| limit | 10 | Max 50. |
| include_retired | 0 | Set 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.
| Param | Default | Description |
|---|---|---|
| from | now-3600 | Unix seconds. |
| to | now | Unix seconds. Range capped at 5 years. |
| buckets | 200 | Max 2000. |
| format | json | json 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.
| Param | Default | Description |
|---|---|---|
| metrics | cpm | Colon-separated names, e.g. cpm:usv:hv. |
| results | 100 | Rows per metric. Max 1000. |
| source | raw | raw for individual posts; lt for 15-minute averages. |
| format | json | json 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.
| Param | Default | Description |
|---|---|---|
| format | json | json 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).
| Param | Default | Description |
|---|---|---|
| days | 7 | 1–30. |
| window | 14400 | Bin 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.
| Param | Default | Description |
|---|---|---|
| threshold | configured warning band | Float ≥ 0. |
| limit | 20 | Max 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:
400: bad query parameters (malformed bbox, unknown metric, etc.)404: station not found or retired
Notes
- Multi-value query lists use a colon separator
(
?metrics=cpm:usv:hv), not comma. - All numeric values are rounded to 2 decimal places server-side.
- Responses include
Cache-Controlheaders (30 s for live data, 60 s for series, 300 s for census and history). - Source code and issue tracker: github.com/steadramon/ESPGeiger.