API

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

EndpointWhat it returnsKey
GET /api/healthUp or down, for uptime checks and load balancersNo
GET /api/health/detailsEvery health check, for monitoring systemsYes
GET /api/statisticsCall statistics for a period, as on the Statistics pageYes
GET /api/alias-cacheThe radio IDs heard on MRadio channels and their aliasesYes
GET /api/mradios/emergenciesMRadio emergencies and their recordingsYes

Getting started

  1. On the recorder, sign in as a SuperAdmin and open Administration > API Access.
  2. Turn on API access. A key is created the first time; copy it from the API key card.
  3. Send the key in the X-API-Key header:
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/health needs 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

StatusMeaning
400A parameter isn't valid; error says which.
401The key is wrong, or API access is off on the recorder.
403Signed in to the recorder, but not as a SuperAdmin.
500The 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.

StatusBody
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.

CheckWarningCritical
databaseCan't be reached
servicesA recorder service isn't running
storageRecordings disk over 85% fullOver 95% full
recordingsA recorder type with enabled channels has recorded nothing for 12 hours
cleanupThe nightly storage cleanup is overdue or had errors
certificateThe HTTPS certificate expires within 14 daysIt has expired
licenseThe license expires within 30 daysIt's missing or invalid
emailScheduled reports or emergency alerts can't be sent
Example response (shortened)
{
  "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.

ParameterDefaultDescription
start7 days before endFirst day, YYYY-MM-DD
endYesterdayLast day, YYYY-MM-DD; up to 366 days from start
channelAllOnly these channels, comma-separated
typeAllOnly these recorder types, comma-separated: MRadio, MPhone, ETC, Generic RTP, Cisco BiB, Solacom
tzThe recorder'sTime zone for days and hours, e.g. America/New_York
top10How many talkers, phone numbers and positions to list, 1 to 100
  • summary: calls, total talk_time and avg_duration in seconds, unique talkers, channels with calls, and the busiest_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 under phone_numbers).
  • phone_numbers and positions: 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; otherwise null.
Example request
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"
Example response (shortened)
{
  "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.

Example response
{
  "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.
ParameterDefaultDescription
start7 days ago (30 with active_only)From this time (see Times)
endNowUp to this time
channelAllOnly these channels, comma-separated
active_onlyfalseOnly emergencies that haven't ended
include_recordingstrueList each emergency's transmissions
limit100Most emergencies to return, 1 to 1,000
Example request
curl -H "X-API-Key: <your key>" \
  "https://recorder.example.org/api/mradios/emergencies?start=2026-10-01&channel=Fire"
Example response
{
  "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.