Skip to content

Configuration

Sendspin Bluetooth Bridge stores persistent settings in config.json inside the /config directory. You can manage those values through the web UI, through the Home Assistant addon options, or by editing the file directly.

There are two main ways to manage settings:

SurfaceBest forNotes
Web UIDay-to-day management of devices, adapters, Bluetooth inventory, auth, and runtime behaviorWorks for Docker, LXC, and addon installs
HA addon Configuration tabSupervisor-managed addon optionsUseful when you prefer HA-native editing or need to seed addon options outside the web UI

General tab of the redesigned Configuration section

The configuration area is organized into five tabs instead of one long form.

TabWhat lives there
GeneralBridge name, timezone, latency, ports, restart behavior, update policy, and guidance visibility
DevicesSaved speaker fleet table and advanced per-device fields
BluetoothAdapter naming, paired-device inventory, scan modal, reconnect policy, codec preference
Music AssistantConnection status, reconfigure flow, token helpers, MA endpoint, monitor, volume/mute routing
SecurityLocal auth, session timeout, and brute-force protection

The General tab contains settings that apply to the whole bridge instance:

  • Bridge name — appended to player names as Player @ Name.
  • Timezone — with a live preview of the current local time.
  • PulseAudio latency (ms) — higher values trade latency for stability on slower hardware.
  • Web UI port — direct browser port for Docker / LXC / systemd installs. Ignored in HA addon mode, where Home Assistant Supervisor assigns the ingress port dynamically.
  • Base player listen port — starting port for automatically assigned per-device sendspin listeners.
  • Smooth restart — mutes before restart and shows restart progress.
  • Show empty-state onboarding guidance — controls whether the first-run checklist stays visible when setup is incomplete.
  • Show recovery banners — controls whether top-level operator/recovery notices stay visible.
  • Check for updates / Update channel / Auto-update — available outside HA addon mode, with auto-update limited to supported runtimes.

If you leave the port fields empty, standalone installs default to 8080 for the web UI and 8928 for player listeners. In Home Assistant addon mode, all three channels (stable / rc / beta) ship with ingress_port: 0, so HA Supervisor picks a free port at runtime — that change shipped in v2.55.0 to stop addon variants from colliding with Matter / Thread and other host-network addons. The per-channel base listener defaults (8928 / 9028 / 9128) are still meaningful — they keep the device listeners disjoint when several addon variants run side-by-side. WEB_PORT set inside the addon is ignored; access the UI via the Home Assistant sidebar entry instead.

Devices tab with the saved fleet table

The Devices tab is now the canonical saved fleet table.

Each device row can store:

FieldPurpose
EnabledSkip a device in the saved fleet without deleting it
Player nameDisplay name in Music Assistant
MACSpeaker Bluetooth address
AdapterSpecific adapter binding, if needed
PortOptional custom listen_port; otherwise the bridge uses BASE_LISTEN_PORT + device index
Delaystatic_delay_ms post-sink hardware latency compensation (0–5 000 ms, default 300 for new devices)
LiveRuntime badge such as Playing, Connected, Released, or Not seen
ActionsRemove the row or act on the saved configuration

Advanced row details expose:

  • Preferred format such as flac:44100:16:2.
  • Listen host override (listen_host) for the advertised device address.
  • Keepalive interval (keepalive_interval) for speakers that go to sleep aggressively.

Current runtime behavior is interval-based: any positive keepalive_interval enables silence keepalive, values below 30 seconds are raised to 30, and 0 or an empty field disables it. Older Home Assistant addon configs may still contain the legacy keepalive_silence flag, but the current bridge behavior keys off keepalive_interval > 0.

Bluetooth tab with adapter inventory and recovery policy

The Bluetooth tab combines inventory, discovery, and policy:

  • Rename adapters for clearer dashboard labels.
  • Add manual adapter entries when auto-detect is incomplete.
  • Refresh detection without leaving the page.
  • Work from the Paired devices inventory for import, repair, or cleanup.
  • Open the dedicated Scan nearby modal from this tab.
  • Set BT check interval for reconnect probing.
  • Set Auto-disable threshold for unstable devices. When the threshold is reached, the device is persisted as disabled until you re-enable it.
  • Toggle Prefer SBC codec to reduce CPU load.

The Bluetooth scan flow is now its own modal rather than an inline card:

  • choose All adapters or a specific adapter;
  • leave Audio devices only enabled for normal speaker discovery;
  • watch the countdown and progress bar while the scan runs;
  • use Add or Add & Pair directly from the results;
  • use Rescan after the cooldown without reopening the modal.

If the host already knows the speaker, the Already paired devices list is usually faster than scanning again.

Music Assistant connection status card with Reconfigure action and current bridge integration state

Music Assistant tab with token actions and bridge integration settings

The Music Assistant tab includes both connection state and auth helpers:

  • Connection status card showing whether the bridge is connected and which account/token last authenticated it.
  • Reconfigure button on that status card once a working token already exists.
  • Discover nearby MA servers or reuse a known URL.
  • Get token with MA credentials. On success the bridge stores MA_API_URL, a long-lived MA_API_TOKEN, and MA_USERNAME; the password is not stored.
  • Home Assistant fallback when the MA instance is HA-backed and direct MA login needs HA OAuth / MFA.
  • Get token automatically for HA-backed MA targets.
  • Manual MA API token field.
  • MA server and MA WebSocket port for the sendspin endpoint.
  • WebSocket monitor, Route volume through MA, and Route mute through MA toggles.

A few behaviors matter when documenting or operating the MA flow:

  • The Sign in & token card hides itself when the connection is healthy, then reappears when you click Reconfigure or when the bridge is not connected.
  • Get token automatically is relevant only for HA-backed Music Assistant targets.
  • Auto-get token on UI open is an addon-oriented convenience setting. It works only when the page is running under HA Ingress and the browser already has a valid Home Assistant session/token.
  • If silent auth cannot complete, the UI falls back to the visible HA-assisted or manual token flow.

In standalone deployments, the Security tab controls local access to the web UI:

  • Enable web UI authentication.
  • Session timeout in hours (1–168).
  • Brute-force protection toggle.
  • Max attempts, Window, and Lockout policy fields.
  • Set password flow for the local password hash.

Standalone login uses CSRF-protected forms plus a SameSite=Lax, HttpOnly session cookie. In Home Assistant addon mode, auth is always enforced by Home Assistant / Ingress instead, so the standalone security controls are hidden.

The footer is shared across tabs:

  • Save writes config.json now and applies changes on-line where possible. Per-device tuning (static_delay_ms, idle mode, room metadata) is hot-applied via IPC to the running daemon subprocess. Fields that bind at spawn time (listen_port, preferred_format, adapter, player_name) trigger a warm restart of the single affected subprocess (~3–5 s of silence on that speaker). Global fields that drive every subprocess (SENDSPIN_SERVER, BRIDGE_NAME, PULSE_LATENCY_MSEC, BT tuning) warm-restart every running speaker in parallel. The save response toast shows exactly what was applied, what is restarting, and what still needs a full bridge restart.
  • Save & Restart saves and restarts immediately. Use this for Flask-bound fields (WEB_PORT, auth/session settings, SECRET_KEY, brute-force limits) or when adding a brand-new device MAC that wasn’t running before.
  • Cancel restores the last saved values in the form.
  • Download exports a share-safe timestamped JSON file with secrets such as MA_API_TOKEN, password hash, and secret key removed.
  • Upload imports a previously exported config and preserves the existing password hash, secret key, and stored MA token on the server.

HA addon configuration panel with core options

When running as a Home Assistant addon, the Supervisor exposes a native Configuration tab in HA.

Go to Settings → Add-ons → Sendspin Bluetooth Bridge → Configuration.

Core addon options include:

OptionPurpose
sendspin_serverHostname/IP of the Music Assistant server (no scheme prefix, no port, no path — just 192.168.1.11 or ma.local), or auto for mDNS
sendspin_portSendspin WebSocket port — default 8927 matches Music Assistant’s Sendspin provider. Legacy 9000 configs keep working; the bridge probes and auto-shifts if the configured port is closed
web_portRead but not applied in addon mode; Supervisor assigns the ingress port dynamically. Kept in the schema for legacy parity — you can leave it blank.
base_listen_portStarting port for auto-assigned device listeners
bridge_nameOptional instance label appended to players
tzIANA timezone
pulse_latency_msecAudio buffer latency hint
prefer_sbc_codecLower-CPU codec preference
bt_check_intervalReconnect polling interval
bt_max_reconnect_failsAuto-disable threshold
auth_enabledDirect-listener auth toggle; HA addon mode still enforces HA auth regardless
ma_api_url / ma_api_tokenMusic Assistant REST integration
ma_auto_silent_authAllow addon/Ingress pages to try silent HA-backed MA token creation on open
volume_via_ma / mute_via_maRoute controls through MA to keep UI state aligned
update_channelIn-app release-lane preference for update checks and warnings
log_levelBase logging verbosity

