Every RockRecorder server has a small JSON API for monitoring and for connecting it to
other systems. It runs on your own recorder, at https://<your recorder>/api/….
Endpoints
| Endpoint | What it returns | Key |
|---|---|---|
GET /api/health | Up or down, for uptime checks and load balancers | No |
GET /api/health/details | Every health check, for monitoring systems | Yes |
GET /api/statistics | Call statistics for a period, as on the Statistics page | Yes |
GET /api/alias-cache | The radio IDs heard on MRadio channels and their aliases | Yes |
GET /api/mradios/emergencies | MRadio emergencies and their recordings | Yes |
Getting started
- On the recorder, sign in as a SuperAdmin and open Administration > API Access.
- Turn on API access. A key is created the first time; copy it from the API key card.
- Send the key in the
X-API-Keyheader:
curl -H "X-API-Key: <your key>" https://recorder.example.org/api/health/details
The recorder makes its own HTTPS certificate unless you install one. If your client doesn't trust it yet,
install the recorder's certificate on the client, or install a certificate from your own CA on the
recorder (Administration > Certificate). For a quick test, curl -k skips the check.
Authentication
- Each recorder has one API key. Regenerate key on the API Access page replaces it at once: update every system that uses the old one.
- API access is off until a SuperAdmin turns it on. Turning it off stops the key working, and turning it back on brings the same key back.
- A key can read every channel on the recorder, whatever the users' groups.
- Signed in to the recorder as a SuperAdmin, you can open the endpoints in your browser without the key
to try them. Other users get
403. /api/healthneeds no key and shows nothing but up or down.
Times
Times in responses are UTC, in ISO 8601: 2026-10-05T12:57:33.574Z.
Times you send can be a date (2026-10-05) or a date and time
(2026-10-05 14:30, or 2026-10-05T14:30), with a time zone if you like
(Z, -04:00). Without one they're read in the recorder's own time zone. An end
given as a date includes that whole day.
Errors
| Status | Meaning |
|---|---|
400 | A parameter isn't valid; error says which. |
401 | The key is wrong, or API access is off on the recorder. |
403 | Signed in to the recorder, but not as a SuperAdmin. |
500 | The recorder couldn't read its database. |
503 | /api/health only: the recorder is down. |
{"detail": "Invalid API key or API access disabled"}
Endpoints
GET/api/health
No key
Whether the recorder's web app is answering and can reach its database. Nothing else is shown, so it's safe to leave open to uptime checks and load balancers.
| Status | Body |
|---|---|
200 | {"status": "ok"} |
503 | {"status": "down"} |
GET/api/health/details
Every health check, an overall status and the problems in words: what a monitoring system needs. It
answers 200 whenever it can, so read status_code: 0 ok,
1 warning, 2 critical. The checks are refreshed every 30 seconds.
| Check | Warning | Critical |
|---|---|---|
database | Can't be reached | |
services | A recorder service isn't running | |
storage | Recordings disk over 85% full | Over 95% full |
recordings | A recorder type with enabled channels has recorded nothing for 12 hours | |
cleanup | The nightly storage cleanup is overdue or had errors | |
certificate | The HTTPS certificate expires within 14 days | It has expired |
license | The license expires within 30 days | It's missing or invalid |
email | Scheduled reports or emergency alerts can't be sent |
{
"status": "warning",
"status_code": 1,
"problems": ["Recordings disk 86.2% full"],
"version": "2.9.14",
"site": "County Dispatch",
"hostname": "recorder",
"checked_at": "2026-10-14T15:59:19+00:00",
"checks": {
"database": {"status": "ok"},
"services": {"status": "ok", "units": {"rockrecorder-mradios.service": "active"}},
"storage": {"status": "warning", "path": "/opt/rockrecorder/recordings",
"used_percent": 86.2, "free_bytes": 4650975232, "total_bytes": 32626225152},
"recordings": {"status": "ok", "quiet_hours": 12.0,
"types": {"MRadio": {"status": "ok", "channels": 16,
"last": "2026-10-14T15:57:35+00:00", "hours_ago": 0.0}}},
"cleanup": {"status": "ok", "last_run": "2026-10-14T06:00:12+00:00", "hours_ago": 10.0,
"ok": true, "errors": []},
"certificate": {"status": "ok", "expires": "August 5 2028", "days_left": 660},
"license": {"status": "ok", "expires": null, "days_remaining": null},
"email": {"status": "ok", "in_use": true, "reports": 1, "alerts": 1, "configured": true}
}
}
GET/api/statistics
The figures from the recorder's Statistics page for whole days: totals, calls per day and per weekday and hour, each channel, the top talkers, phone numbers and call-taker positions, and the MPhone call figures. For dashboards (Grafana, Power BI) and your own reports.
| Parameter | Default | Description |
|---|---|---|
start | 7 days before end | First day, YYYY-MM-DD |
end | Yesterday | Last day, YYYY-MM-DD; up to 366 days from start |
channel | All | Only these channels, comma-separated |
type | All | Only these recorder types, comma-separated: MRadio,
MPhone, ETC, Generic RTP, Cisco BiB, Solacom |
tz | The recorder's | Time zone for days and hours, e.g. America/New_York |
top | 10 | How many talkers, phone numbers and positions to list, 1 to 100 |
summary: calls, totaltalk_timeandavg_durationin seconds, uniquetalkers, channels with calls, and thebusiest_hour.by_day: every day of the period, including days without calls.by_weekday_hour: calls per hour (0 to 23) for each weekday, added up over the period.by_channel: each channel with calls, busiest first.talkers: the busiest radio talkers (phone lines are underphone_numbers).phone_numbersandpositions: the most frequent callers and called numbers on phone lines, and calls per Solacom call-taker position.mphone_calls: answered, abandoned, ring and talk times for Mindshare console phone lines, when the console parser is on; otherwisenull.
curl -H "X-API-Key: <your key>" \ "https://recorder.example.org/api/statistics?start=2026-10-01&end=2026-10-07&type=MRadio&top=2"
{
"success": true,
"start": "2026-10-01",
"end": "2026-10-07",
"time_zone": "America/New_York",
"channels": null,
"types": ["MRadio"],
"summary": {
"calls": 16292,
"talk_time": 52921.8,
"avg_duration": 3.25,
"talkers": 363,
"channels": 12,
"busiest_hour": {"weekday": "Sat", "hour": 14, "calls": 420}
},
"by_day": [
{"date": "2026-10-01", "calls": 2386},
{"date": "2026-10-02", "calls": 2303}
],
"by_weekday_hour": {
"Mon": [9, 26, 42, 67, 58, 4, 52, 73, 125, 123, 135, 88, 89, 180, 158, 127, 135, 126, 164, 140, 92, 129, 143, 64]
},
"by_channel": [
{"channel": "Fire", "type": "MRadio", "calls": 8901, "talk_time": 29276.9, "avg_duration": 3.29, "share": 54.6}
],
"talkers": [
{"name": "Engine 12", "radio_ids": ["24271"], "calls": 1496, "avg_duration": 3.21, "share": 9.2,
"main_channel": "Fire"}
],
"phone_numbers": [],
"positions": [],
"mphone_calls": null
}
GET/api/alias-cache
The radio recorder's learned aliases: every radio ID heard on the MRadio channels and the alias it last
transmitted with (the most recent 1,000 IDs). enabled says whether the recorder uses the
cache to fill in missing aliases.
{
"success": true,
"enabled": true,
"count": 2,
"aliases": [
{"uid": "24271", "alias": "Engine 12", "updated_at": "2026-10-01T18:46:25.000Z"},
{"uid": "24277", "alias": "Medic 4", "updated_at": "2026-10-01T17:30:19.000Z"}
]
}
GET/api/mradios/emergencies
MRadio emergencies, newest first, built from the transmissions the radio flags as in emergency. An
emergency is a run of flagged transmissions on one channel with the same event number. It's
active until it ends, and ended_by says how:
transmission: the next transmission on the channel wasn't in emergency.new_emergency: a flagged transmission with a different event number started a new one.timeout: the radio didn't key up again for an hour.
| Parameter | Default | Description |
|---|---|---|
start | 7 days ago (30 with active_only) | From this time (see Times) |
end | Now | Up to this time |
channel | All | Only these channels, comma-separated |
active_only | false | Only emergencies that haven't ended |
include_recordings | true | List each emergency's transmissions |
limit | 100 | Most emergencies to return, 1 to 1,000 |
curl -H "X-API-Key: <your key>" \ "https://recorder.example.org/api/mradios/emergencies?start=2026-10-01&channel=Fire"
{
"success": true,
"start": "2026-10-01T04:00:00.000Z",
"end": "2026-10-14T15:59:19.430Z",
"count": 1,
"emergencies": [
{
"channel": "Fire",
"event_number": 7,
"source_id": "24271",
"alias": "Engine 12",
"status": "ended",
"started_at": "2026-10-13T15:04:28.327Z",
"last_transmission_at": "2026-10-13T15:05:57.561Z",
"ended_at": "2026-10-13T15:06:12.520Z",
"ended_by": "transmission",
"latitude": 34.9249,
"longitude": -81.0251,
"transmissions": 1,
"recordings": [
{
"id": 159271,
"timestamp": "2026-10-13T15:04:28.327Z",
"alias": "Engine 12",
"uid": "24271",
"duration": 1.12,
"latitude": 34.9249,
"longitude": -81.0251,
"url": "/recordings/Fire/2026/10/13/Fire_Engine_12_rx_20261013_110428_327.wav"
}
]
}
]
}
A recording's url is on the recorder and needs a signed-in user to play.
latitude and longitude are the last position the radio sent, or
null.