Skip to content

Station Manager API

foonerd edited this page Oct 6, 2026 · 7 revisions

Station Manager API

The Station Manager is the web interface of the FM/DAB Radio plugin (rtlsdr_radio) for Volumio. The plugin serves the page, and the HTTP API the page uses, from the player itself. This page describes every endpoint of that API, for those who want to drive the plugin from a script or another program. The page itself is described under Station Manager.

  • Base URL: http://<player>:3456, for example http://192.168.1.50:3456.
  • Authentication: none. Requests are not authenticated and their origin is not checked. The API is for use inside the home network.
  • Request bodies: JSON, sent with Content-Type: application/json. File uploads use multipart/form-data with the file in a field named file.
  • Responses: JSON, except where an endpoint says otherwise: the page, pictures, CSV and zip downloads, and two event streams.
  • Times: ISO 8601 in UTC, for example 2026-01-15T09:30:00.000Z.
curl http://192.168.1.50:3456/api/status

Endpoints that change something answer { "success": true }, with further members where stated.

Errors

A request that fails is answered with a status of 400 or above and a JSON body that has an error member. The form of that member depends on the area.

Area Error body
Most endpoints { "error": "<message>" }. The message is plain text. Where it is the text of an underlying error it may begin with Error:.
Saving the station list { "error": "Invalid station data", "errors": ["<problem>", ...] } when stations lack required fields.
CSV import { "error": "Validation failed", "errors": [{ "line": 3, "message": "<problem>" }] }. A list that cannot be saved gives { "success": false, "error": "<message>", "type": "...", "operation": "..." }.
Logos { "error": "<code>" }, where the code is a short word such as no-station or too-small. Some codes come with message or further members.
Updates { "error": { "code": "<code>", "message": "<text>" } }. On install and rollback the body is the update view with this error added.
Dongle report As for updates: { "error": { "code": "<code>", "message": "<text>" } }, on start added to the report view.
GET /logos/:file Status 404 with an empty body.
Event streams Before the stream starts, a JSON error body as above. Once it has started, problems are reported as events.
Status Used for
200 Success.
400 The request is refused: something required is missing or not usable.
404 What the request names does not exist: a station (logo endpoints), a backup (download) or a logo file.
409 Antenna tools: a later request for the tuner took this one's place. Updates: an install or rollback cannot be started.
413 An uploaded logo is larger than the limit.
500 A failure on the player.
502 A chosen logo could not be fetched from the broadcaster.

Endpoints

Area Method Path Purpose
Status GET /api/status What the plugin is doing, station counts, reception, scan progress
Stations GET /api/stations The station list
Stations POST /api/stations Replace the station list
Stations POST /api/stations/clear-fm Move all FM stations to the recycle bin
Stations POST /api/stations/clear-dab Move all DAB stations to the recycle bin
Stations POST /api/stations/purge Remove the stations in the recycle bin for good
Scanning POST /api/stations/scan-fm Start an FM scan
Scanning POST /api/stations/scan-dab Start a DAB scan
Tuning POST /api/tune/survey What a scan would say of the channels around an FM frequency
Tuning POST /api/tune/listen Play an FM frequency to be listened to
Tuning POST /api/tune/end Stop listening and put the player back
Tuning POST /api/tune/keep Give an FM station another frequency
CSV GET /api/csv/template/fm Download an FM template file
CSV GET /api/csv/template/dab Download a DAB template file
CSV GET /api/csv/export/fm Download the FM stations as CSV
CSV GET /api/csv/export/dab Download the DAB stations as CSV
CSV POST /api/csv/validate Check a CSV file without importing it
CSV POST /api/csv/import Import a CSV file into the station list
Logos GET /api/logos/status How many stations have a logo, and whether logos are being fetched
Logos POST /api/logos/refresh Fetch missing logos again and check kept ones for newer versions
Logos GET /api/logos/stations The logo each station is shown with
Logos GET /api/logos/library The logos a station's logo can be chosen from
Logos POST /api/logos/upload Give a station a picture of the user's own as its logo
Logos POST /api/logos/choose Give a station a logo from the library
Logos POST /api/logos/clear Remove the user's choice of logo for a station
Logos GET /logos/:file A logo picture
Backup GET /api/maintenance/backup/list The backups kept on the player
Backup POST /api/maintenance/backup/create Create a backup
Backup POST /api/maintenance/backup/restore Restore backups kept on the player
Backup DELETE /api/maintenance/backup/delete Delete a backup
Backup GET /api/maintenance/backup/download Download a backup as a zip file
Backup POST /api/maintenance/backup/upload Check an uploaded backup zip
Backup POST /api/maintenance/backup/upload-restore Restore from an uploaded backup zip
Maintenance GET /api/maintenance/settings Read the maintenance setting
Maintenance POST /api/maintenance/settings Change the maintenance setting
Antenna POST /api/antenna/spectrum-scan Measure power across DAB Band III
Antenna POST /api/antenna/validate-dab Check DAB channels for an ensemble and its services (event stream)
Antenna POST /api/antenna/snr-scan Measure signal-to-noise ratio of DAB channels across gain settings (event stream)
Song recognition GET /api/recognition Whether it is on, whether a token is set (its last four characters and its length), when it asks, what was recognised last
Song recognition GET /api/recognition/key The token itself
Song recognition POST /api/recognition Switch it on or off, set the token and when to ask
Updates GET /api/update Installed version, version on offer, progress of an update
Updates POST /api/update/check Look for a newer version now
Updates POST /api/update/channel Choose the update channel
Updates POST /api/update/install Install the version on offer
Updates POST /api/update/rollback Put back the version installed before the last update
Dongle report GET /api/dongle-report How far a report is, or what the last one found
Dongle report POST /api/dongle-report/start Make a report on the dongle that is plugged in
Dongle report POST /api/dongle-report/cancel Stop the report being made
Dongle report GET /api/dongle-report/summary The summary of the last report, as text
Dongle report GET /api/dongle-report/download The last report as a ZIP file
Translations GET /api/language The player's language
Translations GET /api/i18n/:lang The texts of the interface in one language
Block list GET /api/blocklist The artwork block list
Block list POST /api/blocklist Replace the artwork block list
Block list POST /api/blocklist/reset Put the default artwork block list back
Pages GET / The Station Manager page
Pages GET /manage The Station Manager page
Pages GET /icon The antenna icon

Status

GET /api/status

Reports what the plugin is doing with the tuner, how many stations it has, the reception of the station being played and the progress of a scan.

Request: no parameters.

Response: 200.

{
  "deviceState": "playing_fm",
  "fmStationsLoaded": 24,
  "dabStationsLoaded": 61,
  "dbLoadedAt": "2026-01-15T08:00:12.345Z",
  "dbVersion": 2,
  "serverPort": 3456,
  "fmScanDepth": "fast",
  "tune": null,
  "fmBand": { "start": 87.5, "end": 108, "step_khz": 100 },
  "signal": { "type": "fm", "level": 4, "percent": 72, "db": 33.4, "mono": false, "frequency": 94.9 },
  "scan": null,
  "timestamp": "2026-01-15T09:30:00.000Z"
}
Field Type Meaning
deviceState string idle, scanning_fm, scanning_dab, playing_fm or playing_dab.
fmStationsLoaded number FM stations in the list that are not in the recycle bin.
dabStationsLoaded number DAB stations in the list that are not in the recycle bin.
dbLoadedAt string or null When the station list was last loaded from disk.
dbVersion number Format version of the station list; 0 when the list carries none.
serverPort number The port of this API.
fmScanDepth string The kind of FM scan set: fast or detailed.
tune string or null The frequency POST /api/tune/listen is playing, in MHz; null when nothing is being tried.
fmBand object The FM band of the region set: start and end in MHz, step_khz the channel raster.
signal object or null Reception of the station being played; null when none is played or no reading is there yet. See below.
scan object or null { "type": "fm", "percent": 45 } while an FM scan runs, percent from 0 to 100. null at all other times; a DAB scan reports no progress here.
timestamp string When the answer was made.

