Repository navigation
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 examplehttp://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 usemultipart/form-datawith the file in a field namedfile. - 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/statusEndpoints that change something answer { "success": true }, with further members where stated.
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. |
| 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 |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
These four endpoints are what the tune dialog of the Station Manager works with.
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.
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.
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.
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.
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
frequencyis FM; one containingchannelandexactNameis 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.
-
favoriteandhiddenare true fortrue,1oryesand false for anything else. - An FM row needs a
frequencybetween the lower edge of the band of the region set in the plugin settings and 108.0 MHz. An emptynamebecomesFM <frequency>. - A DAB row needs a
channelthat is a DAB channel (5Ato13F, any letter case) and anexactName. An emptynamebecomes theexactName; an emptyserviceIdbecomes0.
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 notesErrors: none.
Side effects: none.
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,notesErrors: none.
Side effects: none.
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.
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.
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.
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.
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. |
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The setting is the same as Song Recognition on the plugin's settings page; see Artwork and Metadata.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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. |
Response: 200, the report view.
Errors: none.
Side effects: none.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Serves the Station Manager page.
Request: no parameters.
Response: 200, HTML.
Side effects: none.
Serves the Station Manager page, the same as GET /.
Request: no parameters.
Response: 200, HTML.
Side effects: none.
Serves the antenna icon of the plugin.
Request: no parameters.
Response: 200, an SVG picture.
Side effects: none.
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-scananswers 500 withfn-rtl_power failed with <signal>. -
POST /api/antenna/validate-dabends its stream without thecompleteevent.
An antenna-tool request that is still waiting for the tuner when a later request for it arrives is answered with 409.
- 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/updatethen shows the outcome underlast.
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.
FM/DAB Radio for Volumio · Source · Community thread · GPL-3.0
Using the radio
- Installation
- Getting Started
- Playing Radio
- Scanning
- Gain, Frequency and Loudness
- Artwork and Metadata
- Station Logos
Managing it
Hardware
Help
Reference