HA addon device and adapter lists plus device edit dialog

Home Assistant addon device edit dialog

The addon form also exposes the full Bluetooth devices and Bluetooth adapters structures. Older addon configs may still preserve a legacy keepalive_silence field during translation, but current runtime behavior is driven by keepalive_interval.

The bridge supports two optional top-level port overrides:

KeyApplies toDefaultNotes
WEB_PORTWeb UI listener8080 outside HA addon modeIgnored in HA addon mode. HA Supervisor dynamically assigns the ingress port at runtime (the addon manifest declares ingress_port: 0), so there is no fixed addon port for WEB_PORT to override. Access the UI via the addon’s Home Assistant sidebar entry.
BASE_LISTEN_PORTAuto-assigned per-device Sendspin listeners8928 outside HA addon modeUsed as the starting port when a device does not define its own listen_port. Honoured in all runtimes including HA addon mode.

In Home Assistant addon mode, the listener ranges are intentionally separated by channel so that multiple addon variants can co-exist on the same HAOS host without colliding on per-device listener ports:

Installed addon trackChannel base listen portChannel web-port fallback
Stable89288080
RC90288081
Beta91288082

The base listen port column is the active default for that channel and shows up directly in the device listener URLs. The web-port fallback column is a safety value used only if the bridge cannot reach /addons/self/info on the Supervisor REST API to read the dynamically assigned ingress port — in normal operation you will never see those numbers, because Supervisor returns its own runtime-assigned port and the UI is served on that.

Use overrides only when you need:

  • a custom web port for Docker / LXC / systemd deployments (HA addon mode does not currently support a direct non-Ingress listener — WEB_PORT is read but discarded);
  • a different starting point for the device-listener range on the same host (e.g. running two non-addon bridge instances).

Each Bluetooth device may also define its own listen_host and listen_port.

Use device-level overrides when:

  • a single speaker must keep a stable known port;
  • you are splitting devices across several bridge instances and want explicit port planning;
  • you need to avoid a collision without moving the whole bridge’s base range.
KeyTypeDescription
SENDSPIN_SERVERstringMusic Assistant host or auto
SENDSPIN_PORTintegerSendspin WebSocket port
WEB_PORTinteger or nullOptional web UI port override
BASE_LISTEN_PORTinteger or nullOptional base port for auto-assigned device listeners
BRIDGE_NAMEstringOptional label appended to player names
TZstringIANA timezone
PULSE_LATENCY_MSECintegerAudio buffer latency hint
BT_CHECK_INTERVALintegerProbe interval for Bluetooth recovery
BT_MAX_RECONNECT_FAILSintegerAuto-disable threshold; 0 means unlimited
BT_CHURN_THRESHOLDintegerRapid-reconnect churn-isolation threshold; 0 disables
BT_CHURN_WINDOWnumberTime window in seconds for churn detection (default 300)
PREFER_SBC_CODECbooleanLower-CPU codec preference
DISABLE_PA_RESCUE_STREAMSbooleanUnload PulseAudio module-rescue-streams at startup to prevent sink drift on reconnect
DUPLICATE_DEVICE_CHECKbooleanCross-bridge duplicate device detection
AUTH_ENABLEDbooleanEnable local web auth outside HA addon mode; HA addon mode always enforces auth
SESSION_TIMEOUT_HOURSintegerBrowser session lifetime
BRUTE_FORCE_PROTECTIONbooleanEnable temporary lockout after failed sign-ins
BRUTE_FORCE_MAX_ATTEMPTSintegerMaximum failed attempts within the window
BRUTE_FORCE_WINDOW_MINUTESintegerRolling window for failed attempts
BRUTE_FORCE_LOCKOUT_MINUTESintegerLockout duration
MA_API_URLstringMusic Assistant REST URL
MA_API_TOKENstringMusic Assistant API token
MA_USERNAMEstringUsername last used for a successful MA connection flow
MA_AUTO_SILENT_AUTHbooleanAllow addon/Ingress pages to try silent HA-backed MA token creation on open
MA_WEBSOCKET_MONITORbooleanLive queue/now-playing monitor
VOLUME_VIA_MAbooleanRoute volume changes through MA
MUTE_VIA_MAbooleanRoute mute changes through MA
SMOOTH_RESTARTbooleanMute before restart and show restart progress
UPDATE_CHANNELstringUpdate channel: stable, rc, or beta
AUTO_UPDATEbooleanAllow bridge-managed auto-update where supported
CHECK_UPDATESbooleanEnable update checks
LOG_LEVELstringBase log verbosity: DEBUG, INFO, WARNING, ERROR
HA_AREA_NAME_ASSIST_ENABLEDbooleanAuto-resolve Home Assistant area names for adapters
STARTUP_BANNER_GRACE_SECONDSintegerSeconds before the startup banner appears (0–300)
RECOVERY_BANNER_GRACE_SECONDSintegerSeconds before recovery/error banners appear (0–300)
TRUSTED_PROXIESarrayExtra proxy IPs allowed to supply trusted Ingress headers

