Apollo is a local music server for personal client apps. It ships with:
- a desktop Electron control UI
- a headless CLI that runs the same server stack
- a shared config file used by both UI and CLI
- Windows and Linux packaging targets for easy distribution
- starts a local HTTP API for tracks, playlists, downloads, and streaming
- manages a persistent library catalog and playlist data
- downloads tracks into an organised music library using
yt-dlpandffmpeg - normalizes and persists richer track metadata across providers and library rescans
- repairs weak downloaded metadata on later rescans when the original source can still be resolved
- imports playlists from supported external platforms with ordered track metadata
- supports provider search with pagination and direct-link ingest
- exposes stream URLs for future client apps through
/stream/:trackId
- Node.js 20+
yt-dlpffmpeg- no-key MusicBrainz and iTunes metadata are built in
- optional Spotify API credentials for Spotify metadata search
Install the dependencies, install the project packages, then start Apollo:
winget install OpenJS.NodeJS.LTS
winget install yt-dlp.yt-dlp
winget install Gyan.FFmpeg.Essentials
npm.cmd install
npm.cmd startDefault local API:
http://127.0.0.1:4848
MusicBrainz: no-key artist identity, artist profile data, and release groupsiTunes: no-key general song metadata and default artist track listingsDeezer: no-key fast track and artist metadataYouTube: search, playback resolution, and download fallbackSoundCloud: search, playback resolution, and download fallbackSpotify: optional metadata source when credentials work, plus direct Spotify track URL handling
Playlist import providers:
Spotify: playlist API import when client credentials are configuredDeezer: no-key playlist API importYouTubeandSoundCloud: playlist import throughyt-dlp
Install Node.js 20 or newer.
Windows:
winget install OpenJS.NodeJS.LTSApollo needs yt-dlp and ffmpeg for provider search, playback resolution, and server-side downloads.
Windows:
winget install yt-dlp.yt-dlp
winget install Gyan.FFmpeg.EssentialsIf you already have custom installs of either tool, you can point Apollo at them in the UI or shared config file.
From the project folder:
npm.cmd installSpotify support is metadata-only. It is optional.
If you want Spotify search:
- Go to
https://developer.spotify.com/dashboard - Sign in with your Spotify account
- Click
Create app - Give the app a name and description, then create it
- Open the app you just created in the Spotify Developer Dashboard
- Copy the
Client ID - Click
View client secret, then copy theClient Secret - Paste both values into Apollo in either:
- the Electron UI config screen
- the shared config file at
C:\Users\<you>\AppData\Roaming\apollo\config.json
If you edit the config file directly, set the Spotify fields there and restart Apollo so the new credentials are loaded.
Apollo UI and Apollo CLI use the same config file:
C:\Users\<you>\AppData\Roaming\apollo\config.json
You can print the exact path on your machine with:
npm.cmd run config:pathApollo can start the server automatically when the user signs in.
Setup in the UI:
- Start Apollo with
npm.cmd start - In
Config, enableStart server on login - Click
Save
How it works:
- Windows packaged builds register Apollo as a login item and start it in hidden
--backgroundmode - development builds fall back to the user Startup folder helper
- Linux desktop sessions install an XDG autostart entry that launches Apollo in hidden
--backgroundmode - the server runs without opening the UI window
Notes:
- when enabled, closing the UI window keeps Apollo running in the background
- opening Apollo again reuses the background instance instead of starting a second server
- to stop background mode completely, open Apollo, turn
Start server on loginoff, clickSave, then close the app
Apollo now includes packaging targets through electron-builder.
Build all desktop artifacts:
npm.cmd run distBuild Windows installers only:
npm.cmd run dist:winBuild a portable Windows executable:
npm.cmd run dist:portableBuild Linux packages only:
npm.cmd run dist:linuxBuild output goes to dist/.
Current targets:
- Windows:
nsisinstaller andportable - Linux:
AppImageanddeb
Apollo now keeps a tray icon alive while the background server is enabled.
- closing the main window hides Apollo instead of quitting when background mode is on
- the tray menu can reopen Apollo, open the library folder, toggle
Start with system, check for updates, and quit cleanly - Windows packaged builds use the native login-item integration
- Linux desktop builds use XDG autostart when
Start server on loginis enabled
Apollo now includes electron-updater wiring for packaged builds and is configured to use GitHub Releases for this repository by default.
For local release publishing:
$env:GH_TOKEN = "github-personal-access-token"
npm.cmd run release:winThat publishes the Windows installer artifacts to the ProtonDev-sys/apollo GitHub releases feed.
Installed Windows builds that use the NSIS installer can then use that GitHub feed automatically. Portable builds should be updated manually by replacing the executable. You can still override the feed URL for installed builds with:
$env:APOLLO_UPDATE_URL = "https://your-domain.example/apollo"Apollo exposes update checks through both the UI and the tray menu.
Linux background service helper:
Use the user-service helper if you want Apollo managed by systemd --user instead of desktop-session autostart:
npm run linux:service:install -- --exec /absolute/path/to/apolloRemove it with:
npm run linux:service:removeIf you want branded installers, add installer assets under build/, such as:
build/icon.icofor Windowsbuild/icon.pngfor Linux
Apollo can require one shared secret for all API, stream, and playlist artwork requests. There are no user accounts.
Setup in the UI:
- Start Apollo with
npm.cmd start - In
Config, enableRequire API auth - Set
Session TTL (hours) - Set
API shared secret - Click
Save - Give the shared secret to your client through a secure channel
Or configure it in config.json:
{
"settings": {
"apiAuthEnabled": true,
"apiSessionTtlHours": 168,
"apiSharedSecret": "replace-this-with-a-long-secret"
}
}Restart Apollo after editing the file.
Client flow:
Clients should follow this sequence with any HTTP client.
- Check whether auth is enabled:
GET /api/auth/status HTTP/1.1
Host: 127.0.0.1:4848Response example:
{
"enabled": true,
"configured": true,
"sessionTtlHours": 168
}- Create a session token with the shared secret:
POST /api/auth/session HTTP/1.1
Host: 127.0.0.1:4848
Content-Type: application/json
{
"secret": "replace-this-with-the-shared-secret"
}Response example:
{
"token": "session-token",
"tokenType": "Bearer",
"expiresAt": "2026-03-16T10:00:00.000Z"
}- Send the returned token on later API requests:
GET /api/health HTTP/1.1
Host: 127.0.0.1:4848
Authorization: Bearer session-tokenNotes:
- leave
API shared secretblank in the UI if you want to keep the current secret - changing the shared secret revokes all existing sessions
- restarting Apollo revokes all existing sessions
- the shared secret is stored as a hash, not plaintext
- session tokens are stored only in memory
- for browser media or images, use
?access_token=...on/stream/...and/media/...URLs if headers are not possible - auth does not add TLS; for anything beyond localhost or a trusted LAN, use HTTPS or a VPN
npm.cmd startIf Start server on login is enabled, Apollo launches in hidden background mode at sign-in and the UI window stays closed until you open the app manually.
npm.cmd run start:cliThe Electron app and CLI use the same config file and data directory.
Print the config path:
npm.cmd run config:pathPrint the current config:
npm.cmd run config:printExport a copy of the current config:
node cli.js export-config .\apollo.config.jsonApollo exposes a local HTTP API on the host and port in your config.
Default base URL:
http://127.0.0.1:4848
General notes:
- request and response bodies are JSON unless noted otherwise
- set header
Content-Type: application/jsononPOSTrequests - when API auth is enabled, send
Authorization: Bearer <token>on all API requests after login - send
X-Client-Id: <stable-client-id>on search requests so Apollo can cancel older in-flight searches from the same client without affecting other clients - CORS is enabled with
Access-Control-Allow-Origin: * - Apollo reuses in-flight work for identical expensive requests and keeps a short in-memory cache for recent search, playback, download-resolution, and inspect-link responses
- library rescans batch catalog writes instead of persisting each discovered file individually
- library rescans read embedded file tags and try to repair weak stored metadata from the original source when possible
- MusicBrainz-backed artist requests are throttled and cached because MusicBrainz documents a 1 request/second rate limit
- multi-provider search fans out across the requested providers in parallel unless the client disconnects or a newer request from the same client supersedes the older one
- artist search results are enriched with Deezer artwork and top releases when possible to reduce client follow-up calls
- pagination is 1-based
pageSizeis capped internally for search and track listing- track-like responses include normalized metadata fields:
normalizedTitle,normalizedArtist,normalizedAlbum,normalizedDuration, andmetadataSource - track-like responses can also include richer metadata such as
artists,albumArtist,trackNumber,discNumber,releaseDate,releaseYear,genre,explicit,isrc,sourcePlatform, andsourceUrl - shared-track resolution accepts compact public IDs like
deezer:<id>,spotify:<id>,youtube:<id>, anditunes:<id>
Returns whether API auth is enabled and whether a shared secret is configured.
Response example:
{
"enabled": true,
"configured": true,
"sessionTtlHours": 168
}Authenticates a client with the shared secret and returns a session token.
Request body:
{
"secret": "your-shared-secret"
}Response example:
{
"token": "session-token",
"tokenType": "Bearer",
"expiresAt": "2026-03-16T10:00:00.000Z"
}Revokes the current session token.
Returns server status and library overview.
Response example:
{
"status": "ok",
"server": {
"running": true,
"host": "127.0.0.1",
"port": "4848",
"baseUrl": "http://127.0.0.1:4848"
},
"overview": {
"trackCount": 0,
"playlistCount": 0,
"downloadCount": 0,
"completedDownloads": 0
}
}Lists downloaded library tracks only.
Query params:
q: optional text search across title, artist, album, and file pathpage: optional, default1pageSize: optional, default20
Response example:
{
"items": [
{
"id": "track-id",
"title": "Harder Better Faster Stronger",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "Discovery",
"albumArtist": "Daft Punk",
"trackNumber": 4,
"discNumber": 1,
"duration": 224,
"releaseDate": "2001-03-07",
"releaseYear": 2001,
"genre": "French House",
"explicit": false,
"provider": "library",
"sourcePlatform": "youtube",
"artwork": "",
"sourceUrl": "https://www.youtube.com/watch?v=abc123",
"providerIds": {
"deezer": "",
"itunes": "",
"spotify": "",
"youtube": "",
"soundcloud": "",
"isrc": ""
},
"isrc": "",
"externalUrl": "http://127.0.0.1:4848/stream/track-id",
"downloadTarget": "http://127.0.0.1:4848/stream/track-id?download=1",
"trackId": "track-id",
"fileName": "Harder Better Faster Stronger.mp3",
"normalizedTitle": "harder better faster stronger",
"normalizedArtist": "daft punk",
"normalizedAlbum": "discovery",
"normalizedDuration": 224,
"metadataSource": "library"
}
],
"total": 1,
"page": 1,
"pageSize": 20,
"totalPages": 1
}Deletes a downloaded track from the Apollo library.
Behavior:
- removes the audio file from disk
- removes the track from the catalog
- unlinks the track from any playlists that reference it
- preserves imported remote playlist entries when Apollo still has stored source metadata for them
Path params:
trackId: Apollo track ID
Response example:
{
"ok": true,
"id": "track-id",
"filePath": "C:\\Music\\Apollo\\library\\Daft Punk\\Discovery\\One More Time.mp3"
}Searches both the local library and remote providers so clients can search tracks whether they are downloaded or not.
Query params:
queryorq: search termscope:all,library, orremoteprovider:all,youtube,soundcloud,spotify,itunes, ordeezerstream: optional, set to1to stream partial results over Server-Sent EventsclientId: optional fallback for client identification if you cannot sendX-Client-Idpage: optional, default1pageSize: optional, default20
Response example:
{
"query": "daft punk",
"provider": "all",
"scope": "all",
"library": {
"items": [],
"total": 0,
"page": 1,
"pageSize": 10,
"totalPages": 1
},
"remote": {
"items": [
{
"id": "youtube:abc123",
"provider": "youtube",
"title": "One More Time",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "YouTube",
"albumArtist": "Daft Punk",
"duration": 321,
"releaseDate": "",
"releaseYear": null,
"genre": "",
"explicit": null,
"sourcePlatform": "youtube",
"artwork": "",
"externalUrl": "https://www.youtube.com/watch?v=abc123",
"downloadTarget": "https://www.youtube.com/watch?v=abc123",
"sourceUrl": "https://www.youtube.com/watch?v=abc123",
"providerIds": {
"deezer": "",
"itunes": "",
"spotify": "",
"youtube": "abc123",
"soundcloud": "",
"isrc": ""
},
"isrc": "",
"normalizedTitle": "one more time",
"normalizedArtist": "daft punk",
"normalizedAlbum": "youtube",
"normalizedDuration": 321,
"metadataSource": "youtube"
}
],
"total": 1,
"page": 1,
"pageSize": 10,
"totalPages": 1,
"providerErrors": {},
"warning": ""
}
}Notes:
library.itemscontain downloaded tracks formatted for playback through Apolloremote.itemscontain provider results that can be played directly or queued for downloadstream=1orAccept: text/event-streamswitches the endpoint to SSE mode and emitssnapshotevents as providers finish, followed by a finaldoneevent- SSE search snapshots include
remote.progress.complete,remote.progress.completedProviders,remote.progress.pendingProviders,remote.progress.lastProvider, andremote.progress.lastStatus - if the same client sends a newer search before the previous one completes, Apollo cancels the older search and keeps the newer one
- different
X-Client-Idvalues are isolated, so one client does not cancel another client's search - Apollo keeps a short in-memory cache of recent identical searches to avoid repeating provider work
- duplicate remote results from fallback/provider overlap are collapsed into a single best result
itunesis the built-in no-key metadata provider for general song searchdeezeris another no-key fast metadata provider for cold search- multi-provider search fans out to each selected provider in parallel unless the client disconnects or a newer search from the same client supersedes it
- Spotify track URLs work through
query=<spotify track url>orPOST /api/inspect-link - if Spotify catalog search is blocked by Spotify, Apollo falls back to YouTube audio results and returns the original Spotify failure in
remote.providerErrors.spotify provider=allincludesspotify,youtube,soundcloud,itunes, anddeezer
Searches artists using MusicBrainz. This does not require API keys.
Query params:
queryorq: artist namepage: optional, default1pageSize: optional, default20
Response example:
{
"items": [
{
"id": "056e4f3e-d505-4dad-8ec1-d04f521cbb56",
"name": "Daft Punk",
"sortName": "Daft Punk",
"type": "Group",
"country": "FR",
"area": "France",
"disambiguation": "French electronic duo",
"lifeSpan": {
"begin": "1993",
"end": "2021-02-22",
"ended": true
},
"tags": ["electronic", "house"],
"source": "musicbrainz"
}
],
"total": 1,
"page": 1,
"pageSize": 20,
"totalPages": 1
}Notes:
- Apollo enriches artist results with artwork, Deezer IDs, and a few top releases when a Deezer match is available
Returns a MusicBrainz artist profile with aliases, genres, and a trimmed set of external links.
Returns release groups for the artist from MusicBrainz.
Response items include:
idtitleprimaryTypesecondaryTypesfirstReleaseDate
Returns a release tracklist for a MusicBrainz release-group ID.
Behavior:
- Apollo resolves the release group to the best release available
- the response includes track order, release metadata, and Apollo-compatible
downloadTargetvalues
Returns a practical artist song list without API keys.
Behavior:
- prefers iTunes song metadata for cleaner artist-track results
- falls back to MusicBrainz recordings if iTunes has no usable tracks
- each track still includes a
downloadTargetthat Apollo can resolve through YouTube for playback/download
Returns related playable tracks from no-key metadata sources.
Request body:
{
"title": "One More Time",
"artist": "Daft Punk",
"album": "Discovery",
"limit": 5
}Or seed from a downloaded Apollo track:
{
"trackId": "track-id",
"limit": 5
}Notes:
- recommendations prefer local library matches, Deezer artist top tracks, and iTunes artist tracks
- responses include provider IDs, artwork, duration, and stable IDs so clients can dedupe cleanly
Convenience wrapper around recommendations for a downloaded Apollo track.
Query params:
limit: optional, default12
Resolves a playable URL for either a downloaded library track or a remote result.
Request body for a library track:
{
"trackId": "track-id"
}Request body for a remote result:
{
"provider": "youtube",
"title": "One More Time",
"artist": "Daft Punk",
"album": "Discovery",
"downloadTarget": "https://www.youtube.com/watch?v=abc123"
}Response for a library track:
{
"type": "library",
"streamUrl": "http://127.0.0.1:4848/stream/track-id",
"downloadUrl": "http://127.0.0.1:4848/stream/track-id?download=1",
"title": "One More Time",
"artist": "Daft Punk",
"album": "Discovery"
}Response for a remote result:
{
"type": "remote",
"streamUrl": "https://resolved-media-url",
"downloadUrl": "https://resolved-media-url",
"title": "One More Time",
"artist": "Daft Punk",
"album": "Discovery",
"fileName": "Daft Punk - One More Time.mp3"
}Notes:
- identical concurrent playback-resolution requests share one upstream lookup
- recent identical playback-resolution requests are served from a short in-memory cache
Apollo has two download endpoints:
POST /api/downloads/serverdownloads a remote track into the Apollo server libraryPOST /api/downloads/clientreturns a direct download URL so the client can download the file itself
Queues a remote item to be downloaded, converted, organised into the library, and indexed on the server.
Request body:
{
"provider": "youtube",
"title": "One More Time",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "Discovery",
"albumArtist": "Daft Punk",
"trackNumber": 1,
"discNumber": 1,
"releaseDate": "2001-11-30",
"genre": "French House",
"explicit": false,
"downloadTarget": "https://www.youtube.com/watch?v=abc123",
"externalUrl": "https://www.youtube.com/watch?v=abc123"
}Response example:
{
"id": "download-id",
"title": "One More Time",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "Discovery",
"albumArtist": "Daft Punk",
"trackNumber": 1,
"discNumber": 1,
"releaseDate": "2001-11-30",
"releaseYear": 2001,
"genre": "French House",
"explicit": false,
"provider": "youtube",
"sourcePlatform": "youtube",
"artwork": "https://example.com/cover.jpg",
"isrc": "GBDUW0100001",
"sourceUrl": "https://www.youtube.com/watch?v=abc123",
"status": "queued",
"progress": 0,
"message": "Waiting for worker...",
"outputPath": "",
"trackId": "",
"createdAt": "2026-03-09T10:00:00.000Z",
"updatedAt": "2026-03-09T10:00:00.000Z"
}Statuses:
queuedrunningcompletedfailed
Notes:
- Apollo rejects duplicate downloads when the same track is already in the library or already queued
- Apollo resolves provider metadata before downloading, merges in stronger metadata from supported public APIs when useful, then tags the audio file before import
- written/imported metadata includes
title,artist,album,albumArtist,trackNumber,discNumber,releaseDate,genre, andisrcwhen available
Lists download jobs, newest first.
Response example:
{
"items": [
{
"id": "download-id",
"status": "completed",
"progress": 100,
"message": "Downloaded and indexed in the library.",
"trackId": "track-id",
"outputPath": "C:\\Music\\Apollo\\library\\Daft Punk\\Discovery\\One More Time.mp3"
}
]
}Resolves a download URL for the client without storing the file on the Apollo server.
Request body for a library track:
{
"trackId": "track-id"
}Request body for a remote result:
{
"provider": "youtube",
"title": "One More Time",
"artist": "Daft Punk",
"album": "Discovery",
"downloadTarget": "https://www.youtube.com/watch?v=abc123"
}Response example:
{
"type": "remote",
"downloadUrl": "https://resolved-media-url",
"fileName": "Daft Punk - One More Time.mp3",
"title": "One More Time",
"artist": "Daft Punk"
}Notes:
- identical concurrent client-download resolution requests share one upstream lookup
- recent identical client-download resolution requests are served from a short in-memory cache
Inspects a direct media URL and returns a normalized item that can be used for playback or download.
Request body:
{
"url": "https://www.youtube.com/watch?v=abc123"
}Response example:
{
"id": "link:abc123",
"provider": "youtube",
"title": "One More Time",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "Singles",
"albumArtist": "Daft Punk",
"duration": 321,
"releaseDate": "",
"releaseYear": null,
"genre": "",
"explicit": null,
"sourcePlatform": "youtube",
"artwork": "",
"externalUrl": "https://www.youtube.com/watch?v=abc123",
"sourceUrl": "https://www.youtube.com/watch?v=abc123",
"downloadTarget": "https://www.youtube.com/watch?v=abc123",
"providerIds": {
"deezer": "",
"itunes": "",
"spotify": "",
"youtube": "abc123",
"soundcloud": "",
"isrc": ""
},
"isrc": "",
"normalizedTitle": "one more time",
"normalizedArtist": "daft punk",
"normalizedAlbum": "singles",
"normalizedDuration": 321,
"metadataSource": "youtube"
}Notes:
- identical concurrent inspect-link requests share one upstream lookup
- recent identical inspect-link requests are served from a short in-memory cache
Resolves a compact shared track token into a full Apollo-compatible track object.
Request body:
{
"id": "deezer:3709069532"
}Response example:
{
"id": "deezer:3709069532",
"title": "Veridis Quo",
"artist": "Daft Punk",
"artists": ["Daft Punk"],
"album": "Discovery",
"albumArtist": "Daft Punk",
"duration": 345,
"provider": "deezer",
"sourcePlatform": "deezer",
"artwork": "https://example.com/discovery.jpg",
"externalUrl": "https://www.deezer.com/track/3709069532",
"downloadTarget": "ytsearch1:Daft Punk Veridis Quo audio",
"sourceUrl": "https://www.deezer.com/track/3709069532",
"providerIds": {
"deezer": "3709069532",
"itunes": "",
"spotify": "",
"youtube": "",
"soundcloud": "",
"isrc": "GBDUW0100002"
},
"isrc": "GBDUW0100002",
"playable": true,
"normalizedTitle": "veridis quo",
"normalizedArtist": "daft punk",
"normalizedAlbum": "discovery",
"normalizedDuration": 345,
"metadataSource": "deezer"
}Supported namespaces:
deezer:<track-id>spotify:<track-id>youtube:<video-id>itunes:<track-id>library:<apollo-track-id>only when the receiving server already has that exact local Apollo track ID
Notes:
- this endpoint exists so clients do not need provider-specific hydration rules in the renderer
soundcloud:<id>is rejected because a bare SoundCloud ID is not reliably resolvable without a canonical URLlibrary:<id>is local to one Apollo server; if another server does not know that track ID it cannot resolve it- identical concurrent shared-track resolution requests share one upstream lookup
Rescans the configured library directory and reindexes files.
Response example:
{
"items": [],
"total": 0,
"page": 1,
"pageSize": 8,
"totalPages": 1
}Notes:
- if several rescans are requested at the same time, Apollo shares one in-flight rescan instead of starting multiple scans
- discovered tracks are written back in batches so large rescans do not hammer the state file
- rescans read embedded file tags instead of relying only on folder names
- tracks with weak stored metadata are re-enriched from their original source URL or provider identity when Apollo can still resolve them
Lists playlists with expanded ordered entries and flattened track objects.
Response example:
{
"items": [
{
"id": "playlist-id",
"name": "Favorites",
"title": "Favorites",
"description": "",
"artworkUrl": "https://example.com/playlist.jpg",
"sourcePlatform": "spotify",
"sourcePlaylistId": "37i9dQZF1DXcBWIGoYBM5M",
"sourceUrl": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
"sourceSnapshotId": "snapshot-id",
"ownerName": "Spotify",
"importedAt": "2026-03-11T12:00:00.000Z",
"trackIds": ["track-id"],
"entries": [
{
"id": "entry-id",
"order": 0,
"trackId": "track-id",
"unavailable": false,
"error": "",
"addedAt": "2026-03-11T12:00:00.000Z",
"track": {
"id": "track-id",
"title": "One More Time",
"artist": "Daft Punk",
"provider": "library"
}
}
],
"tracks": [
{
"id": "track-id",
"title": "One More Time",
"artist": "Daft Punk",
"provider": "library"
}
],
"createdAt": "2026-03-09T10:00:00.000Z",
"updatedAt": "2026-03-09T10:00:00.000Z"
}
]
}Creates a playlist.
Request body:
{
"name": "Favorites",
"description": "Main rotation"
}Imports a playlist from a supported external provider and stores ordered playlist entries.
Request body:
{
"url": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
}Behavior:
- supported providers are Spotify, Deezer, YouTube, and SoundCloud
- Spotify playlist import requires configured Spotify client credentials
- imported playlist entries keep provider metadata even when Apollo does not already have the track downloaded
- imported entries are linked to matching Apollo library tracks when possible
- unavailable provider items are preserved as playlist entries with
unavailableanderror
Adds a downloaded library track to a playlist.
Path params:
id: playlist ID
Request body:
{
"trackId": "track-id"
}Removes a track from a playlist.
Path params:
id: playlist IDtrackId: library track ID
Notes:
- playlist order is preserved through
entries[].order tracks[]is a convenience flattened view of the resolved entry tracks- imported playlists can contain a mix of linked local tracks and provider-backed remote entries
When API auth is enabled, either:
- send
Authorization: Bearer <token> - or append
?access_token=<token>to the URL
Streams a downloaded library file from Apollo.
Notes:
- supports
Rangerequests for media playback - long-lived audio streams are kept open without the normal HTTP request timeout
- add
?download=1to force a file download trackIdis the ID of a downloaded library track- only works for downloaded library tracks, not remote provider items
When a request fails, Apollo returns JSON like:
{
"error": "Track not found."
}Common cases:
404for unknown routes or missing stream tracks500for validation, dependency, provider, or runtime failures