From 2aa529f8e4564975d6cc7831089d11e6757fad29 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Sun, 22 Feb 2026 19:15:11 -0800 Subject: [PATCH] Use new Spotify /items endpoint with fallback Add _get_playlist_items_page to call the new playlists/{id}/items endpoint (Feb 2026 API migration) and fall back to spotipy.playlist_items (old /tracks) on 403/404. Update _get_playlist_tracks and web_server.get_playlist_tracks to use the new helper to avoid 403 errors for Development Mode apps while preserving compatibility with Extended Quota Mode. --- core/spotify_client.py | 34 ++++++++++++++++++++++++++++------ web_server.py | 2 +- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/core/spotify_client.py b/core/spotify_client.py index 3f3ecf1c..13f5128a 100644 --- a/core/spotify_client.py +++ b/core/spotify_client.py @@ -505,15 +505,37 @@ class SpotifyClient: logger.error(f"Error fetching saved tracks: {e}") return [] + def _get_playlist_items_page(self, playlist_id: str, limit: int = 100, offset: int = 0) -> dict: + """Fetch playlist items using the /items endpoint (Feb 2026 Spotify API migration). + + Spotipy's playlist_items() still uses the deprecated /tracks endpoint internally, + which returns 403 for Development Mode apps after the Feb 2026 API changes. + Tries the new /items endpoint first, falls back to spotipy's /tracks for + Extended Quota Mode apps where /items may not be available yet. + """ + plid = self.sp._get_id("playlist", playlist_id) + try: + return self.sp._get( + f"playlists/{plid}/items", + limit=limit, + offset=offset, + additional_types="track,episode" + ) + except spotipy.SpotifyException as e: + if e.http_status in (403, 404): + # /items not available — fall back to old /tracks endpoint + return self.sp.playlist_items(playlist_id, limit=limit, offset=offset) + raise + @rate_limited def _get_playlist_tracks(self, playlist_id: str) -> List[Track]: if not self.is_spotify_authenticated(): return [] - + tracks = [] - + try: - results = self.sp.playlist_items(playlist_id, limit=100) + results = self._get_playlist_items_page(playlist_id, limit=100) while results: for item in results['items']: @@ -522,11 +544,11 @@ class SpotifyClient: if track_data and track_data.get('id'): track = Track.from_spotify_track(track_data) tracks.append(track) - + results = self.sp.next(results) if results['next'] else None - + return tracks - + except Exception as e: logger.error(f"Error fetching playlist tracks: {e}") return [] diff --git a/web_server.py b/web_server.py index c4784a0b..05f79470 100644 --- a/web_server.py +++ b/web_server.py @@ -16396,7 +16396,7 @@ def get_playlist_tracks(playlist_id): # Fetch all tracks with full album data tracks = [] - results = spotify_client.sp.playlist_items(playlist_id, limit=100) + results = spotify_client._get_playlist_items_page(playlist_id, limit=100) while results: for item in results['items']: