Summary

BluOS is an advanced operating system and music management software found in products from Bluesound, NAD Electronics, DALI Loudspeakers, and others. This spec covers the BluOS Custom Integration API (version 1.7), which provides HTTP GET-based control over playback, volume, presets, content browsing, queue management, player grouping, and direct input selection. All requests are sent as HTTP GET (except reboot which uses POST) to http://<player_ip>:<port>/<command> and responses are returned as UTF-8 encoded XML.

Transport

protocols:
  - http
addressing:
  port: 11000  # Default port for all BluOS players except CI580; CI580 node 1=11000, node 2=11010, node 3=11020, node 4=11030
  base_url: "http://<player_ip>:11000"
auth:
  type: none  # inferred: no auth procedure in source

Traits

- queryable    # inferred from query command examples (/Status, /SyncStatus, /Volume, /Playlist, /Presets)
- levelable    # inferred from volume control commands (/Volume with level, db, abs_db, mute parameters)
- routable     # inferred from direct input selection commands (/Play?url=, /Play?inputTypeIndex=)

Actions

# --- Playback Control ---

- id: play
  label: Play
  kind: action
  params: []
  notes: "GET /Play — starts playback of current audio source"

- id: play_seek
  label: Play with Seek
  kind: action
  params:
    - name: seek
      type: integer
      description: Jump to position in seconds in the current track (only valid if /Status includes <totlen>)
  notes: "GET /Play?seek=<seconds>"

- id: play_seek_track
  label: Play Track at Position
  kind: action
  params:
    - name: seek
      type: integer
      description: Position in seconds
    - name: id
      type: integer
      description: Track id (position in queue)
  notes: "GET /Play?seek=<seconds>&id=<trackid>"

- id: play_url
  label: Play Stream URL
  kind: action
  params:
    - name: url
      type: string
      description: URL-encoded stream URL of audio to play
  notes: "GET /Play?url=<encodedStreamURL>"

- id: pause
  label: Pause
  kind: action
  params: []
  notes: "GET /Pause"

- id: pause_toggle
  label: Pause Toggle
  kind: action
  params: []
  notes: "GET /Pause?toggle=1 — toggles current pause state"

- id: stop
  label: Stop
  kind: action
  params: []
  notes: "GET /Stop"

- id: skip
  label: Skip to Next Track
  kind: action
  params: []
  notes: "GET /Skip — skips to next track in queue; wraps to first if at last"

- id: back
  label: Back / Previous Track
  kind: action
  params: []
  notes: "GET /Back — goes to start of track if >4s played, else previous track"

- id: shuffle
  label: Set Shuffle
  kind: action
  params:
    - name: state
      type: integer
      description: "0 = disable shuffle, 1 = enable shuffle"
  notes: "GET /Shuffle?state=<0|1>"

- id: repeat
  label: Set Repeat
  kind: action
  params:
    - name: state
      type: integer
      description: "0 = repeat queue, 1 = repeat current track, 2 = repeat off"
  notes: "GET /Repeat?state=<0|1|2>"

- id: action_service
  label: Streaming Radio Action (skip/back/love/ban)
  kind: action
  params:
    - name: action_url
      type: string
      description: Full action URI as provided in <action> element from /Status response
  notes: "Streaming radio actions (skip, back, love, ban); URL is taken from <action> element in /Status; not all stations support all actions"

# --- Volume Control ---

- id: volume_set
  label: Set Volume Level
  kind: action
  params:
    - name: level
      type: integer
      description: Absolute volume level 0–100
    - name: tell_slaves
      type: integer
      description: "Optional. 0 = only this player, 1 = all players in group"
  notes: "GET /Volume?level=<level>&tell_slaves=<0|1>"

- id: volume_set_db
  label: Set Volume (Absolute dB)
  kind: action
  params:
    - name: abs_db
      type: number
      description: Absolute volume in dB (range typically -80 to 0)
    - name: tell_slaves
      type: integer
      description: "Optional. 0 = only this player, 1 = all players in group"
  notes: "GET /Volume?abs_db=<db>"

