Skip to content

API Reference

The web interface exposes a REST/HTML API on the resolved web port. In Home Assistant add-on mode the effective port is track-aware (8080 stable, 8081 rc, 8082 beta), and most operators reach it through HA Ingress rather than directly.

Bridge snapshot used by the dashboard and SSE stream. For backward compatibility the top-level object still mirrors one device; multi-device deployments also include a devices array plus bridge-wide fields such as groups, disabled_devices, startup_progress, runtime_mode, and operator_guidance.

{
"player_name": "Living Room Speaker",
"connected": true,
"bluetooth_connected": true,
"has_sink": true,
"volume": 48,
"devices": [
{ "player_name": "Living Room Speaker", "connected": true },
{ "player_name": "Kitchen Speaker", "connected": false }
],
"groups": [],
"disabled_devices": [],
"ma_connected": true,
"startup_progress": {
"status": "complete",
"phase": "ready",
"current_step": 6,
"total_steps": 6,
"percent": 100,
"message": "Startup complete"
},
"runtime_mode": "production",
"operator_guidance": {
"mode": "healthy",
"header_status": {
"tone": "success",
"label": "1/1 active devices ready",
"summary": "All active devices have sinks and are ready for playback."
},
"issue_groups": []
}
}

Server-Sent Events stream that emits the same bridge snapshot shape as /api/status.

Runtime contract:

  • updates are batched with a 100 ms debounce window to avoid event storms;
  • the first response starts with a 2 KiB SSE comment padding block so HA ingress/proxies flush immediately;
  • a heartbeat comment is sent every 15 seconds;
  • sessions are capped at 30 minutes and the server limits the stream to 4 concurrent listeners.

Current startup/shutdown lifecycle snapshot.

{
"status": "running",
"phase": "web",
"current_step": 4,
"total_steps": 6,
"percent": 67,
"message": "Web interface and event loop ready",
"details": { "web_thread": "WebServer" },
"started_at": "2026-03-22T19:00:00+00:00",
"updated_at": "2026-03-22T19:00:02+00:00",
"completed_at": null
}

Explains whether the bridge is running in production or demo/mock mode.

Key fields: mode, is_mocked, simulator_active, fixture_devices, fixture_groups, disclaimer, mocked_layers, details, updated_at.

Bridge-level telemetry snapshot assembled from runtime state.

{
"bridge": {
"uptime_seconds": 1234,
"process_rss_mb": 84.1,
"python": "3.13.2",
"platform": "Linux-...",
"arch": "x86_64",
"kernel": "6.8.0",
"audio_server": "PulseAudio 17.0",
"bluez": "bluetoothctl: 5.79"
},
"startup_progress": { "status": "complete" },
"runtime_info": { "mode": "production" },
"subprocesses": [],
"event_hooks": { "delivery_mode": "runtime", "summary": { "registered_hooks": 0 } }
}

Returns the runtime webhook registry snapshot:

  • delivery_mode: "runtime"
  • summary with registered/success/failure counts
  • hooks[] with current registrations
  • recent_deliveries[] with the latest delivery attempts

Register a runtime-scoped webhook for bridge/device events.

Body parameters:

FieldTypeDescription
urlstringRequired absolute http:// or https:// URL
categoriesstring[]Optional event-category filter
event_typesstring[]Optional event-type filter
timeout_secnumberOptional request timeout, default 5.0

Notes: registrations are in-memory only; loopback, .local, and private-network targets are rejected.

Response: 201 Created

{
"success": true,
"hook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://example.net/sendspin-events",
"categories": ["bridge"],
"event_types": ["bridge.startup.completed"],
"timeout_sec": 5.0
}
}

Unregister a runtime hook. Returns { "success": true } or 404 when the hook does not exist.

Comprehensive masked diagnostics snapshot. In addition to environment/adapters/sinks/device state, the response includes:

  • contract_versions (config_schema_version, ipc_protocol_version)
  • startup_progress and runtime_info
  • ma_integration, sink_inputs, subprocesses, event_hooks
  • onboarding_assistant, recovery_assistant, operator_guidance
  • telemetry (same shape as /api/bridge/telemetry)

Builds a GitHub-issue-friendly bug-report bundle from masked diagnostics.

Response fields:

FieldDescription
markdown_shortShort markdown summary for issue bodies or clipboard use
text_fullFull plain-text report
suggested_descriptionEditable issue template derived from diagnostics
reportFull masked structured report

