Documentation for controlling and preserving Bose SoundTouch devices
This document provides a complete API reference for the Bose SoundTouch navigation and station management functionality. For usage examples and workflows, see NAVIGATION-GUIDE.md.
Navigate(source, sourceAccount string, startItem, numItems int) (*models.NavigateResponse, error)Browse content within a source.
Parameters:
source (string, required): Content source identifier
"TUNEIN", "PANDORA", "SPOTIFY", "STORED_MUSIC", "BLUETOOTH", "AUX"sourceAccount (string, optional): Account identifier for authenticated sourcesstartItem (int, required): Starting position (1-based index)numItems (int, required): Number of items to retrieveReturns:
*models.NavigateResponse: Navigation results with items and metadataerror: Error if request failsExample:
response, err := client.Navigate("TUNEIN", "", 1, 25)
Validation:
source cannot be emptystartItem must be >= 1numItems must be >= 1NavigateWithMenu(source, sourceAccount, menu, sort string, startItem, numItems int) (*models.NavigateResponse, error)Browse content with specific menu and sorting options (primarily for Pandora).
Parameters:
source (string, required): Content source identifiersourceAccount (string, optional): Account identifiermenu (string, optional): Menu context (e.g., "radioStations")sort (string, optional): Sort order (e.g., "dateCreated")startItem (int, required): Starting position (1-based)numItems (int, required): Number of items to retrieveReturns:
*models.NavigateResponse: Navigation resultserror: Error if request failsExample:
response, err := client.NavigateWithMenu("PANDORA", "user123", "radioStations", "dateCreated", 1, 100)
NavigateContainer(source, sourceAccount string, startItem, numItems int, containerItem *models.ContentItem) (*models.NavigateResponse, error)Browse into a specific container/directory.
Parameters:
source (string, required): Content source identifiersourceAccount (string, optional): Account identifierstartItem (int, required): Starting position (1-based)numItems (int, required): Number of items to retrievecontainerItem (*models.ContentItem, required): Container to browse intoReturns:
*models.NavigateResponse: Container contentserror: Error if request failsExample:
response, err := client.NavigateContainer("STORED_MUSIC", "device/0", 1, 100, albumContentItem)
Validation:
containerItem cannot be nilLocation fieldGetTuneInStations(sourceAccount string) (*models.NavigateResponse, error)Browse TuneIn radio stations.
Parameters:
sourceAccount (string, optional): TuneIn account (usually empty)Returns:
*models.NavigateResponse: TuneIn stations and contentExample:
stations, err := client.GetTuneInStations("")
GetPandoraStations(sourceAccount string) (*models.NavigateResponse, error)Browse Pandora radio stations with proper sorting.
Parameters:
sourceAccount (string, required): Pandora user account identifierReturns:
*models.NavigateResponse: Pandora stations sorted by creation dateExample:
stations, err := client.GetPandoraStations("user123")
Validation:
sourceAccount cannot be emptyGetStoredMusicLibrary(sourceAccount string) (*models.NavigateResponse, error)Browse stored/local music library.
Parameters:
sourceAccount (string, required): Device account identifier (format: deviceID/index)Returns:
*models.NavigateResponse: Music library root contentsExample:
library, err := client.GetStoredMusicLibrary("A81B6A536A98/0")
Validation:
sourceAccount cannot be emptySearchStation(source, sourceAccount, searchTerm string) (*models.SearchStationResponse, error)Search for stations and content within a music service.
Parameters:
source (string, required): Service to searchsourceAccount (string, optional): Account identifiersearchTerm (string, required): Search queryReturns:
*models.SearchStationResponse: Search results categorized by typeerror: Error if request failsExample:
results, err := client.SearchStation("PANDORA", "user123", "jazz")
Validation:
source cannot be emptysearchTerm cannot be emptySearchTuneInStations(searchTerm string) (*models.SearchStationResponse, error)Search TuneIn radio stations.
Parameters:
searchTerm (string, required): Search queryReturns:
*models.SearchStationResponse: TuneIn search resultsExample:
results, err := client.SearchTuneInStations("classical music")
SearchPandoraStations(sourceAccount, searchTerm string) (*models.SearchStationResponse, error)Search Pandora for artists and stations.
Parameters:
sourceAccount (string, required): Pandora account identifiersearchTerm (string, required): Artist or genre to search forReturns:
*models.SearchStationResponse: Pandora search results with songs, artists, stationsExample:
results, err := client.SearchPandoraStations("user123", "Taylor Swift")
Validation:
sourceAccount cannot be emptySearchSpotifyContent(sourceAccount, searchTerm string) (*models.SearchStationResponse, error)Search Spotify for tracks, albums, and playlists.
Parameters:
sourceAccount (string, required): Spotify account identifiersearchTerm (string, required): Content to search forReturns:
*models.SearchStationResponse: Spotify search resultsExample:
results, err := client.SearchSpotifyContent("user@example.com", "Queen")
Validation:
sourceAccount cannot be emptyAddStation(source, sourceAccount, token, name string) errorAdd a station to music service collection and immediately start playing it.
Parameters:
source (string, required): Music service identifiersourceAccount (string, optional): Account identifiertoken (string, required): Station token from search resultsname (string, required): Display name for the stationReturns:
error: Error if operation failsExample:
err := client.AddStation("PANDORA", "user123", "R4328162", "Classic Rock Radio")
Behavior:
presetsUpdated WebSocket event if station is stored as presetValidation:
source cannot be emptytoken cannot be emptyname cannot be emptyRemoveStation(contentItem *models.ContentItem) errorRemove a station from music service collection.
Parameters:
contentItem (*models.ContentItem, required): Station content item with source and locationReturns:
error: Error if operation failsExample:
err := client.RemoveStation(stationContentItem)
Behavior:
nowPlayingUpdated WebSocket event if playing station was removedValidation:
contentItem cannot be nilcontentItem.Source cannot be emptycontentItem.Location cannot be emptyRequest structure for /navigate endpoint.
type NavigateRequest struct {
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
Menu string `xml:"menu,attr,omitempty"`
Sort string `xml:"sort,attr,omitempty"`
StartItem int `xml:"startItem"`
NumItems int `xml:"numItems"`
Item *NavigateItem `xml:"item,omitempty"`
}
Constructors:
NewNavigateRequest(source, sourceAccount string, startItem, numItems int)NewNavigateRequestWithMenu(source, sourceAccount, menu, sort string, startItem, numItems int)NewNavigateRequestWithItem(source, sourceAccount string, startItem, numItems int, item *ContentItem)Response structure from navigation operations.
type NavigateResponse struct {
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
TotalItems int `xml:"totalItems"`
Items []NavigateItem `xml:"items>item"`
}
Helper Methods:
GetPlayableItems() []NavigateItem - Filter items with Playable="1"GetDirectories() []NavigateItem - Filter directory items (type="dir")GetTracks() []NavigateItem - Filter track items (type="track")GetStations() []NavigateItem - Filter station items (type="stationurl")IsEmpty() bool - Check if response has no itemsIndividual item within navigation response.
type NavigateItem struct {
Playable int `xml:"Playable,attr,omitempty"`
Name string `xml:"name"`
Type string `xml:"type"`
ContentItem *ContentItem `xml:"ContentItem,omitempty"`
MediaItemContainer *MediaItemContainer `xml:"mediaItemContainer,omitempty"`
ArtistName string `xml:"artistName,omitempty"`
AlbumName string `xml:"albumName,omitempty"`
}
Helper Methods:
GetDisplayName() string - Get formatted display nameIsPlayable() bool - Check if Playable="1"IsDirectory() bool - Check if type="dir"IsTrack() bool - Check if type="track"IsStation() bool - Check if type="stationurl"GetContentItem() *ContentItem - Get associated content itemGetArtwork() string - Get artwork URL from content itemCommon Type Values:
"dir" - Directory/container"track" - Music track"stationurl" - Radio station"playlist" - Playlist"album" - AlbumRequest structure for station search.
type SearchStationRequest struct {
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
SearchTerm string `xml:",chardata"`
}
Constructor:
NewSearchStationRequest(source, sourceAccount, searchTerm string)Response structure from search operations.
type SearchStationResponse struct {
DeviceID string `xml:"deviceID,attr"`
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
Songs []SearchResult `xml:"songs>searchResult"`
Artists []SearchResult `xml:"artists>searchResult"`
Stations []SearchResult `xml:"stations>searchResult"`
}
Helper Methods:
GetSongs() []SearchResult - Get song resultsGetArtists() []SearchResult - Get artist resultsGetStations() []SearchResult - Get station resultsGetAllResults() []SearchResult - Get all results combinedGetResultCount() int - Count total resultsHasResults() bool - Check if any results foundIsEmpty() bool - Check if no resultsIndividual search result item.
type SearchResult struct {
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
Token string `xml:"token,attr"`
Name string `xml:"name"`
Artist string `xml:"artist,omitempty"`
Album string `xml:"album,omitempty"`
Logo string `xml:"logo,omitempty"`
Description string `xml:"description,omitempty"`
}
Helper Methods:
IsSong() bool - Check if result is a song (has Artist field)IsArtist() bool - Check if result is an artist (no Artist or Description)IsStation() bool - Check if result is a station (has Description)GetDisplayName() string - Get formatted nameGetFullTitle() string - Get name with artist for songsGetArtworkURL() string - Get logo/artwork URLToken Usage:
The Token field is used with AddStation() to add the result to your collection.
Request structure for adding stations.
type AddStationRequest struct {
Source string `xml:"source,attr"`
SourceAccount string `xml:"sourceAccount,attr,omitempty"`
Token string `xml:"token,attr"`
Name string `xml:"name"`
}
Constructor:
NewAddStationRequest(source, sourceAccount, token, name string)Response structure from station management operations.
type StationResponse struct {
Status string `xml:",chardata"`
}
Common Values:
"/addStation" - Station added successfully"/removeStation" - Station removed successfullyBrowse content within a source.
Request Body:
<navigate source="TUNEIN" sourceAccount="">
<startItem>1</startItem>
<numItems>25</numItems>
</navigate>
Response Body:
<navigateResponse source="TUNEIN">
<totalItems>5</totalItems>
<items>
<item Playable="1">
<name>Station Name</name>
<type>stationurl</type>
<ContentItem source="TUNEIN" location="/v1/playback/station/s12345" isPresetable="true">
<itemName>Station Name</itemName>
</ContentItem>
</item>
</items>
</navigateResponse>
Search for stations and content.
Request Body:
<search source="PANDORA" sourceAccount="user123">Taylor Swift</search>
Response Body:
<results deviceID="A81B6A536A98" source="PANDORA" sourceAccount="user123">
<songs>
<searchResult source="PANDORA" sourceAccount="user123" token="S123">
<name>Love Story</name>
<artist>Taylor Swift</artist>
<logo>http://example.com/artwork.jpg</logo>
</searchResult>
</songs>
<artists>
<searchResult source="PANDORA" sourceAccount="user123" token="R456">
<name>Taylor Swift</name>
<logo>http://example.com/artist.jpg</logo>
</searchResult>
</artists>
</results>
Add a station to collection and start playing.
Request Body:
<addStation source="PANDORA" sourceAccount="user123" token="R456">
<name>Taylor Swift Radio</name>
</addStation>
Response Body:
<status>/addStation</status>
Remove a station from collection.
Request Body:
<ContentItem source="PANDORA" location="126740707481236361" sourceAccount="user123" isPresetable="true">
<itemName>Taylor Swift Radio</itemName>
</ContentItem>
Response Body:
<status>/removeStation</status>
<xs:element name="navigate">
<xs:complexType>
<xs:sequence>
<xs:element name="startItem" type="xs:int"/>
<xs:element name="numItems" type="xs:int"/>
<xs:element name="item" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="type" type="xs:string"/>
<xs:element name="ContentItem" type="ContentItemType"/>
</xs:sequence>
<xs:attribute name="Playable" type="xs:int"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="source" type="xs:string" use="required"/>
<xs:attribute name="sourceAccount" type="xs:string"/>
<xs:attribute name="menu" type="xs:string"/>
<xs:attribute name="sort" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="search">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="source" type="xs:string" use="required"/>
<xs:attribute name="sourceAccount" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:complexType name="ContentItemType">
<xs:sequence>
<xs:element name="itemName" type="xs:string" minOccurs="0"/>
<xs:element name="containerArt" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="source" type="xs:string" use="required"/>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute name="location" type="xs:string"/>
<xs:attribute name="sourceAccount" type="xs:string"/>
<xs:attribute name="isPresetable" type="xs:boolean"/>
</xs:complexType>
| Status | Meaning | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid parameters or XML |
| 404 | Not Found | Endpoint or content not found |
| 500 | Internal Server Error | Device error |
Invalid Source:
<error>
<code>INVALID_SOURCE</code>
<message>Source 'INVALID' is not available</message>
</error>
Authentication Required:
<error>
<code>AUTH_REQUIRED</code>
<message>Source account required for this service</message>
</error>
Service Unavailable:
<error>
<code>SERVICE_UNAVAILABLE</code>
<message>PANDORA service is not configured</message>
</error>
The Go client performs validation before sending requests:
| Error Message | Cause | Solution |
|---|---|---|
"source cannot be empty" |
Empty source parameter | Provide valid source |
"search term cannot be empty" |
Empty search query | Provide search term |
"startItem must be >= 1" |
Invalid start position | Use 1-based indexing |
"numItems must be >= 1" |
Invalid page size | Use positive number |
"content item cannot be nil" |
Nil ContentItem | Provide valid ContentItem |
"container item cannot be nil" |
Nil container for NavigateContainer | Provide valid container |
"Pandora source account cannot be empty" |
Missing Pandora account | Configure Pandora account |
"token cannot be empty" |
Missing station token | Use token from search results |
"station name cannot be empty" |
Missing station name | Provide station name |
Navigation and station operations generate WebSocket events:
Generated when stations are added/removed that affect presets.
<presetsUpdated deviceID="A81B6A536A98">
<presets>
<!-- Updated preset list -->
</presets>
</presetsUpdated>
Generated when station operations affect current playback.
<nowPlayingUpdated deviceID="A81B6A536A98">
<nowPlaying source="PANDORA">
<ContentItem source="PANDORA" location="R456" sourceAccount="user123" isPresetable="true">
<itemName>Taylor Swift Radio</itemName>
</ContentItem>
<track>Love Story</track>
<artist>Taylor Swift</artist>
<playStatus>PLAY_STATE</playStatus>
</nowPlaying>
</nowPlayingUpdated>
Always validate parameters before API calls:
func validateNavigateParams(source string, startItem, numItems int) error {
if source == "" {
return fmt.Errorf("source cannot be empty")
}
if startItem < 1 {
return fmt.Errorf("startItem must be >= 1")
}
if numItems < 1 {
return fmt.Errorf("numItems must be >= 1")
}
return nil
}
Handle both network and API errors:
response, err := client.Navigate("TUNEIN", "", 1, 25)
if err != nil {
// Check if it's a known API error
if strings.Contains(err.Error(), "not available") {
log.Printf("TuneIn not configured on device")
return
}
return fmt.Errorf("navigation failed: %w", err)
}
Use appropriate page sizes for different contexts:
// Small pages for interactive browsing
response, err := client.Navigate("TUNEIN", "", 1, 25)
// Larger pages for bulk processing
response, err := client.Navigate("STORED_MUSIC", "device/0", 1, 100)
Cache frequently accessed data:
type CachedClient struct {
client *client.Client
sources *models.Sources
sourcesTime time.Time
}
func (c *CachedClient) GetSources() (*models.Sources, error) {
if c.sources == nil || time.Since(c.sourcesTime) > 5*time.Minute {
var err error
c.sources, err = c.client.GetSources()
c.sourcesTime = time.Now()
return c.sources, err
}
return c.sources, nil
}
For complete usage examples and workflows, see NAVIGATION-GUIDE.md.