While an FM station plays, signal is { "type": "fm", "level", "percent", "db", "mono", "frequency" }: level from 0 to 5, percent from 0 to 100, db the reading behind the level in dB (null until there is one), mono whether that reading is of the carrier of a station without a stereo pilot, frequency in MHz as a number.

While a DAB station plays, signal is { "type": "dab", "level", "percent", "station" }: level and percent as the DAB decoder reports them, and station either null or the station being played:

{
  "type": "dab",
  "level": 4,
  "percent": 80,
  "station": {
    "channel": "12B",
    "serviceName": "Example DAB     ",
    "exactName": "Example DAB     ",
    "stationTitle": "Example DAB",
    "uri": "rtlsdr://dab/12B/Example%20DAB%20%20%20%20%20"
  }
}

Errors: 500 { "error": "<message>" }.

Side effects: none.

Stations

The station list is one document with two arrays:

{
  "fm": [
    {
      "frequency": "94.9",
      "name": "Example FM",
      "customName": null,
      "favorite": false,
      "hidden": false,
      "deleted": false,
      "availableAgain": false,
      "notes": "",
      "playCount": 3,
      "lastPlayed": "2026-01-14T18:05:00.000Z",
      "dateAdded": "2026-01-10T10:00:00.000Z",
      "last_seen": "2026-01-10T10:00:00.000Z"
    }
  ],
  "dab": [
    {
      "channel": "12B",
      "serviceId": "c1d2",
      "exactName": "Example DAB     ",
      "name": "Example DAB",
      "ensemble": "Example Ensemble",
      "ensembleId": "c181",
      "customName": null,
      "favorite": true,
      "hidden": false,
      "deleted": false,
      "availableAgain": false,
      "notes": "",
      "playCount": 0,
      "lastPlayed": null,
      "dateAdded": "2026-01-10T10:05:00.000Z",
      "last_seen": "2026-01-10T10:05:00.000Z"
    }
  ]
}

Fields of every station:

Field Type Meaning
name string The station's name. For an FM station it is FM <frequency> after a scan, until a name is learnt from RDS or from the broadcaster's list.
customName string or null The user's own name for the station. When set it is shown in place of name.
favorite boolean Marked as a favourite.
hidden boolean Left out of the station lists shown on the player.
deleted boolean In the recycle bin: not listed on the player and not played.
availableAgain boolean Set by a scan that finds the station again while it is in the recycle bin.
notes string Free text.
playCount number How often the station has been played.
lastPlayed string or null When it was last played.
dateAdded string When it was added to the list.
last_seen string When a scan last found it.

Fields of an FM station:

Field Type Meaning
frequency string Frequency in MHz with one decimal, or two where needed, for example "94.9" or "94.95". Identifies the station.
quality number Strength of the stereo pilot in dB at the last scan; for a station without a pilot, how far a pilot would stand above its noise.
mono boolean The last scan found the station without a stereo pilot. Absent on a station no scan has found since version 1.4.6.
level number Tune level from 1 to 5 at the last scan.
pi string The RDS PI code, four hexadecimal digits, once it has been read.

Fields of a DAB station:

Field Type Meaning
channel string DAB channel, for example "12B".
serviceId string Identifier of the service within its ensemble.
exactName string The service name exactly as the scan reported it, trailing spaces included. Playback and logos use this name.
ensemble string Name of the ensemble.
ensembleId string Identifier of the ensemble.
bitrate, audioType as reported Bit rate in kbit/s and audio type as the scan reported them.

A scan matches DAB stations by channel and serviceId. The logo endpoints and the CSV import match them by channel and exactName. FM stations are always matched by frequency, compared as a number.

Stations may carry further fields. The API returns them as kept. Because saving replaces the whole list, a client sends back every field of every station it wants to keep.

GET /api/stations

Returns the station list, including hidden stations and those in the recycle bin.

Request: no parameters.

Response: 200, { "fm": [...], "dab": [...] } as shown above.

Errors: 500 { "error": "Failed to get stations" }.

Side effects: none.

POST /api/stations

Replaces the FM and the DAB list with the lists sent.

Request body:

Field Type Required Meaning
fm array yes The FM stations. Each needs frequency, and favorite and hidden as booleans.
dab array yes The DAB stations. Each needs channel, a non-empty serviceId, and favorite and hidden as booleans.

An FM frequency sent as a number is stored as a string with one decimal, or two where needed.

Response: 200.

{ "success": true }

Errors:

Status Body When
400 { "error": "Invalid data format" } fm or dab is not an array.
400 { "error": "Invalid station data", "errors": ["FM station 0 missing frequency", "DAB station 2 missing hidden flag"] } Stations lack required fields. The number is the station's position in its array, counted from 0.
500 { "error": "The station list could not be saved" } The list could not be written.
500 { "error": "Failed to process update" } Any other failure.

Side effects: the station list in use and on disk is replaced; stations left out of the request are gone. A request refused with 400 changes nothing.

POST /api/stations/clear-fm

Moves every FM station to the recycle bin by setting deleted on it.

Request: no body.

Response: 200. count is the number of stations moved by this request.

{ "success": true, "count": 24 }

Errors: 500 { "error": "The station list could not be saved" } or { "error": "Failed to clear FM stations" }.

Side effects: saves the station list. The stations stay in the list until purged.

POST /api/stations/clear-dab

Moves every DAB station to the recycle bin by setting deleted on it.

Request: no body.

Response: 200. count is the number of stations moved by this request.

{ "success": true, "count": 61 }

Errors: 500 { "error": "The station list could not be saved" } or { "error": "Failed to clear DAB stations" }.

Side effects: saves the station list. The stations stay in the list until purged.

POST /api/stations/purge

Removes every station that is in the recycle bin, FM and DAB, from the list.

Request: no body.

Response: 200.

{ "success": true }

Errors: 500 { "error": "The station list could not be saved" } or { "error": "Failed to purge stations" }.

Side effects: the stations are removed from the list for good and the list is saved.

Scanning

Both endpoints answer at once. The answer says that the request was taken, not that a scan is running or has finished. To follow a scan, poll GET /api/status: deviceState is scanning_fm or scanning_dab while it runs and leaves that state when it ends. Then read the result with GET /api/stations.

What happens to the request depends on what the plugin is doing:

  • When the plugin is idle, the scan takes the tuner and starts.
  • When the plugin is playing a station or already scanning, the scan does not start by itself. A dialog opens in the Volumio interface with three choices: stop the current activity and scan, queue the scan, or cancel the request. A queued scan starts when the tuner becomes free, provided that happens within 60 seconds of the request.