suggested_description is intentionally operator-editable. It is seeded from recent issue logs, Bluetooth connection health, device last_error fields, subprocess health, D-Bus / bluetoothd state, MA connectivity, and the top recovery-guidance issue.

Returns {"available": true|false} indicating whether the GitHub-App issue-submission proxy is configured on this bridge. The web UI uses this to decide whether to offer in-UI “Report a problem” submissions for users without GitHub accounts.

Creates a GitHub issue through the App proxy on behalf of a user who does not have a GitHub account. Requires the proxy to be configured (see /api/bugreport/proxy-available); otherwise returns 503.

Request body (JSON):

FieldDescription
titleIssue title (5–200 characters)
descriptionIssue description (10–5000 characters)
emailContact email, must contain @
diagnostics_textOptional masked diagnostics text to attach (truncated to fit GitHub’s 65 536-char limit)

Rate-limited per client IP by the proxy. Typical error responses: 400 (validation), 429 (rate limit), 503 (proxy not configured).

Downloads the masked diagnostics report as a plain-text attachment (diagnostics-<timestamp>.txt).

Returns configured players grouped by MA syncgroup. Groups may include external_members / external_count when the same MA syncgroup also contains players from other bridges.

First-run/setup guidance derived from preflight checks, config, device state, and MA connectivity.

Key fields: runtime_mode, counts, checks[], next_steps[], and checklist.

The checklist always orders steps as: bluetooth, audio, sink_verification, ma_auth, latency.

Recovery-oriented guidance derived from live device health and startup state.

Key fields: summary, issues[], traces[], safe_actions[], latency_assistant, and known_good_test_path.

Unified dashboard guidance surface assembled from onboarding + recovery.

Key fields:

  • modeempty_state, progress, attention, or healthy
  • visibility_keys — local UI preference keys for dismissible cards
  • header_status — compact status pill shown at the top of the dashboard
  • banner — optional attention banner
  • onboarding_card — optional guided setup card
  • issue_groups[] — grouped recovery/setup issues with recommended actions
{ "version": "2.x.y", "build_date": "2026-03-22" }

Lightweight health endpoint. Returns 200 OK with:

{ "ok": true }

Setup-verification endpoint used by onboarding and diagnostics.

{
"platform": "x86_64",
"audio": { "system": "pulseaudio", "socket": "unix:/run/pulse/native", "sinks": 2 },
"bluetooth": { "controller": true, "adapter": "C0:FB:F9:62:D6:9D", "paired_devices": 3 },
"dbus": true,
"memory_mb": 2048,
"version": "2.x.y",
"ok": true
}

Structured chronological recovery timeline built from device health and startup events.

{
"summary": { "entry_count": 3 },
"entries": [
{ "timestamp": "2026-03-22T19:00:00+00:00", "severity": "warning", "label": "BT reconnect", "summary": "..." }
]
}

Download the current recovery timeline as a CSV attachment (sendspin-recovery-timeline-<timestamp>.csv).

Rerun a single safe, non-destructive operator check (e.g. Bluetooth connectivity, audio sink verification).

Body:

{ "check_key": "bluetooth", "device_names": ["Living Room Speaker"] }
FieldTypeDescription
check_keystringRequired. The check identifier to rerun
device_namesstring[]Optional. Scope the check to specific devices

Response:

{ "check_key": "bluetooth", "summary": "All devices connected", "status": "pass" }

Returns 400 if check_key is unknown.

Return the current latency assistant payload from the recovery assistant. Includes recommended PULSE_LATENCY_MSEC value and explanations.

Persist a recommended Pulse latency value to config.json. Requires a restart to take effect.

Body: { "pulse_latency_msec": 800 }

Response:

{
"success": true,
"pulse_latency_msec": 800,
"restart_required": true,
"summary": "Saved Pulse latency 800 ms. Restart the bridge to apply the new buffer.",
"latency_assistant": { }
}

Pause/resume all players.

Body: { "action": "pause" } or { "action": "play" }

Pause or resume a specific MA sync group. For action="play", uses the MA REST API if configured so all group members resume in sync; falls back to Sendspin session command.

Body: { "group_id": "abc123", "action": "pause" } — action is "pause" or "play"

Set volume on one or more devices. Supports individual, group, and multi-target modes.

Body parameters:

