API Reference
Documentation of the Revive ST streaming API and Bose SoundTouch Marge emulation endpoints.
Revive ST hosts a custom API server that acts both as the backend for our application and as an emulator for the Bose SoundTouch "Marge" streaming server.
Every endpoint is also described in an OpenAPI document, /openapi.json, with an interactive reference at /docs. A self-hosted server serves both too.
GET / answers whenever the server is up with its name, version and the paths of both, { "name": "Revive ST API", "version": "…", "docs": "/docs", "openapi": "/openapi.json" }. Railway uses it as the deployment health check.
Accounts
Each household gets its own Marge account, and presets are stored per account and device, so knowing a household's account id is what grants access to its presets. The id is never logged: the server masks it in every log line.
The id is not secret on the household's own network. SoundTouch speakers only speak plain HTTP: a speaker reports its account id unauthenticated in its /info, and the app sends it over HTTP when pairing a speaker (setMargeAccount). Anyone on that network can read it; this risk is accepted, as no speaker offers HTTPS. For the same reason the Android app allows cleartext HTTP, which can't be limited to speakers: their addresses are discovered at runtime. Every request to the Revive ST server uses HTTPS.
When the app configures a speaker that is not paired with an account yet, it pairs it with the household's account: the one another of the household's speakers on this server uses, else the one the phone used before, else a new one minted by POST /api/internal/accounts. Only ids minted by the server are shared this way. A speaker already paired with an account keeps it.
Marge Emulation Endpoints (Bose SoundTouch)
These endpoints are requested directly by the Bose SoundTouch speakers. They emulate the behavior of Bose's legacy streaming services so the speakers play internet radio from TuneIn and Radio Browser.
Responses that carry an ETag answer a request whose If-None-Match matches it with a bare 304 Not Modified. The tag is derived from the body, and the header is spelled exactly ETag: SoundTouch firmware ignores any other spelling.
GET /streaming/sourceproviders
Returns the source providers the speaker resolves source provider ids against: TUNEIN (id 25) and RADIO_BROWSER (id 39). Includes an ETag.
GET /streaming/account/:accountId/full
Returns the full account XML structure including the list of sources, root element is <account>. The speaker copies the sources into its own source list at boot, and only plays and saves presets for sources listed there: TUNEIN (id 10004) and RADIO_BROWSER (id 10005). Neither belongs to a user, so their <username> is empty. Includes an ETag.
The speaker activates the source types it had saved when it started. A source it learns from this document only becomes playable at its next boot, so after the sources change a speaker needs to restart twice, unless it fetched this document before restarting.
<devices> lists every speaker paired with the account, each with its presets. A speaker missing from it drops its account and presets at boot. A speaker with no presets restores them from here; one that has presets keeps its own. A speaker with no stored presets is listed without a <presets> element, as an empty one wipes the speaker's presets. Only the device id is listed: the speaker's IP address, serial numbers and name are never stored.
A speaker is paired with an account when it registers with it, saves or deletes a preset or adds a recent in it, looks its group up in it, or has its presets synced to it by the app. Pairing never removes a speaker from another account: its device id is its MAC address, which anyone can name, so pairing it elsewhere must not unpair it from its household.
GET /streaming/account/:accountId/device/:deviceId/presets
Returns the device's presets in Marge's format: one <preset buttonNumber="n"> per stored preset, with its location, name, artwork, content type and the <source> it plays from. Includes an ETag that changes whenever a preset does.
PUT /streaming/account/:accountId/device/:deviceId/preset/:presetId
Called by the speaker when a preset button (1 to 6) is held. The speaker's <sourceid> identifies the source: the id copied from the account's sources in /full, or, for TuneIn, the source's name. A preset from any other source is rejected with 400, rather than stored under the wrong source. Returns the stored preset.
DELETE /streaming/account/:accountId/device/:deviceId/preset/:presetId
Called by the speaker when a preset is removed from it. Deletes the stored preset, so the speaker doesn't restore it at its next boot, and returns 200 OK with <status>OK</status>.
GET /streaming/software/update/account/:accountId
Mandatory endpoint called by the speaker to check for firmware updates. Returns a <software_update> response with an empty <softwareUpdateLocation> stub to prevent the speaker from entering an update failure state.
POST /streaming/account/:accountId/device/:deviceId/recent
Endpoint called by the speaker when it starts playing something. Returns 201 Created with the recent and the source it played from; a recent from a source the account does not offer is rejected with 400. Recents are not stored.
POST /streaming/support/power_on
Endpoint called by the speaker upon boot to signal it has turned on. We return a standard <status>OK</status> response with a 200 OK status to complete the handshake and allow the device to boot.
POST /streaming/account/:accountId/device/
Called by the speaker when it is paired with an account (setMargeAccount on the speaker). Pairs the device with the account and returns 201 Created with a <device> document, which the speaker accepts.
A bare 200 OK status reply is not enough: the speaker then refuses the pairing with SET_MARGE_ACCOUNT_ERROR.
A body without a deviceid gets 400 Bad Request.
GET /streaming/account/:accountId/device/:deviceId/group/
Called by the speaker at boot to look up its stereo pair group. Stereo pairs are not supported, so this always returns an empty <group/>.
GET /streaming/account/:accountId/provider_settings
Called by the speaker at boot to read the account's music provider settings. Returns a <providerSettings> document with one setting, ELIGIBLE_FOR_TRIAL set to false for Deezer (provider 14), as the account offers no Deezer source.
BMX Endpoints
GET /v2/registry.json
The BMX service registry (bmxRegistryUrl). It points the speaker at two adapters on this server, built from the host the speaker called: TuneIn and Radio Browser.
TuneIn: /bmx/tunein
GET /v1/playback/station/:stationId: resolves a TuneIn station to its streams through TuneIn's public OPML API. HLS streams are never requested, as speakers cannot play them. Returns404for a station TuneIn has no stream for,503when TuneIn is unavailable.POST /v1/token: an anonymous account token. Speakers refuse a source whose token is empty.POST /v1/report: listening reports, acknowledged and dropped.
Radio Browser: /bmx/radiobrowser
GET /stations/byuuid/:stationUuid: plays a Radio Browser station by itsstationuuid. A Radio Browser preset's location is/stations/byuuid/<uuid>, relative to the registry's base URL, so it names the station rather than its stream and holds no server address. Every play looks the station up through Radio Browser's API and returns its current stream (url_resolved, elseurl), name and logo. Returns404for a station Radio Browser doesn't know or that only has an HLS stream, which speakers cannot play, and503when Radio Browser is unavailable. A failed lookup is tried once more.POST /token: an anonymous account token.
App Endpoints
POST /api/internal/accounts
Returns a new household account id, { "accountId": "…" }.
POST /api/internal/account/:accountId/device/:deviceId/presets
Stores the presets the app read from the speaker, { "presets": [...] }, so the speaker gets them back from the presets endpoint. Pairs the device with the account, and replaces the device's stored presets: slots left out are deleted, and an empty list clears them all. Presets from a source the account does not offer are dropped.