# MBXHub API
> MusicBee REST API - v0.5.0.9
>
> llms.txt specification: https://llmstxt.org/
## Endpoints
- Base URL: `http://localhost:8080`
- WebSocket: `ws://localhost:8080/ws`
- API Docs: `http://localhost:8080/docs`
## Player Control
- `POST /player/play` - Start playback
- `POST /player/pause` - Pause playback
- `POST /player/playpause` - Toggle play/pause (alias: `/player/play-pause`)
- `POST /player/stop` - Stop playback
- `POST /player/next` - Next track
- `POST /player/previous` - Previous track
- `GET /player/volume` - Get volume (0-100)
- `PUT /player/volume` - Set volume (body: {"volume":50})
- `GET /player/shuffle` - Get shuffle state
- `PUT /player/shuffle` - Set shuffle (body: {"shuffle":true/false})
- `GET /player/autodj` - AutoDJ state
- `POST /player/autodj/start` - Start AutoDJ
- `POST /player/autodj/stop` - Stop AutoDJ
- `GET /player/repeat` - Get repeat mode
- `PUT /player/repeat` - Set repeat (off/all/one)
- `GET /player/status` - Full player state
- `GET /player/mute` - Mute state
- `PUT /player/mute` - Set mute (body: {"mute":true})
- `GET /player/position` - Playback position in ms
- `PUT /player/position` - Seek (body: {"position":ms})
- `POST /player/next-album` - Skip to next album
- `POST /player/previous-album` - Skip to previous album
- `POST /player/queue-random` - Queue a random track (alias: `/player/queuerandom`)
### Audio Processing
- `GET /player/equaliser` - Get equalizer state (also `/player/equalizer`)
- `PUT /player/equaliser` - Set equalizer
- `GET /player/dsp` - Get DSP enabled state
- `PUT /player/dsp` - Set DSP enabled
- `GET /player/crossfade` - Get crossfade state
- `PUT /player/crossfade` - Set crossfade
- `GET /player/replaygain` - Get replay gain mode
- `PUT /player/replaygain` - Set replay gain mode
- `GET /player/scrobble` - Get scrobble (Last.fm) state
- `PUT /player/scrobble` - Set scrobble
### Display Settings
- `GET /player/stopaftercurrent` - Get stop-after-current state
- `POST /player/stopaftercurrent` - Toggle stop-after-current
- `GET /player/show-time-remaining` - Show time remaining preference
- `GET /player/show-rating-track` - Show rating on track preference
- `GET /player/show-rating-love` - Show love rating preference
- `POST /player/show-equaliser` - Open the equaliser window in MusicBee (also `/player/show-equalizer`)
- `GET /player/button-enabled?button=N` - Check if a UI button is enabled
- `GET /player/output-devices` - List audio output devices
- `PUT /player/output-device` - Switch output device (body: {"device":"name"})
### Streaming
- `POST /player/open-stream` - Open a stream handle for playback
- `POST /player/update-play-statistics` - Update play count and statistics
## Now Playing
- `GET /nowplaying` - Current track metadata
- `GET /nowplaying/artwork` - Album art (binary image)
- `GET /nowplaying/lyrics` - Track lyrics
- `GET /nowplaying/position` - Playback position in ms
- `GET /nowplaying/property?type=X` - File property (Size, Bitrate, DateAdded, etc.)
- `GET /nowplaying/tag?field=X` - File tag (Artist, Album, Genre, Rating, etc.)
- `GET /nowplaying/artwork-url` - Artwork as data URL (base64)
- `GET /nowplaying/downloaded-artwork` - Downloaded artwork (binary)
- `GET /nowplaying/downloaded-artwork-url` - Downloaded artwork as data URL
- `GET /nowplaying/downloaded-lyrics` - Downloaded lyrics from provider
- `GET /nowplaying/artist-picture` - Artist picture (binary, ?fadingPercent=0)
- `GET /nowplaying/artist-thumbnail` - Artist thumbnail (smaller)
- `GET /nowplaying/artist-picture-urls` - Artist picture URLs (?localOnly=false)
- `GET /nowplaying/is-soundtrack` - Whether current track is a soundtrack
- `GET /nowplaying/soundtrack-pictures` - Soundtrack picture URLs
- `GET /nowplaying/spectrum` - Real-time spectrum data (frequency analysis)
- `GET /nowplaying/sound-graph` - Waveform graph data
- `GET /nowplaying/sound-graph-ex` - Extended waveform graph data
## Queue
- `GET /queue` - List queue (?offset=0&limit=50)
- `GET /queue/current` - Current track index
- `POST /queue/add` - Add tracks (body: {"url":"path","position":"next|last"} or batch: {"urls":["path1","path2"],"position":"last"})
- `POST /queue/playnow` - Play track immediately (body: {"url":"path","cueTrack":3})
- `POST /queue/clear` - Clear queue
- `POST /queue/move` - Reorder (body: {"from":0,"to":5})
- `DELETE /queue/{index}` - Remove track by index
- `POST /queue/play` - Play track at index (body: {"index":3,"cueTrack":1})
- `GET /queue/next-index?offset=1` - Get index N tracks ahead/behind
- `GET /queue/has-prior` - Whether queue has prior tracks
- `GET /queue/has-following` - Whether queue has following tracks
- `POST /queue/play-library-shuffled` - Clear queue and play library shuffled
- `GET /queue/{index}/url` - Get file URL for track at index
- `GET /queue/{index}/tag?field=X` - Get tag for track at index
- `GET /queue/{index}/property?type=X` - Get property for track at index
### Virtual Tracks (CUE)
Virtual Tracks share the same audio file URL. The queue response includes Virtual Track metadata:
- `cueTrack` - Track number when detected
- `cueStartMs` - Start offset in milliseconds within the physical file
- Track titles from CUE sheet (not embedded audio metadata)
- Include `cueTrack` in add/playnow to preserve track identity
### CUE Track Resolution
MBXHub detects CUE-backed audio files and resolves per-track metadata automatically across all surfaces:
- `GET /player/status` - Returns CUE track title, artist, trackNo, cueTrack field
- `GET /nowplaying/position` - Includes `cueTrack` and `cueTitle` when active
- `GET /nowplaying/tag?field=TrackTitle` - Returns CUE track title (not base file)
- WebSocket `TrackChanged` events include `cueTrack` field when CUE active
- Dashboard shows resolved CUE track metadata
- Listen Here streaming: seeks to `cueStartMs`, shows track-relative progress, auto-advances at track end
CUE parsing uses encoding detection (BOM, UTF-8 validation, Windows-1252 fallback).
Optional: drop CueSharp.dll in Plugins folder for enhanced parsing (loaded via reflection).
## Library
- `GET /library/files` - Query files (?query=&artist=&album=&genre=&offset=&limit=&sort=)
- `GET /library/artists` - List artists (?offset=&limit=&sort=)
- `GET /library/albums` - List albums (?artist=&offset=&limit=&sort=)
- `GET /library/albums/detailed` - Albums with firstTrackUrl for artwork lookups (?offset=&limit=) — eliminates per-album /library/files round-trips
- `GET /library/genres` - List genres (?sort=)
- `GET /library/search?q=term` - Full-text search, multi-word AND logic, diacritic and punctuation normalized (?sort=)
- `GET /library/file/{url}` - Get file metadata
- `PUT /library/file/{url}` - Update tags (body: {"Artist":"name","Title":"name"})
- `GET /library/files/raw` - Raw file path list (no metadata, faster)
- `GET /library/cuetest?query=` - Test CUE track resolution for a query
- `POST /library/add` - Add file to library (body: {"url":"path"})
- `POST /library/artwork/batch` - Batch artwork fetch (body: {"urls":["path1","path2",...]}, max 50). Returns base64 data URIs keyed by URL, null for missing artwork
- `POST /library/find-device-ids` - Find device persistent IDs (body: {"urls":[...]})
- `POST /library/sync-delta` - Get sync delta (body: {"deviceId":"...","since":"ISO date"})
- `GET /library/file/{url}/lyrics` - Lyrics for specific file
- `GET /library/file/{url}/artwork` - Artwork for specific file (binary)
- `GET /library/file/{url}/artwork-url` - Artwork as data URL for specific file
- `GET /library/file/{url}/device-id` - Device persistent ID
- `PUT /library/file/{url}/device-id` - Set device persistent ID
- `GET /library/artist/{name}/similar` - Similar artists (?limit=10)
- `GET /library/artist/{name}/picture` - Artist picture (binary)
- `GET /library/artist/{name}/thumbnail` - Artist thumbnail (smaller)
- `GET /library/artist/{name}/pictures` - Artist picture URLs (?localOnly=false)
## Radio
- `GET /radio/stations` - List radio stations (returns `{total, stations: [{url, name}]}`)
## Playlists
- `GET /playlists` - List all playlists
- `POST /playlists` - Create playlist (body: {"name":"My Playlist","files":["path1","path2"]})
- `GET /playlists/{url}/files` - Get playlist tracks
- `POST /playlists/{url}/files` - Add tracks (body: {"urls":["path1","path2"]})
- `POST /playlists/{url}/play` - Play playlist
- `GET /playlists/{url}` - Get playlist details
- `PUT /playlists/{url}` - Update playlist name
- `DELETE /playlists/{url}` - Delete playlist
## WebSocket
Connect to `ws://localhost:8080/ws` for real-time events:
```javascript
const ws = new WebSocket('ws://localhost:8080/ws');
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
// msg.event: TrackChanged, PlayStateChanged, VolumeChanged, etc.
// msg.data: event-specific payload
// msg.timestamp: ISO 8601 timestamp
};
// Subscribe to specific events:
ws.send(JSON.stringify({ subscribe: ['TrackChanged', 'PlayStateChanged'] }));
```
Event types:
- `TrackChanged` - {"fileUrl":"...","title":"...","artist":"...","album":"...","duration":ms,"artworkUrl":"/nowplaying/artwork"}
- `PlayStateChanged` - {"state":"playing|paused|stopped"}
- `PositionChanged` - {"position":ms,"duration":ms}
- `VolumeChanged` - {"volume":0-100,"muted":bool}
- `ShuffleChanged` - {"enabled":bool}
- `RepeatChanged` - {"mode":"none|all|one"}
- `QueueChanged` - {"action":"changed","index":-1,"totalTracks":-1}
- `MetadataChanged` - {"fileUrl":"...","rating":-1 to 5,"love":"L|B|"}
- `Reaction` - {"emoji":"fire","type":"fire","nickname":"Guest","trackTitle":"...","trackArtist":"..."}
## Static Pages (/pages/)
MBXHub serves HTML, CSS, and JS from the `/pages/` directory.
### How It Works
- Files location: `%APPDATA%\MusicBee\MBXHub\pages\`
- Access via: `http://{host}/pages/yourfile.html`
- Supports: .html, .css, .js, .json, images
- No build step - just save and refresh
### Included Pages
Read these for working reference code:
- `GET /pages/index.html` - Landing page listing available views
- `GET /pages/player.html` - Full 3-column player (browse, now playing, queue) with drag-drop queue reorder and touch support
- `GET /pages/nowplaying.html` - Focused now-playing view with artwork, lyrics, and queue. Drag-drop queue reorder on upcoming tracks. Auto-stacks below 800px with accordion sections (Now Playing, Queue, Lyrics)
- `GET /pages/browse.html` - Library browser: 7 tabs (Albums/Artists/Genres/Playlists/Podcasts/Radio/Moods) with responsive tab sizing, drilldown and breadcrumbs, diacritic-normalized fuzzy search with 8-category grouped results, album art grid with lazy loading, batch queue via long-press, playlist picker, light/dark theme
- `GET /pages/config.html` - Settings and configuration interface for dashboard and AutoQ
- `GET /pages/autoq.html` - AutoQ Tuning Console: mixer-style sliders for all scoring weights, reaction scores, and normalization ranges
- `GET /pages/phantom.html` - Devialet Phantom speaker control: transport, volume, source selection
### Shared Resources
- `GET /pages/queue-dragdrop.js` - Shared drag-and-drop queue reorder module. Mouse drag (event delegation) + touch long-press (300ms) with visual clone and auto-scroll. Used by player.html, nowplaying.html, and partymode/dj.html
### Creating New Pages
To build a custom UI:
1. Create an HTML file
2. Save to `%APPDATA%\MusicBee\MBXHub\pages\`
3. Access at `http://{host}/pages/yourfile.html`
4. Use fetch() for REST API, WebSocket for live updates
5. Reference `/pages/player.html` for working patterns
### Minimal Starter
```html
My Player
Loading...
```
Save as `myplayer.html` in pages folder, access at `/pages/myplayer.html`
### Best Practices
Follow these patterns for robust, performant, accessible custom pages.
#### CSS Foundation
All MBXHub pages use these CSS best practices:
```css
:root {
color-scheme: dark; /* Or dark light for theme switching */
/* ... CSS variables ... */
}
/* Respect user motion preferences */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Box-sizing reset */
*, *::before, *::after { box-sizing: border-box; }
/* Focus styles for keyboard navigation */
button:focus-visible,
input:focus-visible,
a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Cross-browser scrollbar styling */
.scrollable {
scrollbar-width: thin; /* Firefox */
scrollbar-color: rgba(255,255,255,0.1) transparent;
}
.scrollable::-webkit-scrollbar { width: 6px; } /* WebKit */
.scrollable::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
```
## ARiA (Automation)
- `GET /aria/status` - Check if ARiA is enabled
- `POST /aria/send-keys` - Send keyboard input (body: {"keys":"^a"})
- `GET /aria/wake` - Wake PC from sleep (also POST)
- `GET /aria/presets` - List available presets
- `GET /aria/preset/{name}` - Execute preset (also POST)
- `POST /aria/focus` - Focus MusicBee window
- `POST /aria/mouse/move` - Move mouse (body: {"x":100,"y":200})
- `POST /aria/mouse/click` - Click mouse (body: {"button":"left","x":100,"y":200})
### ARiA Settings
Key settings in `mbxhub.json`:
- `ariaEnabled` (default: false) - Enable ARiA automation endpoints
- `ariaWebhookTimeoutMs` (default: 10000) - Timeout for webhook commands in milliseconds
- `ariaPresets` - Named scripts for quick execution (default: RIA1-9 presets mapped to Ctrl+Alt + home row keys)
- `ariaCommands` - Custom command aliases and macros extending the built-in command set
- `allowRemoteExit` (default: false) - Allow remote shutdown of MusicBee via ARiA
## RemoteApp
- `GET /remoteapp/status` - RemoteApp status (configured, supported, rdpEnabled, edition, enabled, apiEnabled)
- `GET /remoteapp/rdp` - Download .rdp file for MusicBee RemoteApp connection (blocked when remoteAppApiDisabled)
- Query: `?hostname=192.168.1.100` (optional, defaults to request Host header)
- Any other query param forwarded as .rdp setting override (e.g. `?audioqualitymode=0&redirectprinters=1`)
- Client (Pro/Enterprise): uses full exe path from registry. Server: uses `||AppName` alias for RDS lookup.
### RemoteApp Settings
Key settings in `mbxhub.json`:
- `remoteAppEnabled` (default: false) - Enable RemoteApp feature
- `remoteAppApiDisabled` (default: false) - Disable /remoteapp/rdp endpoint (API is on by default when feature is enabled)
- Dashboard footer links configurable via `dashboardFooterLinks` in settings (null = defaults)
### Prerequisites
- Windows Client (Pro/Enterprise): (1) Settings > System > Remote Desktop > ON. (2) Allow through firewall (auto-prompted, or manually via Control Panel > Firewall > Allow an app > Remote Desktop). (3) NLA is on by default; disable if older clients cannot connect.
- Windows Server: Install RDS role (`Install-WindowsFeature RDS-Connection-Broker, RDS-Web-Access, RDS-RD-Server -IncludeManagementTools`)
### RemoteApp CLI
- `MBXHub.exe remoteapp setup --path ` - Configure RemoteApp (requires elevation, Pro/Enterprise/Server)
- `MBXHub.exe remoteapp setup --detect` - Auto-detect MusicBee and configure
- `MBXHub.exe remoteapp remove` - Remove RemoteApp configuration (requires elevation)
- `MBXHub.exe remoteapp status` - Show current status
- `MBXHub.exe remoteapp rdp --hostname ` - Generate .rdp file content
## Audio Streaming
- `GET /stream/{path}` - Stream an audio file from the MusicBee library
- Path: URL-encoded absolute file path (e.g. `/stream/C%3A%5CMusic%5Csong.mp3`)
- Supports HTTP Range requests for seeking (206 Partial Content)
- MIME types: mp3=audio/mpeg, flac=audio/flac, m4a/mp4=audio/mp4, ogg/oga=audio/ogg, wav=audio/wav, opus=audio/opus, aac=audio/aac, wma=audio/x-ms-wma, aiff/aif=audio/aiff
- Security: path traversal blocked, audio extensions only, must be in MusicBee library
- 400=invalid path, 403=not in library, 404=not found or streaming disabled, 416=invalid range
- CUE tracks: client uses `cueStartMs` from track data to seek to correct offset; progress and boundaries handled client-side
- Disable: `disableStreaming` setting (returns 404 when true)
- Feature flag: `streaming` in `GET /system/features` response
## Device Proxy
- `POST /api/proxy` - Forward HTTP request to a LAN device (CORS bypass)
- Body: `{"method":"GET|POST|PUT", "url":"http://192.168.x.x/...", "body":{} }`
- Only private IPs allowed (RFC 1918 + loopback). Public internet blocked (403).
- Response: target device response passed through verbatim (status code + body).
- 502 if device unreachable. 5-second timeout.
- Disable: `apiDisableProxy` setting (returns 404 when true).
## Charms
Charms are configurable action buttons on the dashboard charm bar. Each charm is a `.json` manifest in the `charms/` folder (MBXHub data directory). MBXHub seeds built-in charms on first run: `phantom.json` (Phantom webapp) and `browse.json` (Library Browser). Hidden in party mode.
### Manifest fields
- `id` - Unique identifier
- `icon` - Emoji or character for the button
- `label` - Tooltip / display name
- `action` - Action type:
- `webapp /path` - Open HTML page (standalone tab or inline iframe)
- `iframe-cmd ` - Send postMessage to charm iframe (auto-loads if needed)
- `http://...` - Fire HTTP request (cross-origin routed via `/api/proxy`)
- `display` - `standalone` (new tab), `inline` (iframe), or `both` (click=inline, shift+click=standalone)
- `msg` - Status message after execution
- `context` - Optional: `library-only` or `stream-only`
- `expand` - Array of sub-actions (icon, label, action, display, msg). Renders as grouped button row with connection status dot.
### Settings (`charmBar` in mbxhub.json)
- `order` (default: []) - Charm IDs in display order. New charms appended automatically.
- `hidden` (default: []) - Charm IDs to hide from the dashboard.
The charm bar is also a dashboard layout section (`charms`) that can be reordered/hidden via `dashboardLayout`.
## System
- `GET /ping` - Health check (alias: `GET /system/ping`)
- `GET /system/status` - Server status
- `GET /system/version` - Version info (includes `host` field with discovery name or machine name)
- `GET /system/features` - Enabled feature flags. Response fields: `banlist`, `ratings`, `loved`, `reactions`, `streaming`, `autoq`
- `GET /system/metrics` - Performance metrics
- `GET /system/settings` - Get MBXHub settings
- `PUT /system/settings` - Update settings
- `GET /system/settings/schema` - Schema for all configurable settings (type, range, default, current value). Blocked during party mode and when remote config is disabled.
- `PUT /system/config` - Update configurable settings via dotted keys. Only [ConfigSetting]-scoped properties. Blocked by read-only mode, party mode, remote config disabled.
- `POST /system/config` - Alias for `PUT /system/config`
- `GET /system/default-page` - Get default redirect page
- `PUT /system/default-page` - Set default page (body: `{"defaultPage":"/pages/player.html"}`)
- `GET /system/qr` - QR code PNG for base URL (optional `?url=` override)
## Network Discovery
MBXHub advertises on the local network via three protocols, all controlled by the `discoveryEnabled` setting:
- **SSDP/UPnP** (UDP 1900) — UPnP device advertisement
- **WS-Discovery** (UDP 3702) — Windows Network folder integration
- **mDNS/DNS-SD** (UDP 5353) — Bonjour/zero-conf (Win10 1809+, graceful fallback on older)
The `discoveryName` setting controls the friendly name shown across all protocols. Empty = machine name.
- `GET /device.xml` - UPnP device description XML (friendlyName, presentationURL, services)
- `POST /wsd` - WS-Discovery metadata exchange. Windows sends SOAP GetMetadata after UDP Probe discovery; response includes PresentationUrl → `/dashboard`. This makes MBXHub appear in the Windows Explorer Network folder with a "Device webpage" link.
Firewall: TCP (REST port) + UDP 1900,3702 must be open. mDNS uses UDP 5353 managed by the OS.
## Shuffle (MBXQ)
- `GET /shuffle/status` - Shuffle cycle progress
- `POST /shuffle/reset` - Reset shuffle cycle
- `GET /shuffle/played` - Tracks played this cycle
- `GET /shuffle/remaining` - Tracks remaining
## Banlist (MBXQ)
- `GET /banlist` - List banned tracks
- `POST /banlist` - Ban track (body: {"url":"path","reason":"optional"})
- `DELETE /banlist/{url}` - Unban track
## Influences (MBXQ)
- `GET /influences` - List shuffle influences
- `POST /influences` - Add influence (body: {"type":"++","target":"Genre|Artist","value":"Rock"})
- `DELETE /influences/{target}/{value}` - Remove influence
- `POST /influences/clear` - Clear all influences
- `GET /influences/current` - Current track's influence data
## PartyMode
Web-based party music system for group listening. Three roles: Guest (browse/request), DJ (full control), Display (TV mode).
### Pages
- `GET /pages/partymode/` - Entry point with PIN + nickname form
- `GET /pages/partymode/guest.html` - Guest: browse 7 tabs (Albums/Artists/Genres/Playlists/Podcasts/Radio/Moods), fuzzy search, request songs, vote, react
- `GET /pages/partymode/dj.html` - DJ: full playback control, drag-drop queue reorder, AutoQ control
- `GET /pages/partymode/display.html` - TV: large artwork, lyrics, floating reactions, request feed
- `GET /pages/partymode/leaderboard.html` - Party stats: top guests, top tracks, reaction counts
### Endpoints
- `GET /partymode/status` - Check if party is active
- `POST /partymode/start` - Start party (body: {"guestPin":"1234","djPin":"5678"})
- `POST /partymode/stop` - End party session
- `GET /partymode/validate?pin=1234&nickname=Haro` - Validate PIN, register join, returns role (guest/dj)
- `POST /partymode/verify-dj` - Verify DJ PIN for DJ page access (body: {"pin":"5678"})
- `POST /partymode/vote` - Submit vote with attribution (body: {"type":"++","target":"Artist","value":"Rock","nickname":"Haro"})
- `POST /partymode/request` - Submit song request (body: {"url":"path","nickname":"Haro"})
- `GET /partymode/requests` - Recent requests (for DJ view)
- `GET /partymode/feed` - Activity feed: joins, requests, votes, and reactions (for display)
- `GET /partymode/qr` - Generate QR code image (PNG) with party URL
- `GET /partymode/role` - Current user's role (guest/dj/anonymous) + QR sharing flags
### QR Code Sharing
- `GET /system/qr` returns QR for base URL; `?url=` overrides target
- Guest and DJ pages have QR buttons for viral party sharing
- Dashboard QR auto-switches to party join URL when party is active
- Settings: `disableGuestQr` hides QR on guest page, `disableHostQr` hides QR on DJ/dashboard
### Quick Start
1. DJ visits `/pages/partymode/`, starts party with PIN
2. Display page shows QR code with embedded PIN
3. Guests scan QR, enter nickname, browse and request songs
4. Requests appear on DJ page and display feed
### Integration with Influences
Guests can vote (thumbs up/down) on currently playing and queued tracks. Uses the Influences API to adjust shuffle preferences in real-time.
## AutoQ (v0.4.9+)
Vibe-based automatic track selection. Learns from party reactions and influences to queue tracks that match the room's energy.
### Scoring System
- **Reactions** on now playing: Fire (+3, triggers queue refresh), Heart (+2), Like (+1), Dislike (-1), Ban (-100, excludes). Reactions auto-create influences: fire/heart → artist++, like → genre++, dislike → genre--, ban → artist--
- **Influences**: Thumbs up/down on artists and genres (also created automatically from reactions)
- **Recency**: Small boost for recently reacted tracks
- **Mood Matching**: Tracks matching target mood score better
### Mood Channels
Default mood channels with arousal/valence targets (customizable via `autoQ.moodChannels` in mbxhub.json):
- Energetic (0.95, 0.85) | Dance (0.80, 0.70) | Intense (0.90, 0.20) | Upbeat (0.65, 0.85)
- Morning (0.50, 0.80) | Chill (0.30, 0.70) | Mellow (0.20, 0.50) | Night (0.15, 0.30)
- Emotional (0.45, 0.20) | Relax (0.10, 0.60)
Custom channels example in mbxhub.json:
```json
"moodChannels": [
{ "name": "Energetic", "emoji": "🔥", "arousal": 0.90, "valence": 0.80 },
{ "name": "Chill", "emoji": "😌", "arousal": 0.35, "valence": 0.65 }
]
```
### Mood Quadrants
Arousal (energy) is the vertical axis, valence (positivity) is the horizontal. Each mood channel targets a point in this 2D space.
- **High arousal + high valence** = Energetic, upbeat (EDM, pop, funk). Fast tempo, bright timbre, strong beats.
- **High arousal + low valence** = Tense, aggressive (metal, hard rock, industrial). Distortion, high energy, dissonance.
- **Low arousal + low valence** = Sad, subdued (ambient drone, slow blues, lo-fi). Slow tempo, dark timbre, soft dynamics.
- **Low arousal + high valence** = Calm, pleasant (chillhop, acoustic folk, soft jazz). Warm timbre, consonance, smooth textures.
### AutoQ Settings
Key settings in `mbxhub.json` under `autoQ`:
- `pickMode` (default: "weighted") - Track selection mode: off (shuffle fill only), favorites (highest-scored), weighted (score-proportional random), random (uniform random, diversity caps still apply)
- `artistQuota` (default: 2) - Max tracks from same artist in batch (0=disabled)
- `genreQuota` (default: 3) - Max consecutive same-genre tracks (0=disabled)
- `moodMatchWeight` (default: 0.4) - Weight for mood matching in scoring (0-1)
- `moodTagField` (default: "Custom1") - MusicBee custom tag field for writing mood labels (null=disabled)
- `moodTagFieldName` (default: "AutoQ Mood") - Expected display name for the custom tag field (must match MusicBee config)
- `minReplayMinutes` (default: 30) - Minimum minutes before a track can be replayed
- `diversityWindowSize` (default: 10) - Recent tracks considered for diversity calculations
- `minSessionEntropy` (default: 0.5) - Entropy threshold before boosting diversity (0-2)
### Estimation Settings
Normalization parameters for mood matching and feature extraction, tunable via `autoQ.estimation` in mbxhub.json:
- `usePercentileNormalization` (true) - Use percentile-based normalization (library-adaptive). When false, uses absolute min/max ranges below. Requires >= 10 Essentia-analyzed tracks.
- `moodMatchMinSimilarity` (0.5) - Minimum similarity to match a mood channel
- `bpmMin` (80), `bpmMax` (170) - BPM normalization range (used when percentile off)
- `ratingScale` (5.0) - Rating scale for valence normalization
- `yearMin` (1950), `yearMax` (2030) - Year normalization range
- `playCountLogDivisor` (4.0) - Play count log scaling divisor
- **Valence weights** (positivity) under `autoQ.estimation`: `valenceWeightMode` (0.35), `valenceWeightCentroid` (0.0 — centroid is an arousal feature), `valenceWeightDance` (0.05), `valenceWeightFlatness` (0.0), `valenceWeightDissonance` (0.25), `valenceWeightPitchSalience` (0.10), `valenceWeightChords` (0.15), `valenceWeightMfcc` (0.10). Mode scoring: `modeScoreMajor` (0.85), `modeScoreMinor` (0.4).
- **Arousal weights** (energy/intensity) under `autoQ.estimation`: `arousalWeightBpm` (0.25), `arousalWeightLoudness` (0.20), `arousalWeightFlux` (0.15), `arousalWeightCentroid` (0.05), `arousalWeightDance` (0.02), `arousalWeightOnsetRate` (0.15), `arousalWeightZcr` (0.08), `arousalWeightRms` (0.10).
- **Normalization ranges** (used when percentile off) under `autoQ.estimation`: `centroidMin/Max` (500/2300 Hz), `loudnessMin/Max` (-23/-5 dB), `onsetRateMax` (5.5), `zcrMax` (0.12), `rmsMax` (0.008), `chordsRateMax` (0.2), `mfccMin/Max` (70/220).
### Endpoints
- `GET /autoq/status` - AutoQ status and configuration
- `POST /autoq/start` - Start AutoQ (body: `{"mode":"autopilot"|"djassist"}`)
- `POST /autoq/stop` - Stop AutoQ
- `GET /autoq/moods` - Get available mood channels with arousal/valence coordinates
- `GET /autoq/moods/browse?channel=Energetic` - Browse tracks matching a mood channel (?limit=200)
- `GET /autoq/track-mood` - Raw mood data for current track (or ?url=file://...). Returns Essentia features, percentile ranks, computed valence/arousal, best mood match, confidence score (0-1), genre profile applied
- `POST /autoq/mood` - Set target mood (body: {"channel":"Energetic"})
- `POST /autoq/moods/reload` - Reload mood cache from disk
- `GET /autoq/vibe-list` - Current candidate tracks with scores (?count=50)
- `POST /autoq/react` - Submit reaction (body: {"emoji":"fire","nickname":"Haro"})
- `GET /autoq/reactions` - Reaction history (?trackUrl=, ?limit=50)
- `GET /autoq/stats` - Leaderboard data (top guests, top tracks, reaction breakdown)
- `GET /autoq/settings` - All tunable AutoQ parameters (weights, scores, normalization, genre profiles, confidence thresholds)
- `PUT /autoq/settings` - Partial update AutoQ parameters (body: JSON with changed fields only). Changes apply on next scoring pass. Supports genreProfiles dictionary for genre-aware weight adjustment.
- `POST /autoq/retag-moods` - Bulk write mood tags to MusicBee custom field (Essentia-analyzed tracks only)
- `POST /autoq/vibe-list/refresh` - Force refresh vibe list
- `POST /autoq/pick` - Pick next track from vibe list (returns best track without queueing)
- `POST /autoq/unban` - Unban a track (body: {"url":"file://..."})
- `GET /autoq/banned` - Check if current track is banned
- `POST /autoq/reset` - Reset AutoQ session state (clears reactions, taste vector, ban list)
### Reactions
- `fire` - This track is fire! (+3, triggers queue refresh)
- `heart` - Love this song (+2)
- `like` - Good choice (+1)
- `dislike` - Not feeling it (-1)
- `ban` - Skip and exclude (-100)
### Quick Start
1. Enable AutoQ in MBXHub settings or via `POST /autoq/start`
2. Guests react to now playing tracks via guest page or `/autoq/react`
3. AutoQ learns preferences and adds matching tracks when queue runs low
4. View leaderboard at `/pages/partymode/leaderboard.html` or via `/autoq/stats`
5. Tune algorithm live at `/pages/autoq.html` - mixer-style sliders for all weights, scores, and normalization ranges
## Access Control (v0.4.8+)
### Roles
- **DJ** - Full playback control, queue management, see requests. Authenticated via DJ PIN.
- **Guest** - Browse library, request songs, vote on vibes. Authenticated via Guest PIN.
- **Anonymous** - Unauthenticated access. Subject to protection level restrictions.
### Protection Levels
- **Default** - Normal operation. Granular read-only controls available (player, queue, library, playlists).
- **Kiosk** - All navigation redirects to default page. For party displays or public terminals.
- **Read-Only** - Master switch blocks all write operations. PartyMode endpoints exempt.
### PIN Authentication
PartyMode uses PIN-based authentication:
- `GET /partymode/validate?pin=1234&nickname=Haro` - Validate PIN, returns role (dj/guest)
- `POST /partymode/verify-dj` - Verify DJ PIN (body: {"pin":"5678"})
- `X-Party-PIN` header - Include PIN in request header for authenticated API calls
### Access Control Response
Blocked requests return 403 with error code:
```json
{"success":false,"error":{"code":"READ_ONLY","message":"API is in read-only mode"}}
{"success":false,"error":{"code":"PARTY_LOCKED","message":"Player controls locked during PartyMode"}}
```
## Settings
- `GET /settings` - All MusicBee settings
- `GET /settings/storage-path` - Library storage path
- `GET /settings/skin` - Current skin name
- `GET /settings/field-name?field=N` - Field display name
- `GET /settings/data-type?field=N` - Field data type
- `GET /settings/skin-element-color?element=N&state=N&is498=false` - Skin element color
- `GET /settings/window-borders-skinned` - Whether window borders are skinned
- `GET /settings/lastfm-user` - Last.fm username
- `GET /settings/web-proxy` - Web proxy settings
- `GET /settings/value?id=N&defaultValue=` - Get setting value by ID
- `GET /settings/convert-command?format=N` - Conversion command line
## Podcasts
- `GET /podcasts` - List subscriptions
- `GET /podcasts/{id}` - Subscription details
- `GET /podcasts/{id}/artwork` - Subscription artwork
- `GET /podcasts/{id}/episodes` - List episodes (also: `GET /podcasts/episodes?id={url}` for feed URL IDs)
- `GET /podcasts/{id}/episodes/{index}` - Episode details
## Pending
- `GET /pending` - Pending file info
- `GET /pending/url` - Pending file URL
- `GET /pending/property?type=X` - Pending file property
- `GET /pending/tag?tag=X` - Pending file tag
## MusicBee App
- `POST /app/exit` - Exit MusicBee (requires allowRemoteExit). Optional body: `{"restart":true,"delay":22}` to schedule restart via Task Scheduler
- `POST /app/restart` - Restart MusicBee (requires allowRemoteExit). Optional body: `{"delay":22}`. Convenience alias for exit with restart=true
- `GET /mb/window-handle` - Window handle
- `POST /mb/refresh-panels` - Refresh UI panels
- `POST /mb/command` - Invoke MusicBee command
- `GET /mb/visualisers` - List visualizers
- `POST /mb/visualiser` - Show visualizer
- `POST /mb/plugin-view` - Show plugin view
- `GET /mb/plugin-views` - List plugin views
- `GET /mb/localisation?id=X` - Get localized string
- `POST /mb/nowplaying-assistant` - Show now-playing assistant panel
- `POST /mb/filter` - Open filter in MusicBee (body: {"filter":"..."})
- `POST /mb/window-size` - Set window size (body: {"width":800,"height":600})
- `POST /mb/download` - Download file (body: {"url":"..."})
## RPC
Direct access to all 137 MusicBee API methods:
- `POST /rpc/{methodName}` - Call any API method
Example:
```
POST /rpc/Player_PlayPause
POST /rpc/NowPlaying_GetFileTag (body: {"tag":"Artist"})
POST /rpc/Library_QueryFiles (body: {"query":"genre=Rock"})
```
Method categories: Player_*, NowPlaying_*, NowPlayingList_*, Library_*, Playlist_*, Setting_*, MB_*, Podcasts_*, Sync_*
**GOTCHA:** After writing tags via RPC, call `MB_RefreshPanels` or MusicBee UI won't update:
```
POST /rpc/Library_SetFileTag (body: {"file":"...","tag":"Rating","value":"5"})
POST /rpc/Library_CommitTagsToFile (body: {"file":"..."})
POST /rpc/MB_RefreshPanels ← Don't forget this!
```
Note: REST endpoints (PUT /library/file/...) call refresh automatically.
## Sync (Preview)
Library sync between MBXHub instances (coming in .6 LP):
- `GET /sync/status` - Sync status
- `GET /sync/peers` - Known peers
- `GET /sync/discover` - Discover peers on network
- `GET /sync/delta` - Library delta since last sync
- `GET /sync/operations` - All sync operations
- `GET /sync/operations/{syncId}` - Single sync operation by ID
- `POST /sync/start` - Start sync operation
- `POST /sync/stop` - Stop sync
- `POST /sync/pull` - Pull files from peer
- `POST /sync/push` - Push files to peer
### Sync Settings
Settings in `mbxhub.json`:
- `syncEnabled` (default: false) - Enable library sync
- `syncRole` (default: "island") - Sync role: "island" (standalone), "hub", or "spoke"
- `syncLibraryPath` (default: "") - Path to local library for sync operations
- `syncSharePath` (default: "") - Shared path for peer file transfer
## Device Sync (MBSync)
Internal MusicBee device synchronization callbacks. Used by MusicBee's built-in sync feature when syncing with mobile devices:
- `POST /mbsync/file/start` - Notify sync file transfer starting
- `POST /mbsync/file/end` - Notify sync file transfer complete
- `POST /mbsync/file/delete/start` - Notify sync file deletion starting
- `POST /mbsync/file/delete/end` - Notify sync file deletion complete
## Debug
Diagnostic endpoints for testing and validation (used by MBXHVAL test toolkit):
- `GET /debug/clouseau/status` - Clouseau inspector plugin status
- `GET /debug/clouseau/state` - Get Clouseau state snapshot
- `POST /debug/clouseau/state` - Save Clouseau state
- `GET /debug/clouseau/files` - Get Clouseau file list
## Response Format
Success:
```json
{"success":true,"data":{...}}
```
Error:
```json
{"success":false,"error":{"code":"ERROR_CODE","message":"Description"}}
```
## Usage Rules
### File URLs
All file URLs are Windows paths. URL-encode when passing in path parameters:
- Original: `C:\Music\Artist\Track.mp3`
- Encoded: `C%3A%5CMusic%5CArtist%5CTrack.mp3`
### Pagination
Most list endpoints support `offset` and `limit` query parameters:
- Default limit: 50
- Maximum limit: 10000
- Maximum offset: 1000000
- Example: `GET /library/files?offset=100&limit=50`
### Sorting
Library endpoints support `?sort=` parameter for server-side sorting:
- `alpha` (default) - Alphabetical by title+artist
- `artist` - By artist name, then title
- `album` - By album name, then track number
- `title` - By title only
- `date` - By date added (newest first)
- `track` - By disc number, then track number (natural album order)
- `name` - By display name
- Example: `GET /library/files?artist=Pink+Floyd&sort=album`
### Request Limits
- Maximum request body size: 1 MB
- Content-Type must be `application/json` for POST/PUT with body
### CORS Policy
- `localhost`, `127.0.0.1` - Always allowed
- `192.168.x.x`, `10.x.x.x`, `172.16-31.x.x` - Allowed when remote connections enabled
- External origins - Blocked
### Access Control (Read-Only Mode)
MBXHub can restrict write operations via settings:
- **Master read-only**: Disables all write operations API-wide
- **Granular controls**: Player, Queue, Library, Playlists can be individually restricted
- **PartyMode exempt**: PartyMode endpoints always work (voting, requests, etc.)
Blocked requests return:
```json
{"success":false,"error":{"code":"READ_ONLY","message":"API is in read-only mode"}}
```
Handle in code:
```javascript
const res = await fetch('/player/play', {method:'POST'});
if (res.status === 403) {
const json = await res.json();
if (json.error?.code === 'READ_ONLY') {
showMessage('Controls are locked');
}
}
```
### Security Settings
Key security settings in `mbxhub.json`:
- `protectMetadata` (default: true) - Block metadata writes (love, rate, tag edits) for all users including DJ
- `rateLimitEnabled` (default: true) - Enable rate limiting for PartyMode endpoints
- `rateLimitRequestsPerMinute` (default: 5) - Max song requests per IP per minute
- `rateLimitVotesPerMinute` (default: 5) - Max votes per IP per minute
- `rateLimitPinAttemptsPerMinute` (default: 5) - Max PIN validation attempts per IP per minute
- `trustForwardedFor` (default: false) - Trust X-Forwarded-For header for client IP detection. Only enable if behind a reverse proxy that sets this header.
### IP Filtering
Control which IP addresses can connect when remote connections are enabled:
- `filteringMode` (default: "All") - All (any IP), Range (subnet range), or Specific (whitelist)
- `filterBaseIp` - Base IP for Range mode (e.g., "192.168.1.")
- `filterLastOctetMax` (default: 254) - Max last octet for Range mode
- `filterAllowedIps` - Array of allowed IPs for Specific mode
Example Range mode (allow 192.168.1.1 to 192.168.1.50):
```json
{"filteringMode": "Range", "filterBaseIp": "192.168.1.", "filterLastOctetMax": 50}
```
Example Specific mode (whitelist):
```json
{"filteringMode": "Specific", "filterAllowedIps": ["192.168.1.10", "192.168.1.20"]}
```
### Dashboard Footer Links
Configure which links appear in the dashboard footer via `dashboardFooterLinks` in mbxhub.json:
- `dashboardFooterLinks` (default: null) - Null = built-in defaults (Player, Pages, QR*, RDP*, API*). Array of `{label, url, enabled}` for custom links.
- Conditional visibility: `/system/qr` requires `restEnabled`, `/remoteapp/rdp` requires `remoteAppEnabled` + `!remoteAppApiDisabled`.
- The footer is a dashboard layout section (`footer`) — hide/show and reorder via `dashboardLayout`.
### Dashboard Layout
Configure which dashboard panels are shown, their order, and which are collapsible via `dashboardLayout` in mbxhub.json:
- `order` (default: ["status","nowplaying","rating","controls","volume","charms","mood","playlists","toggles","footer"]) - Render order of sections
- `hidden` (default: []) - Section IDs to hide entirely
- `collapseAfter` (default: 5) - First N visible sections always shown; rest go in collapsible group
Section IDs: `status` (status bar), `nowplaying` (track info/artwork), `rating` (stars/ban/love), `controls` (prev/play/next), `volume` (+/-/mute), `playlists` (playlist selector), `toggles` (shuffle/repeat/ARiA), `mood` (mood/reactions), `charms` (charm bar), `footer` (footer links)
### Dashboard Display Settings
- `hideFileInfo` (default: false) - Hide file format info (codec, sample rate/bitrate) and hi-res badge from the now-playing section. Hi-res badge appears when sample rate exceeds 44.1kHz.
- `hideInfluenceControls` (default: false) - Hide thumbs up/down influence buttons in the now-playing section.
- `hideStatusMessage` (default: false) - Hide the status bar message after dashboard actions.
- `disableGuestQr` (default: false) - Hide QR code from guest/party mode dashboard.
- `disableHostQr` (default: false) - Hide QR code from host dashboard.
### Feature Disable Settings
Disable specific API features. When disabled, related endpoints return 404. Feature state (except proxy) is exposed via `GET /system/features`.
- `apiDisableBanlist` (default: false) - Disable ban list endpoints (`/banlist/*`).
- `apiDisableRatings` (default: false) - Disable rating controls (`/dashboard/rate/*`, `/dashboard/love`).
- `apiDisableLoved` (default: false) - Disable love/heart tag feature.
- `apiDisableReactions` (default: false) - Disable reactions/mood feature.
- `apiDisableProxy` (default: false) - Disable device proxy endpoint (`POST /api/proxy`).
- `disableStreaming` (default: false) - Disable audio streaming endpoint (`GET /stream/*`).
### MBXHub Shell (MBXHub.exe)
Standalone companion providing Windows SMTC (System Media Transport Controls), firewall configuration, and more. The Shell is a client of MBXHub, not a server; it has no API endpoints.
Plugin setting in `mbxhub.json`:
- `startShellOnStartup` (default: false) - Launch MBXHub.exe when MusicBee starts. Plugin stops it on exit.
Shell config in `mbxhub-shell.json` (next to MBXHub.exe):
- `host` (default: "127.0.0.1") - MBXHub REST API host
- `port` (default: 8080) - MBXHub REST API port
- `wsPort` (default: 8080) - WebSocket port
- `retryIntervalMs` (default: 3000) - Retry interval on connection loss (ms)
- `connectTimeoutMs` (default: 5000) - Connection timeout (ms)
- `logLevel` (default: "Info") - Log verbosity: Trace, Debug, Info, Warn, Error
- `logFile` (default: "mbxhub-shell.log") - Log file path (empty to disable)
- `logDebugOutput` (default: false) - Write to debug output (DebugView/VS Output)
- `debug` (default: false) - Debug mode: smaller log files, more archives
Features: AUMID (`HALRAD.MBXHub`), auto-reconnect, single-instance enforcement. Uninstalling MBXHub via MusicBee cleans up AUMID, Start Menu shortcut, and Apps & Features entry. Requires .NET 8.0 Desktop Runtime.
#### MBXHub.exe CLI
- `MBXHub.exe` (no args) - Start SMTC bridge mode
- `MBXHub.exe --install` - Register AUMID and create Start Menu shortcut
- `MBXHub.exe --uninstall` - Remove AUMID registration and shortcut
- `MBXHub.exe --detect` - Detect MusicBee installations
- `MBXHub.exe --version` - Show version information
- `MBXHub.exe --help` - Show help
#### MBXHub.exe firewall
Windows Firewall configuration (replaces standalone firebug.exe). Self-elevates via UAC when needed.
- `MBXHub.exe firewall add --name MBXHub --tcp 8080 --udp 1900,3702 --urlacl 8080` - Add firewall rules and URL ACL
- `MBXHub.exe firewall add --name MBXHub --port 8080 --urlacl` - Add single-port rule with URL ACL
- `MBXHub.exe firewall remove --name MBXHub --port 8080` - Remove firewall rules and URL ACL
- `MBXHub.exe firewall check --name MBXHub --port 8080` - Check if rules exist
- `MBXHub.exe firewall status` - Show firewall and elevation status
- `MBXHub.exe firewall open` - Open Windows Firewall settings UI
### REST vs RPC
- Use REST for client apps, clean URLs, resource-oriented design
- Use RPC (`POST /rpc/{methodName}`) for direct MusicBee API access, automation scripts
### Real-time Updates
- Use REST for commands (play, pause) and queries (get queue, search)
- Use WebSocket for real-time UI updates, progress bars, visualizations
### MBXQ Availability
Shuffle, banlist, and influence endpoints require MBXQ plugin:
- Returns `503 SERVICE_UNAVAILABLE` if MBXQ not connected
- Check with `GET /shuffle/status`
## Anatomy of a Player
Common UI regions and the endpoints that power them:
### Now Playing Panel
Current track display - artwork, title, artist, album
- `GET /nowplaying` - Track metadata
- `GET /nowplaying/artwork` - Album art
- `GET /nowplaying/lyrics` - Lyrics (if available)
- WebSocket `TrackChanged` event - Live updates on track change
### Transport Controls
Play, pause, stop, next, previous buttons
- `POST /player/play`
- `POST /player/pause`
- `POST /player/playpause` - Toggle
- `POST /player/stop`
- `POST /player/next`
- `POST /player/previous`
- WebSocket `PlayStateChanged` event - Button state updates
### Progress / Seek Bar
Position slider, elapsed time, total duration
- `GET /nowplaying/position` - Current position
- `PUT /player/position` - Seek to position
- WebSocket `PositionChanged` event - Live position updates (see seek bar sample code)
### Volume Control
Volume slider, mute button
- `GET /player/volume` - Current volume (0-100)
- `PUT /player/volume` - Set volume
- `GET /player/mute` - Mute state
- `PUT /player/mute` - Toggle mute
- WebSocket `VolumeChanged` event - Live updates
### Mode Controls
Shuffle toggle, repeat cycle, AutoDJ
- `GET /player/shuffle` - Shuffle state (true/false)
- `PUT /player/shuffle` - Set shuffle (body: {"shuffle":true/false})
- `GET /player/autodj` - AutoDJ state
- `POST /player/autodj/start` - Start AutoDJ
- `POST /player/autodj/stop` - Stop AutoDJ
- `GET /player/repeat` - Repeat mode (off/all/one)
- `PUT /player/repeat` - Set repeat (body: {"repeat":"off|all|one"})
- WebSocket `ShuffleChanged` ({enabled:bool}), `RepeatChanged` events
### Dashboard Volume
- `POST /dashboard/volup` - Volume +5%
- `POST /dashboard/voldown` - Volume -5%
- `POST /dashboard/volup1` - Volume +1% (fine)
- `POST /dashboard/voldown1` - Volume -1% (fine)
- `POST /dashboard/mute` - Toggle mute
- Keyboard: Arrow Up/Down (1%), Shift+Arrow (5%), M (mute)
### Rating / Love / Ban
Star rating (1-5), bomb (0=don't play), love/heart, ban from shuffle
- `POST /dashboard/rate/0` - Toggle bomb (don't play). Click on, click off
- `POST /dashboard/rate/1-5` - Toggle star rating. Click to set, click same to clear
- `POST /dashboard/love` - Toggle love tag
- `POST /dashboard/ban` - Ban from shuffle and skip (undo: skip back within 10s)
- `POST /dashboard/setban` - Set ban flag without skipping
- WebSocket `MetadataChanged` event for changes
### AutoQ Mood & Reactions
Mood-based track selection and user reactions
- `POST /dashboard/mood` (form: mood=Energetic) - Set mood channel
- `POST /dashboard/react/fire|heart|like|dislike` - Submit reaction
- `POST /dashboard/ban` - Ban current track (skip + add to banlist)
- `POST /dashboard/refresh-queue` - Refresh vibe list and queue tracks
- `POST /dashboard/influence/artist|genre/up|down` - Thumbs up/down for artist or genre
- Fire reaction triggers immediate queue refresh
### Queue / Up Next
List of upcoming tracks
- `GET /queue` - Full queue with pagination
- `GET /queue/current` - Current track index
- `POST /queue/add` - Add track (next or last)
- `DELETE /queue/{index}` - Remove track
- `POST /queue/move` - Reorder
- WebSocket `QueueChanged` event - Queue changes
### Library Browser
Browse by artist, album, genre
- `GET /library/artists` - Artist list
- `GET /library/albums` - Album list (filter by artist)
- `GET /library/albums/detailed` - Albums with firstTrackUrl (for artwork, avoids N+1)
- `GET /library/genres` - Genre list
- `GET /library/files` - Track list (filter by artist/album/genre)
- `GET /library/search?q=` - Search
### Playlists Panel
List and manage playlists
- `GET /playlists` - All playlists
- `GET /playlists/{url}/files` - Tracks in playlist
- `POST /playlists/{url}/play` - Play playlist
- `POST /playlists` - Create new
### Status Bar
Connection status, server info
- `GET /ping` - Health check
- `GET /system/version` - Version info
- WebSocket connection state
## Sample Code (player.html patterns)
### Fetch Now Playing
```javascript
async function getNowPlaying() {
const res = await fetch('/nowplaying');
const json = await res.json();
if (json.success) {
document.getElementById('title').textContent = json.data.title;
document.getElementById('artist').textContent = json.data.artist;
document.getElementById('album').textContent = json.data.album;
}
}
```
### Display Artwork
```javascript
document.getElementById('artwork').src = '/nowplaying/artwork?' + Date.now();
```
### Player Controls
```javascript
async function play() { await fetch('/player/play', {method:'POST'}); }
async function pause() { await fetch('/player/pause', {method:'POST'}); }
async function next() { await fetch('/player/next', {method:'POST'}); }
async function prev() { await fetch('/player/previous', {method:'POST'}); }
async function setVolume(v) {
await fetch('/player/volume', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({volume:v})
});
}
```
### WebSocket Live Updates
```javascript
const ws = new WebSocket('ws://' + location.host + '/ws');
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch(msg.event) {
case 'TrackChanged':
document.getElementById('title').textContent = msg.data.title;
document.getElementById('artwork').src = '/nowplaying/artwork?' + Date.now();
break;
case 'PlayStateChanged':
updatePlayButton(msg.data.state);
break;
case 'PositionChanged':
updateProgress(msg.data.position, msg.data.duration);
break;
case 'VolumeChanged':
updateVolumeSlider(msg.data.volume);
break;
}
};
```
### Browse Library
```javascript
async function getArtists() {
const res = await fetch('/library/artists?limit=100');
const json = await res.json();
return json.data; // array of artist names
}
async function getAlbumsByArtist(artist) {
const res = await fetch('/library/albums?artist=' + encodeURIComponent(artist));
const json = await res.json();
return json.data; // array of album objects
}
async function searchLibrary(query) {
const res = await fetch('/library/search?q=' + encodeURIComponent(query));
const json = await res.json();
return json.data; // array of track objects
}
```
### Queue Management
```javascript
async function addToQueue(url, position='last') {
await fetch('/queue/add', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({url:url, position:position})
});
}
async function playNow(url) {
await fetch('/queue/playnow', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({url:url})
});
}
```
### Playlists - File vs URL Paths
```javascript
// GOTCHA: Playlist URLs from API are file paths, must be encoded for use in URLs
// Get all playlists
async function getPlaylists() {
const res = await fetch('/playlists');
const json = await res.json();
return json.data; // [{name:'My Playlist', url:'C:\\Users\\...\\My Playlist.m3u'}, ...]
}
// Get tracks in a playlist - MUST encode the path
async function getPlaylistTracks(playlistUrl) {
// playlistUrl is a Windows path like 'C:\Users\...\My Playlist.m3u'
const encoded = encodeURIComponent(playlistUrl);
const res = await fetch('/playlists/' + encoded + '/files');
const json = await res.json();
return json.data; // array of file paths
}
// Play a playlist
async function playPlaylist(playlistUrl) {
const encoded = encodeURIComponent(playlistUrl);
await fetch('/playlists/' + encoded + '/play', {method:'POST'});
}
// Add tracks to playlist - tracks are also file paths
async function addToPlaylist(playlistUrl, trackUrls) {
const encoded = encodeURIComponent(playlistUrl);
await fetch('/playlists/' + encoded + '/files', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({urls:trackUrls}) // trackUrls are file paths, not encoded here
});
}
// Create a playlist - returns the new playlist's file path
async function createPlaylist(name) {
const res = await fetch('/playlists', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({name:name})
});
const json = await res.json();
return json.data.url; // file path to new playlist
}
// Key insight:
// - In URL path: encode with encodeURIComponent()
// - In JSON body: use raw file paths (no encoding)
```
### Love/Unlove Toggle (Rating)
```javascript
let currentRating = 0;
async function toggleLove() {
// Preferred: POST /dashboard/love (uses RatingLove tag)
const newRating = currentRating === 5 ? 0 : 5;
await fetch('/rpc/NowPlaying_SetFileTag', {
method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({tag:'Rating', value:String(newRating)})
});
currentRating = newRating;
updateLoveButton();
}
function updateLoveButton() {
const btn = document.getElementById('love-btn');
btn.classList.toggle('loved', currentRating === 5);
}
// On track change, get current rating
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event === 'TrackChanged') {
// TrackChanged does not include rating — fetch it separately
fetch('/nowplaying').then(r => r.json()).then(j => {
if (j.success) currentRating = parseFloat(j.data.rating) || 0;
updateLoveButton();
});
}
};
```
### 3-Way Shuffle Toggle (off → shuffle → autodj → off)
Shuffle is a boolean. AutoDJ is a separate mode with its own endpoints.
```javascript
let shuffleEnabled = false;
let autoDjEnabled = false;
// Determine current mode from player status
async function loadShuffleState() {
const res = await fetch('/player/status');
const json = await res.json();
shuffleEnabled = json.data.shuffle;
autoDjEnabled = json.data.autoDj;
updateShuffleButton();
}
// Cycle: off → shuffle → autodj → off
async function cycleShuffle() {
if (!shuffleEnabled && !autoDjEnabled) {
// off → shuffle
await fetch('/player/shuffle', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({shuffle:true})
});
} else if (shuffleEnabled && !autoDjEnabled) {
// shuffle → autodj
await fetch('/player/autodj/start', {method:'POST'});
} else {
// autodj → off
await fetch('/player/autodj/stop', {method:'POST'});
await fetch('/player/shuffle', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({shuffle:false})
});
}
// Don't update local state - wait for WebSocket confirmation
}
function updateShuffleButton() {
const btn = document.getElementById('shuffle-btn');
const mode = autoDjEnabled ? 'autodj' : shuffleEnabled ? 'shuffle' : 'off';
btn.className = 'shuffle-' + mode;
btn.title = mode === 'off' ? 'Shuffle Off' : mode === 'shuffle' ? 'Shuffle' : 'AutoDJ';
}
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event === 'ShuffleChanged') {
shuffleEnabled = msg.data.enabled;
updateShuffleButton();
}
};
```
### Seek Bar with Position Updates
```javascript
let duration = 0;
let position = 0;
let isSeeking = false; // Prevent WebSocket updates while user is dragging
const seekBar = document.getElementById('seek-bar');
// User starts dragging
seekBar.addEventListener('mousedown', () => { isSeeking = true; });
seekBar.addEventListener('touchstart', () => { isSeeking = true; });
// User releases - send seek command
seekBar.addEventListener('mouseup', doSeek);
seekBar.addEventListener('touchend', doSeek);
async function doSeek() {
isSeeking = false;
const newPos = Math.round((seekBar.value / 100) * duration);
await fetch('/player/position', {
method:'PUT',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({position:newPos})
});
}
// WebSocket position updates - ignore while seeking
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (msg.event === 'PositionChanged' && !isSeeking) {
position = msg.data.position;
duration = msg.data.duration;
seekBar.value = duration > 0 ? (position / duration) * 100 : 0;
document.getElementById('time-current').textContent = formatTime(position);
document.getElementById('time-total').textContent = formatTime(duration);
}
if (msg.event === 'TrackChanged') {
duration = msg.data.duration || 0;
position = 0;
seekBar.value = 0;
}
};
function formatTime(ms) {
const s = Math.floor(ms / 1000);
const m = Math.floor(s / 60);
return m + ':' + String(s % 60).padStart(2, '0');
}
```
## Further Reading
These docs are fetchable from a running MBXHub instance. Read them for complete details.
### GET /docs - Full API Reference
Complete documentation for all 140+ endpoints with:
- Request/response examples for every endpoint
- Query parameters and body schemas
- Error codes and responses
- Integration notes and best practices
**Read when:** You need exact parameter names, response shapes, or edge cases
### GET /aria - ARiA Automation Guide
Remote input simulation and automation:
- DuckyScript syntax for keyboard macros
- Preset configuration (JSON format)
- Wake-on-LAN for sleeping PCs
- MusicBee hotkey integration
- Security considerations
**Read when:** Building automation, macros, or remote wake features
### GET /changelog - Version History
Release notes for all versions:
- New features and endpoints
- Breaking changes
- Bug fixes
**Read when:** Checking what's new or if an endpoint exists in current version
### GET /pages/player.html - Working Example
Full source code of the included web player:
- 3-column responsive layout
- All UI patterns implemented
- WebSocket integration
- Browse, queue, and playback
**Read when:** You want to see how something is actually implemented
### GET /pages/player.html - JavaScript Patterns
The example player includes all JS inline:
- All fetch() calls
- WebSocket handling
- State management
- UI updates
**Read when:** You need working code to copy/adapt
## Website
- https://mbxhub.com - Latest releases and downloads
- https://mbxhub.com/features.html - Feature overview with screenshots
- https://mbxhub.com/api.html - API docs (same as /docs)