- id: volume_relative_db
  label: Volume Relative Change (dB)
  kind: action
  params:
    - name: db
      type: number
      description: Relative dB change; positive = up, negative = down (typical: +2 or -2)
    - name: tell_slaves
      type: integer
      description: "Optional. 0 = only this player, 1 = all players in group"
  notes: "GET /Volume?db=<delta>"

- id: mute_on
  label: Mute
  kind: action
  params: []
  notes: "GET /Volume?mute=1"

- id: mute_off
  label: Unmute
  kind: action
  params: []
  notes: "GET /Volume?mute=0"

# --- Presets ---

- id: list_presets
  label: List Presets
  kind: action
  params: []
  notes: "GET /Presets — returns all presets on the player"

- id: load_preset
  label: Load Preset
  kind: action
  params:
    - name: id
      type: string
      description: "Preset id number, or +1 for next preset, or -1 for previous preset"
  notes: "GET /Preset?id=<id|+1|-1>"

# --- Play Queue Management ---

- id: list_tracks
  label: List Play Queue
  kind: action
  params:
    - name: length
      type: integer
      description: "Optional. Set to 1 to return only queue status (no track details)"
    - name: start
      type: integer
      description: "Optional. First entry to include (0-based) for pagination"
    - name: end
      type: integer
      description: "Optional. Last entry to include for pagination"
  notes: "GET /Playlist — not recommended without length or start/end due to potentially large response"

- id: delete_track
  label: Delete Track from Queue
  kind: action
  params:
    - name: id
      type: integer
      description: Track id (position in queue) to remove
  notes: "GET /Delete?id=<position>"

- id: move_track
  label: Move Track in Queue
  kind: action
  params:
    - name: new
      type: integer
      description: New position in queue
    - name: old
      type: integer
      description: Original position in queue
  notes: "GET /Move?new=<dest>&old=<origin>"

- id: clear_queue
  label: Clear Play Queue
  kind: action
  params: []
  notes: "GET /Clear — removes all tracks from the play queue"

- id: save_queue
  label: Save Play Queue as Playlist
  kind: action
  params:
    - name: name
      type: string
      description: Name for the saved playlist
  notes: "GET /Save?name=<playlist_name>"

# --- Content Browsing ---

- id: browse
  label: Browse Music Content
  kind: action
  params:
    - name: key
      type: string
      description: "Optional. Browse key (URL-encoded) from a prior /Browse response; omit for top-level browse"
    - name: withContextMenuItems
      type: integer
      description: "Optional. Set to 1 to include inline context menus in response"
  notes: "GET /Browse?key=<key>&withContextMenuItems=1"

- id: search
  label: Search Music Content
  kind: action
  params:
    - name: key
      type: string
      description: Value from a 'searchKey' attribute in a prior /Browse response
    - name: q
      type: string
      description: Search string
  notes: "GET /Browse?key=<searchKey>&q=<searchText>"

# --- Player Grouping ---

- id: add_slave
  label: Group Secondary Player
  kind: action
  params:
    - name: slave
      type: string
      description: IP address of secondary player to add
    - name: port
      type: integer
      description: "Port of secondary player (default 11000)"
    - name: group
      type: string
      description: "Optional. Group name; BluOS assigns default if not provided"
  notes: "GET /AddSlave?slave=<ip>&port=<port>&group=<name>"

- id: add_slaves
  label: Group Multiple Secondary Players
  kind: action
  params:
    - name: slaves
      type: string
      description: Comma-separated IP addresses of secondary players
    - name: ports
      type: string
      description: Comma-separated port numbers of secondary players
  notes: "GET /AddSlave?slaves=<ip1,ip2>&ports=<port1,port2>"

- id: remove_slave
  label: Remove Player from Group
  kind: action
  params:
    - name: slave
      type: string
      description: IP address of player to remove
    - name: port
      type: integer
      description: Port of player to remove
  notes: "GET /RemoveSlave?slave=<ip>&port=<port>"