When a scan completes, its results are merged into the station list and the list is saved. New stations are added. Stations already in the list keep what the user has set on them: name, favourite, hidden, recycle bin, notes and play counts. Logos for the stations are then looked for in the background. Messages about the start, progress and result of a scan appear in the Volumio interface.

POST /api/stations/scan-fm

Starts a scan of the FM band of the region set in the plugin settings.

Request: no body, or { "depth": "fast" } or { "depth": "detailed" } to choose the kind of scan. The kind chosen is kept as the FM Scan setting; anything else is ignored and the scan runs as set.

Response: 200.

{ "success": true, "message": "FM scan started" }

Errors: 500 { "error": "Failed to start FM scan" }.

Side effects: takes the tuner as described above. Progress is reported under scan of GET /api/status. The survey of the band is stopped if it has not finished within ten minutes. Besides adding and updating stations, an FM scan removes a station it no longer finds when the scan covered its frequency and the station carries nothing of the user's, such as an own name, a favourite, hidden or recycle-bin mark, notes or plays.

POST /api/stations/scan-dab

Starts a scan of DAB Band III.

Request: no body.

Response: 200.

{ "success": true, "message": "DAB scan started" }

Errors: 500 { "error": "Failed to start DAB scan" }.

Side effects: takes the tuner as described above. The scan is stopped if it has not finished within 300 seconds. A DAB scan removes no stations.

Tuning an FM station

These four endpoints are what the tune dialog of the Station Manager works with.

POST /api/tune/survey

Looks at the channels around a frequency, each of them listened to, and tells what a scan would say of each.

Request:

Field Type Required Meaning
frequency string or number yes The frequency to look around, in MHz.
station string or number no The frequency of the station being tuned, where it is not frequency. It is left out of held and marked own.

Response: 200, when the look is done (a few seconds).

{
  "frequency": "96.7",
  "step_khz": 100,
  "gain": 49.6,
  "best": "96.6",
  "channels": [
    { "frequency": "96.6", "says": "stereo", "reading": 6.5, "level": 1, "mono": false, "offset": 687, "held": null, "own": false, "best": true },
    { "frequency": "96.7", "says": "no pilot", "reading": 1.7, "level": null, "mono": false, "offset": -3929, "held": null, "own": true, "best": false },
    { "frequency": "96.9", "says": "stereo", "reading": 8.5, "level": 1, "mono": false, "offset": -7470, "held": "Example FM", "own": false, "best": false }
  ]
}

channels holds the frequency asked for and five channels either side on the raster of the FM region, as far as the band goes.

Field Type Meaning
frequency string The channel, in MHz.
says string stereo or mono for a channel a scan would list. Otherwise why not: beside (the channel next to it shows the same station better), spill (what a far stronger station spills), neighbour (right beside a far stronger channel, and not listened to), no pilot, off channel, made in the tuner, not there again, mirror, or unread (the look did not reach it).
reading number or null What the channel is judged by, in dB: the stereo pilot over the noise, or for mono how clear the carrier is of it. null where the channel was not listened to.
level number or null The tune level from 1 to 5 of a channel a scan would list.
mono boolean Judged as a station without a stereo pilot.
offset number or null How far the carrier lies from the channel's centre, in Hz.
held string or null The name of another station of the list that holds this frequency.
own boolean The frequency of the station being tuned.
best boolean The channel best names.

best is the nearest channel to frequency that a scan would list and no other station of the list holds; of two equally near, the better received. null when there is none. gain is the tuner gain the look was made with, in dB.

Errors: 400 { "error": "Not a frequency of the band" }; 409 when the dongle was taken for something else before the look was done; 500 { "error": "<message>" } when the channels could not be read.

Side effects: a station the plugin is playing is stopped. The station list is not changed.

POST /api/tune/listen

Plays an FM frequency through Volumio's queue, as an item at its end. A second request plays the next frequency as the same item.

Request: { "frequency": "96.6" }.

Response: 200 { "success": true, "frequency": "96.6" }, when the player has been told to play. Reception follows under signal of GET /api/status, and tune there names the frequency.

Errors: 400 { "error": "Not a frequency of the band" }; 500 { "error": "<message>" }.

Side effects: whatever is playing is stopped. The play is not counted as a play of a station, and no station is added to the list.

POST /api/tune/end

Ends the listening: what listen played is stopped, its queue item is removed, and the player is put back on the item it was on before the first listen, playing it again if it was playing. What the user has started meanwhile is left alone.

Request: no body.

Response: 200 { "success": true, "ended": true }; ended is false when there was nothing to end.

Errors: 500 { "error": "<message>" }.

Side effects: as described.

POST /api/tune/keep

Gives the station at one frequency another frequency.

Request:

Field Type Required Meaning
from string or number yes The station's frequency in the list, in MHz.
to string or number yes The frequency it is to have.
keep string no Where another station of the list holds to: this keeps the station being tuned and removes the other from the list; other keeps the other and moves the station being tuned to the recycle bin.

Response: 200.

{ "success": true, "station": { "frequency": "96.6", "name": "FM 96.6", "customName": "Example FM" }, "removed": null }

station is the station that now holds the frequency, as in GET /api/stations. removed is the frequency of an entry that left the list, or null; with keep set to other the answer also carries "deleted": true and removed is from.

Errors: 400 { "error": "Not a frequency of the band" }; 404 { "error": "No station at <from> MHz" }; 409 { "conflict": { "frequency": "96.9", "name": "Example FM" } } when another station holds to and keep is not given, with nothing changed; 500 { "error": "<message>" }.

Side effects: the station list is saved. The station keeps its name, favourite and hidden marks, notes and play count; a name that was only its frequency follows the new one. Its logos are kept under the new frequency. Items of Volumio's queue that are this station are given the new frequency. Where POST /api/tune/survey read the new frequency in the last ten minutes, quality, level and mono are set from that reading.

CSV import and export

A CSV file holds either FM or DAB stations. Its first line names the columns:

Type Header line
FM frequency,name,customName,favorite,hidden,notes
DAB channel,exactName,name,customName,ensemble,serviceId,favorite,hidden,notes

How a file is read:

  • The type is taken from the header line: a header containing frequency is FM; one containing channel and exactName is DAB. Letter case does not matter.
  • Columns are matched by their header name, without regard to letter case.
  • A value may be enclosed in double quotes; a double quote inside it is written twice. A value does not span lines.
  • Spaces around a value are removed. Empty lines are skipped.
  • favorite and hidden are true for true, 1 or yes and false for anything else.
  • An FM row needs a frequency between the lower edge of the band of the region set in the plugin settings and 108.0 MHz. An empty name becomes FM <frequency>.
  • A DAB row needs a channel that is a DAB channel (5A to 13F, any letter case) and an exactName. An empty name becomes the exactName; an empty serviceId becomes 0.

GET /api/csv/template/fm

Downloads a template for FM stations: the FM header line and one example row.

Request: no parameters.

Response: 200, Content-Type: text/csv, offered as the file fm_template.csv.

frequency,name,customName,favorite,hidden,notes
94.9,Example FM,My Radio,false,false,Optional notes

Errors: none.

Side effects: none.

GET /api/csv/template/dab

Downloads a template for DAB stations: the DAB header line and one example row.

Request: no parameters.

Response: 200, Content-Type: text/csv, offered as the file dab_template.csv. The first line is:

channel,exactName,name,customName,ensemble,serviceId,favorite,hidden,notes