FieldTypeDescription
volumeintegerTarget volume (0–100). Required.
macstringTarget a single device by MAC address
player_namestringTarget a single device by player name
player_namesstring[]Target multiple devices by name
group_idstringTarget all devices in a specific MA sync group
groupbooleanWhen true, uses MA’s proportional group_volume for sync group members
force_localbooleanWhen true, bypasses MA API and uses direct PulseAudio (pactl)

If no targeting field is provided (mac, player_name, player_names, group_id), volume is applied to all devices.

Routing logic (when VOLUME_VIA_MA is enabled and MA is connected):

  • group: true — sends group_volume once per unique MA sync group among selected targets. MA applies a proportional delta, preserving relative volumes. Devices not in any sync group receive the exact value via direct PulseAudio.
  • group: false (default) — sends volume_set to each target individually via MA API.
  • The response returns immediately with "via": "ma". The UI updates when bridge_daemon receives the echo from MA (~500 ms).

Fallback: if MA is offline, VOLUME_VIA_MA is disabled, or force_local: true, volume is set directly via PulseAudio and status updates immediately.

// Individual
{ "mac": "AA:BB:CC:DD:EE:FF", "volume": 75 }
// Group (proportional for sync groups, exact for solo devices)
{ "volume": 40, "group": true }
// Force local pactl
{ "mac": "AA:BB:CC:DD:EE:FF", "volume": 50, "force_local": true }

Pause or resume a single player. Sends the command via IPC to the target daemon subprocess which forwards it over the existing WebSocket connection to MA.

Body: { "player_name": "Living Room Speaker", "action": "pause" } — action is "pause" or "play"

Toggle mute on a device. When MUTE_VIA_MA is enabled and MA is connected, the mute command is routed through the MA API. Otherwise, mute is applied directly via PulseAudio.

Body: { "mac": "AA:BB:CC:DD:EE:FF", "muted": true }

Recovery action: force-unmute the PulseAudio sink for a device, bypassing MA routing. Intended for situations where the PA sink is muted at the system level (for example after a crash or restart) while the application-level mute is already off, which would otherwise leave the speaker silent.

Body: { "player_name": "Living Room Speaker" }

Returns 404 if no matching device is found, 400 if the device has no configured audio sink, or 500 if pactl reports the sink could not be unmuted.

Send a native Sendspin transport command to a specific device. Bypasses the MA REST API for lower latency by sending the command through the Sendspin Controller WebSocket channel.

Body:

FieldTypeDescription
actionstringRequired. One of: play, pause, stop, next, previous, volume, mute, repeat_off, repeat_one, repeat_all, shuffle, unshuffle, switch
device_indexintegerRequired. Zero-based index into the active device list
valueanyOptional. For volume (0–100) or mute (boolean)

Returns 400 if the action is invalid or not in the device’s supported_commands set.

Response: { "success": true }

MA credentials are persisted in config.json (MA_API_URL, MA_API_TOKEN). Successful auth flows automatically trigger group rediscovery so /api/status, /api/groups, and queue metadata can update without a restart.