- id: remove_slaves
  label: Remove Multiple Players from Group
  kind: action
  params:
    - name: slaves
      type: string
      description: Comma-separated IP addresses of players to remove
    - name: ports
      type: string
      description: Comma-separated ports of players to remove
  notes: "GET /RemoveSlave?slaves=<ip1,ip2>&ports=<port1,port2>"

# --- Player Reboot ---

- id: reboot
  label: Soft Reboot
  kind: action
  params: []
  notes: "POST /reboot with body yes=1 (HTTP POST, not GET)"

# --- Doorbell ---

- id: doorbell_play
  label: Play Doorbell Chime
  kind: action
  params: []
  notes: "GET /Doorbell?play=1"

# --- Direct Input Selection ---

- id: play_input_url
  label: Play Active Input by URL
  kind: action
  params:
    - name: url
      type: string
      description: "URL-encoded input URL from /RadioBrowse?service=Capture response"
  notes: "GET /Play?url=<URL_value>; requires querying /RadioBrowse?service=Capture first"

- id: play_input_index
  label: Play Input by Index (firmware < 4.2.0)
  kind: action
  params:
    - name: inputIndex
      type: integer
      description: "1-based index of inputs from /Settings?id=capture&schemaVersion=32 (Bluetooth excluded)"
  notes: "GET /Play?inputIndex=<index>; valid for firmware newer than v3.8.0 and older than v4.2.0"

- id: play_input_type_index
  label: Play Input by Type and Index (firmware >= 4.2.0)
  kind: action
  params:
    - name: inputTypeIndex
      type: string
      description: "Format: <type>-<index>, e.g. spdif-1, analog-1, coax-1, bluetooth-1, arc-1, earc-1, phono-1, computer-1, aesebu-1, balanced-1, microphone-1"
  notes: "GET /Play?inputTypeIndex=<type>-<index>; valid for firmware v4.2.0 or newer"

# --- Bluetooth Mode ---

- id: set_bluetooth_mode
  label: Set Bluetooth Mode
  kind: action
  params:
    - name: bluetoothAutoplay
      type: integer
      description: "0 = Manual, 1 = Automatic, 2 = Guest, 3 = Disabled"
  notes: "GET /audiomodes?bluetoothAutoplay=<value>; no response body"

Feedbacks

# --- /Status response fields ---

- id: playback_state
  type: enum
  values: [play, pause, stop, stream, connecting]
  notes: "From /Status <state>; 'play' and 'stream' are equivalent"

- id: volume
  type: integer
  notes: "From /Status or /Volume; 0–100 scale; -1 means fixed volume"

- id: volume_db
  type: number
  notes: "From /Status or /Volume; volume level in dB"

- id: mute
  type: enum
  values: ["0", "1"]
  notes: "From /Status or /Volume; 1 = muted, 0 = unmuted"

- id: mute_volume
  type: integer
  notes: "From /Volume or /Status; unmuted volume (0–100) when player is muted"

- id: mute_db
  type: number
  notes: "From /Volume or /Status; unmuted volume in dB when player is muted"

- id: title1
  type: string
  notes: "First line of now-playing metadata; MUST be used in any 3-line UI"

- id: title2
  type: string
  notes: "Second line of now-playing metadata"

- id: title3
  type: string
  notes: "Third line of now-playing metadata"

- id: twoline_title1
  type: string
  notes: "First of two lines of now-playing metadata; MUST be used in any 2-line UI if present"

- id: twoline_title2
  type: string
  notes: "Second of two lines of now-playing metadata"

- id: artist
  type: string
  notes: "Artist name of current track; prefer title2 for UI display"

- id: album
  type: string
  notes: "Album name of current track; prefer title3 for UI display"

- id: track_name
  type: string
  notes: "Track name; prefer title1 for UI display (from <name> element)"

- id: image
  type: string
  notes: "URL of image for current audio; may redirect — use followRedirects=1 to avoid"

- id: service
  type: string
  notes: "Service id of current audio; not suitable for UI display"

