Appearance
spotify
9 endpoints at a glance
| Method | Path | Summary |
|---|---|---|
GET | /spotify/auth/callback | Auth Callback |
DELETE | /spotify/me | Unlink Spotify |
GET | /spotify/me | Get My Spotify Link |
POST | /spotify/me/auth/start | Start Auth |
POST | /spotify/me/import-favorites | Import Liked Songs To Favorites |
GET | /spotify/me/playlists | List My Playlists |
GET | /spotify/me/playlists/{spotify_playlist_id} | Get My Playlist |
GET | /spotify/me/playlists/{spotify_playlist_id}/plex-coverage | Get Playlist Plex Coverage |
POST | /spotify/me/sync | Sync My Library |
spotify
Get My Spotify Link
GET
/spotify/me
Return the caller's Spotify identity, or {link: null} if
not linked.
Mirrors the shape of GET /plex/me so the SPA can use the same
"is this service connected?" pattern.
Fetches fresh from Mongo rather than trusting the dependency dict
— Spotify access tokens rotate (refresh flow writes back to prefs
on every refresh), so a request-scoped snapshot can lag behind
reality. The cost is one extra single-doc read per call which is
negligible for a per-user endpoint.
Parameters
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}
Unlink Spotify
DELETE
/spotify/me
Drop all Spotify creds + identity from the caller's prefs.
Doesn't revoke on Spotify's side — that's only possible by the
user visiting accounts.spotify.com/apps. The refresh token becomes
inert as soon as they do; until then we just stop using it.
Parameters
Responses
Successful Response
application/json
JSON "additionalProperties": true
{
}
Start Auth
POST
/spotify/me/auth/start
Begin the OAuth flow — returns the URL the SPA should redirect to.
The SPA does the actual navigation; we don't 302 directly because
that would lose the user's session cookie context (Spotify needs
to redirect back to our origin, where the same cookie is in
play).
Parameters
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}
Auth Callback
GET
/spotify/auth/callback
OAuth callback — Spotify redirects here after the user consents.
Three possible shapes:
?code=...&state=...— success path; exchange and persist.?error=...— user clicked Cancel on Spotify's consent
screen, or Spotify rejected the request. Redirect with the
reason so the Settings view can show a meaningful message.- Missing everything — someone hit the URL directly. 400.
On any failure we redirect to the SPA's Settings page with a
spotify=error&reason=... query so the user sees the failure
on the same page they started the flow.
Parameters
Query Parameters
code
state
error
Responses
Successful Response
application/json
JSON
[
]
Sync My Library
POST
/spotify/me/sync
Kick off a Spotify library + playlists + recently-played sync.
Fire-and-forget: returns a task_id immediately so the SPA's
HTTP client doesn't time out while the sync walks pages of saved
tracks / albums / followed-artists / playlists. The SPA polls
GET /admin/task-history/{task_id} (or uses the
useBackgroundTask composable) to surface progress + the
final summary.
Sync time is roughly linear in (saved-tracks + Σ playlist tracks),
plus one /me/player/recently-played call. A typical user with ~1k
saved tracks + 30 playlists lands in 30–60 seconds — well past
the SPA's 15s axios timeout, hence the background-task shape.
412 if the user hasn't linked Spotify.
Parameters
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}
Get Playlist Plex Coverage
GET
/spotify/me/playlists/{spotify_playlist_id}/plex-coverage
Report how many tracks in a Spotify playlist resolve to Plex.
Used by the SPA's "preview before sync" UI: before turning a
Spotify playlist into a Plex playlist, the user sees X of Y tracks have a Plex match; Z are Spotify-only. Doesn't touch
Plex — everything resolves from local refs.
The special id __liked_songs__ reports against the user's
Spotify Liked Songs (which lives in spotify_saved_tracks,
not spotify_playlists).
Parameters
Path Parameters
spotify_playlist_id*
Type
Requiredstring
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}
List My Playlists
GET
/spotify/me/playlists
List the caller's Spotify playlists from the local mirror.
owned_only=True (default) hides playlists the user follows
but didn't create — matches Settings's default. Pass
owned_only=false to include followed playlists.
Reads from spotify_playlists only; doesn't call Spotify. Run
POST /spotify/me/sync first to populate the mirror.
Parameters
Query Parameters
owned_only
Type
boolean
Default
trueResponses
Successful Response
application/json
JSON { "additionalProperties": "string" }
[
]
Get My Playlist
GET
/spotify/me/playlists/{spotify_playlist_id}
Get a single Spotify playlist with its embedded items.
Items carry canonical_track_id when our sync resolved them
against an existing canonical. Resolution happens at sync time
— calling this endpoint never triggers a Spotify network call.
Parameters
Path Parameters
spotify_playlist_id*
Type
Requiredstring
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}
Import Liked Songs To Favorites
POST
/spotify/me/import-favorites
One-shot: import the caller's Spotify Liked Songs into
canonical favorites.
Walks spotify_saved_tracks and upserts each row with a
resolved canonical onto media_favorites. Idempotent —
already-favorited tracks count separately in the summary so the
SPA can render "imported 12 new; 35 already favorited".
Prerequisite: a Spotify sync must have run already (so saved
tracks exist in the mirror with canonical_id stamped). If the
user has just linked Spotify but hasn't synced yet, they get
candidates: 0 and nothing happens — the SPA should suggest
"click Sync first".
Parameters
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}