Summary
This spec covers BluOS-based media players (Bluesound and NAD product lines) controlled via the BluOS Custom Integration HTTP API. All requests are HTTP GET (or POST for /reboot) sent to http://<player_ip>:<port>/<request>, returning UTF-8 encoded XML. Default port is 11000; NAD CI580 uses 11000/11010/11020/11030 per node. Discovery is via mDNS services musc.tcp and musp.tcp, or via the custom Lenbrook Service Discovery Protocol (LSDP) over UDP 11430.
Transport
protocols:
- http
addressing:
port: 11000
base_url: "http://<player_ip>:11000"
auth:
type: none # inferred: no auth procedure in source
# LSDP discovery uses separate transport - emitted as informational note,
# not as a primary control protocol.
# protocols: [udp]
# addressing:
# port: 11430
Traits
traits:
- levelable # /Volume command sets absolute, relative dB, mute
- queryable # /Status, /SyncStatus, /Playlist, /Presets return state
- routable # /Play?url / /Play?inputTypeIndex select inputs/sources
Actions
- id: status
label: Get Playback Status
kind: query
command: "GET /Status?timeout={timeout}&etag={etag}"
params:
- name: timeout
type: integer
description: "Optional. Long-poll duration in seconds. Recommended 100, min 10."
- name: etag
type: string
description: "Optional. etag from previous /Status response."
- id: sync_status
label: Get Player and Group Sync Status
kind: query
command: "GET /SyncStatus?timeout={timeout}&etag={etag}"
params:
- name: timeout
type: integer
description: "Optional. Long-poll duration in seconds. Recommended 180."
- name: etag
type: string
description: "Optional. etag from previous /SyncStatus response."
- id: set_volume_level
label: Set Volume (0..100)
kind: action
command: "GET /Volume?level={level}&tell_slaves={tell_slaves}"
params:
- name: level
type: integer
description: "Integer 0..100."
- name: tell_slaves
type: integer
description: "0 = this player only, 1 = apply to all grouped players."
- id: set_volume_abs_db
label: Set Volume (absolute dB)
kind: action
command: "GET /Volume?abs_db={db}&tell_slaves={tell_slaves}"
params:
- name: db
type: number
description: "Absolute dB level."
- name: tell_slaves
type: integer
description: "0 = this player only, 1 = apply to all grouped players."
- id: set_volume_delta_db
label: Adjust Volume (relative dB)
kind: action
command: "GET /Volume?db={delta_db}&tell_slaves={tell_slaves}"
params:
- name: delta_db
type: number
description: "Signed dB delta (positive = louder, negative = quieter)."
- name: tell_slaves
type: integer
description: "0 = this player only, 1 = apply to all grouped players."
- id: volume_up
label: Volume Up
kind: action
command: "GET /Volume?db=2"
params: []
- id: volume_down
label: Volume Down
kind: action
command: "GET /Volume?db=-2"
params: []
- id: mute_on
label: Mute On
kind: action
command: "GET /Volume?mute=1"
params: []
- id: mute_off
label: Mute Off
kind: action
command: "GET /Volume?mute=0"
params: []
- id: play
label: Play
kind: action
command: "GET /Play"
params: []
- id: play_with_seek
label: Play with Seek
kind: action
command: "GET /Play?seek={seconds}"
params:
- name: seconds
type: integer
description: "Position within current track. Requires /Status response totlen."
- id: play_with_seek_and_track
label: Play at Seek in Track
kind: action
command: "GET /Play?seek={seconds}&id={trackid}"
params:
- name: seconds
type: integer
description: "Position within track."
- name: trackid
type: integer
description: "Track id from /Status song attribute."
- id: play_url
label: Play URL
kind: action
command: "GET /Play?url={encodedStreamURL}"
params:
- name: encodedStreamURL
type: string
description: "URL-encoded stream URL."
- id: pause
label: Pause
kind: action
command: "GET /Pause"
params: []
- id: pause_toggle
label: Pause Toggle
kind: action
command: "GET /Pause?toggle=1"
params: []
- id: stop
label: Stop
kind: action
command: "GET /Stop"
params: []
- id: skip
label: Skip to Next Track
kind: action
command: "GET /Skip"
params: []
- id: back
label: Back / Previous Track
kind: action
command: "GET /Back"
params: []
- id: shuffle_on
label: Shuffle On
kind: action
command: "GET /Shuffle?state=1"
params: []
- id: shuffle_off
label: Shuffle Off
kind: action
command: "GET /Shuffle?state=0"
params: []
- id: repeat_set
label: Set Repeat Mode
kind: action
command: "GET /Repeat?state={mode}"
params:
- name: mode
type: integer
description: "0 = repeat queue, 1 = repeat track, 2 = repeat off."
- id: action_radio
label: Radio Station Action (skip/love/ban/back)
kind: action
command: "GET /Action?service={service}&{action}={value}"
params:
- name: service
type: string
description: "Music service name, e.g. Slacker."
- name: action
type: string
description: "Action name from <action> element in /Status: skip, back, love, ban."
- name: value
type: string
description: "Action-specific identifier from /Status response."
- id: playlist_list
label: List Play Queue Tracks
kind: query
command: "GET /Playlist?start={first}&end={last}"
params:
- name: first
type: integer
description: "First entry to include (0-based)."
- name: last
type: integer
description: "Last entry to include."
- id: playlist_status
label: Get Play Queue Status
kind: query
command: "GET /Playlist?length=1"
params: []
- id: delete_track
label: Delete Track from Queue
kind: action
command: "GET /Delete?id={position}"
params:
- name: position
type: integer
description: "Track position in queue (1-based)."
- id: move_track
label: Move Track in Queue
kind: action
command: "GET /Move?new={destination}&old={origin}"
params:
- name: destination
type: integer
description: "New position."
- name: origin
type: integer
description: "Old position."
- id: clear_queue
label: Clear Play Queue
kind: action
command: "GET /Clear"
params: []
- id: save_queue
label: Save Play Queue as Playlist
kind: action
command: "GET /Save?name={playlist_name}"
params:
- name: playlist_name
type: string
description: "Name for the saved playlist."
- id: presets_list
label: List Presets
kind: query
command: "GET /Presets"
params: []
- id: preset_load
label: Load Preset
kind: action
command: "GET /Preset?id={presetId}"
params:
- name: presetId
type: string
description: "Preset id, '+1' for next, '-1' for previous."
- id: browse
label: Browse Music Content
kind: query
command: "GET /Browse?key={key}&withContextMenuItems=1"
params:
- name: key
type: string
description: "URL-encoded browseKey / nextKey / parentKey / contextMenuKey from prior response. Omit for top level."
- name: withContextMenuItems
type: integer
description: "1 to include inline context menu."
- id: browse_search
label: Search Music Content
kind: query
command: "GET /Browse?key={searchKey}&q={searchText}"
params:
- name: searchKey
type: string
description: "searchKey from prior browse response."
- name: searchText
type: string
description: "Search query."
- id: add_slave_single
label: Group One Secondary Player
kind: action
command: "GET /AddSlave?slave={secondaryPlayerIP}&port={secondaryPlayerPort}&group={GroupName}"
params:
- name: secondaryPlayerIP
type: string
description: "IP address of the secondary player."
- name: secondaryPlayerPort
type: integer
description: "Port of the secondary player. Default 11000."
- name: GroupName
type: string
description: "Optional group name."
- id: add_slaves_multi
label: Group Multiple Secondary Players
kind: action
command: "GET /AddSlave?slaves={secondaryPlayerIPs}&ports={secondaryPlayerPorts}"
params:
- name: secondaryPlayerIPs
type: string
description: "Comma-separated IP addresses."
- name: secondaryPlayerPorts
type: string
description: "Comma-separated port numbers."
- id: remove_slave_single
label: Ungroup One Secondary Player
kind: action
command: "GET /RemoveSlave?slave={secondaryPlayerIP}&port={secondaryPlayerPort}"
params:
- name: secondaryPlayerIP
type: string
description: "Secondary player IP."
- name: secondaryPlayerPort
type: integer
description: "Secondary player port."
- id: remove_slaves_multi
label: Ungroup Multiple Secondary Players
kind: action
command: "GET /RemoveSlave?slaves={secondaryPlayerIPs}&ports={secondaryPlayerPorts}"
params:
- name: secondaryPlayerIPs
type: string
description: "Comma-separated secondary IPs."
- name: secondaryPlayerPorts
type: string
description: "Comma-separated secondary ports."
- id: reboot
label: Reboot Player
kind: action
command: "POST /reboot with parameter yes=1"
params: []
- id: doorbell_chime
label: Doorbell Chime
kind: action
command: "GET /Doorbell?play=1"
params: []
- id: play_input_capture
label: Select Active Input (Capture URL)
kind: action
command: "GET /Play?url={URL_value}"
params:
- name: URL_value
type: string
description: "URL attribute from /RadioBrowse?service=Capture response."
- id: play_input_index_legacy
label: Select External Input by Index (firmware >3.8.0 and <4.2.0)
kind: action
command: "GET /Play?inputIndex={IndexId}"
params:
- name: IndexId
type: integer
description: "1-based index from /Settings?id=capture&schemaVersion=32. Bluetooth excluded."
- id: play_input_typeindex
label: Select External Input by Type and Index (firmware >=4.2.0)
kind: action
command: "GET /Play?inputTypeIndex={type}-{index}"
params:
- name: type
type: string
description: "spdif|analog|coax|bluetooth|arc|earc|phono|computer|aesebu|balanced|microphone"
- name: index
type: integer
description: "1-based index for inputs of the same type."
- id: radio_browse
label: List Capture Inputs
kind: query
command: "GET /RadioBrowse?service=Capture"
params: []
- id: settings_capture
label: Get Capture Settings
kind: query
command: "GET /Settings?id=capture&schemaVersion=32"
params: []
- id: set_bluetooth_mode
label: Set Bluetooth Autoplay Mode
kind: action
command: "GET /audiomodes?bluetoothAutoplay={value}"
params:
- name: value
type: integer
description: "0=Manual, 1=Automatic, 2=Guest, 3=Disabled."
Feedbacks
- id: playback_state
type: enum
values: [play, pause, stop, stream, connecting]
description: "From /Status <state> and post-action responses."
- id: volume_percent
type: integer
description: "0..100 (or -1 for fixed volume). From /Status and /SyncStatus."
- id: volume_db
type: number
description: "Volume in dB. From /Status <db>."
- id: mute_state
type: boolean
description: "From <mute> element."
- id: shuffle_state
type: enum
values: [0, 1]
description: "0=off, 1=on."
- id: repeat_state
type: enum
values: [0, 1, 2]
description: "0=queue, 1=track, 2=off."
- id: current_track_metadata
type: object
description: "title1/title2/title3, name, artist, album, quality, streamFormat from /Status."
- id: sync_stat
type: string
description: "Opaque id indicating /SyncStatus or /Status change."
- id: etag
type: string
description: "Opaque value used for long-polling change detection."
- id: group_name
type: string
description: "From /SyncStatus <group>."
- id: group_volume
type: integer
description: "Group volume 0..100 from /SyncStatus."
Variables
# Each preset and each entry in the play queue is identifiable by id but the API does not
# expose them as generic addressable parameters beyond id-based queries.
# UNRESOLVED: no enumerator-style parameters in source; section omitted intentionally.
Events
# The BluOS API is request/response; the device pushes no unsolicited events.
# Long-polling to /Status and /SyncStatus is the source's documented change-detection
# mechanism, not a true push event channel.
# UNRESOLVED: no unsolicited event channel documented in source.
Macros
# UNRESOLVED: source does not document any multi-step macro primitives. Group
# teardown on removing a primary player of 3+ is described behavior, not a
# user-defined macro. No section content to enumerate.
Safety
confirmation_required_for:
- reboot # POST /reboot restarts the player; affects availability
interlocks: [] # UNRESOLVED: source documents no interlock sequences
# UNRESOLVED: source contains no safety warnings, voltage/power specs, or
# power-on sequencing requirements. /reboot is the only state-disruptive action.
Notes
- All endpoints accept HTTP GET and return UTF-8 XML. /reboot is the only documented POST endpoint.
- Default TCP port 11000; NAD CI580 uses 11010/11020/11030 for nodes 2/3/4. Discover actual port via mDNS services
musc.tcpandmusp.tcpor via LSDP. - Polling discipline: regular poll ≤ once per 30 s; long-poll ≥ 1 s between requests for the same resource.
- Long-poll returns early only on
etagchange; track position (secs) does not trigger it. - Volumes below 0 dB floor of the player's configured range are clamped; floor is typically -80..0 dB and is set in the BluOS Controller app.
- Two
/Playinput-selection forms coexist:inputIndex(firmware between v3.8.0 and v4.2.0) andinputTypeIndex(firmware v4.2.0+). Pick based on deployed firmware. - LSDP (Lenbrook Service Discovery Protocol) runs over UDP broadcast on port 11430 and is documented in §13 of the source. It is a discovery-only side-channel, not a control transport, so it is not included in
protocols.
Spec written.
## Provenance
```yaml
source_domains:
- bluos.io
source_urls:
- https://bluos.io/wp-content/uploads/2025/06/BluOS-Custom-Integration-API_v1.7.pdf
retrieved_at: 2026-05-22T15:38:02.168Z
last_checked_at: 2026-09-09T22:16:48.223Z
Verification Summary
verdict: verified
checked_at: 2026-09-09T22:16:48.223Z
matched_actions: 44
action_count: 44
confidence: medium
summary: "All 44 spec actions match HTTP endpoints documented verbatim in the source; transport and shapes agree; source catalogue is fully represented. (7 unresolved item(s) noted in Known Gaps.)"
Known Gaps
- "source document does not enumerate specific model numbers beyond citing CI580 and PULSE P300 examples; the set of compatible models is assumed from document scope."
- "no enumerator-style parameters in source; section omitted intentionally."
- "no unsolicited event channel documented in source."
- "source does not document any multi-step macro primitives. Group"
- "source documents no interlock sequences"
- "source contains no safety warnings, voltage/power specs, or"
- "firmware compatibility range not stated per model; full set of compatible models not enumerated in source beyond scope title. Bluetooth behavior (per-mode restrictions noted for /Skip, /Back) is service-dependent, not a global constraint."
From the AI4AV catalog (https://ai4av.net) · ODbL-1.0