- id: service_icon
  type: string
  notes: "URL of current service icon"

- id: shuffle
  type: enum
  values: ["0", "1"]
  notes: "0 = shuffle off, 1 = shuffle on"

- id: repeat
  type: enum
  values: ["0", "1", "2"]
  notes: "0 = repeat queue, 1 = repeat track, 2 = repeat off"

- id: current_position_secs
  type: integer
  notes: "Seconds played of current track; not used in etag; must be incremented client-side during playback"

- id: total_length_secs
  type: integer
  notes: "Total length of current track in seconds (from <totlen>)"

- id: can_seek
  type: enum
  values: ["0", "1"]
  notes: "1 if seek is possible for current track"

- id: can_move_playback
  type: boolean
  notes: "True if current content can be moved to another player"

- id: stream_url_flag
  type: string
  notes: "Presence of <streamUrl> means play queue is not the source; shuffle/repeat irrelevant; next/previous unavailable"

- id: sleep_minutes_remaining
  type: integer
  notes: "Minutes remaining before sleep timer activates"

- id: pid
  type: integer
  notes: "Unique play queue id; matches /Playlist id attribute"

- id: prid
  type: integer
  notes: "Unique preset id; changes if preset is modified"

- id: sync_stat
  type: integer
  notes: "Unique id indicating /SyncStatus changes; matches syncStat attribute of /SyncStatus response"

- id: quality
  type: string
  notes: "Audio quality: 'cd', 'hd', 'dolbyAudio', 'mqa', 'mqaAuthored', or numeric approximate bitrate"

- id: stream_format
  type: string
  notes: "Format of audio, e.g. 'MP3 320 kb/s'"

# --- /SyncStatus response fields ---

- id: sync_player_name
  type: string
  notes: "Player name from /SyncStatus <name>"

- id: sync_model_name
  type: string
  notes: "Player model name from /SyncStatus <modelName>"

- id: sync_model
  type: string
  notes: "Player model id from /SyncStatus <model>"

- id: sync_brand
  type: string
  notes: "Player brand name from /SyncStatus <brand>"

- id: sync_group
  type: string
  notes: "Group name from /SyncStatus <group>"

- id: sync_volume
  type: integer
  notes: "Volume level 0–100 from /SyncStatus; -1 = fixed volume"

- id: sync_initialized
  type: boolean
  notes: "True if player is set up; false if setup required via BluOS Controller app"

- id: sync_mac
  type: string
  notes: "Player MAC address / unique network interface id"

- id: sync_master
  type: string
  notes: "Master player IP; only present if this player is secondary in a group"

- id: sync_slaves
  type: array
  notes: "Secondary player IP addresses; only present if this player is the primary"

- id: sync_zone
  type: string
  notes: "Name of fixed group"

Variables

- id: polling_timeout
  type: integer
  description: "Long-poll timeout in seconds for /Status (recommended: 100s, minimum: 10s) and /SyncStatus (recommended: 180s)"
  notes: "Clients must not make two consecutive long-poll requests for same resource less than 1 second apart; regular polling limit is 1 request per 30 seconds"

- id: etag
  type: string
  description: "Opaque etag value returned in responses; used as parameter for subsequent long-poll requests to detect changes"

Events

# The BluOS API uses long-polling rather than push notifications.
# /Status and /SyncStatus support long-polling via timeout + etag parameters.
# Unsolicited push notifications are not described in source.
# UNRESOLVED: no unsolicited server-push event mechanism documented; all state changes are discovered via long-poll or regular poll

Macros

# Multi-step input selection workflow (required before playing a physical input):
# Step 1: GET /RadioBrowse?service=Capture to retrieve available input URLs
# Step 2: GET /Play?url=<URL_value> using URL from step 1
# Alternatively for firmware >= 4.2.0: GET /Play?inputTypeIndex=<type>-<index>

# UNRESOLVED: no other explicit multi-step macros described in source

Safety