Start asynchronous Music Assistant discovery.

  • In HA add-on mode the bridge prefers add-on-local MA URLs before falling back to saved config / mDNS.
  • The response is always async.
{ "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "running", "is_addon": true }

Poll MA discovery.

  • While running: { "status": "running", "is_addon": true }
  • On completion: returns the stored job payload (for example discovered servers or an error)

Direct Music Assistant login using MA credentials.

Body:

{ "url": "http://192.168.1.10:8095", "username": "ma_user", "password": "ma_pass" }

Contract notes:

  • url is optional. If omitted, the bridge tries the saved MA URL, SENDSPIN_SERVER, connected Sendspin hosts, then mDNS.
  • This endpoint is for MA’s own credential flow. It does not switch between HA auth providers.
  • On success the bridge saves the long-lived token and triggers rediscovery immediately.

Returns a self-contained HTML popup document for browser-driven Home Assistant login/MFA.

Query parameter: ma_url

The popup posts its success/failure result back to window.opener; it is not a JSON endpoint.

Silent add-on-mode auth using an existing HA access token.

Body:

{ "ha_token": "<HA access token>", "ma_url": "http://homeassistant.local:8095" }

Contract notes:

  • Intended for HA add-on runtime only.
  • The bridge validates the HA token over the HA WebSocket API, creates an MA token via ingress JSON-RPC, validates that token against the regular MA API, then saves it.
  • If a previously saved MA token already matches the same MA instance and still validates, the endpoint returns success without minting a new token.

Explicit Home Assistant credential flow with optional MFA.

Step 1 — init:

{ "step": "init", "ma_url": "http://192.168.1.10:8095", "username": "ha_user", "password": "ha_pass" }

Possible response:

{ "success": true, "step": "mfa", "auth_mode": "ha_direct", "flow_id": "...", "ha_url": "http://haos:8123", "client_id": "http://haos:8123/", "state": "...", "mfa_module_name": "Authenticator app" }

Step 2 — mfa:

{ "step": "mfa", "ma_url": "http://192.168.1.10:8095", "flow_id": "...", "ha_url": "http://haos:8123", "client_id": "http://haos:8123/", "auth_mode": "ha_direct", "code": "123456" }

A successful completion returns step: "done", saves the MA token, and triggers rediscovery.

Returns cached MA syncgroup players from the MA API. Empty list means discovery has not run yet or MA is not configured.

Re-run MA syncgroup discovery from the currently saved MA_API_URL / MA_API_TOKEN.

Response: 202 Accepted

{ "success": true, "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "running" }

Poll the async rediscovery job.

  • While running: { "status": "running" }
  • On completion: returns the stored job payload (for example syncgroups, mapped_players, groups, or an error)

Returns the bridge’s current MA now-playing cache.

  • When MA is inactive: { "connected": false }
  • When active: includes state, track, artist, album, image_url, elapsed, duration, shuffle, repeat, queue_index, queue_total, syncgroup_id, and optional adjacent-track metadata.

Send an asynchronous playback-control command to the active MA queue.

Body:

{ "action": "next", "syncgroup_id": "ma-syncgroup-abc123" }
FieldDescription
action"next", "previous", "shuffle", "repeat", or "seek"
valueFor shuffle: boolean. For repeat: "off", "all", "one". For seek: seconds
syncgroup_idOptional syncgroup target
player_idOptional explicit player target
group_idOptional legacy group target

Accepted response:

{
"success": true,
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"op_id": "6a6f...",
"syncgroup_id": "ma-syncgroup-abc123",
"queue_id": "up_abc123def456",
"accepted": false,
"confirmed": false,
"pending": true,
"ma_now_playing": { "state": "playing" }
}

The HTTP response is optimistic: it includes a predicted ma_now_playing patch immediately, while final confirmation arrives through the async job plus MaMonitor updates.

Poll the async MA queue-command job.

  • While running: { "status": "running" }
  • On completion: returns the stored job payload

Debug dump of MA cache keys, discovered groups, per-client player IDs, and live queue IDs fetched from the MA WebSocket.

Reload MA runtime pieces (credentials, WebSocket monitor, syncgroup cache) without restarting the full bridge service. Reads current MA_API_URL / MA_API_TOKEN from config.json and triggers group rediscovery.

Response (202):

{
"success": true,
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "running",
"monitor_reloaded": true
}

Proxy MA artwork images through the bridge so the web UI can use same-origin image URLs (avoids CORS issues).

Query parameters:

ParamTypeDescription
urlstringRequired. Artwork path or full URL on the MA server
sigstringRequired. HMAC signature for SSRF protection

Returns the artwork binary with the original Content-Type (e.g. image/jpeg). The MA bearer token is attached only when the URL targets the MA server origin. Returns 400 for invalid URLs or signatures, 413 for images exceeding 10 MB.

Force Bluetooth reconnect.

Body: { "mac": "AA:BB:CC:DD:EE:FF" }

Start pairing (~25 sec). Device must already be in pairing mode.

Body: { "mac": "AA:BB:CC:DD:EE:FF", "adapter": "hci0" }

Toggle Bluetooth management (Release/Reclaim).

Body: { "player_name": "Living Room", "enabled": false }

Toggle global device enabled/disabled state. Requires a restart to take effect — disabled devices are skipped completely (no client, no BT manager, no listen port).

Body: { "player_name": "Living Room", "enabled": false }

Response:

{
"success": true,
"enabled": false,
"restart_required": true,
"message": "Device will be disabled after restart"
}

Start a background Bluetooth scan (~10 s). Returns immediately with a job ID.

Response: { "job_id": "550e8400-e29b-41d4-a716-446655440000" }

Poll for scan results.

Response while running:

{ "status": "running" }

Response when complete:

{
"status": "done",
"devices": [
{ "mac": "AA:BB:CC:DD:EE:FF", "name": "JBL Flip 5" }
]
}

Response on error:

{ "status": "done", "error": "Scan failed: bluetoothctl timed out" }

List available Bluetooth adapters.

List currently paired devices (name + MAC).

Remove (unpair) a device from the BlueZ stack.

Body: { "mac": "AA:BB:CC:DD:EE:FF" }

Response: { "ok": true, "mac": "AA:BB:CC:DD:EE:FF" }

Return detailed bluetoothctl info for a device including pairing/trust/connection status.

Body: { "mac": "AA:BB:CC:DD:EE:FF" }

Response:

{
"mac": "AA:BB:CC:DD:EE:FF",
"name": "Living Room Speaker",
"alias": "Living Room Speaker",
"paired": "yes",
"bonded": "yes",
"trusted": "yes",
"connected": "yes",
"icon": "audio-card",
"raw": ["Device AA:BB:CC:DD:EE:FF ...", "..."]
}

Disconnect a Bluetooth device without removing its pairing.

Body: { "mac": "AA:BB:CC:DD:EE:FF" }

Response: { "ok": true, "mac": "AA:BB:CC:DD:EE:FF" }

Toggle a Bluetooth adapter’s power state.

Body:

FieldTypeDescription
adapterstringAdapter identifier (hci0, hci1, or MAC address). Empty = default adapter
powerbooleantrue to power on, false to power off (default: true)

Response: { "ok": true, "power": true }

Wake a device from idle-timeout standby (reconnects Bluetooth and restarts the daemon subprocess).

Body: { "player_name": "Living Room Speaker" }

Returns 409 if the device is not currently in standby.

Response: { "success": true, "message": "Device waking from standby" }

Put a device into standby mode (disconnects Bluetooth and parks the daemon subprocess on a null sink). Reduces power consumption for idle speakers.

Body: { "player_name": "Living Room Speaker" }

Returns 409 if the device is already in standby.

Response: { "success": true, "message": "Device entering standby" }

Remove a device and re-pair from scratch. Runs asynchronously: remove → power cycle → scan → pair → trust → connect.

Body:

FieldTypeDescription
macstringRequired. Device MAC address
adapterstringOptional. Adapter identifier (hci0 or MAC address)

Returns 409 if another Bluetooth operation is in progress.

Response: { "job_id": "550e8400-e29b-41d4-a716-446655440000" }

GET /api/bt/reset_reconnect/result/<job_id>

Section titled “GET /api/bt/reset_reconnect/result/<job_id>”

Poll for reset & reconnect result.

Response (running): { "status": "running" }

Response (done):

{ "status": "done", "success": true, "connected": true, "mac": "AA:BB:CC:DD:EE:FF" }

Pair a new Bluetooth device by MAC address (no existing bridge client required). Used for devices discovered via scan that are not yet in the bridge config.

Body:

FieldTypeDescription
macstringRequired. Device MAC address
adapterstringOptional. Adapter identifier

Returns 409 if another Bluetooth operation is in progress.

Response: { "job_id": "550e8400-e29b-41d4-a716-446655440000" }

Poll for standalone pair result.

Response (running): { "status": "running" }

Response (done): { "status": "done", "success": true, "mac": "AA:BB:CC:DD:EE:FF" }

Recent log lines from the bridge. Useful for debugging without SSH access.

Query parameters:

  • lines — number of lines to return (default 100)

Download full service logs (up to 500 lines) as a text file attachment (sendspin-logs-<timestamp>.txt). Reads from journalctl, HA Supervisor, or docker logs depending on the runtime.

Restart the bridge process (causes container/service restart).

Set or change the web UI password. Not available in HA addon mode (use HA user management instead).

Body: { "password": "mysecretpassword" } (min 8 characters)

Response: { "success": true }

Change log level immediately and persist to config.json. Propagates to all running subprocesses via stdin IPC — no restart needed.

Body: { "level": "debug" }"info" or "debug"

Response: { "success": true, "level": "DEBUG" }

Start an asynchronous version check against GitHub releases. Accepts an optional channel field (stable, beta, rc); defaults to the configured UPDATE_CHANNEL.

Body (optional): { "channel": "stable" }

Response (202): { "job_id": "...", "status": "running", "channel": "stable" }

Poll for async update-check result.

Response (running): { "status": "running", "channel": "stable" }

Response (done):

{ "success": true, "update_available": true, "tag": "v2.28.2", "version": "2.28.2", "current_version": "2.28.1" }

Get cached update information without triggering a new check.

Apply a pending update. In HA addon mode, triggers addon update via Supervisor API. In Docker/LXC mode, returns instructions.

Current configuration from config.json. In HA add-on mode the response also exposes _delivery_channel, _effective_web_port, and _effective_base_listen_port, while WEB_PORT is intentionally returned as null because ingress owns the external port. Per-device preferred_format defaults to flac:44100:16:2 when omitted from stored config.

Save configuration. Body: JSON config object. Effective add-on track defaults (_delivery_channel, effective ports) are runtime-derived rather than user-settable fields.

Download the current config.json as a file attachment. The response includes a Content-Disposition header with a timestamped filename (e.g. config-2026-03-15T10-30-00.json).

Upload a config.json file. Accepts multipart/form-data with a file field containing the JSON config.

The uploaded file is validated as valid JSON before saving. Sensitive keys (AUTH_PASSWORD_HASH, SECRET_KEY, MA_API_TOKEN) are preserved from the current config and not overwritten by the upload.

Response:

{ "success": true, "message": "Configuration uploaded successfully" }

Error response (invalid JSON):

{ "success": false, "error": "Invalid JSON in uploaded file" }

Validate a config payload without persisting it. Returns validation errors, warnings, and the normalized config.

Body: A complete or partial JSON config object.

Response (200 — valid):

{
"valid": true,
"errors": [],
"warnings": [{ "field": "BLUETOOTH_DEVICES[0].mac", "message": "..." }],
"normalized_config": { }
}

Response (400 — invalid):

{
"valid": false,
"errors": [{ "field": "SENDSPIN_PORT", "message": "Invalid SENDSPIN_PORT: abc" }],
"warnings": [],
"normalized_config": { }
}

Fetch Home Assistant area suggestions using a transient HA token. Useful for the adapter-to-area mapping UI. Only works in HA addon mode or when HA is reachable.

Body:

FieldTypeDescription
ha_tokenstringRequired. A valid HA access token
adaptersobject[]Optional. List of adapter objects to match against HA devices
include_devicesbooleanOptional. Include HA device details in the response

Response:

{
"success": true,
"areas": [{ "area_id": "living_room", "name": "Living Room" }],
"bridge_name_suggestions": ["Living Room Bridge"]
}

Render the login page. Automatically detects available authentication methods:

  • HA addon mode — Home Assistant login flow with 2FA/TOTP support
  • MA connected — Music Assistant credential validation (or HA-via-MA)
  • Standalone — Local password authentication (PBKDF2-SHA256)

Process login form submission. Validates credentials against the detected backend, enforces CSRF protection, and applies brute-force lockout (configurable via BRUTE_FORCE_* settings).

On success, sets an authenticated session and redirects to the requested page. On failure, re-renders the login page with an error message.

Clear the authenticated session and redirect to the login page.

Terminal window
# Get all player status
curl http://localhost:8080/api/status
# Subscribe to live status updates (SSE)
curl -N http://localhost:8080/api/status/stream
# Set volume to 50% on a specific device
curl -X POST http://localhost:8080/api/volume \
-H 'Content-Type: application/json' \
-d '{"mac": "AA:BB:CC:DD:EE:FF", "volume": 50}'
# Pause all players
curl -X POST http://localhost:8080/api/pause_all \
-H 'Content-Type: application/json' \
-d '{"action": "pause"}'
# Pause a specific MA sync group
curl -X POST http://localhost:8080/api/group/pause \
-H 'Content-Type: application/json' \
-d '{"group_id": "abc123", "action": "pause"}'
# Skip to next track (requires MA API configured)
curl -X POST http://localhost:8080/api/ma/queue/cmd \
-H 'Content-Type: application/json' \
-d '{"action": "next"}'
# Start BT scan and poll for results
JOB=$(curl -s -X POST http://localhost:8080/api/bt/scan | python3 -c "import sys,json; print(json.load(sys.stdin)['job_id'])")
curl http://localhost:8080/api/bt/scan/result/$JOB
# Get diagnostics
curl http://localhost:8080/api/diagnostics | python3 -m json.tool
# Change log level at runtime
curl -X POST http://localhost:8080/api/settings/log_level \
-H 'Content-Type: application/json' \
-d '{"level": "debug"}'