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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
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
Header Parameters
authorization
x-api-key
Responses
Successful Response
application/json
JSON "additionalProperties": "string"
{
}