The following keys are written by the bridge at runtime and should not normally be edited by hand:

KeyTypeDescription
CONFIG_SCHEMA_VERSIONintegerInternal schema version for config migrations
AUTH_PASSWORD_HASHstringPBKDF2-SHA256 hash of the web UI password
SECRET_KEYstringFlask session encryption key; auto-generated on first run
LAST_VOLUMESobjectPer-device persisted volume (MAC → integer)
LAST_SINKSobjectPer-device persisted PulseAudio sink name (MAC → string)
MA_AUTH_PROVIDERstringAuth provider used for the current MA connection ("ha", etc.)
MA_TOKEN_INSTANCE_HOSTNAMEstringHostname of the MA instance that issued the token
MA_TOKEN_LABELstringHuman-readable label for the stored MA token
MA_ACCESS_TOKENstringCurrent MA OAuth access token
MA_REFRESH_TOKENstringMA OAuth refresh token for automatic renewal
HA_ADAPTER_AREA_MAPobjectAdapter MAC → Home Assistant area mapping

Sensitive fields: AUTH_PASSWORD_HASH, SECRET_KEY, MA_ACCESS_TOKEN, and MA_REFRESH_TOKEN are stripped from config downloads. Upload preserves the server-side values for these keys.

{
"BLUETOOTH_DEVICES": [
{
"mac": "AA:BB:CC:DD:EE:FF",
"player_name": "Living Room Speaker",
"adapter": "hci0",
"static_delay_ms": 300,
"listen_host": "0.0.0.0",
"listen_port": 8928,
"preferred_format": "flac:44100:16:2",
"keepalive_interval": 60,
"enabled": true
}
]
}
FieldDescription
macSpeaker Bluetooth MAC
player_nameDisplay name in Music Assistant
adapterAdapter ID or MAC
static_delay_msDeclares the hardware latency this speaker adds beyond the audio sink, in milliseconds (0–5 000). The sendspin client subtracts this value from each chunk’s play-time, so audio is emitted earlier by exactly this amount and lands on schedule once the hardware adds its real latency back. Default for new devices is 300; raise it for speakers that consistently play behind the rest of a group, lower it for those that play ahead. See Delay tuning and keepalive and Measuring per-speaker latency with MassDroid.
listen_hostAdvertised host override for this device listener
listen_portCustom sendspin listener port; if missing, runtime uses BASE_LISTEN_PORT + device index
preferred_formatPreferred output format
keepalive_silenceLegacy compatibility flag from older addon configs; current runtime behavior does not expose a separate toggle in the web UI
keepalive_intervalSilence keepalive interval in seconds; any positive value enables keepalive, minimum effective interval is 30 seconds
room_idHome Assistant area / room ID for this device
room_nameHuman-readable room name
idle_disconnect_minutesDisconnect Bluetooth after this many idle minutes; 0 disables
enabledSkip on startup when false