confirmation_required_for: []
interlocks: []
# Notes: The reboot command (/reboot POST) is irreversible in the short term; the source
# does not describe any safety interlock or confirmation requirement for this command,
# but integrators should consider adding a confirmation step in their UI.
# UNRESOLVED: no safety warnings or interlock procedures documented in source

Notes

Protocol overview: All requests are HTTP GET to http://<player_ip>:<port>/<command> except player reboot which is HTTP POST. Responses are UTF-8 encoded XML. Port 11000 is used for all standard BluOS players. The CI580 has four streamer nodes sharing one chassis: node 1 = port 11000, node 2 = 11010, node 3 = 11020, node 4 = 11030. Actual port should be discovered via mDNS (services musc._tcp and musp._tcp) or the proprietary Lenbrook Service Discovery Protocol (LSDP) over UDP broadcast on port 11430.

Long polling: BluOS supports long polling on /Status and /SyncStatus via timeout and etag parameters. Regular polling is limited to at most one request per 30 seconds. Long polling clients must not send two consecutive requests for the same resource less than 1 second apart. The /Status response includes a <syncStat> element that indicates whether /SyncStatus has changed, reducing the need to poll both endpoints.

Grouped players: When players are grouped, the primary player controls the source. Requests directed to secondary players for status, playback, queue, and browsing are internally proxied to the primary player. Volume of each secondary player must be tracked via /SyncStatus long polling.

Image URL redirects: Any URL beginning with /Artwork may result in an HTTP redirect. Add followRedirects=1 as a request parameter when retrieving such images to avoid redirect handling in integrations.

streamUrl flag: The presence of a <streamUrl> element in /Status means the play queue is not the source. When present, shuffle and repeat are irrelevant and should be hidden from UI; next/previous track navigation may be unavailable unless streaming radio actions are provided.

Now-playing metadata display rule: Always use title1, title2, title3 for 3-line UI displays, and twoline_title1 / twoline_title2 (when present) for 2-line UI displays. Do not use raw name, artist, album fields for UI text.

Input selection firmware variants: The /Play?inputIndex= command works only for firmware newer than v3.8.0 and older than v4.2.0. The /Play?inputTypeIndex= command works for firmware v4.2.0 or newer. Integrators should implement both and detect firmware version at runtime to select the appropriate variant.

Device discovery (LSDP): BluOS implements a custom discovery protocol (LSDP) using UDP broadcast on port 11430 (IANA registered). Class ID 0x0001 = BluOS Player, 0x0003 = BluOS Player secondary (CI580), 0x0008 = BluOS Hub. Players also support mDNS (_musc._tcp, _musp._tcp). LSDP is preferred for networks where multicast is unreliable.

Provenance

source_domains: []
source_urls: []
retrieved_at: 2026-06-02T21:54:07.819Z
last_checked_at: 2026-06-02T21:54:07.819Z

Verification Summary

verdict: verified
checked_at: 2026-06-02T21:54:07.819Z
matched_actions: 36
action_count: 36
confidence: medium
summary: "All 36 spec actions traced to BluOS Custom Integration API v1.7. Full player control including playback, volume, grouping, presets, and queue confirmed. (8 unresolved item(s) noted in Known Gaps.)"

Known Gaps

- "authentication/security model not described in source; no login or token procedure documented"
- "no unsolicited server-push event mechanism documented; all state changes are discovered via long-poll or regular poll"
- "no other explicit multi-step macros described in source"
- "no safety warnings or interlock procedures documented in source"
- "authentication/authorization model not described — no login, token, or session mechanism documented in source"
- "error response format not fully documented — source mentions <error> root element with <message> and <detail> nodes for /Browse errors only; error handling for other endpoints not described"
- "firmware version compatibility ranges for most API features not stated (exception: input selection commands in section 11.2 document v3.8.0 and v4.2.0 thresholds)"
- "HTTPS/TLS support not mentioned in source; assumed plain HTTP"
- "model-specific source not located"

From the AI4AV catalog (https://ai4av.net) · ODbL-1.0