Errors: none.

Side effects: none.

GET /api/csv/export/fm

Downloads the FM stations as CSV. Stations in the recycle bin are left out.

Request: no parameters.

Response: 200, Content-Type: text/csv, offered as the file stations_fm_<time>.csv, for example stations_fm_2026-01-15T09-30-00.csv.

frequency,name,customName,favorite,hidden,notes
94.9,Example FM,,true,false,

Errors: 500 { "error": "<message>" }.

Side effects: none.

GET /api/csv/export/dab

Downloads the DAB stations as CSV. Stations in the recycle bin are left out.

Request: no parameters.

Response: 200, Content-Type: text/csv, offered as the file stations_dab_<time>.csv.

channel,exactName,name,customName,ensemble,serviceId,favorite,hidden,notes
12B,Example DAB     ,Example DAB,,Example Ensemble,c1d2,true,false,

Errors: 500 { "error": "<message>" }.

Side effects: none.

POST /api/csv/validate

Reads a CSV file and reports what an import would take from it. Nothing is imported.

Request: multipart/form-data.

Field Type Required Meaning
file file yes The CSV file, at most 1 MB.

Response: 200, also when the file has problems; valid tells.

{
  "valid": false,
  "type": "fm",
  "filename": "stations.csv",
  "totalRows": 2,
  "validCount": 1,
  "errors": [
    { "line": 3, "message": "Frequency 120 out of range (87.5-108)" }
  ],
  "stations": [
    { "frequency": "94.9", "name": "Example FM", "customName": "", "favorite": false, "hidden": false, "notes": "" }
  ]
}
Field Type Meaning
valid boolean True when no row has a problem.
type string fm or dab.
filename string The name of the uploaded file.
totalRows number Rows after the header line.
validCount number Rows that can be imported.
errors array One { "line", "message" } for each row that cannot. line counts the non-empty lines of the file; the header line is 1.
stations array The stations read. A DAB entry has channel, exactName, name, customName, ensemble, serviceId, favorite, hidden and notes.

A file without usable content gives only valid and errors: { "valid": false, "errors": [{ "line": 0, "message": "Empty file" }] }, or with line 1 the message Invalid headers - cannot detect FM or DAB format.

Errors:

Status Body When
400 { "error": "No file uploaded" } The request has no file.
400 { "error": "File too large (max 1MB)" } The file is larger than 1 MB.
500 { "error": "<message>" } Any other failure.

Side effects: none.

POST /api/csv/import

Imports a CSV file into the FM or the DAB list, whichever the file holds. A file with a problem in any row is refused as a whole.

Request: multipart/form-data.

Field Type Required Meaning
file file yes The CSV file.
operation string no extend (the default), amend, replace or remove.
Operation What it does
extend Adds the stations of the file that are not in the list. Stations already there are skipped.
amend Updates the stations of the file that are in the list: name, customName, favorite, hidden, notes and, for DAB, ensemble. A station in the recycle bin is taken out of it. Stations not in the list are skipped.
replace Replaces the list of that type with the stations of the file.
remove Moves the stations of the file that are in the list to the recycle bin. Stations not in the list are skipped.

Any other value of operation changes nothing; the counts in the answer are then all 0.

Response: 200.

{
  "success": true,
  "type": "fm",
  "operation": "extend",
  "imported": 2,
  "updated": 0,
  "removed": 0,
  "skipped": 1
}

Errors:

Status Body When
400 { "error": "No file uploaded" } The request has no file.
400 { "error": "Validation failed", "errors": [{ "line": 3, "message": "Missing exactName (required)" }] } The file has problems; errors as in POST /api/csv/validate.
500 { "success": false, "error": "The station list could not be saved", "type": "fm", "operation": "extend" } The list could not be written.
500 { "error": "<message>" } Any other failure.

Side effects: changes the station list and saves it. replace discards the stations of that type that are not in the file.

Logos

Station logos are fetched from the broadcasters in the background and kept on the player. The user can put a logo of their own choice in place of the one found.

The endpoints that act on one station name it with a station reference:

Station Reference
FM { "type": "fm", "frequency": "94.9" }
DAB { "type": "dab", "channel": "12B", "exactName": "Example DAB " }

channel and exactName are compared exactly as the station list has them, trailing spaces of exactName included.

A logo is described by an object with three members:

Field Type Meaning
url string or null Path of the picture on this server, for example /logos/fm-09490.png?v=mf3k2a1b; null when the station has no logo.
from string or null Where the logo comes from: user (chosen by the user), broadcaster (the station's own), pi or name (an FM station's, found by its PI code or by its name), group (the logo of the station's broadcaster), or null.
ref string or null A reference that goes with from. For user: the name of the uploaded file, the name of the listed service, or the name sent with a kept logo. For pi: the PI code. For name: the name the logo was found under. For group: the broadcaster's name. Otherwise null.

GET /api/logos/status

Reports how many stations have a logo and whether logos are being fetched.

Request: no parameters.

Response: 200.

{
  "state": "idle",
  "queued": 0,
  "stations": 85,
  "own": 60,
  "group": 15,
  "none": 10
}
Field Type Meaning
state string idle, fetching, or waiting when there is no network and the work waits to look again.
queued number Lookups still to be done.
stations number Stations, FM and DAB, not in the recycle bin.
own number Stations with a logo of their own, the user's choice included.
group number Stations shown with the logo of their broadcaster.
none number Stations without a logo.

Errors: none.

Side effects: none.

POST /api/logos/refresh

Asks again for the logo of every station that has none, then checks the logos kept for newer versions. The work runs in the background. Logos chosen by the user are not touched.

Request: no body.

Response: 200, the same object as GET /api/logos/status, as it stands once the work is queued.

Errors: none.

Side effects: fetches from the broadcasters' servers; logos kept on the player may be replaced by newer ones.

GET /api/logos/stations

Returns the logo each station of the list is shown with.

Request: no parameters.

Response: 200. Under fm the keys are the stations' frequencies; under dab they are the channel, a vertical bar and the exact name. Every station of the list is there, also those in the recycle bin.

{
  "fm": {
    "94.9": { "url": "/logos/fm-09490.png?v=mf3k2a1b", "from": "pi", "ref": "c201" }
  },
  "dab": {
    "12B|Example DAB     ": { "url": null, "from": null, "ref": null }
  }
}

Errors: none.

Side effects: none.

GET /api/logos/library

Returns what a station's logo can be chosen from: the services the broadcasters' lists name with a logo, and the logos kept on the player.

Query parameters:

Parameter Type Required Meaning
q string no Words to search for, at most 80 characters. An entry is returned when its name contains every word. Without q all entries qualify.
limit number no Most entries returned in each of the two lists, from 1 to 200. The default is 60.

Response: 200. Names that match the words sought as a whole come first, then names that begin with them, then the others; each group is in alphabetical order.

{
  "listed": [
    {
      "name": "Example FM",
      "url": "https://broadcaster.example/logos/example-fm-600.png",
      "small": "https://broadcaster.example/logos/example-fm-128.png"
    }
  ],
  "kept": [
    { "name": "Example DAB", "file": "dab-c181-c1d2.png", "url": "/logos/dab-c181-c1d2.png?v=mf3k2a1b" }
  ],
  "rules": { "bytes": 2097152, "svgBytes": 524288, "least": 128 }
}
Field Type Meaning
listed array Services named in the broadcasters' lists. url is the logo at the broadcaster; small is the picture to show among many.
kept array Logos kept on the player, each under the name of a station it is shown for. file is the file name; url is its path on this server.
rules object Limits for an uploaded picture: bytes the largest file, svgBytes the largest SVG file, least the smallest side in pixels.

Errors: none.

Side effects: none.

POST /api/logos/upload

Gives a station a picture of the user's own as its logo.

Request: multipart/form-data. The station is named by fields of the form, not by a nested object.

Field Type Required Meaning
file file yes The picture: PNG, JPEG or SVG. The kind is read from the content, not from the file name.
type string yes fm or dab.
frequency string for FM The station's frequency.
channel string for DAB The station's channel.
exactName string for DAB The station's exact name.

Limits: the file is at most 2,097,152 bytes, an SVG at most 524,288 bytes. A PNG or JPEG picture measures at least 128 and at most 4096 pixels on each side. An SVG that holds script, event handlers, embedded documents, entity declarations or references to anything outside itself is refused.

Response: 200. logo is the logo now shown for the station.

{
  "success": true,
  "logo": { "url": "/logos/user-fm-09490.png?v=mf3k2a1b", "from": "user", "ref": "example.png" }
}

Errors:

Status error Further members When
404 no-station The station named is not in the list.
400 not-a-picture No file was sent, the upload could not be read, or the file is neither PNG, JPEG nor SVG.
413 too-large limit The file is larger than 2,097,152 bytes.
400 too-large limit An SVG is larger than 524,288 bytes.
400 too-small width, height, least A side is shorter than least pixels.
400 too-many width, height, most A side is longer than most pixels.
400 unsafe what An SVG holds something it may not: what is script, outside or entity.
400 refused message The station has nothing a logo can be kept by.
500 failed message Any other failure.

When the picture itself is refused with status 400, the body also has "ok": false and reason, which repeats error:

{ "ok": false, "reason": "too-small", "width": 96, "height": 96, "least": 128, "error": "too-small" }

Side effects: the picture is kept on the player as the station's logo and takes the place of an earlier choice. It is shown from then on in place of any logo found for the station. If the station is playing, the player's display is updated.

POST /api/logos/choose

Gives a station a logo from the library: one a broadcaster's list names, or one kept on the player.

Request body:

Field Type Required Meaning
station object yes The station reference.
source string yes list or kept.
url string for list The url or small of an entry of listed in GET /api/logos/library.
file string for kept The file of an entry of kept in GET /api/logos/library.
name string no For kept: a name to note with the choice, at most 80 characters. It is returned as ref.
{
  "station": { "type": "fm", "frequency": "94.9" },
  "source": "list",
  "url": "https://broadcaster.example/logos/example-fm-600.png"
}

Response: 200. logo is the logo now shown for the station.

{
  "success": true,
  "logo": { "url": "/logos/user-fm-09490.png?v=mf3k2a1b", "from": "user", "ref": "Example FM" }
}

Errors:

Status Body When
404 { "error": "no-station" } The station named is not in the list.
400 { "error": "refused", "message": "<text>" } source is neither list nor kept; the url is not one the broadcasters' lists name; the file is not a logo kept on the player; or the station has nothing a logo can be kept by.
502 { "error": "not-a-picture", "message": "<text>" } What the broadcaster's server sent is not a PNG or JPEG picture.
502 { "error": "not-fetched", "message": "<text>" } The picture could not be fetched or kept.

Side effects: with list, the picture is fetched from the broadcaster's server. The logo is kept on the player as the user's choice for the station, as with an upload.

POST /api/logos/clear

Removes the user's choice of logo for a station, so that the logo found for the station is shown again.

Request body:

Field Type Required Meaning
station object yes The station reference.

Response: 200. logo is the logo now shown for the station; its url is null when none is kept.

{
  "success": true,
  "logo": { "url": "/logos/fm-09490.png?v=mf3k2a1b", "from": "pi", "ref": "c201" }
}

Errors: 404 { "error": "no-station" }; 500 { "error": "failed", "message": "<text>" }.

Side effects: the picture the user chose is removed from the player. If the station is playing, the player's display is updated.

GET /logos/:file

Serves a logo picture kept on the player. The paths are those given as url by the other logo endpoints.

Request:

Parameter Where Required Meaning
file path yes The file name: letters, digits, ., _ and -, beginning with a letter or digit and ending in .png, .jpg or .svg.
v query no The picture's mark. With it the answer may be kept by the browser for a year (Cache-Control: public, max-age=31536000, immutable); without it the answer carries Cache-Control: no-cache.

Response: 200, the picture. An SVG is sent with a Content-Security-Policy header that lets it draw and nothing else.

Errors: 404 with an empty body when the name is not of that form or no such file is kept.

Side effects: none.

Backup and restore

There are three kinds of backup: stations (the station list), config (the plugin settings) and blocklist (the artwork block list). Each backup is a JSON file kept on the player under /data/rtlsdr_radio_backups/<kind>/<kind>-<timestamp>.json. The newest five of each kind are kept.

A backup is named by its kind and its timestamp. The timestamp is the time of creation with - in place of : and ., for example 2026-01-15T09-30-00-000Z.

A backup of the stations carries the logos the user chose for stations; a restore puts them back.

GET /api/maintenance/backup/list

Lists the backups kept on the player, the newest first.

Request: no parameters.

Response: 200.

{
  "stations": [
    {
      "filename": "stations-2026-01-15T09-30-00-000Z.json",
      "timestamp": "2026-01-15T09-30-00-000Z",
      "size": 48213,
      "date": "2026-01-15T09:30:00.012Z"
    }
  ],
  "config": [],
  "blocklist": []
}

size is the size of the file in bytes, date the time the file was last written.

Errors: 500 { "error": "<message>" }.

Side effects: none.

POST /api/maintenance/backup/create

Creates a backup of one kind, or of all three.

Request body:

Field Type Required Meaning
type string yes stations, config, blocklist, or full for all three under one timestamp.

Response: 200.

{ "success": true }

When no block list has been saved yet, a blocklist or full request succeeds without writing a block-list backup.

Errors:

Status Body When
400 { "error": "Invalid backup type" } type is none of the four.
500 { "error": "Stations database file not found" } There is no station list on disk.
500 { "error": "Config file not found" } There is no settings file on disk.
500 { "error": "<message>" } Any other failure.

Side effects: writes the backup files and removes the oldest of each kind beyond five.

POST /api/maintenance/backup/restore

Restores backups kept on the player: any of the three kinds, each named by its timestamp.

Request body: at least one of the three.

Field Type Required Meaning
stationsTimestamp string no The stations backup to restore.
configTimestamp string no The settings backup to restore.
blocklistTimestamp string no The block-list backup to restore.

Response: 200.

{ "success": true }

Errors:

Status Body When
400 { "error": "No backups specified" } None of the three fields is given.
500 { "error": "Backup file not found: <timestamp>" } A backup named does not exist. The other backups named in the same request may have been restored.
500 { "error": "<message>" } Any other failure.

Side effects: the station list, the settings and the block list on the player are replaced by the backups named. About one second after a successful answer the plugin stops and starts again: playback, a scan or an antenna tool in progress is stopped, and this API does not answer until the plugin has started.

DELETE /api/maintenance/backup/delete

Deletes one backup kept on the player.

Request body:

Field Type Required Meaning
type string yes stations, config or blocklist.
timestamp string yes The backup's timestamp.

Response: 200.

{ "success": true }

Errors: 500 { "error": "Backup file not found" } when there is no such backup; 500 { "error": "<message>" } for any other failure.

Side effects: the backup file is removed.

GET /api/maintenance/backup/download

Downloads one backup as a zip file.

Query parameters:

Parameter Type Required Meaning
type string yes stations, config or blocklist.
timestamp string yes The backup's timestamp.

Response: 200, the zip file, offered as <type>-<timestamp>.zip. It holds the backup's JSON file.

Errors: 404 { "error": "Backup file not found" }; 500 { "error": "<message>" }.

Side effects: none.

POST /api/maintenance/backup/upload

Checks an uploaded backup zip and says what it holds. Nothing is restored.

Request: multipart/form-data.

Field Type Required Meaning
file file yes A zip file, as GET /api/maintenance/backup/download produces it, with a .json file at its top level.

Response: 200. The kind is recognised from the content of the JSON file.

{ "success": true, "info": { "type": "stations", "fmCount": 24, "dabCount": 61 } }
Kind info
Stations { "type": "stations", "fmCount": 24, "dabCount": 61 }
Settings { "type": "config" }
Block list { "type": "blocklist", "phraseCount": 21 }

Errors:

Status Body When
400 { "error": "No file uploaded" } The request has no file.
400 { "error": "No JSON file found in backup" } The zip holds no .json file at its top level.
400 { "error": "Invalid backup file format" } The JSON file is none of the three kinds.
400 { "error": "<message>" } The file cannot be unpacked or read.
500 { "error": "<message>" } Any other failure.

Side effects: none.

POST /api/maintenance/backup/upload-restore

Restores from an uploaded backup zip.

Request: multipart/form-data.

Field Type Required Meaning
file file yes A zip file as for POST /api/maintenance/backup/upload.

Response: 200.

{ "success": true }

Errors: the same as for POST /api/maintenance/backup/upload; a failure while putting the backup in place is answered with 400 { "error": "<message>" }.

Side effects: the station list, the settings or the block list on the player, whichever the file holds, is replaced. About one second after a successful answer the plugin stops and starts again, as after POST /api/maintenance/backup/restore.

Maintenance settings

GET /api/maintenance/settings

Reads the maintenance setting.

Request: no parameters.

Response: 200. autoBackup says whether a backup of the stations and the settings is made when the plugin is uninstalled. The default is false.

{ "autoBackup": false }

Errors: 500 { "error": "<message>" }.

Side effects: none.

POST /api/maintenance/settings

Changes the maintenance setting.

Request body:

Field Type Required Meaning
autoBackup boolean yes Whether a backup is made when the plugin is uninstalled.

Response: 200.

{ "success": true }

Errors: 500 { "error": "<message>" }.

Side effects: the setting is stored in the plugin settings.

Antenna tools

The antenna tools use the tuner themselves. Each takes it at once: a station the plugin is playing is stopped, and so is a scan in progress. Playback by other Volumio sources is left alone. While a tool runs, deviceState in GET /api/status is idle.

A DAB channel is one of 5A to 5D, 6A to 6D, 7A to 7D, 8A to 8D, 9A to 9D, 10A to 10D, 11A to 11D, 12A to 12D and 13A to 13F. Lower-case letters are accepted.

Two of the tools answer with an event stream: Content-Type: text/event-stream, each event one line data: <JSON> followed by an empty line. The request is a POST, so a client reads the events from the response body.

POST /api/antenna/spectrum-scan

Measures the power received across DAB Band III, from 174 to 240 MHz in steps of 1 MHz.

Request: no body.

Response: 200, once the measurement is done. Each entry of spectrum is taken from one line of the measuring program's output: freq is the frequency the line starts at, in MHz, and power the first power reading of that line, in dB.

{
  "success": true,
  "spectrum": [
    { "freq": 174, "power": -41.2 },
    { "freq": 175, "power": -40.8 }
  ],
  "timestamp": "2026-01-15T09:30:00.000Z"
}

Errors:

Status Body When
409 { "error": "Error: superseded by a later request" } A later request for the tuner arrived before this one got it.
500 { "error": "Failed to start fn-rtl_power: <reason>" } The measuring program could not be started.
500 { "error": "fn-rtl_power failed with <code or signal>" } The measuring program ended with an error, or was stopped: after 30 seconds, or because something else took the tuner.
500 { "error": "<message>" } Any other failure.

Side effects: takes the tuner as described above.

POST /api/antenna/validate-dab

Checks DAB channels one after another: whether an ensemble is received on the channel and how many audio services it carries. Each channel is given up to 30 seconds. The check uses the DAB gain and frequency-correction settings of the plugin.

Request body:

Field Type Required Meaning
channels array of strings yes The DAB channels to check, from 1 to 38 of them.
{ "channels": ["11D", "12B"] }

Response: 200, an event stream.

data: {"status":"started","total":2}

data: {"channel":"11D","sync":false,"services":0,"quality":"none","progress":1,"total":2}

data: {"channel":"12B","sync":true,"services":12,"quality":"excellent","progress":2,"total":2}

data: {"status":"complete","timestamp":"2026-01-15T09:30:45.000Z"}
Event Fields
Start status is started; total is the number of channels.
One for each channel channel; sync, whether an ensemble was recognised; services, the number of audio services found; quality; progress, the number of channels checked so far; total. When the check of a channel could not be run, quality is error and error has the reason.
End status is complete; timestamp.

quality follows from sync and services:

quality When
none No ensemble recognised, or no audio service found.
poor 1 service.
weak 2 or 3 services.
good 4 to 6 services.
strong 7 to 9 services.
excellent 10 services or more.

Errors:

Status Body When
400 { "error": "Channels array required, each a DAB channel such as 12B" } channels is missing, empty, too long or holds something that is not a DAB channel.
409 { "error": "Error: superseded by a later request" } A later request for the tuner arrived before this one got it.
500 { "error": "<message>" } The tuner could not be taken.

Side effects: takes the tuner as described above and holds it until the last channel is done. Closing the connection ends the check. When something else takes the tuner during the check, the stream ends without the complete event.

POST /api/antenna/snr-scan

Measures the signal-to-noise ratio of DAB channels at a series of gain settings and names the gain with the best average.

Request body:

Field Type Required Meaning
channels array of strings yes The DAB channels to measure, from 1 to 38 of them.
gainStart integer no First gain. Default -10; kept within -10 and 100.
gainStop integer no Last gain. Default 49; kept within -10 and 100.
gainStep integer no Step from one gain to the next. Default 5; kept within 1 and 50.
integration integer no Seconds measured at each gain. Default 2; kept within 1 and 30.

A value that is not a number is replaced by its default. When gainStop is below gainStart the two are exchanged.

{ "channels": ["11D", "12B"], "gainStart": 0, "gainStop": 40, "gainStep": 10, "integration": 2 }

Response: 200, an event stream.

data: {"status":"started","channels":["11D","12B"],"gainRange":{"start":0,"stop":40,"step":10}}

data: {"status":"progress","gain":0,"results":[{"channel":"11D","gain":0,"peak":-38.2,"noise":-52.6,"snr":14.4}],"progress":1,"total":5}

data: {"status":"complete","measurements":[{"channel":"11D","gain":0,"peak":-38.2,"noise":-52.6,"snr":14.4}],"summary":{"byGain":[{"gain":0,"minSnr":14.4,"maxSnr":16.1,"avgSnr":15.25,"samples":2}],"bestGain":20,"bestAvgSnr":21.7},"channels":["11D","12B"],"timestamp":"2026-01-15T09:31:10.000Z"}
Event Fields
started channels, the channels measured; gainRange with start, stop and step as they are used.
progress, one for each gain gain; results, one entry for each channel measured at that gain; progress, the number of gains done; total, the number of gains. results is empty when the measurement at that gain failed.
complete measurements, the results of all gains; summary; channels; timestamp.
error error, the reason.

An entry of results and measurements has channel, gain, peak (the highest power within the channel), noise (the mean power outside it) and snr (peak less noise).

summary.byGain has one entry for each gain with minSnr, maxSnr and avgSnr over the channels and samples, the number of results. summary.bestGain is the gain with the highest average and summary.bestAvgSnr that average; both are -999 when nothing could be measured.

Errors:

Status Body When
400 { "error": "Channels array required, each a DAB channel such as 12B" } channels is missing, empty, too long or holds something that is not a DAB channel.
409 { "error": "Error: superseded by a later request" } A later request for the tuner arrived before this one got it.
500 { "error": "<message>" } The tuner could not be taken.

Side effects: takes the tuner as described above and holds it until the last gain is measured.

Song recognition

The setting is the same as Song Recognition on the plugin's settings page; see Artwork and Metadata.

GET /api/recognition

Request: no parameters.

Response: 200.

{
  "enabled": true,
  "on": true,
  "when": "missing",
  "keyFailed": false,
  "key": { "set": true, "tail": "0c7c", "length": 32 },
  "song": { "artist": "Ariana Grande", "title": "One Last Time" },
  "candidate": null,
  "asked": 7,
  "lastAskedAt": "2026-10-06T10:12:03.000Z",
  "lastSong": { "artist": "Ariana Grande", "title": "One Last Time", "at": "2026-10-06T10:12:03.000Z" }
}
Field Type Meaning
enabled boolean Switched on, with a token the service has not refused: questions are asked.
on boolean The switch. Off keeps the token.
when string missing (ask when the station's text names no song) or always.
keyFailed boolean The service refused the token; nothing is asked until another is set.
key object set, tail (the token's last four characters, or null) and length. The token itself comes from GET /api/recognition/key only.
song object or null The song being shown, recognised from the sound.
candidate object or null An answer waiting to be confirmed by the next.
asked number Questions sent since the plugin started.
lastAskedAt string or null When the last question was sent.
lastSong object or null The last song recognised, with at.

Errors: none.

Side effects: none.

POST /api/recognition

Request:

Field Type Required Meaning
on boolean no The switch. Off keeps the token.
key string no The AudD token. An empty string removes it. Left out, the token that is set stays.
when string no missing or always.

Response: 200, the same view as GET /api/recognition.

Errors: 400 { "error": "<message>" } for a key that is not text, a when that is neither value or an on that is not a boolean; 500 { "error": "<message>" }.

Side effects: the setting is saved, and the player's log says the state in words.

GET /api/recognition/key

The token itself, for the eye beside the Station Manager's field.

Request: no parameters.

Response: 200, { "key": "<the token>" }; an empty string when none is set.

Errors: none.

Side effects: a line in the player's log says the token was shown.

Updates

The plugin can update itself. A version comes from the Volumio plugin store or from the project's releases on GitHub. The update channel says which versions are offered: stable, beta or preview. A channel other than stable applies only on a player that is in Volumio's plugin test mode.

All update endpoints answer with the update view:

{
  "current": "1.4.0",
  "channel": "stable",
  "chosen": "stable",
  "testMode": false,
  "checkedAt": "2026-01-15T09:00:00.000Z",
  "offer": {
    "version": "1.4.1",
    "channel": "stable",
    "source": "store",
    "notes": "",
    "page": "",
    "bytes": 0,
    "publishedAt": null
  },
  "available": true,
  "newest": { "stable": "1.4.1", "beta": null, "preview": null },
  "problems": {},
  "previous": null,
  "last": null,
  "job": null
}
Field Type Meaning
current string The version installed.
channel string The channel in force: the one chosen on a player in plugin test mode, stable on any other.
chosen string The channel chosen.
testMode boolean Whether the player is in Volumio's plugin test mode.
checkedAt string or null When a newer version was last looked for.
offer object or null The newest version the channel in force offers; null when none is known.
offer.version string Its version.
offer.channel string The channel it belongs to: stable, beta or preview.
offer.source string store or github.
offer.notes string Release notes; empty for a version from the store.
offer.page string Address of the release page; empty for a version from the store.
offer.bytes number Size of the download; 0 for a version from the store.
offer.publishedAt string or null When it was published; null for a version from the store.
available boolean Whether offer is newer than current.
newest object The newest version each channel carries by itself: stable, beta and preview, each a version or null.
problems object The sources that did not answer at the last look, store and github, each with a code. For store: store-login when the player is not signed in to MyVolumio, store when the plugin store gave no answer. For github: bad-answer when the answer could not be read. Otherwise the code of the underlying error, or network.
previous object or null The version kept for a rollback: { "version", "at" }.
last object or null The last update or rollback: from, to, source (store, github or kept), at and phase: applying, restarting, done or failed. With done come ok, whether the version running is the one aimed for, and endedAt. With failed comes error, the reason.
job object or null An install or rollback started since the plugin was started: kind (install or rollback), state, progress and error.

job.state is one of starting, downloading, verifying, backing-up, keeping, applying, restarting and failed. While downloading, job.progress is { "done", "total" } in bytes; otherwise it is null. When the state is failed, job.error is { "code", "message" } with one of the codes network, size, checksum, keep, apply or failed; otherwise it is null.

GET /api/update

Returns the update view. A newer version is looked for first when the last look is more than 24 hours old or was made for another channel.

Request: no parameters.

Response: 200, the update view. If the look fails altogether, the view is returned with error added: { "code": "failed", "message": "<text>" }.

Errors: none; the status is always 200.

Side effects: may ask the plugin store and GitHub for the versions they carry.

POST /api/update/check

Looks for a newer version now.

Request: no body.

Response: 200, the update view. A source that did not answer is named under problems.

Errors: none.

Side effects: asks the plugin store and GitHub for the versions they carry.

POST /api/update/channel

Chooses the update channel and looks for a newer version on it.

Request body:

Field Type Required Meaning
channel string yes stable, beta or preview.

Response: 200, the update view.

Errors: 400 when channel is none of the three:

{ "error": { "code": "bad-channel", "message": "the channel is one of stable, beta, preview" } }

Side effects: stores the channel in the plugin settings and asks the plugin store and GitHub for the versions they carry. On a player that is not in plugin test mode the channel in force stays stable.

POST /api/update/install

Installs the version on offer. The answer comes at once; the work goes on in the background and GET /api/update tells how far it is under job.

Request: no body.

Response: 200, the update view with job set.

Errors: 409, the update view with error added, when the install cannot be started:

{
  "current": "1.4.0",
  "available": false,
  "job": null,
  "error": { "code": "up-to-date", "message": "1.4.0 is the newest on this channel" }
}

(The example leaves out the other members of the view.)

error.code When
busy An install or rollback is under way.
no-offer No version is known yet; POST /api/update/check first.
up-to-date The version on offer is not newer than the one installed.
no-digest The release on GitHub states no checksum for its file.
bad-answer The release on GitHub states an unusable size for its file.

Side effects: a version from GitHub is downloaded and checked against the size and the SHA-256 digest its release states. The station list, the settings and the block list are backed up, and the version installed is kept for a rollback. The player's plugin manager then installs the new version, and the Volumio backend is restarted. Playback stops, and this API does not answer during the restart.

POST /api/update/rollback

Puts back the version that was installed before the last update. The answer comes at once; the work goes on in the background as for an install.

Request: no body.

Response: 200, the update view with job set.

Errors: 409, the update view with error added, as for an install:

error.code When
busy An install or rollback is under way.
no-previous No previous version is kept.

Side effects: as for an install: backups are made, the version now installed is kept as the new previous version, the player's plugin manager installs the kept version, and the Volumio backend is restarted.

Dongle report

A report on the dongle that is plugged in: the plugin runs its own tools on it one after another and packs what they find into a ZIP file. What the report does and holds is described under Dongle Report. It is made in the background; the endpoints answer with the report view:

{
  "phase": "running",
  "step": 3,
  "of": 6,
  "at": "survey",
  "doing": "surveying the FM band",
  "error": null,
  "startedAt": "2026-10-05T06:35:53.241Z",
  "endedAt": null,
  "summary": null,
  "zip": null
}
Member Type Meaning
phase string idle (none made since the plugin started), running, done, failed or cancelled.
step, of number The step being worked on and how many there are: 6 with recordings, 5 without.
at string or null The step's name: test, selftest, survey, gains, record, together.
doing string or null The same in words, in English.
error object or null { code, message } when the report failed or was cancelled.
startedAt, endedAt string or null When the report was started and when it ended.
summary object or null When done: device, tuner, gainSteps, lostBytes, selftest (boolean), stations, sensitivity, overloaded and slices (FM slices in which the gain was taken down, of how many), mirrors (channels refused as a tuner's mirror), recordings, problems (list of strings).
zip object or null When done: { name, bytes } of the file to download.
error.code When
no-dongle No dongle answered.
busy A report is being made already.
in-use A scan or an antenna tool has the dongle.
interrupted The dongle was taken for something else while the report was being made, a station for instance.
cancelled The report was cancelled.
failed Anything else; message says what.

GET /api/dongle-report

Response: 200, the report view.

Errors: none.

Side effects: none.

POST /api/dongle-report/start

Starts a report. The answer comes at once; the work goes on in the background.

Request body:

Field Type Required Meaning
recordings boolean no false leaves the recordings out. Default: with recordings.

Response: 200, the report view with phase running.

Errors: 409, the report view with error added: busy or in-use.

Side effects: a station the plugin is playing is stopped. The dongle is in use until the report ends. The report before this one and its file are removed.

POST /api/dongle-report/cancel

Request: no body.

Response: 200, the report view, once the dongle is free again: phase is cancelled if a report was being made, unchanged otherwise.

Errors: none.

Side effects: the tool that is running is stopped.

GET /api/dongle-report/summary

Response: 200, text/plain: the summary of the last report.

Errors: 404 { "error": { "code": "no-report", "message": "no report has been made" } }.

Side effects: none.

GET /api/dongle-report/download

Response: 200, the ZIP file of the last report, sent as an attachment named dongle-report-<date>-<time>.zip.

Errors: 404 no-report, as above.

Side effects: none.

Translations

GET /api/language

Returns the language the player is set to.

Request: no parameters.

Response: 200. The code is en when the player's language cannot be read.

{ "language": "en" }

Errors: none.

Side effects: none.

GET /api/i18n/:lang

Returns the texts of the plugin and the Station Manager in one language.

Request:

Parameter Where Required Meaning
lang path yes A language code as GET /api/language gives it, for example en or de.

Response: 200, one object with a text for each key. When there are no texts in the language asked for, the English ones are returned.

{
  "PLUGIN_NAME": "FM/DAB Radio",
  "FM_RADIO": "FM Radio",
  "DAB_RADIO": "DAB Radio"
}

Errors: 500 { "error": "Failed to load translations" } or { "error": "Failed to parse translations" }.

Side effects: none.

Artwork block list

The artwork block list is a list of phrases, such as station slogans and programme names. A text from a station that contains one of them is not used to look up artwork.

GET /api/blocklist

Returns the block list. Until one has been saved, the default list is returned.

Request: no parameters.

Response: 200.

{ "phrases": ["We love music", "The best hits", "More music"] }

Errors: 500 { "error": "<message>" }.

Side effects: none.

POST /api/blocklist

Replaces the block list.

Request body:

Field Type Required Meaning
phrases array of strings no The phrases. Without it an empty list is saved.

Response: 200. count is the number of phrases saved.

{ "success": true, "count": 3 }

Errors: 500 { "error": "<message>" }.

Side effects: the list is saved and applies at once.

POST /api/blocklist/reset

Puts the default block list back.

Request: no body.

Response: 200, with the default phrases.

{ "success": true, "phrases": ["We love pop", "We love music", "We love hits"] }

Errors: 500 { "error": "<message>" }.

Side effects: the default list is saved and applies at once.

Pages and static files

GET /

Serves the Station Manager page.

Request: no parameters.

Response: 200, HTML.

Side effects: none.

GET /manage

Serves the Station Manager page, the same as GET /.

Request: no parameters.

Response: 200, HTML.

Side effects: none.

GET /icon

Serves the antenna icon of the plugin.

Request: no parameters.

Response: 200, an SVG picture.

Side effects: none.

Notes

One tuner, one use at a time

An RTL-SDR dongle serves one use at a time: playing a station, an FM scan, a DAB scan, or one of the three antenna tools. A request that needs the tuner is treated as follows.

Request While the plugin is idle While the plugin plays or scans While an antenna tool runs
POST /api/stations/scan-fm, POST /api/stations/scan-dab The scan starts. The answer is the same 200. The scan waits for the choice made in the dialog of the Volumio interface. The tool is stopped and the scan starts.
POST /api/antenna/spectrum-scan, POST /api/antenna/validate-dab, POST /api/antenna/snr-scan The tool starts. The station or the scan is stopped and the tool starts. A stopped scan leaves the station list as it was. The running tool is stopped and the new one starts.

What the stopped antenna tool answers:

  • POST /api/antenna/spectrum-scan answers 500 with fn-rtl_power failed with <signal>.
  • POST /api/antenna/validate-dab ends its stream without the complete event.

An antenna-tool request that is still waiting for the tuner when a later request for it arrives is answered with 409.

Restarts

  • A restore, from a backup on the player or from an uploaded file, stops and starts the plugin about one second after the answer. The API does not answer in between.
  • An install or a rollback ends with a restart of the Volumio backend. The API does not answer during the restart. GET /api/update then shows the outcome under last.

The recycle bin

A station with deleted set is in the recycle bin. It stays in the station list and in GET /api/stations, is not counted by GET /api/status, is left out of CSV exports, and is not played. POST /api/stations/clear-fm, POST /api/stations/clear-dab and a CSV import with remove put stations there; a CSV import with amend takes them out; POST /api/stations/purge removes them for good.

Clone this wiki locally