Each effective listen_port must be unique across devices. If you run multiple bridge instances on the same host, either give each bridge a different BASE_LISTEN_PORT range or set explicit listen_port values per device.

{
"BLUETOOTH_ADAPTERS": [
{
"id": "hci0",
"mac": "C0:FB:F9:62:D6:9D",
"name": "Living room dongle"
}
]
}
FieldDescription
idAdapter interface name such as hci0
macAdapter MAC address
nameFriendly label shown in the UI
  • HA Ingress uses a dynamically assigned port from Home Assistant Supervisor (ingress_port: 0 in the addon manifest). WEB_PORT set inside the addon does not override this and does not open a parallel direct listener.
  • Multi-bridge setups outside HA addon mode should use non-overlapping WEB_PORT and BASE_LISTEN_PORT ranges. Multiple addon variants on the same HAOS host already get distinct BASE_LISTEN_PORT defaults per channel (8928 / 9028 / 9128) to avoid listener collisions.
  • Per-device overrides win: listen_port and listen_host override top-level defaults.
  • Port conflicts are fatal for the daemon: duplicate listen_port values will prevent a device listener from binding.

The bridge reads environment variables at startup. They fall into three groups: bootstrap variables you may set yourself, container internals set by entrypoint.sh, and Home Assistant addon variables injected by the Supervisor.

These are the most commonly used overrides. CONFIG_DIR always determines where config.json lives, and WEB_PORT / BASE_LISTEN_PORT environment values are resolved before stored config values.

VariableDefaultDescription
CONFIG_DIR/configConfig directory path
WEB_PORT8080 (standalone)Direct web UI port override for Docker / LXC / systemd. Ignored in HA addon mode — Supervisor assigns the ingress port dynamically and the bridge does not open a parallel direct listener.
BASE_LISTEN_PORT8928 (standalone)Starting port for auto-assigned player listeners. In HA addon mode the per-channel defaults are stable 8928, rc 9028, beta 9128 (so multiple addon variants do not clash).
TZfrom configTimezone override applied when the runtime initializes local time handling
BRIDGE_NAMEfrom configOptional bridge-name override before a stored name exists
LOG_LEVELINFOLogging level: DEBUG, INFO, WARNING, ERROR. Also settable via config or web UI
WEB_THREADS8Waitress HTTP worker thread count; increase to 16 for 20+ devices
DEMO_MODE(unset)Set to 1, true, or yes to run in demo/simulation mode without real Bluetooth hardware
SENDSPIN_NAMESendspin-{hostname}Override the default player name prefix
SENDSPIN_STATIC_DELAY_MS0Global static audio delay override in milliseconds (0–5 000). Values outside the range are clamped; legacy negative defaults are no longer accepted by sendspin 7.0+.

Set automatically by entrypoint.sh during container startup. Rarely need manual adjustment.

VariableDefaultDescription
PULSE_SERVER(auto-detected)PulseAudio / PipeWire socket path (e.g. unix:/run/audio/pulse.sock)
PULSE_LATENCY_MSECfrom config (600)PulseAudio latency hint in milliseconds; set from PULSE_LATENCY_MSEC config key
PULSE_SINK(per-subprocess)Default PulseAudio sink for playback; set per device subprocess to route audio to the correct speaker
AUDIO_UID1000User ID for PulseAudio socket access
AUDIO_GID(from socket)Group ID for PulseAudio socket access
DBUS_SYSTEM_BUS_ADDRESS(auto-detected)D-Bus system bus socket path
STARTUP_DEPENDENCY_WAIT_ATTEMPTS45Maximum attempts to wait for D-Bus, Bluetooth, and audio during startup
STARTUP_DEPENDENCY_WAIT_DELAY_SECONDS1Delay in seconds between startup dependency checks

Injected by the HA Supervisor; read-only from the bridge’s perspective.

VariableDefaultDescription
SUPERVISOR_TOKEN(HA only)Home Assistant Supervisor API token; its presence enables addon mode
HA_CORE_URLhttp://homeassistant:8123Home Assistant Core URL used for auth flows and API calls
HOSTNAME(system)Container hostname; used for addon-type detection
SENDSPIN_HA_OPTIONS_FILE/data/options.jsonPath to the addon options file written by the Supervisor
SENDSPIN_HA_CONFIG_FILE/data/config.jsonPath to the translated config file used in addon mode