[GH-ISSUE #135] Constant API Rate Limit Exceeded Error 429 #118

Closed
opened 2026-02-27 04:57:37 +03:00 by kerem · 97 comments
Owner

Originally created by @the-vainglory-consensus on GitHub (Dec 22, 2025).
Original GitHub issue: https://github.com/Googolplexed0/zotify/issues/135

Originally assigned to: @Googolplexed0 on GitHub.

Zotify Version
v0.10.30

Bug Description
Getting API ERROR messages reading "429: API rate limit exceeded" whenever I try to do anything with Zotify. Just getting my list of favorited artists or playlists does this.

Bug Triggering Command
The command that caused the error/crash, including relevant url arguments.

Error Traceback / Logs

WARNING: API ERROR (TRY 0) - RETRYING

429: API rate limit exceeded

WARNING: API ERROR (TRY 1) - RETRYING

429: API rate limit exceeded

API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED

429: API rate limit exceeded

WARNING: Key "items" not found in API response: {'error': {'status': 429, 'message': 'API rate limit exceeded'}}

Traceback (most recent call last):
File "", line 198, in run_module_as_main
File "", line 88, in run_code
File "c:\users\pr4yi.local\bin\zotify.exe_main
.py", line 6, in
sys.exit(main())
~~~~^^
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify_main
.py", line 140, in main
args.func(args, modes)
~~~~~~~~~^^^^^^^^^^^^^
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 78, in client
perform_query(args)
~~~~~~~~~~~~~^^^^^^
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 69, in perform_query
raise e
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 50, in perform_query
UserPlaylist(Zotify.DATETIME_LAUNCH).execute()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1760, in execute
user_item_resps = self.fetch_user_items()
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1742, in fetch_user_items
user_item_resps = Zotify.invoke_url_nextable(f"{self.url}?{MARKET_APPEND}", stripper=self.outer_stripper)
File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\config.py", line 812, in invoke_url_nextable
return items[strip] if len(strippers) == 1 and not isinstance(stripper, tuple) else items
~~~~~^^^^^^^
KeyError: None

Config File
{
"DEBUG": "False",
"ROOT_PATH": "",
"SAVE_CREDENTIALS": "True",
"CREDENTIALS_LOCATION": "",
"OUTPUT": "",
"OUTPUT_SINGLE": "{artist}/{album}/{artist}{song_name}",
"OUTPUT_ALBUM": "{artist}/{album}/{album_num}
{artist}{song_name}",
"OUTPUT_PLAYLIST_EXT": "{playlist}/{playlist_num}
{artist}{song_name}",
"OUTPUT_LIKED_SONGS": "Liked Songs/{artist}
{song_name}",
"ROOT_PODCAST_PATH": "",
"SPLIT_ALBUM_DISCS": "False",
"MAX_FILENAME_LENGTH": "0",
"OPTIMIZED_DOWNLOADING": "True",
"BULK_WAIT_TIME": "45",
"DOWNLOAD_REAL_TIME": "False",
"TEMP_DOWNLOAD_DIR": "",
"DOWNLOAD_PARENT_ALBUM": "False",
"NO_COMPILATION_ALBUMS": "False",
"REGEX_ENABLED": "False",
"REGEX_TRACK_SKIP": "",
"REGEX_EPISODE_SKIP": "",
"REGEX_ALBUM_SKIP": "",
"DOWNLOAD_FORMAT": "ogg",
"DOWNLOAD_QUALITY": "very_high",
"TRANSCODE_BITRATE": "auto",
"CUSTOM_FFMEPG_ARGS": "",
"SONG_ARCHIVE_LOCATION": "",
"DISABLE_SONG_ARCHIVE": "False",
"DISABLE_DIRECTORY_ARCHIVES": "False",
"SKIP_EXISTING": "True",
"SKIP_PREVIOUSLY_DOWNLOADED": "False",
"EXPORT_M3U8": "False",
"M3U8_LOCATION": "",
"M3U8_REL_PATHS": "True",
"LIKED_SONGS_ARCHIVE_M3U8": "True",
"DOWNLOAD_LYRICS": "True",
"LYRICS_LOCATION": "",
"LYRICS_FILENAME": "{artist}_{song_name}",
"ALWAYS_CHECK_LYRICS": "False",
"LYRICS_MD_HEADER": "False",
"LANGUAGE": "en",
"STRICT_LIBRARY_VERIFY": "True",
"MD_DISC_TRACK_TOTALS": "True",
"MD_SAVE_GENRES": "False",
"MD_ALLGENRES": "False",
"MD_GENREDELIMITER": ",",
"MD_ARTISTDELIMITER": ", ",
"MD_SAVE_LYRICS": "True",
"ALBUM_ART_JPG_FILE": "False",
"SEARCH_QUERY_SIZE": "10",
"RETRY_ATTEMPTS": "1",
"CHUNK_SIZE": "20000",
"REDIRECT_ADDRESS": "127.0.0.1",
"PRINT_SPLASH": "False",
"PRINT_PROGRESS_INFO": "True",
"PRINT_SKIPS": "True",
"PRINT_DOWNLOADS": "False",
"PRINT_DOWNLOAD_PROGRESS": "True",
"PRINT_URL_PROGRESS": "True",
"PRINT_ALBUM_PROGRESS": "True",
"PRINT_ARTIST_PROGRESS": "True",
"PRINT_PLAYLIST_PROGRESS": "True",
"PRINT_WARNINGS": "True",
"PRINT_ERRORS": "True",
"PRINT_API_ERRORS": "True",
"STANDARD_INTERFACE": "False",
"FFMPEG_LOG_LEVEL": "error",
"vvv___DEPRECIATED_BELOW_HERE___vvv": "vvv___REMOVE_THESE___vvv",
"SONG_ARCHIVE": "",
"OVERRIDE_AUTO_WAIT": "False"
}

Originally created by @the-vainglory-consensus on GitHub (Dec 22, 2025). Original GitHub issue: https://github.com/Googolplexed0/zotify/issues/135 Originally assigned to: @Googolplexed0 on GitHub. **Zotify Version** v0.10.30 **Bug Description** Getting API ERROR messages reading "429: API rate limit exceeded" whenever I try to do anything with Zotify. Just getting my list of favorited artists or playlists does this. **Bug Triggering Command** The command that caused the error/crash, including relevant url arguments. **Error Traceback / Logs** ### WARNING: API ERROR (TRY 0) - RETRYING ### ### 429: API rate limit exceeded ### ### WARNING: API ERROR (TRY 1) - RETRYING ### ### 429: API rate limit exceeded ### ### API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED ### ### 429: API rate limit exceeded ### ### WARNING: Key "items" not found in API response: {'error': {'status': 429, 'message': 'API rate limit exceeded'}} ### Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "c:\users\pr4yi\.local\bin\zotify.exe\__main__.py", line 6, in <module> sys.exit(main()) ~~~~^^ File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 140, in main args.func(args, modes) ~~~~~~~~~^^^^^^^^^^^^^ File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 78, in client perform_query(args) ~~~~~~~~~~~~~^^^^^^ File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 69, in perform_query raise e File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 50, in perform_query UserPlaylist(Zotify.DATETIME_LAUNCH).execute() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1760, in execute user_item_resps = self.fetch_user_items() File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1742, in fetch_user_items user_item_resps = Zotify.invoke_url_nextable(f"{self.url}?{MARKET_APPEND}", stripper=self.outer_stripper) File "C:\Users\pr4yi\pipx\venvs\zotify\Lib\site-packages\zotify\config.py", line 812, in invoke_url_nextable return items[strip] if len(strippers) == 1 and not isinstance(stripper, tuple) else items ~~~~~^^^^^^^ KeyError: None **Config File** { "DEBUG": "False", "ROOT_PATH": "", "SAVE_CREDENTIALS": "True", "CREDENTIALS_LOCATION": "", "OUTPUT": "", "OUTPUT_SINGLE": "{artist}/{album}/{artist}_{song_name}", "OUTPUT_ALBUM": "{artist}/{album}/{album_num}_{artist}_{song_name}", "OUTPUT_PLAYLIST_EXT": "{playlist}/{playlist_num}_{artist}_{song_name}", "OUTPUT_LIKED_SONGS": "Liked Songs/{artist}_{song_name}", "ROOT_PODCAST_PATH": "", "SPLIT_ALBUM_DISCS": "False", "MAX_FILENAME_LENGTH": "0", "OPTIMIZED_DOWNLOADING": "True", "BULK_WAIT_TIME": "45", "DOWNLOAD_REAL_TIME": "False", "TEMP_DOWNLOAD_DIR": "", "DOWNLOAD_PARENT_ALBUM": "False", "NO_COMPILATION_ALBUMS": "False", "REGEX_ENABLED": "False", "REGEX_TRACK_SKIP": "", "REGEX_EPISODE_SKIP": "", "REGEX_ALBUM_SKIP": "", "DOWNLOAD_FORMAT": "ogg", "DOWNLOAD_QUALITY": "very_high", "TRANSCODE_BITRATE": "auto", "CUSTOM_FFMEPG_ARGS": "", "SONG_ARCHIVE_LOCATION": "", "DISABLE_SONG_ARCHIVE": "False", "DISABLE_DIRECTORY_ARCHIVES": "False", "SKIP_EXISTING": "True", "SKIP_PREVIOUSLY_DOWNLOADED": "False", "EXPORT_M3U8": "False", "M3U8_LOCATION": "", "M3U8_REL_PATHS": "True", "LIKED_SONGS_ARCHIVE_M3U8": "True", "DOWNLOAD_LYRICS": "True", "LYRICS_LOCATION": "", "LYRICS_FILENAME": "{artist}_{song_name}", "ALWAYS_CHECK_LYRICS": "False", "LYRICS_MD_HEADER": "False", "LANGUAGE": "en", "STRICT_LIBRARY_VERIFY": "True", "MD_DISC_TRACK_TOTALS": "True", "MD_SAVE_GENRES": "False", "MD_ALLGENRES": "False", "MD_GENREDELIMITER": ",", "MD_ARTISTDELIMITER": ", ", "MD_SAVE_LYRICS": "True", "ALBUM_ART_JPG_FILE": "False", "SEARCH_QUERY_SIZE": "10", "RETRY_ATTEMPTS": "1", "CHUNK_SIZE": "20000", "REDIRECT_ADDRESS": "127.0.0.1", "PRINT_SPLASH": "False", "PRINT_PROGRESS_INFO": "True", "PRINT_SKIPS": "True", "PRINT_DOWNLOADS": "False", "PRINT_DOWNLOAD_PROGRESS": "True", "PRINT_URL_PROGRESS": "True", "PRINT_ALBUM_PROGRESS": "True", "PRINT_ARTIST_PROGRESS": "True", "PRINT_PLAYLIST_PROGRESS": "True", "PRINT_WARNINGS": "True", "PRINT_ERRORS": "True", "PRINT_API_ERRORS": "True", "STANDARD_INTERFACE": "False", "FFMPEG_LOG_LEVEL": "error", "vvv___DEPRECIATED_BELOW_HERE___vvv": "vvv___REMOVE_THESE___vvv", "SONG_ARCHIVE": "", "OVERRIDE_AUTO_WAIT": "False" }
kerem 2026-02-27 04:57:37 +03:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@the-vainglory-consensus commented on GitHub (Dec 23, 2025):

I should add that I've tried using both the regular zotify and the efficient API branch and have gotten the same error.

<!-- gh-comment-id:3684619167 --> @the-vainglory-consensus commented on GitHub (Dec 23, 2025): I should add that I've tried using both the regular zotify and the efficient API branch and have gotten the same error.
Author
Owner

@SapientAsh commented on GitHub (Dec 23, 2025):

Getting the same error on efficient-api

<!-- gh-comment-id:3684872113 --> @SapientAsh commented on GitHub (Dec 23, 2025): Getting the same error on efficient-api
Author
Owner

@thatguyuno commented on GitHub (Dec 23, 2025):

Have the same issue

<!-- gh-comment-id:3684903151 --> @thatguyuno commented on GitHub (Dec 23, 2025): Have the same issue
Author
Owner

@IsaacAgulhas commented on GitHub (Dec 23, 2025):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api.

Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction.

tokenmanager.py

import base64
import requests
import time

SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"

class SpotifyTokenManager:
    def __init__(self, client_id, client_secret):
        self.client_id = client_id
        self.client_secret = client_secret
        self.access_token = None
        self.expires_at = 0

    def get_token(self):
        if self.access_token and time.time() < self.expires_at:
            return self.access_token

        auth_header = base64.b64encode(
            f"{self.client_id}:{self.client_secret}".encode()
        ).decode()

        response = requests.post(
            SPOTIFY_TOKEN_URL,
            headers={
                "Authorization": f"Basic {auth_header}",
                "Content-Type": "application/x-www-form-urlencoded",
            },
            data={"grant_type": "client_credentials"},
        )

        response.raise_for_status()
        data = response.json()

        self.access_token = data["access_token"]
        self.expires_at = time.time() + data["expires_in"] - 30
        return self.access_token

get_song_info function in track.py

from zotify.tokenmanager import SpotifyTokenManager

token_manager = SpotifyTokenManager(
    client_id="",
    client_secret=""
)

def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]:
    """ Retrieves metadata for downloaded songs using own Spotify API credentials """

    with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."):
        token = token_manager.get_token()

        response = requests.get(
            f"https://api.spotify.com/v1/tracks",
            params={"ids": song_id, "market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    info = response.json()

    if TRACKS not in info:
        raise ValueError(f'Invalid response from TRACKS_URL:\n{info}')

    try:
        track = info[TRACKS][0]

        artists = [a[NAME] for a in track[ARTISTS]]
        album = track[ALBUM]

        album_name = album[NAME]
        name = track[NAME]
        release_year = album[RELEASE_DATE].split('-')[0]
        disc_number = track[DISC_NUMBER]
        track_number = track[TRACK_NUMBER]
        scraped_song_id = track[ID]
        is_playable = track[IS_PLAYABLE]
        duration_ms = track[DURATION_MS]

        image = max(album[IMAGES], key=lambda i: i[WIDTH])
        image_url = image[URL]

        return (
            artists,
            track[ARTISTS],
            album_name,
            name,
            image_url,
            release_year,
            disc_number,
            track_number,
            scraped_song_id,
            is_playable,
            duration_ms,
        )

    except Exception as e:
        raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}')

I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well.

<!-- gh-comment-id:3686587380 --> @IsaacAgulhas commented on GitHub (Dec 23, 2025): @Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api. Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction. tokenmanager.py ```python import base64 import requests import time SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token" class SpotifyTokenManager: def __init__(self, client_id, client_secret): self.client_id = client_id self.client_secret = client_secret self.access_token = None self.expires_at = 0 def get_token(self): if self.access_token and time.time() < self.expires_at: return self.access_token auth_header = base64.b64encode( f"{self.client_id}:{self.client_secret}".encode() ).decode() response = requests.post( SPOTIFY_TOKEN_URL, headers={ "Authorization": f"Basic {auth_header}", "Content-Type": "application/x-www-form-urlencoded", }, data={"grant_type": "client_credentials"}, ) response.raise_for_status() data = response.json() self.access_token = data["access_token"] self.expires_at = time.time() + data["expires_in"] - 30 return self.access_token ``` get_song_info function in track.py ```python from zotify.tokenmanager import SpotifyTokenManager token_manager = SpotifyTokenManager( client_id="", client_secret="" ) def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]: """ Retrieves metadata for downloaded songs using own Spotify API credentials """ with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): token = token_manager.get_token() response = requests.get( f"https://api.spotify.com/v1/tracks", params={"ids": song_id, "market": "US"}, headers={ "Authorization": f"Bearer {token}" } ) if response.status_code == 429: raise RuntimeError("Spotify API rate limit exceeded (your app)") response.raise_for_status() info = response.json() if TRACKS not in info: raise ValueError(f'Invalid response from TRACKS_URL:\n{info}') try: track = info[TRACKS][0] artists = [a[NAME] for a in track[ARTISTS]] album = track[ALBUM] album_name = album[NAME] name = track[NAME] release_year = album[RELEASE_DATE].split('-')[0] disc_number = track[DISC_NUMBER] track_number = track[TRACK_NUMBER] scraped_song_id = track[ID] is_playable = track[IS_PLAYABLE] duration_ms = track[DURATION_MS] image = max(album[IMAGES], key=lambda i: i[WIDTH]) image_url = image[URL] return ( artists, track[ARTISTS], album_name, name, image_url, release_year, disc_number, track_number, scraped_song_id, is_playable, duration_ms, ) except Exception as e: raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}') ``` I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well.
Author
Owner

@RGPZ commented on GitHub (Dec 23, 2025):

I'm getting a similar error with a couple of different statements.
` [∙∙∙] Fetching artist information...

WARNING: API ERROR (TRY 0) - RETRYING

429: API rate limit exceeded

    [∙∙●] Fetching artist information...                                                                            

WARNING: API ERROR (TRY 1) - RETRYING

429: API rate limit exceeded

    [∙∙∙] Fetching artist information...                                                                            

API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED

429: API rate limit exceeded

    [∙●∙] Fetching artist information...                                                                                    [∙∙∙] Parsing artist information...                                                                             

DEBUG
Total API Calls: 2

Traceback (most recent call last):
File "", line 198, in run_module_as_main
File "", line 88, in run_code
File "c:\users\x.local\bin\zotify.exe_main
.py", line 6, in
sys.exit(main())
~~~~^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify_main
.py", line 140, in main
args.func(args, modes)
~~~~~~~~~^^^^^^^^^^^^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 78, in client
perform_query(args)
~~~~~~~~~~~~~^^^^^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 69, in perform_query
raise e
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 44, in perform_query
Query(Zotify.DATETIME_LAUNCH).request(urls).execute()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1686, in execute
self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs))
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1510, in parse_direct_metadata
obj.parse_metadata(item_resp)
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1350, in parse_metadata
self.update_id(artist_resp[ID])
~~~~~~~~~~~^^^^
KeyError: 'id'`, this comes from specifically from the effective API branch.

<!-- gh-comment-id:3686597807 --> @RGPZ commented on GitHub (Dec 23, 2025): I'm getting a similar error with a couple of different statements. ` [∙∙∙] Fetching artist information... ### WARNING: API ERROR (TRY 0) - RETRYING ### ### 429: API rate limit exceeded ### [∙∙●] Fetching artist information... ### WARNING: API ERROR (TRY 1) - RETRYING ### ### 429: API rate limit exceeded ### [∙∙∙] Fetching artist information... ### API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED ### ### 429: API rate limit exceeded ### [∙●∙] Fetching artist information... [∙∙∙] Parsing artist information... DEBUG Total API Calls: 2 Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "c:\users\x\.local\bin\zotify.exe\__main__.py", line 6, in <module> sys.exit(main()) ~~~~^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 140, in main args.func(args, modes) ~~~~~~~~~^^^^^^^^^^^^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 78, in client perform_query(args) ~~~~~~~~~~~~~^^^^^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 69, in perform_query raise e File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 44, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1686, in execute self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs)) ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1510, in parse_direct_metadata obj.parse_metadata(item_resp) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^ File "C:\Users\x\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1350, in parse_metadata self.update_id(artist_resp[ID]) ~~~~~~~~~~~^^^^ KeyError: 'id'`, this comes from specifically from the effective API branch.
Author
Owner

@ThatElemental commented on GitHub (Dec 23, 2025):

I'm experiencing the same issue

###   WARNING:  API ERROR (TRY 0) - RETRYING   ###
###   429: API rate limit exceeded   ###

###   WARNING:  API ERROR (TRY 1) - RETRYING   ###
###   429: API rate limit exceeded   ###

###   API_ERROR:  API ERROR (TRY 2) - RETRY LIMIT EXCEDED   ###
###   429: API rate limit exceeded   ###
                                                                                                                       Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "c:\users\xxxxx\.local\bin\zotify.exe\__main__.py", line 6, in <module>
    sys.exit(main())
             ~~~~^^
  File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 119, in main
    args.func(args)
    ~~~~~~~~~^^^^^^
  File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 257, in client
    download_from_urls(args.urls)
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 36, in download_from_urls
    download_album(album_id, pbar_stack)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\album.py", line 51, in download_album
    album_name, album_artists, tracks, total_discs, compilation = get_album_info(album_id)
                                                                  ~~~~~~~~~~~~~~^^^^^^^^^^
  File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\album.py", line 13, in get_album_info
    album_name = fix_filename(resp[NAME])
                              ~~~~^^^^^^
<!-- gh-comment-id:3687170808 --> @ThatElemental commented on GitHub (Dec 23, 2025): I'm experiencing the same issue ``` ### WARNING: API ERROR (TRY 0) - RETRYING ### ### 429: API rate limit exceeded ### ### WARNING: API ERROR (TRY 1) - RETRYING ### ### 429: API rate limit exceeded ### ### API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED ### ### 429: API rate limit exceeded ### Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "c:\users\xxxxx\.local\bin\zotify.exe\__main__.py", line 6, in <module> sys.exit(main()) ~~~~^^ File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 119, in main args.func(args) ~~~~~~~~~^^^^^^ File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 257, in client download_from_urls(args.urls) ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^ File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 36, in download_from_urls download_album(album_id, pbar_stack) ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\album.py", line 51, in download_album album_name, album_artists, tracks, total_discs, compilation = get_album_info(album_id) ~~~~~~~~~~~~~~^^^^^^^^^^ File "C:\Users\xxxxx\pipx\venvs\zotify\Lib\site-packages\zotify\album.py", line 13, in get_album_info album_name = fix_filename(resp[NAME]) ~~~~^^^^^^ ```
Author
Owner

@Melf11 commented on GitHub (Dec 24, 2025):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api.

Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction.

tokenmanager.py

import base64
import requests
import time

SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"

class SpotifyTokenManager:
def init(self, client_id, client_secret):
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.expires_at = 0

def get_token(self):
    if self.access_token and time.time() < self.expires_at:
        return self.access_token

    auth_header = base64.b64encode(
        f"{self.client_id}:{self.client_secret}".encode()
    ).decode()

    response = requests.post(
        SPOTIFY_TOKEN_URL,
        headers={
            "Authorization": f"Basic {auth_header}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={"grant_type": "client_credentials"},
    )

    response.raise_for_status()
    data = response.json()

    self.access_token = data["access_token"]
    self.expires_at = time.time() + data["expires_in"] - 30
    return self.access_token

get_song_info function in track.py

from zotify.tokenmanager import SpotifyTokenManager

token_manager = SpotifyTokenManager(
client_id="",
client_secret=""
)

def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]:
""" Retrieves metadata for downloaded songs using own Spotify API credentials """

with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."):
    token = token_manager.get_token()

    response = requests.get(
        f"https://api.spotify.com/v1/tracks",
        params={"ids": song_id, "market": "US"},
        headers={
            "Authorization": f"Bearer {token}"
        }
    )

if response.status_code == 429:
    raise RuntimeError("Spotify API rate limit exceeded (your app)")

response.raise_for_status()
info = response.json()

if TRACKS not in info:
    raise ValueError(f'Invalid response from TRACKS_URL:\n{info}')

try:
    track = info[TRACKS][0]

    artists = [a[NAME] for a in track[ARTISTS]]
    album = track[ALBUM]

    album_name = album[NAME]
    name = track[NAME]
    release_year = album[RELEASE_DATE].split('-')[0]
    disc_number = track[DISC_NUMBER]
    track_number = track[TRACK_NUMBER]
    scraped_song_id = track[ID]
    is_playable = track[IS_PLAYABLE]
    duration_ms = track[DURATION_MS]

    image = max(album[IMAGES], key=lambda i: i[WIDTH])
    image_url = image[URL]

    return (
        artists,
        track[ARTISTS],
        album_name,
        name,
        image_url,
        release_year,
        disc_number,
        track_number,
        scraped_song_id,
        is_playable,
        duration_ms,
    )

except Exception as e:
    raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}')

I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well.

i basicly fixed it but its no good code but its working, did it last night very late. I thins this are the changes:

add tokenmanager.py from @IsaacAgulhas and add this in album.py (replace get_album_info()):

import requests
from zotify.tokenmanager import SpotifyTokenManager

token_manager = SpotifyTokenManager(
    client_id="ID FROM SPOTIFY DEV API",
    client_secret="SECRET FROM SPOTIFY DEV API"
)

def get_album_info(album_id):
    # Album-Daten abrufen
    #resp = requests.get(
    #    f"{ALBUM_URL}/{album_id}",
    #    params={"market": "US"},
    #    headers={"Authorization": f"Bearer {token_manager.get_token()}"}
    #).json()
    token = token_manager.get_token()
    response = requests.get(
        f"https://api.spotify.com/v1/albums",
        params={"ids": album_id, "market": "US"},
        headers={
            "Authorization": f"Bearer {token}"
        }
    )
    response.raise_for_status()
    resp = response.json()

    album_data = resp['albums'][0]

    album_name = fix_filename(album_data['name'])
    album_artists = [artist['name'] for artist in album_data['artists']]
    compilation = album_data['album_type'] == 'compilation'

    # Track-IDs abrufen
    track_items = album_data['tracks']['items']
    track_ids = [track['id'] for track in track_items]

    # Metadaten für jeden Track abrufen
    tracks = []
    for tid in track_ids:
        try:
            track_meta = get_track_metadata(tid)
            tracks.append(track_meta)
        except Exception as e:
            print(f"Failed to fetch track {tid}: {e}")

    # Anzahl der Discs ermitteln
    total_discs = max(track['disc_number'] for track in tracks) if tracks else 1

    return album_name, album_artists, tracks, total_discs, compilation

and this in track.py (ore more replace get_track_metadata())

import requests
from typing import Tuple, List, Any

from zotify.tokenmanager import SpotifyTokenManager

token_manager = SpotifyTokenManager(
    client_id="ID FROM SPOTIFY DEV API",
    client_secret="SECRET FROM SPOTIFY DEV API"
)


def get_track_metadata(track_id) -> dict[str, list[str] | str | int | bool]:
    """ Retrieves metadata for downloaded songs """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."):
        token = token_manager.get_token()
        response = requests.get(
            f"https://api.spotify.com/v1/tracks",
            params={"ids": track_id, "market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

        response.raise_for_status()
        info = response.json()

        #(raw, info) = Zotify.invoke_url(f'{TRACK_URL}?ids={track_id}&market=from_token')
        #print(info)
        if not TRACKS in info:
            raise ValueError(f'Invalid response from TRACK_URL:\n{raw}')

        try:
            return parse_track_metadata(info[TRACKS][0])
        except Exception as e:
            raise ValueError(f'Failed to parse TRACK_URL response: {str(e)}\n{raw}')
<!-- gh-comment-id:3689067374 --> @Melf11 commented on GitHub (Dec 24, 2025): > [@Googolplexed0](https://github.com/Googolplexed0) I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api. > > Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction. > > tokenmanager.py > > import base64 > import requests > import time > > SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token" > > class SpotifyTokenManager: > def __init__(self, client_id, client_secret): > self.client_id = client_id > self.client_secret = client_secret > self.access_token = None > self.expires_at = 0 > > def get_token(self): > if self.access_token and time.time() < self.expires_at: > return self.access_token > > auth_header = base64.b64encode( > f"{self.client_id}:{self.client_secret}".encode() > ).decode() > > response = requests.post( > SPOTIFY_TOKEN_URL, > headers={ > "Authorization": f"Basic {auth_header}", > "Content-Type": "application/x-www-form-urlencoded", > }, > data={"grant_type": "client_credentials"}, > ) > > response.raise_for_status() > data = response.json() > > self.access_token = data["access_token"] > self.expires_at = time.time() + data["expires_in"] - 30 > return self.access_token > get_song_info function in track.py > > from zotify.tokenmanager import SpotifyTokenManager > > token_manager = SpotifyTokenManager( > client_id="", > client_secret="" > ) > > def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]: > """ Retrieves metadata for downloaded songs using own Spotify API credentials """ > > with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): > token = token_manager.get_token() > > response = requests.get( > f"https://api.spotify.com/v1/tracks", > params={"ids": song_id, "market": "US"}, > headers={ > "Authorization": f"Bearer {token}" > } > ) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > response.raise_for_status() > info = response.json() > > if TRACKS not in info: > raise ValueError(f'Invalid response from TRACKS_URL:\n{info}') > > try: > track = info[TRACKS][0] > > artists = [a[NAME] for a in track[ARTISTS]] > album = track[ALBUM] > > album_name = album[NAME] > name = track[NAME] > release_year = album[RELEASE_DATE].split('-')[0] > disc_number = track[DISC_NUMBER] > track_number = track[TRACK_NUMBER] > scraped_song_id = track[ID] > is_playable = track[IS_PLAYABLE] > duration_ms = track[DURATION_MS] > > image = max(album[IMAGES], key=lambda i: i[WIDTH]) > image_url = image[URL] > > return ( > artists, > track[ARTISTS], > album_name, > name, > image_url, > release_year, > disc_number, > track_number, > scraped_song_id, > is_playable, > duration_ms, > ) > > except Exception as e: > raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}') > I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well. i basicly fixed it but its no good code but its working, did it last night very late. I thins this are the changes: add tokenmanager.py from @IsaacAgulhas and add this in album.py (replace get_album_info()): ``` import requests from zotify.tokenmanager import SpotifyTokenManager token_manager = SpotifyTokenManager( client_id="ID FROM SPOTIFY DEV API", client_secret="SECRET FROM SPOTIFY DEV API" ) def get_album_info(album_id): # Album-Daten abrufen #resp = requests.get( # f"{ALBUM_URL}/{album_id}", # params={"market": "US"}, # headers={"Authorization": f"Bearer {token_manager.get_token()}"} #).json() token = token_manager.get_token() response = requests.get( f"https://api.spotify.com/v1/albums", params={"ids": album_id, "market": "US"}, headers={ "Authorization": f"Bearer {token}" } ) response.raise_for_status() resp = response.json() album_data = resp['albums'][0] album_name = fix_filename(album_data['name']) album_artists = [artist['name'] for artist in album_data['artists']] compilation = album_data['album_type'] == 'compilation' # Track-IDs abrufen track_items = album_data['tracks']['items'] track_ids = [track['id'] for track in track_items] # Metadaten für jeden Track abrufen tracks = [] for tid in track_ids: try: track_meta = get_track_metadata(tid) tracks.append(track_meta) except Exception as e: print(f"Failed to fetch track {tid}: {e}") # Anzahl der Discs ermitteln total_discs = max(track['disc_number'] for track in tracks) if tracks else 1 return album_name, album_artists, tracks, total_discs, compilation ``` and this in track.py (ore more replace get_track_metadata()) ``` import requests from typing import Tuple, List, Any from zotify.tokenmanager import SpotifyTokenManager token_manager = SpotifyTokenManager( client_id="ID FROM SPOTIFY DEV API", client_secret="SECRET FROM SPOTIFY DEV API" ) def get_track_metadata(track_id) -> dict[str, list[str] | str | int | bool]: """ Retrieves metadata for downloaded songs """ with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): token = token_manager.get_token() response = requests.get( f"https://api.spotify.com/v1/tracks", params={"ids": track_id, "market": "US"}, headers={ "Authorization": f"Bearer {token}" } ) response.raise_for_status() info = response.json() #(raw, info) = Zotify.invoke_url(f'{TRACK_URL}?ids={track_id}&market=from_token') #print(info) if not TRACKS in info: raise ValueError(f'Invalid response from TRACK_URL:\n{raw}') try: return parse_track_metadata(info[TRACKS][0]) except Exception as e: raise ValueError(f'Failed to parse TRACK_URL response: {str(e)}\n{raw}') ```
Author
Owner

@alexing commented on GitHub (Dec 24, 2025):

same :/ any solutions until now?

<!-- gh-comment-id:3690538445 --> @alexing commented on GitHub (Dec 24, 2025): same :/ any solutions until now?
Author
Owner

@dijoi commented on GitHub (Dec 25, 2025):

@alexing for what exactly ?

<!-- gh-comment-id:3691620884 --> @dijoi commented on GitHub (Dec 25, 2025): @alexing for what exactly ?
Author
Owner

@ddxy commented on GitHub (Dec 26, 2025):

@Melf11 can you fork this project so we can downlaod your fork directly? :-)

<!-- gh-comment-id:3692521068 --> @ddxy commented on GitHub (Dec 26, 2025): @Melf11 can you fork this project so we can downlaod your fork directly? :-)
Author
Owner

@satonotdead commented on GitHub (Dec 26, 2025):

Looking for a clean solution, thank you all for your contributions as always.

<!-- gh-comment-id:3692974245 --> @satonotdead commented on GitHub (Dec 26, 2025): Looking for a clean solution, thank you all for your contributions as always.
Author
Owner

@Googolplexed0 commented on GitHub (Dec 27, 2025):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials

I created a path to authorize with any Developer API Client ID, which accepts a --client-id argument via the command line (like for a user's token). I can publish this method once librespot-python adopts a small pull request.

You say it is working for you, but even after authorizing with another Developer API Client ID the 429 API Rate Limit error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the Anna's Archive mega-rip.

<!-- gh-comment-id:3693554344 --> @Googolplexed0 commented on GitHub (Dec 27, 2025): > [@Googolplexed0](https://github.com/Googolplexed0) I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials I created a path to authorize with any Developer API Client ID, which accepts a `--client-id` argument via the command line (like for a user's token). I can publish this method once librespot-python adopts [a small pull request](https://github.com/kokarare1212/librespot-python/pull/327). You say it is working for you, but even after authorizing with another Developer API Client ID the `429 API Rate Limit` error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the [Anna's Archive mega-rip](https://annas-archive.org/blog/backing-up-spotify.html).
Author
Owner

@dijoi commented on GitHub (Dec 27, 2025):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials

I created a path to authorize with any Developer API Client ID, which accepts a --client-id argument via the command line (like for a user's token). I can publish this method once librespot-python adopts a small pull request.

You say it is working for you, but even after authorizing with another Developer API Client ID the 429 API Rate Limit error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the Anna's Archive mega-rip.

it works for me tho. I put 45 sec pauses i dont get any api rate limits

<!-- gh-comment-id:3693562495 --> @dijoi commented on GitHub (Dec 27, 2025): > > [@Googolplexed0](https://github.com/Googolplexed0) I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials > > I created a path to authorize with any Developer API Client ID, which accepts a `--client-id` argument via the command line (like for a user's token). I can publish this method once librespot-python adopts [a small pull request](https://github.com/kokarare1212/librespot-python/pull/327). > > You say it is working for you, but even after authorizing with another Developer API Client ID the `429 API Rate Limit` error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the [Anna's Archive mega-rip](https://annas-archive.org/blog/backing-up-spotify.html). it works for me tho. I put 45 sec pauses i dont get any api rate limits
Author
Owner

@Melf11 commented on GitHub (Dec 27, 2025):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials

I created a path to authorize with any Developer API Client ID, which accepts a --client-id argument via the command line (like for a user's token). I can publish this method once librespot-python adopts a small pull request.

You say it is working for you, but even after authorizing with another Developer API Client ID the 429 API Rate Limit error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the Anna's Archive mega-rip.

i have no trouble with rate limits in my solution. There was that rate error coming from old requests during trying to fix it but now i've no problems

<!-- gh-comment-id:3693885356 --> @Melf11 commented on GitHub (Dec 27, 2025): > > [@Googolplexed0](https://github.com/Googolplexed0) I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials > > I created a path to authorize with any Developer API Client ID, which accepts a `--client-id` argument via the command line (like for a user's token). I can publish this method once librespot-python adopts [a small pull request](https://github.com/kokarare1212/librespot-python/pull/327). > > You say it is working for you, but even after authorizing with another Developer API Client ID the `429 API Rate Limit` error is still occurring for me. My guess was that the API isn't actually experiencing rate limits, but that the parent company has shutdown metadata/search API functionality after the announcement of the [Anna's Archive mega-rip](https://annas-archive.org/blog/backing-up-spotify.html). i have no trouble with rate limits in my solution. There was that rate error coming from old requests during trying to fix it but now i've no problems
Author
Owner

@Melf11 commented on GitHub (Dec 27, 2025):

@Melf11 can you fork this project so we can downlaod your fork directly? :-)

i forked, is in main. add tokens in tokenmanager.py
https://github.com/Melf11/zotify

<!-- gh-comment-id:3693897587 --> @Melf11 commented on GitHub (Dec 27, 2025): > [@Melf11](https://github.com/Melf11) can you fork this project so we can downlaod your fork directly? :-) i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify
Author
Owner

@mzilot commented on GitHub (Dec 27, 2025):

@Melf11 can you fork this project so we can downlaod your fork directly? :-)

i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify

By project https://github.com/Melf11/zotify

python -m zotify track https://open.spotify.com/track/6PXY6UtZVXiMVAsLpEYHP3 --bulk-wait-time 45

Also set the BULK_WAIT_TIME=45 in config.json and set my client and secret

I got below error:

                                                                                                                                                            
        [∙●∙] Logging in...                                                                                                                                        
   WARNING:  No valid content_id found in track, skipping...                                                                                      
                                                                                                                                                            
        [∙∙●] Fetching track information...                                                                                                                        
                                                                                                                                                           {}
{'id': '6PXY6UtZVXiMVAsLpEYHP3', 'name': 'Yansın Dünya', 'artists': ['Anadolu Skytrip'], 'artist_ids': ['1LJcmYi5jbkywPtCgg8yQw'], 'release_date': '2025-10-28', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'Yansın Dünya', 'album_artists': ['Anadolu Skytrip'], 'disc_number': '1', 'compilation': 0[●∙∙] Preparing download...                                                                                                                                                                                                                                                                                     
                                                                                                                                                           CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1                                                                           | 0/2 [00:00<?, ?url/s]
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1
###   ERROR:  FAILED TO FETCH AUDIO KEY   ###                                                                                                               
###   MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME`   ###                                                                             
###   GID: e0ae5f4527614566a1b2f3a03c385749 - File_ID: 8b975872373b4c83055c0bc343ab7d1fc7c65b69                                                          
                                                                                                                                                            
###   ERROR:  SKIPPING SONG - FAILED TO GET CONTENT STREAM   ###                                                                                            
###   Track_ID: 6PXY6UtZVXiMVAsLpEYHP3   ###       ``` 
<!-- gh-comment-id:3693985079 --> @mzilot commented on GitHub (Dec 27, 2025): > > [@Melf11](https://github.com/Melf11) can you fork this project so we can downlaod your fork directly? :-) > > i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify By project `https://github.com/Melf11/zotify` python -m zotify track https://open.spotify.com/track/6PXY6UtZVXiMVAsLpEYHP3 --bulk-wait-time 45 Also set the BULK_WAIT_TIME=45 in config.json and set my client and secret I got below error: ``` [∙●∙] Logging in... WARNING: No valid content_id found in track, skipping... [∙∙●] Fetching track information... {} {'id': '6PXY6UtZVXiMVAsLpEYHP3', 'name': 'Yansın Dünya', 'artists': ['Anadolu Skytrip'], 'artist_ids': ['1LJcmYi5jbkywPtCgg8yQw'], 'release_date': '2025-10-28', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'Yansın Dünya', 'album_artists': ['Anadolu Skytrip'], 'disc_number': '1', 'compilation': 0[●∙∙] Preparing download... CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 | 0/2 [00:00<?, ?url/s] CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 ### ERROR: FAILED TO FETCH AUDIO KEY ### ### MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME` ### ### GID: e0ae5f4527614566a1b2f3a03c385749 - File_ID: 8b975872373b4c83055c0bc343ab7d1fc7c65b69 ### ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM ### ### Track_ID: 6PXY6UtZVXiMVAsLpEYHP3 ### ```
Author
Owner

@rulo226 commented on GitHub (Dec 27, 2025):

@Melf11 can you fork this project so we can downlaod your fork directly? :-)

i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify

Hi, with this fork and set client ID /client Secret in tokenmanager.py:

Image Image Image

Look this --->> [∙∙∙] Fetching genre information...

what am I doing wrong? Thanxs

<!-- gh-comment-id:3694051973 --> @rulo226 commented on GitHub (Dec 27, 2025): > > [@Melf11](https://github.com/Melf11) can you fork this project so we can downlaod your fork directly? :-) > > i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify Hi, with this fork and set client ID /client Secret in tokenmanager.py: <img width="1680" height="879" alt="Image" src="https://github.com/user-attachments/assets/4fb64f88-22ef-4926-9d2c-5a03a1437ca1" /> <img width="1680" height="879" alt="Image" src="https://github.com/user-attachments/assets/72b4c1a2-f5bf-41eb-9a27-5b7a84516a03" /> <img width="1680" height="879" alt="Image" src="https://github.com/user-attachments/assets/1dcf77b5-261b-4129-bb99-17a3b8e74346" /> Look this --->> [∙∙∙] Fetching genre information... what am I doing wrong? Thanxs
Author
Owner

@rulo226 commented on GitHub (Dec 27, 2025):

@Melf11 can you fork this project so we can downlaod your fork directly? :-)

i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify

It´s work!!
I have removed the references to Genre in "track.py"

Image Image Image

** THE METADATA IS INCOMPLETE IF YOU SEE

<!-- gh-comment-id:3694089155 --> @rulo226 commented on GitHub (Dec 27, 2025): > > [@Melf11](https://github.com/Melf11) can you fork this project so we can downlaod your fork directly? :-) > > i forked, is in main. add tokens in tokenmanager.py https://github.com/Melf11/zotify It´s work!! I have removed the references to Genre in "track.py" <img width="1680" height="258" alt="Image" src="https://github.com/user-attachments/assets/979aeaa4-0c19-4891-b8a1-a6491fde368f" /> <img width="495" height="455" alt="Image" src="https://github.com/user-attachments/assets/76d9ba8a-4732-4240-b35a-7e9cfc1c66b1" /> <img width="495" height="212" alt="Image" src="https://github.com/user-attachments/assets/c58bd986-f857-4060-9a01-f68f535fbfa4" /> ** THE METADATA IS INCOMPLETE IF YOU SEE
Author
Owner

@CommitCodeicide commented on GitHub (Dec 27, 2025):

I have now tried with two different API credentials from different accounts but still getting API rate limit exceeded error, used @Melf11 fork, too.

<!-- gh-comment-id:3694139169 --> @CommitCodeicide commented on GitHub (Dec 27, 2025): I have now tried with two different API credentials from different accounts but still getting API rate limit exceeded error, used @Melf11 fork, too.
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the playlist download.

Looking fine for the moment, download still in progress (150 tracks).
EDIT: worked just fine for all songs!

As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is.

Followed steps:

  1. Perform install steps from https://github.com/Melf11/zotify
  2. Create Spotify app to get client ID and Client secret
  3. Update tokenmanager.py with those
  4. Perform files updates (see below)
  5. zotify {playlist_URL} --bulk-wait-time 45

Don't know if I will be able to provide a fork on my side, but here are the changes I performed:
playlist.py
EDIT: (thanks for the reminders @RGPZ):

  • Add the following lines at the beginning of the file: import requests
  • and from zotify.termoutput import Loader from zotify.tokenmanager import token_manager
  • Modify get_playlist_info
    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

config.py
EDIT (thanks for the reminders @RGPZ):

  • At the top of the file, add the following imports: import requests
  • and from zotify.tokenmanager import SpotifyTokenManager, token_manager
  • Modify invoke_url
    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

To ignore genre issue as mentioned by @rulo226:
track.py
In update_track_metadata and download_track, comment the original line and set the genres param to empty value, as such:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Feel free to update/fork it if you like :)

Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well!

Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content:

Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.

Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance

<!-- gh-comment-id:3694369314 --> @Genkleable commented on GitHub (Dec 28, 2025): Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the **playlist download**. Looking fine for the moment, download still in progress (150 tracks). _EDIT: worked just fine for all songs!_ As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is. _**Followed steps:**_ 1. Perform install steps from https://github.com/Melf11/zotify 2. Create Spotify app to get client ID and Client secret 3. Update tokenmanager.py with those 4. Perform files updates (see below) 5. zotify {playlist_URL} --bulk-wait-time 45 Don't know if I will be able to provide a fork on my side, but here are the changes I performed: **playlist.py** _EDIT: (thanks for the reminders @RGPZ)_: - Add the following lines at the beginning of the file: `import requests` - and `from zotify.termoutput import Loader from zotify.tokenmanager import token_manager` - Modify _get_playlist_info_ ``` def get_playlist_info(playlist_id) -> tuple[str, str]: """ Returns information scraped from playlist """ with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): token = token_manager.get_token() response = requests.get( f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', params={"market": "US"}, headers={ "Authorization": f"Bearer {token}" } ) if response.status_code == 429: raise RuntimeError("Spotify API rate limit exceeded (your app)") response.raise_for_status() resp = response.json() resp_name = resp['name'] resp_display_name = resp['owner']['display_name'] return resp_name.strip(), resp_display_name.strip() ``` **config.py** _EDIT (thanks for the reminders @RGPZ):_ - At the top of the file, add the following imports: `import requests` - and `from zotify.tokenmanager import SpotifyTokenManager, token_manager` - Modify _invoke_url_ ``` def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: token = token_manager.get_token() headers={ "Authorization": f"Bearer {token}" } tryCount = 0 while tryCount <= cls.CONFIG.get_retry_attempts(): response = requests.get(url, headers=headers, params=_params) if response.status_code == 429: raise RuntimeError("Spotify API rate limit exceeded (your app)") cls.TOTAL_API_CALLS += 1 try: responsetext = response.text responsejson = response.json() if not responsejson: raise json.decoder.JSONDecodeError # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} except json.decoder.JSONDecodeError: responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} if not responsejson or 'error' in responsejson: if not expectFail: Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') sleep(5 if not expectFail else 1) tryCount += 1 continue else: return responsetext, responsejson if not expectFail: Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') return responsetext, responsejson ``` To ignore genre issue as mentioned by @rulo226: **track.py** In _update_track_metadata_ and _download_track_, comment the original line and set the genres param to empty value, as such: ``` # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) genres = [''] ``` Feel free to update/fork it if you like :) Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well! Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content: _Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place_ 1. On the Spotify desktop app, go to the desired playlist 2. Select all 3. Drag and drop in a new text file => all playlist track links will be copied 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" 6. Copy paste in the terminal (I'm using Powershell *sigh*) That'll perform sequential track from playlist download. _Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance_
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the playlist download.

Looking fine for the moment, download still in progress (150 tracks). EDIT: worked just fine for all songs!

As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is.

Followed steps:

  1. Perform install steps from https://github.com/Melf11/zotify
  2. Create Spotify app to get client ID and Client secret
  3. Update tokenmanager.py with those
  4. Perform files updates (see below)
  5. zotify {playlist_URL} --bulk-wait-time 45

Don't know if I will be able to provide a fork on my side, but here are the changes I performed: playlist.py get_playlist_info

    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

config.py invoke_url

    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

To ignore genre issue as mentioned by @rulo226: track.py In update_track_metadata and download_track, comment the original line and set the genres param to empty value, as such:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Feel free to update/fork it if you like :)

Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well!

Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content:

Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.

Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance

Hey friend, I followed your instructions and it's still giving me this error. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45

    [∙●∙] Logging in...                           

WARNING: No valid content_id found in track, skipping...

    [∙●∙] Fetching track information...           
                                                 {}

{'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download...
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1

ERROR: FAILED TO FETCH AUDIO KEY

MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING BULK_WAIT_TIME ### | 0/2 [00:01<?, ?url/s]

GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061

ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM

Track_ID: 70CWF9eT1tXx7gID9oYt3x

<!-- gh-comment-id:3694672819 --> @Cio2566 commented on GitHub (Dec 28, 2025): > Hi there, using [@Melf11](https://github.com/Melf11) fork which includes [@IsaacAgulhas](https://github.com/IsaacAgulhas) initial ideas, I managed to perform a quick and dirty working fix for the **playlist download**. > > Looking fine for the moment, download still in progress (150 tracks). _EDIT: worked just fine for all songs!_ > > As [@rulo226](https://github.com/rulo226) mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is. > > _**Followed steps:**_ > > 1. Perform install steps from https://github.com/Melf11/zotify > 2. Create Spotify app to get client ID and Client secret > 3. Update tokenmanager.py with those > 4. Perform files updates (see below) > 5. zotify {playlist_URL} --bulk-wait-time 45 > > Don't know if I will be able to provide a fork on my side, but here are the changes I performed: **playlist.py** _get_playlist_info_ > > ``` > def get_playlist_info(playlist_id) -> tuple[str, str]: > """ Returns information scraped from playlist """ > with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): > token = token_manager.get_token() > > response = requests.get( > f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', > params={"market": "US"}, > headers={ > "Authorization": f"Bearer {token}" > } > ) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > response.raise_for_status() > resp = response.json() > > resp_name = resp['name'] > resp_display_name = resp['owner']['display_name'] > > return resp_name.strip(), resp_display_name.strip() > ``` > > **config.py** _invoke_url_ > > ``` > def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: > > token = token_manager.get_token() > > headers={ > "Authorization": f"Bearer {token}" > } > > tryCount = 0 > while tryCount <= cls.CONFIG.get_retry_attempts(): > > response = requests.get(url, headers=headers, params=_params) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > cls.TOTAL_API_CALLS += 1 > > try: > responsetext = response.text > responsejson = response.json() > if not responsejson: > raise json.decoder.JSONDecodeError > # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > except json.decoder.JSONDecodeError: > responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > if not responsejson or 'error' in responsejson: > if not expectFail: > Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > sleep(5 if not expectFail else 1) > tryCount += 1 > continue > else: > return responsetext, responsejson > > if not expectFail: > Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > return responsetext, responsejson > ``` > > To ignore genre issue as mentioned by [@rulo226](https://github.com/rulo226): **track.py** In _update_track_metadata_ and _download_track_, comment the original line and set the genres param to empty value, as such: > > ``` > # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) > genres = [''] > ``` > > Feel free to update/fork it if you like :) > > Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well! > > Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content: > > _Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place_ > > 1. On the Spotify desktop app, go to the desired playlist > 2. Select all > 3. Drag and drop in a new text file => all playlist track links will be copied > 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") > 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" > 6. Copy paste in the terminal (I'm using Powershell _sigh_) > > That'll perform sequential track from playlist download. > > _Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance_ Hey friend, I followed your instructions and it's still giving me this error. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45 [∙●∙] Logging in... ### WARNING: No valid content_id found in track, skipping... ### [∙●∙] Fetching track information... {} {'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download... CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 ### ERROR: FAILED TO FETCH AUDIO KEY ### ### MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME` ### | 0/2 [00:01<?, ?url/s] ### GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061 ### ### ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM ### ### Track_ID: 70CWF9eT1tXx7gID9oYt3x ###
Author
Owner

@RGPZ commented on GitHub (Dec 28, 2025):

Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the playlist download.

Looking fine for the moment, download still in progress (150 tracks). EDIT: worked just fine for all songs!

As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is.

Followed steps:

  1. Perform install steps from https://github.com/Melf11/zotify
  2. Create Spotify app to get client ID and Client secret
  3. Update tokenmanager.py with those
  4. Perform files updates (see below)
  5. zotify {playlist_URL} --bulk-wait-time 45

Don't know if I will be able to provide a fork on my side, but here are the changes I performed: playlist.py get_playlist_info

    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

config.py invoke_url

    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

To ignore genre issue as mentioned by @rulo226: track.py In update_track_metadata and download_track, comment the original line and set the genres param to empty value, as such:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Feel free to update/fork it if you like :)

Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well!

Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content:

Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.

Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance

I've done your method, just to let you know that you have to add "from zotify.tokenmanager import token_manager" to the config file so the updated code actually knows where token_manager is defined, I'm not sure if you get the same results that I get, but when I run the code I get the information of the song that I'm downloading printed before downloading, just checking to see if you also get this. (Ignore why I'm downloading a song that already exists, I'm trying to go through the rest of the artist. I can confirm that this method of downloading does work with new songs)
` [∙∙∙] Preparing download...

SKIPPING: "X" (TRACK ALREADY EXISTS)

    [∙∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [●∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [∙∙●] Fetching track information...                                                                             
                                                                                                                   {}

{'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] Preparing download...
DEBUG
Duplicate Check
File Already Exists: 5455249
song_id in Local Archive: True
song_id in Global Archive: True`

<!-- gh-comment-id:3694672831 --> @RGPZ commented on GitHub (Dec 28, 2025): > Hi there, using [@Melf11](https://github.com/Melf11) fork which includes [@IsaacAgulhas](https://github.com/IsaacAgulhas) initial ideas, I managed to perform a quick and dirty working fix for the **playlist download**. > > Looking fine for the moment, download still in progress (150 tracks). _EDIT: worked just fine for all songs!_ > > As [@rulo226](https://github.com/rulo226) mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is. > > _**Followed steps:**_ > > 1. Perform install steps from https://github.com/Melf11/zotify > 2. Create Spotify app to get client ID and Client secret > 3. Update tokenmanager.py with those > 4. Perform files updates (see below) > 5. zotify {playlist_URL} --bulk-wait-time 45 > > Don't know if I will be able to provide a fork on my side, but here are the changes I performed: **playlist.py** _get_playlist_info_ > > ``` > def get_playlist_info(playlist_id) -> tuple[str, str]: > """ Returns information scraped from playlist """ > with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): > token = token_manager.get_token() > > response = requests.get( > f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', > params={"market": "US"}, > headers={ > "Authorization": f"Bearer {token}" > } > ) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > response.raise_for_status() > resp = response.json() > > resp_name = resp['name'] > resp_display_name = resp['owner']['display_name'] > > return resp_name.strip(), resp_display_name.strip() > ``` > > **config.py** _invoke_url_ > > ``` > def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: > > token = token_manager.get_token() > > headers={ > "Authorization": f"Bearer {token}" > } > > tryCount = 0 > while tryCount <= cls.CONFIG.get_retry_attempts(): > > response = requests.get(url, headers=headers, params=_params) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > cls.TOTAL_API_CALLS += 1 > > try: > responsetext = response.text > responsejson = response.json() > if not responsejson: > raise json.decoder.JSONDecodeError > # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > except json.decoder.JSONDecodeError: > responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > if not responsejson or 'error' in responsejson: > if not expectFail: > Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > sleep(5 if not expectFail else 1) > tryCount += 1 > continue > else: > return responsetext, responsejson > > if not expectFail: > Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > return responsetext, responsejson > ``` > > To ignore genre issue as mentioned by [@rulo226](https://github.com/rulo226): **track.py** In _update_track_metadata_ and _download_track_, comment the original line and set the genres param to empty value, as such: > > ``` > # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) > genres = [''] > ``` > > Feel free to update/fork it if you like :) > > Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well! > > Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content: > > _Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place_ > > 1. On the Spotify desktop app, go to the desired playlist > 2. Select all > 3. Drag and drop in a new text file => all playlist track links will be copied > 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") > 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" > 6. Copy paste in the terminal (I'm using Powershell _sigh_) > > That'll perform sequential track from playlist download. > > _Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance_ I've done your method, just to let you know that you have to add "from zotify.tokenmanager import token_manager" to the config file so the updated code actually knows where token_manager is defined, I'm not sure if you get the same results that I get, but when I run the code I get the information of the song that I'm downloading printed before downloading, just checking to see if you also get this. (Ignore why I'm downloading a song that already exists, I'm trying to go through the rest of the artist. I can confirm that this method of downloading does work with new songs) ` [∙∙∙] Preparing download... ### SKIPPING: "X" (TRACK ALREADY EXISTS) ### [∙∙∙] Fetching track information... {} [●∙∙] Fetching track information... {} [∙∙●] Fetching track information... {} {'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] Preparing download... DEBUG Duplicate Check File Already Exists: 5455249 song_id in Local Archive: True song_id in Global Archive: True`
Author
Owner

@RGPZ commented on GitHub (Dec 28, 2025):

Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the playlist download.
Looking fine for the moment, download still in progress (150 tracks). EDIT: worked just fine for all songs!
As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is.
Followed steps:

  1. Perform install steps from https://github.com/Melf11/zotify
  2. Create Spotify app to get client ID and Client secret
  3. Update tokenmanager.py with those
  4. Perform files updates (see below)
  5. zotify {playlist_URL} --bulk-wait-time 45

Don't know if I will be able to provide a fork on my side, but here are the changes I performed: playlist.py get_playlist_info

    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

config.py invoke_url

    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

To ignore genre issue as mentioned by @rulo226: track.py In update_track_metadata and download_track, comment the original line and set the genres param to empty value, as such:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Feel free to update/fork it if you like :)
Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well!
Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content:
Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.
Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance

Hey friend, I followed your instructions and it's still giving me this error. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45

    [∙●∙] Logging in...                           

WARNING: No valid content_id found in track, skipping...

    [∙●∙] Fetching track information...           
                                                 {}

{'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download... CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1

ERROR: FAILED TO FETCH AUDIO KEY

MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING BULK_WAIT_TIME ### | 0/2 [00:01<?, ?url/s]

GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061

ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM

Track_ID: 70CWF9eT1tXx7gID9oYt3x

Are you using an account with Spotify Premium for all of your credentials and client data? That might explain this error.

<!-- gh-comment-id:3694673525 --> @RGPZ commented on GitHub (Dec 28, 2025): > > Hi there, using [@Melf11](https://github.com/Melf11) fork which includes [@IsaacAgulhas](https://github.com/IsaacAgulhas) initial ideas, I managed to perform a quick and dirty working fix for the **playlist download**. > > Looking fine for the moment, download still in progress (150 tracks). _EDIT: worked just fine for all songs!_ > > As [@rulo226](https://github.com/rulo226) mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is. > > _**Followed steps:**_ > > > > 1. Perform install steps from https://github.com/Melf11/zotify > > 2. Create Spotify app to get client ID and Client secret > > 3. Update tokenmanager.py with those > > 4. Perform files updates (see below) > > 5. zotify {playlist_URL} --bulk-wait-time 45 > > > > Don't know if I will be able to provide a fork on my side, but here are the changes I performed: **playlist.py** _get_playlist_info_ > > ``` > > def get_playlist_info(playlist_id) -> tuple[str, str]: > > """ Returns information scraped from playlist """ > > with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): > > token = token_manager.get_token() > > > > response = requests.get( > > f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', > > params={"market": "US"}, > > headers={ > > "Authorization": f"Bearer {token}" > > } > > ) > > > > if response.status_code == 429: > > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > > > response.raise_for_status() > > resp = response.json() > > > > resp_name = resp['name'] > > resp_display_name = resp['owner']['display_name'] > > > > return resp_name.strip(), resp_display_name.strip() > > ``` > > > > > > > > > > > > > > > > > > > > > > > > **config.py** _invoke_url_ > > ``` > > def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: > > > > token = token_manager.get_token() > > > > headers={ > > "Authorization": f"Bearer {token}" > > } > > > > tryCount = 0 > > while tryCount <= cls.CONFIG.get_retry_attempts(): > > > > response = requests.get(url, headers=headers, params=_params) > > > > if response.status_code == 429: > > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > > > cls.TOTAL_API_CALLS += 1 > > > > try: > > responsetext = response.text > > responsejson = response.json() > > if not responsejson: > > raise json.decoder.JSONDecodeError > > # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > except json.decoder.JSONDecodeError: > > responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > > > if not responsejson or 'error' in responsejson: > > if not expectFail: > > Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ > > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > sleep(5 if not expectFail else 1) > > tryCount += 1 > > continue > > else: > > return responsetext, responsejson > > > > if not expectFail: > > Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ > > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > > > return responsetext, responsejson > > ``` > > > > > > > > > > > > > > > > > > > > > > > > To ignore genre issue as mentioned by [@rulo226](https://github.com/rulo226): **track.py** In _update_track_metadata_ and _download_track_, comment the original line and set the genres param to empty value, as such: > > ``` > > # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) > > genres = [''] > > ``` > > > > > > > > > > > > > > > > > > > > > > > > Feel free to update/fork it if you like :) > > Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well! > > Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content: > > _Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place_ > > > > 1. On the Spotify desktop app, go to the desired playlist > > 2. Select all > > 3. Drag and drop in a new text file => all playlist track links will be copied > > 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") > > 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" > > 6. Copy paste in the terminal (I'm using Powershell _sigh_) > > > > That'll perform sequential track from playlist download. > > _Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance_ > > Hey friend, I followed your instructions and it's still giving me this error. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45 > > ``` > [∙●∙] Logging in... > ``` > > ### WARNING: No valid content_id found in track, skipping... > ``` > [∙●∙] Fetching track information... > {} > ``` > > {'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download... CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 > > ### ERROR: FAILED TO FETCH AUDIO KEY > ### MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME` ### | 0/2 [00:01<?, ?url/s] > ### GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061 > ### ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM > ### Track_ID: 70CWF9eT1tXx7gID9oYt3x Are you using an account with Spotify Premium for all of your credentials and client data? That might explain this error.
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Merhaba, @ IsaacAgulhas'ın ilk fikirlerini de içeren @Melf11'in çatalını kullanarak, çalma listesi indirme sorunu için hızlı ve geçici bir çözüm geliştirmeyi başardım .
Şu an için her şey yolunda görünüyor, indirme işlemi hala devam ediyor (150 parça). _DÜZELTME: Tüm şarkılar için sorunsuz çalıştı! @rulo226'nın belirttiği
gibi , türü kaldırmam gerekti (track.py dosyasında boş bir değerle doldurun), ancak bunun kolayca düzeltilebileceğini düşünüyorum - benim kullanımım için özellikle bunlara ihtiyacım yok, bu yüzden olduğu gibi bıraktım. İzlenen adımlar:__
_

  1. https://github.com/Melf11/zotify adresindeki kurulum adımlarını uygulayın .
  2. İstemci kimliği ve istemci gizli kodunu almak için Spotify uygulaması oluşturun.
  3. tokenmanager.py dosyasını bu değişikliklerle güncelleyin.
  4. Dosya güncellemelerini gerçekleştirin (aşağıya bakınız)
  5. zotify {playlist_URL} --bulk-wait-time 45

Kendi tarafımda bir çatal (fork) sağlayıp sağlayamayacağımı bilmiyorum, ancak yaptığım değişiklikler şunlar: **playlist.py ** get_playlist_info

    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

**config.py ** invoke_url

    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

@rulo226'nın bahsettiği tür sorununu göz ardı etmek için : track.py dosyasındaki update_track_metadata ve download_track fonksiyonlarında , orijinal satırı yorum satırı haline getirin ve genres parametresini boş değere ayarlayın, şu şekilde:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Dilerseniz güncelleyebilir/çatallayabilirsiniz :)
Tüm çabalarınız için de teşekkürler, başlangıçta 2 gün boyunca API 429 hatasıyla karşılaştım ve çalma listelerimin de en kısa sürede güncellenmesi gerekiyordu!
Alternatif olarak, çatalı güncellemeden (tür alanını boş değere ayarlamak dışında) çalma listesi içeriğini almak isterseniz, ilk başta çalma listesi içeriğini almak için şu yöntemi kullandım:
Uyarı: Bu daha hızlı ve daha basit bir çözüm ve gelecekte çalma listesi senkronizasyonuyla çalışıp çalışmayacağından emin değilim, bu yüzden ilk başta çalma listeleri için bu yöntemi düzelttim.

  1. Spotify masaüstü uygulamasında istediğiniz çalma listesine gidin.
  2. Tümünü seç
  3. Yeni bir metin dosyasına sürükleyip bırakın => tüm çalma listesi parça bağlantıları kopyalanacaktır.
  4. (İsteğe bağlı: Mevcut bir çalma listesini güncellemek için, config.json dosyasında "OUTPUT" değerini ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}" olarak ayarlayabilirsiniz.)
  5. Oluşturulan metin dosyasındaki tüm bağlantıları "zotify track {track_URL} --bulk-wait-time 45;" şeklinde değiştirin.
  6. Terminale kopyala yapıştır yapın (Powershell kullanıyorum, neyse ki ).

Bu, çalma listesinden sırayla parça indirme işlemini gerçekleştirecektir.
Not: 5. ve 6. adımlar, daha şık bir görünüm için her bağlantıyı parametre olarak kullanan bir döngü ile de yapılabilir.

Merhaba arkadaşım, talimatlarını uyguladım ama yine de bu hatayı alıyorum. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45

    [∙●∙] Logging in...                           

UYARI: Parçada geçerli bir content_id bulunamadı, atlanıyor...

    [∙●∙] Fetching track information...           
                                                 {}

{'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': ' https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4 ', 'is_pl[∙●∙] İndirme hazırlanıyor... KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1 KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1

HATA: SES ANAHTARINI ALMA BAŞARISIZ OLDU

HIZ SINIRLAMALARINDAN KAYNAKLANABİLİR - BULK_WAIT_TIME###'I ARTIRMAYI DÜŞÜNÜN | 0/2 [00:01<?, ?url/s]

GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061

HATA: ŞARKI ATLANIYOR - İÇERİK AKIŞI ALINAMADI

Track_ID: 70CWF9eT1tXx7gID9oYt3x

Tüm kimlik bilgileriniz ve müşteri verileriniz için Spotify Premium hesabı mı kullanıyorsunuz? Bu, hatanın nedenini açıklayabilir.

Yes, I have a Spotify Premium account and I filled in the required information, including Spotify Client ID and Spotify Client Secret.

<!-- gh-comment-id:3694676391 --> @Cio2566 commented on GitHub (Dec 28, 2025): > > > Merhaba, @ [IsaacAgulhas'ın](https://github.com/IsaacAgulhas) ilk fikirlerini de içeren [@Melf11'in çatalını kullanarak, ](https://github.com/Melf11)**çalma listesi indirme sorunu** için hızlı ve geçici bir çözüm geliştirmeyi başardım . > > > Şu an için her şey yolunda görünüyor, indirme işlemi hala devam ediyor (150 parça). _DÜZELTME: Tüm şarkılar için sorunsuz çalıştı! _[@rulo226'nın](https://github.com/rulo226) belirttiği > > > gibi , türü kaldırmam gerekti (track.py dosyasında boş bir değerle doldurun), ancak bunun kolayca düzeltilebileceğini düşünüyorum - benim kullanımım için özellikle bunlara ihtiyacım yok, bu yüzden olduğu gibi bıraktım. _**İzlenen adımlar:**_[](https://github.com/IsaacAgulhas)****__[](https://github.com/rulo226) > > > _****_ > > > > > > 1. https://github.com/Melf11/zotify adresindeki kurulum adımlarını uygulayın .[](https://github.com/Melf11/zotify) > > > 2. İstemci kimliği ve istemci gizli kodunu almak için Spotify uygulaması oluşturun. > > > 3. tokenmanager.py dosyasını bu değişikliklerle güncelleyin. > > > 4. Dosya güncellemelerini gerçekleştirin (aşağıya bakınız) > > > 5. zotify {playlist_URL} --bulk-wait-time 45 > > > > > > Kendi tarafımda bir çatal (fork) sağlayıp sağlayamayacağımı bilmiyorum, ancak yaptığım değişiklikler şunlar: **playlist.py ** _get_playlist_info_ > > > ``` > > > def get_playlist_info(playlist_id) -> tuple[str, str]: > > > """ Returns information scraped from playlist """ > > > with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): > > > token = token_manager.get_token() > > > > > > response = requests.get( > > > f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', > > > params={"market": "US"}, > > > headers={ > > > "Authorization": f"Bearer {token}" > > > } > > > ) > > > > > > if response.status_code == 429: > > > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > > > > > response.raise_for_status() > > > resp = response.json() > > > > > > resp_name = resp['name'] > > > resp_display_name = resp['owner']['display_name'] > > > > > > return resp_name.strip(), resp_display_name.strip() > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > **config.py ** _invoke_url_ > > > ``` > > > def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: > > > > > > token = token_manager.get_token() > > > > > > headers={ > > > "Authorization": f"Bearer {token}" > > > } > > > > > > tryCount = 0 > > > while tryCount <= cls.CONFIG.get_retry_attempts(): > > > > > > response = requests.get(url, headers=headers, params=_params) > > > > > > if response.status_code == 429: > > > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > > > > > cls.TOTAL_API_CALLS += 1 > > > > > > try: > > > responsetext = response.text > > > responsejson = response.json() > > > if not responsejson: > > > raise json.decoder.JSONDecodeError > > > # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > > except json.decoder.JSONDecodeError: > > > responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > > > > > if not responsejson or 'error' in responsejson: > > > if not expectFail: > > > Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ > > > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > > sleep(5 if not expectFail else 1) > > > tryCount += 1 > > > continue > > > else: > > > return responsetext, responsejson > > > > > > if not expectFail: > > > Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ > > > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > > > > > return responsetext, responsejson > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > [@rulo226'nın](https://github.com/rulo226) bahsettiği tür sorununu göz ardı etmek için : **track.py** dosyasındaki _update_track_metadata_ ve _download_track_ fonksiyonlarında , orijinal satırı yorum satırı haline getirin ve genres parametresini boş değere ayarlayın, şu şekilde: > > > ``` > > > # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) > > > genres = [''] > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > Dilerseniz güncelleyebilir/çatallayabilirsiniz :) > > > Tüm çabalarınız için de teşekkürler, başlangıçta 2 gün boyunca API 429 hatasıyla karşılaştım ve çalma listelerimin de en kısa sürede güncellenmesi gerekiyordu! > > > Alternatif olarak, çatalı güncellemeden (tür alanını boş değere ayarlamak dışında) çalma listesi içeriğini almak isterseniz, ilk başta çalma listesi içeriğini almak için şu yöntemi kullandım: > > > _Uyarı: Bu daha hızlı ve daha basit bir çözüm ve gelecekte çalma listesi senkronizasyonuyla çalışıp çalışmayacağından emin değilim, bu yüzden ilk başta çalma listeleri için bu yöntemi düzelttim._ > > > > > > 1. Spotify masaüstü uygulamasında istediğiniz çalma listesine gidin. > > > 2. Tümünü seç > > > 3. Yeni bir metin dosyasına sürükleyip bırakın => tüm çalma listesi parça bağlantıları kopyalanacaktır. > > > 4. (İsteğe bağlı: Mevcut bir çalma listesini güncellemek için, config.json dosyasında "OUTPUT" değerini ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}" olarak ayarlayabilirsiniz.) > > > 5. Oluşturulan metin dosyasındaki tüm bağlantıları "zotify track {track_URL} --bulk-wait-time 45;" şeklinde değiştirin. > > > 6. Terminale kopyala yapıştır yapın (Powershell kullanıyorum, _neyse ki_ ). > > > > > > Bu, çalma listesinden sırayla parça indirme işlemini gerçekleştirecektir. > > > _Not: 5. ve 6. adımlar, daha şık bir görünüm için her bağlantıyı parametre olarak kullanan bir döngü ile de yapılabilir._ > > > > > > Merhaba arkadaşım, talimatlarını uyguladım ama yine de bu hatayı alıyorum. Civan $ python -m zotify track https://open.spotify.com/track/70CWF9eT1tXx7gID9oYt3x?si=fR8c_EjgRteZOTAcPzKPfg --bulk-wait-time 45 > > ``` > > [∙●∙] Logging in... > > ``` > > > > > > > > > > > > > > > > > > > > > > > > ### UYARI: Parçada geçerli bir content_id bulunamadı, atlanıyor... > > ``` > > [∙●∙] Fetching track information... > > {} > > ``` > > > > > > > > > > > > > > > > > > > > > > > > {'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': ' https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4 ', 'is_pl[∙●∙] İndirme hazırlanıyor... KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1 KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1 > > ### HATA: SES ANAHTARINI ALMA BAŞARISIZ OLDU > > ### HIZ SINIRLAMALARINDAN KAYNAKLANABİLİR - `BULK_WAIT_TIME`###'I ARTIRMAYI DÜŞÜNÜN | 0/2 [00:01<?, ?url/s] > > ### GID: e654b4dd9cb446c1864e41e5ca259c8f - File_ID: a9c0d86702f84cc9d14bc130c63e117a88f1d061 > > ### HATA: ŞARKI ATLANIYOR - İÇERİK AKIŞI ALINAMADI > > ### Track_ID: 70CWF9eT1tXx7gID9oYt3x > > Tüm kimlik bilgileriniz ve müşteri verileriniz için Spotify Premium hesabı mı kullanıyorsunuz? Bu, hatanın nedenini açıklayabilir. Yes, I have a Spotify Premium account and I filled in the required information, including Spotify Client ID and Spotify Client Secret.
Author
Owner

@cewlp commented on GitHub (Dec 28, 2025):

do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?

<!-- gh-comment-id:3694744237 --> @cewlp commented on GitHub (Dec 28, 2025): do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?

<!-- gh-comment-id:3694748709 --> @Cio2566 commented on GitHub (Dec 28, 2025): No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

I've done your method, just to let you know that you have to add "from zotify.tokenmanager import token_manager" to the config file so the updated code actually knows where token_manager is defined, I'm not sure if you get the same results that I get, but when I run the code I get the information of the song that I'm downloading printed before downloading, just checking to see if you also get this. (Ignore why I'm downloading a song that already exists, I'm trying to go through the rest of the artist. I can confirm that this method of downloading does work with new songs) ` [∙∙∙] Preparing download...

SKIPPING: "X" (TRACK ALREADY EXISTS)

    [∙∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [●∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [∙∙●] Fetching track information...                                                                             
                                                                                                                   {}

{'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] Preparing download... DEBUG Duplicate Check File Already Exists: 5455249 song_id in Local Archive: True song_id in Global Archive: True`

@RGPZ Good point for the missing import line, I forgot to add it in my solution, will update it.
For the displayed song info, it seems it's from the changes in the @Melf11 fork: I performed a diff between it and @Googolplexed0 fork on track.py and there is a print of all the song data added, it's normal IMO:

track.py
In parse_track_metadata, this line was added in @Melf11's fork, you can remove it if you don't want the print (not tested but I guess that'll do the job):
print(track_metadata)

{'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download...
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1

@Cio2566 I (think) had it at some point (CRITICAL:Librespot rings a bell) but usually I rerun the command several times until the whole playlist is downloaded, maybe try to run it again several times.

Other point: I had a BadCredentials error as Spotify reset my password, to fix it I had to

  • Reset my Spotify password
  • Delete my credentials.json
  • Reconnect through the login link proposed when running zotify

Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks?

No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?

@Cio2566 I have 320kbps but indeed no way to know if the original file before ffmpeg conversion was "very_high" or if they reduced the quality... I have Spotify Premium account but I don't know if it's reduced quality when downloading now. If I have time to spare I might redownload a song and compare it to an original copy on Audacity and check if there are differences (btw FYI Spotify songs are exactly the same as the original 320kbps you can find on Bandcamp from artists, I think it has to do with both compression to .opus then conversion to .mp3 + a slight mastering by Spotify on each file of the database).

If you know if there is a drop in quality since the Anna's Archive incident I would be interested.

EDIT: no changes in the files quality, as you can see in the waveform compare in one of my below comments

<!-- gh-comment-id:3694867953 --> @Genkleable commented on GitHub (Dec 28, 2025): > I've done your method, just to let you know that you have to add "from zotify.tokenmanager import token_manager" to the config file so the updated code actually knows where token_manager is defined, I'm not sure if you get the same results that I get, but when I run the code I get the information of the song that I'm downloading printed before downloading, just checking to see if you also get this. (Ignore why I'm downloading a song that already exists, I'm trying to go through the rest of the artist. I can confirm that this method of downloading does work with new songs) ` [∙∙∙] Preparing download... > > ### SKIPPING: "X" (TRACK ALREADY EXISTS) > ``` > [∙∙∙] Fetching track information... > {} [●∙∙] Fetching track information... > {} [∙∙●] Fetching track information... > {} > ``` > > {'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] Preparing download... DEBUG Duplicate Check File Already Exists: 5455249 song_id in Local Archive: True song_id in Global Archive: True` @RGPZ Good point for the missing import line, I forgot to add it in my solution, will update it. For the displayed song info, it seems it's from the changes in the @Melf11 fork: I performed a diff between it and @Googolplexed0 fork on track.py and there is a print of all the song data added, it's normal IMO: **track.py** In _parse_track_metadata_, this line was added in @Melf11's fork, you can remove it if you don't want the print (not tested but I guess that'll do the job): `print(track_metadata)` > {'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': 'https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4', 'is_pl[∙●∙] Preparing download... > CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 > CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 @Cio2566 I (think) had it at some point (CRITICAL:Librespot rings a bell) but usually I rerun the command several times until the whole playlist is downloaded, maybe try to run it again several times. **Other point:** I had a BadCredentials error as Spotify reset my password, to fix it I had to - Reset my Spotify password - Delete my credentials.json - Reconnect through the login link proposed when running zotify Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks? > No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips? @Cio2566 I have 320kbps but indeed no way to know if the original file before ffmpeg conversion was "very_high" or if they reduced the quality... I have Spotify Premium account but I don't know if it's reduced quality when downloading now. If I have time to spare I might redownload a song and compare it to an original copy on Audacity and check if there are differences (btw FYI Spotify songs are exactly the same as the original 320kbps you can find on Bandcamp from artists, I think it has to do with both compression to .opus then conversion to .mp3 + a slight mastering by Spotify on each file of the database). If you know if there is a drop in quality since the Anna's Archive incident I would be interested. _EDIT: no changes in the files quality, as you can see in the waveform compare in one of my below comments_
Author
Owner

@rulo226 commented on GitHub (Dec 28, 2025):

Hi there, using @Melf11 fork which includes @IsaacAgulhas initial ideas, I managed to perform a quick and dirty working fix for the playlist download.

Looking fine for the moment, download still in progress (150 tracks). EDIT: worked just fine for all songs!

As @rulo226 mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is.

Followed steps:

  1. Perform install steps from https://github.com/Melf11/zotify
  2. Create Spotify app to get client ID and Client secret
  3. Update tokenmanager.py with those
  4. Perform files updates (see below)
  5. zotify {playlist_URL} --bulk-wait-time 45

Don't know if I will be able to provide a fork on my side, but here are the changes I performed: playlist.py get_playlist_info

    def get_playlist_info(playlist_id) -> tuple[str, str]:
    """ Returns information scraped from playlist """
    with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."):
        token = token_manager.get_token()
        
        response = requests.get(
            f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US',
            params={"market": "US"},
            headers={
                "Authorization": f"Bearer {token}"
            }
        )

    if response.status_code == 429:
        raise RuntimeError("Spotify API rate limit exceeded (your app)")

    response.raise_for_status()
    resp = response.json()
    
    resp_name = resp['name']    
    resp_display_name = resp['owner']['display_name']
    
    return resp_name.strip(), resp_display_name.strip()

config.py

  • EDIT: - At the top of the file, add the following import: (thanks for the reminder @RGPZ)
    from zotify.tokenmanager import SpotifyTokenManager, token_manager
  • invoke_url
    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}"
        }
        
        tryCount = 0
        while tryCount <= cls.CONFIG.get_retry_attempts():
            
            response = requests.get(url, headers=headers, params=_params)
            
            if response.status_code == 429:
                raise RuntimeError("Spotify API rate limit exceeded (your app)")
            
            cls.TOTAL_API_CALLS += 1
            
            try:
                responsetext = response.text
                responsejson = response.json()
                if not responsejson:
                    raise json.decoder.JSONDecodeError
                # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            except json.decoder.JSONDecodeError:
                responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}}
            
            if not responsejson or 'error' in responsejson:
                if not expectFail: 
                    Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\
                                                            f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
                sleep(5 if not expectFail else 1)
                tryCount += 1
                continue
            else:
                return responsetext, responsejson
        
        if not expectFail:
            Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\
                                                      f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}')
        
        return responsetext, responsejson

To ignore genre issue as mentioned by @rulo226: track.py In update_track_metadata and download_track, comment the original line and set the genres param to empty value, as such:

    # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name)
    genres = ['']

Feel free to update/fork it if you like :)

Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well!

Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content:

Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.

Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance

Hi there @Genkleable Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in config.py is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed!

Image
<!-- gh-comment-id:3694892994 --> @rulo226 commented on GitHub (Dec 28, 2025): > Hi there, using [@Melf11](https://github.com/Melf11) fork which includes [@IsaacAgulhas](https://github.com/IsaacAgulhas) initial ideas, I managed to perform a quick and dirty working fix for the **playlist download**. > > Looking fine for the moment, download still in progress (150 tracks). _EDIT: worked just fine for all songs!_ > > As [@rulo226](https://github.com/rulo226) mentioned I had to remove the genre (fill it with empty value instead of return in track.py), but I think this can be easily fixed - I don't specifically need those for my use so I just kept it as is. > > _**Followed steps:**_ > > 1. Perform install steps from https://github.com/Melf11/zotify > 2. Create Spotify app to get client ID and Client secret > 3. Update tokenmanager.py with those > 4. Perform files updates (see below) > 5. zotify {playlist_URL} --bulk-wait-time 45 > > Don't know if I will be able to provide a fork on my side, but here are the changes I performed: **playlist.py** _get_playlist_info_ > > ``` > def get_playlist_info(playlist_id) -> tuple[str, str]: > """ Returns information scraped from playlist """ > with Loader(PrintChannel.PROGRESS_INFO, "Fetching playlist information..."): > token = token_manager.get_token() > > response = requests.get( > f'{PLAYLIST_URL}/{playlist_id}?fields=name,owner(display_name)&market=US', > params={"market": "US"}, > headers={ > "Authorization": f"Bearer {token}" > } > ) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > response.raise_for_status() > resp = response.json() > > resp_name = resp['name'] > resp_display_name = resp['owner']['display_name'] > > return resp_name.strip(), resp_display_name.strip() > ``` > > **config.py** > > * _EDIT: - At the top of the file, add the following import: (thanks for the reminder [@RGPZ](https://github.com/RGPZ))_ > `from zotify.tokenmanager import SpotifyTokenManager, token_manager` > * _invoke_url_ > > ``` > def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: > > token = token_manager.get_token() > > headers={ > "Authorization": f"Bearer {token}" > } > > tryCount = 0 > while tryCount <= cls.CONFIG.get_retry_attempts(): > > response = requests.get(url, headers=headers, params=_params) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > cls.TOTAL_API_CALLS += 1 > > try: > responsetext = response.text > responsejson = response.json() > if not responsejson: > raise json.decoder.JSONDecodeError > # responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > except json.decoder.JSONDecodeError: > responsejson = {"error": {"status": "Unknown", "message": "Received an empty response"}} > > if not responsejson or 'error' in responsejson: > if not expectFail: > Printer.hashtaged(PrintChannel.WARNING, f'API ERROR (TRY {tryCount}) - RETRYING\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > sleep(5 if not expectFail else 1) > tryCount += 1 > continue > else: > return responsetext, responsejson > > if not expectFail: > Printer.hashtaged(PrintChannel.API_ERROR, f'API ERROR (TRY {tryCount}) - RETRY LIMIT EXCEDED\n' +\ > f'{responsejson["error"]["status"]}: {responsejson["error"]["message"]}') > > return responsetext, responsejson > ``` > > To ignore genre issue as mentioned by [@rulo226](https://github.com/rulo226): **track.py** In _update_track_metadata_ and _download_track_, comment the original line and set the genres param to empty value, as such: > > ``` > # genres = get_track_genres(track_metadata[ARTIST_IDS], track_name) > genres = [''] > ``` > > Feel free to update/fork it if you like :) > > Thanks for all your efforts too, I was stuck on the API 429 error for 2 days initially, and needed my playlists updated ASAP as well! > > Alternatively, if you want to get the playlist content without updating the fork (except genre to set to empty value), I used this method at first to get playlist content: > > _Disclaimer: even quicker and dirtier solution, and I dunno if it works with playlist sync in the future, that's why I fixed for the playlists in the first place_ > > 1. On the Spotify desktop app, go to the desired playlist > 2. Select all > 3. Drag and drop in a new text file => all playlist track links will be copied > 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") > 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" > 6. Copy paste in the terminal (I'm using Powershell _sigh_) > > That'll perform sequential track from playlist download. > > _Note: steps 5 and 6 can be done with a loop using each link as a param for more elegance_ Hi there @Genkleable Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in `config.py` is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed! <img width="842" height="312" alt="Image" src="https://github.com/user-attachments/assets/4d7a542d-8c95-41cb-b04c-3114dd6a93fe" />
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Yönteminizi denedim, sadece şunu bilmenizi istiyorum: "from zotify.tokenmanager import token_manager" satırını yapılandırma dosyasına eklemeniz gerekiyor, böylece güncellenen kod token_manager'ın nerede tanımlandığını gerçekten biliyor. Aynı sonuçları alıp almadığınızdan emin değilim, ancak kodu çalıştırdığımda indirdiğim şarkının bilgileri indirmeden önce yazdırılıyor, bunu sizin de alıp almadığınızı kontrol etmek istedim. (Zaten var olan bir şarkıyı neden indirdiğimi görmezden gelin, sanatçının geri kalanını denemeye çalışıyorum. Bu indirme yönteminin yeni şarkılarla da çalıştığını doğrulayabilirim.) ` [∙∙∙] İndirme hazırlanıyor...

ATLANIYOR: "X" (PARÇA ZATEN MEVCUT)

    [∙∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [●∙∙] Fetching track information...                                                                             
                                                                                                                   {}       [∙∙●] Fetching track information...                                                                             
                                                                                                                   {}

{'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] İndirme hazırlanıyor... DEBUG Yinelenen Dosya Kontrolü Zaten Mevcut: 5455249 Yerel Arşivdeki song_id: True Küresel Arşivdeki song_id: True`

@RGPZEksik içe aktarma satırı için iyi bir noktaya değindiniz, çözümümde eklemeyi unutmuştum, güncelleyeceğim. Görüntülenen şarkı bilgisine gelince, bunun değişikliklerden kaynaklandığı anlaşılıyor.@Melf11çatal: Bununla arasında bir fark karşılaştırması yaptım.@Googolplexed0track.py dosyasında bir çatallanma işlemi gerçekleşiyor ve eklenen tüm şarkı verilerinin çıktısı alınıyor, bence bu normal:

track.py dosyasındaki parse_track_metadata fonksiyonuna şu satır eklendi:@Melf11Çatalı isterseniz baskıyı kaldırabilirsiniz (denenmedi ama sanırım işe yarar): print(track_metadata)

{'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': ' https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4 ', 'is_pl[∙●∙] İndirme hazırlanıyor...
KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1
KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1

@Cio2566Sanırım bir ara bunu kullanmıştım (CRITICAL:Librespot aklıma geliyor), ama genellikle tüm oynatma listesi indirilene kadar komutu birkaç kez tekrar çalıştırıyorum, belki birkaç kez daha çalıştırmayı deneyebilirsiniz.

Diğer bir nokta: Spotify şifremi sıfırladığı için "BadCredentials" hatası aldım, bunu düzeltmek için şunları yapmam gerekti:

  • Spotify şifremi sıfırla
  • credentials.json dosyamı sil
  • Zotify çalıştırıldığında önerilen giriş bağlantısı üzerinden yeniden bağlanın.

Şimdi sorunsuz çalışıyor => belki Spotify veri sızıntılarını azaltmak için şifreleri sıfırlıyor?

Hayır, dostum, şarkı indirmiyor. Son zamanlarda, Annas Arşivi olayından sonra, API'ler daha sık kullanıma sunuldu ve biz de onları indiremiyoruz. > Doğru mu anladım, bu geçici çözüm çok yüksek (320 kbps) premium mp3 indirme dosyalarını desteklemiyor?

@Cio2566320kbps'lik bir dosyam var ama ffmpeg dönüştürmesinden önce orijinal dosyanın "çok yüksek" mi yoksa kalitenin düşürüldüğü mü olduğunu anlamanın bir yolu yok... Spotify Premium hesabım var ama şimdi indirirken kalitenin düşürülüp düşürülmediğini bilmiyorum. Vaktim olursa bir şarkıyı tekrar indirip Audacity'deki orijinal kopyasıyla karşılaştırıp fark olup olmadığını kontrol edebilirim (bu arada, Spotify şarkıları, Bandcamp'te sanatçıların bulabileceği orijinal 320kbps'lik dosyalarla tamamen aynı, sanırım bunun hem .opus'a sıkıştırma hem de .mp3'e dönüştürme ve Spotify'ın veritabanındaki her dosyaya uyguladığı hafif bir düzenleme ile ilgisi var).

Anna'nın Arşivi olayından beri kalitede bir düşüş olup olmadığını biliyorsanız, ilgilenirim.

https://open.spotify.com/track/1oOkZEqPrAQLpwBsiYKRl8?si=W9irdX-9TKqbkJLDtw4Axg --bulk-wait-time 45

    [∙∙∙] Logging in...                           

WARNING: No valid content_id found in track, skipping...

    [●∙∙] Fetching track information...           
                                                 {}

{'id': '1oOkZEqPrAQLpwBsiYKRl8', 'name': 'kAHpE', 'artists': ['Lvbel C5', 'AKDO', 'ALIZADE'], 'artist_ids': ['0V2oXYR7DtrZAEFeILRW2r', '17EAWIoXAMU9Vo9xRrdZQ0', '1EPZusBDP8yewhsaKtwktz'], 'release_date': '2025-02-27', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'kAHpE', 'album_artists': ['Lvbel C5', 'AKDO', 'ALIZADE'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 141351, 'image_url': 'https://i.scdn.co/image/ab67616d0000b27377530f260cbe8675db65631a', 'is_pl[●∙∙] Preparing download...
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1
CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1

ERROR: FAILED TO FETCH AUDIO KEY

MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING BULK_WAIT_TIME ### | 0/2 [00:00<?, ?url/s]

GID: 2e0183674edf44c18453c36e52f7c182 - File_ID: cc2d8c8c4f6e055e699b1d8b797bdc2a6cb32913

ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM

Track_ID: 1oOkZEqPrAQLpwBsiYKRl8 #.

Dude, I did everything you said, I reset my password, deleted my credentials.json files, logged in again using the link, but the problem persists. python -m zotify track

<!-- gh-comment-id:3694894538 --> @Cio2566 commented on GitHub (Dec 28, 2025): > > Yönteminizi denedim, sadece şunu bilmenizi istiyorum: "from zotify.tokenmanager import token_manager" satırını yapılandırma dosyasına eklemeniz gerekiyor, böylece güncellenen kod token_manager'ın nerede tanımlandığını gerçekten biliyor. Aynı sonuçları alıp almadığınızdan emin değilim, ancak kodu çalıştırdığımda indirdiğim şarkının bilgileri indirmeden önce yazdırılıyor, bunu sizin de alıp almadığınızı kontrol etmek istedim. (Zaten var olan bir şarkıyı neden indirdiğimi görmezden gelin, sanatçının geri kalanını denemeye çalışıyorum. Bu indirme yönteminin yeni şarkılarla da çalıştığını doğrulayabilirim.) ` [∙∙∙] İndirme hazırlanıyor... > > ### ATLANIYOR: "X" (PARÇA ZATEN MEVCUT) > > ``` > > [∙∙∙] Fetching track information... > > {} [●∙∙] Fetching track information... > > {} [∙∙●] Fetching track information... > > {} > > ``` > > > > > > > > > > > > > > > > > > > > > > > > {'id': 'X', 'name': 'X', 'artists': ['X'], 'artist_ids': ['X'], 'release_date': 'X', 'year': 'X', 'track_number': 'X', 'total_tracks': 'X', 'album': 'X', 'album_artists': ['X'], 'disc_number': 'X', 'compilation': X, 'duration_ms': X, 'image_url': 'htt[∙●∙] İndirme hazırlanıyor... DEBUG Yinelenen Dosya Kontrolü Zaten Mevcut: 5455249 Yerel Arşivdeki song_id: True Küresel Arşivdeki song_id: True` > > [@RGPZ](https://github.com/RGPZ)Eksik içe aktarma satırı için iyi bir noktaya değindiniz, çözümümde eklemeyi unutmuştum, güncelleyeceğim. Görüntülenen şarkı bilgisine gelince, bunun değişikliklerden kaynaklandığı anlaşılıyor.[@Melf11](https://github.com/Melf11)çatal: Bununla arasında bir fark karşılaştırması yaptım.[@Googolplexed0](https://github.com/Googolplexed0)track.py dosyasında bir çatallanma işlemi gerçekleşiyor ve eklenen tüm şarkı verilerinin çıktısı alınıyor, bence bu normal: > > **track.py** dosyasındaki _parse_track_metadata_ fonksiyonuna şu satır eklendi:[@Melf11](https://github.com/Melf11)Çatalı isterseniz baskıyı kaldırabilirsiniz (denenmedi ama sanırım işe yarar): `print(track_metadata)` > > > {'id': '70CWF9eT1tXx7gID9oYt3x', 'name': 'fıs?', 'artists': ['KAVAK', 'BAKAN'], 'artist_ids': ['24OGdBr3r58ksMLJkMXZZY', '3pELTNcBXbfHktNPFTUsNW'], 'release_date': '2025-08-22', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'fıs?', 'album_artists': ['KAVAK', 'BAKAN'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 136486, 'image_url': ' https://i.scdn.co/image/ab67616d0000b2731feb941ef64c8c04826e99a4 ', 'is_pl[∙●∙] İndirme hazırlanıyor... > > KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1 > > KRİTİK:Librespot:AudioKeyManager:Ses anahtarı hatası, kod: 1 > > [@Cio2566](https://github.com/Cio2566)Sanırım bir ara bunu kullanmıştım (CRITICAL:Librespot aklıma geliyor), ama genellikle tüm oynatma listesi indirilene kadar komutu birkaç kez tekrar çalıştırıyorum, belki birkaç kez daha çalıştırmayı deneyebilirsiniz. > > **Diğer bir nokta:** Spotify şifremi sıfırladığı için "BadCredentials" hatası aldım, bunu düzeltmek için şunları yapmam gerekti: > > * Spotify şifremi sıfırla > * credentials.json dosyamı sil > * Zotify çalıştırıldığında önerilen giriş bağlantısı üzerinden yeniden bağlanın. > > Şimdi sorunsuz çalışıyor => belki Spotify veri sızıntılarını azaltmak için şifreleri sıfırlıyor? > > > Hayır, dostum, şarkı indirmiyor. Son zamanlarda, Annas Arşivi olayından sonra, API'ler daha sık kullanıma sunuldu ve biz de onları indiremiyoruz. > Doğru mu anladım, bu geçici çözüm çok yüksek (320 kbps) premium mp3 indirme dosyalarını desteklemiyor? > > [@Cio2566](https://github.com/Cio2566)320kbps'lik bir dosyam var ama ffmpeg dönüştürmesinden önce orijinal dosyanın "çok yüksek" mi yoksa kalitenin düşürüldüğü mü olduğunu anlamanın bir yolu yok... Spotify Premium hesabım var ama şimdi indirirken kalitenin düşürülüp düşürülmediğini bilmiyorum. Vaktim olursa bir şarkıyı tekrar indirip Audacity'deki orijinal kopyasıyla karşılaştırıp fark olup olmadığını kontrol edebilirim (bu arada, Spotify şarkıları, Bandcamp'te sanatçıların bulabileceği orijinal 320kbps'lik dosyalarla tamamen aynı, sanırım bunun hem .opus'a sıkıştırma hem de .mp3'e dönüştürme ve Spotify'ın veritabanındaki her dosyaya uyguladığı hafif bir düzenleme ile ilgisi var). > > Anna'nın Arşivi olayından beri kalitede bir düşüş olup olmadığını biliyorsanız, ilgilenirim. https://open.spotify.com/track/1oOkZEqPrAQLpwBsiYKRl8?si=W9irdX-9TKqbkJLDtw4Axg --bulk-wait-time 45 [∙∙∙] Logging in... ### WARNING: No valid content_id found in track, skipping... ### [●∙∙] Fetching track information... {} {'id': '1oOkZEqPrAQLpwBsiYKRl8', 'name': 'kAHpE', 'artists': ['Lvbel C5', 'AKDO', 'ALIZADE'], 'artist_ids': ['0V2oXYR7DtrZAEFeILRW2r', '17EAWIoXAMU9Vo9xRrdZQ0', '1EPZusBDP8yewhsaKtwktz'], 'release_date': '2025-02-27', 'year': '2025', 'track_number': '01', 'total_tracks': '01', 'album': 'kAHpE', 'album_artists': ['Lvbel C5', 'AKDO', 'ALIZADE'], 'disc_number': '1', 'compilation': 0, 'duration_ms': 141351, 'image_url': 'https://i.scdn.co/image/ab67616d0000b27377530f260cbe8675db65631a', 'is_pl[●∙∙] Preparing download... CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 CRITICAL:Librespot:AudioKeyManager:Audio key error, code: 1 ### ERROR: FAILED TO FETCH AUDIO KEY ### ### MAY BE CAUSED BY RATE LIMITS - CONSIDER INCREASING `BULK_WAIT_TIME` ### | 0/2 [00:00<?, ?url/s] ### GID: 2e0183674edf44c18453c36e52f7c182 - File_ID: cc2d8c8c4f6e055e699b1d8b797bdc2a6cb32913 ### ### ERROR: SKIPPING SONG - FAILED TO GET CONTENT STREAM ### ### Track_ID: 1oOkZEqPrAQLpwBsiYKRl8 #. Dude, I did everything you said, I reset my password, deleted my credentials.json files, logged in again using the link, but the problem persists. python -m zotify track
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

@Cio2566 For this one I don't know... You said you had Spotify Premium so it shouldn't be an issue for the Audio key error, otherwise I would have suggested this issue: https://github.com/Googolplexed0/zotify/issues/86

If you have the occasion to test with a VPN or maybe another network (phone Wi-Fi hotspot), maybe you could try it, I don't know if they block or rate limit IP addresses that call the API too often. No idea at the moment otherwise :/

<!-- gh-comment-id:3694901498 --> @Genkleable commented on GitHub (Dec 28, 2025): @Cio2566 For this one I don't know... You said you had Spotify Premium so it shouldn't be an issue for the Audio key error, otherwise I would have suggested this issue: https://github.com/Googolplexed0/zotify/issues/86 If you have the occasion to test with a VPN or maybe another network (phone Wi-Fi hotspot), maybe you could try it, I don't know if they block or rate limit IP addresses that call the API too often. No idea at the moment otherwise :/
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

@Cio2566Bu konuda emin değilim... Spotify Premium'unuz olduğunu söylediniz, bu nedenle Ses anahtarı hatası sorun olmamalı, aksi takdirde şu sorunu önerirdim: #86

Eğer VPN veya başka bir ağ (telefonunuzun Wi-Fi erişim noktası) kullanarak test etme fırsatınız olursa, belki deneyebilirsiniz. Çok sık API çağrısı yapan IP adreslerini engelliyorlar mı veya hız sınırlaması uyguluyorlar mı bilmiyorum. Şu an başka bir fikrim yok :/

Yes, LibreSpot was giving a problem, it was an audio key 1 error.

<!-- gh-comment-id:3694904841 --> @Cio2566 commented on GitHub (Dec 28, 2025): > [@Cio2566](https://github.com/Cio2566)Bu konuda emin değilim... Spotify Premium'unuz olduğunu söylediniz, bu nedenle Ses anahtarı hatası sorun olmamalı, aksi takdirde şu sorunu önerirdim: [#86](https://github.com/Googolplexed0/zotify/issues/86) > > Eğer VPN veya başka bir ağ (telefonunuzun Wi-Fi erişim noktası) kullanarak test etme fırsatınız olursa, belki deneyebilirsiniz. Çok sık API çağrısı yapan IP adreslerini engelliyorlar mı veya hız sınırlaması uyguluyorlar mı bilmiyorum. Şu an başka bir fikrim yok :/ Yes, LibreSpot was giving a problem, it was an audio key 1 error.
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

@Cio2566 For this one I don't know... You said you had Spotify Premium so it shouldn't be an issue for the Audio key error, otherwise I would have suggested this issue: #86

If you have the occasion to test with a VPN or maybe another network (phone Wi-Fi hotspot), maybe you could try it, I don't know if they block or rate limit IP addresses that call the API too often. No idea at the moment otherwise :/

My friend, then I typed "zotify," it said search, I typed the song title, and on the third attempt it gave an error.
Civan $ zotify

    [∙●∙] Logging in...                           

Enter search: paparazzi

WARNING: API ERROR (TRY 0) - RETRYING

429: API rate limit exceeded

WARNING: API ERROR (TRY 1) - RETRYING

429: API rate limit exceeded

API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED

429: API rate limit exceeded

Traceback (most recent call last):
File "/data/data/com.termux/files/usr/bin/zotify", line 7, in
sys.exit(main())
^^^^^^
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/main.py", line 119, in main
args.func(args)
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 324, in client
search(Printer.get_input('Enter search: '))
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 116, in search
tracks = resp[TRACKS][ITEMS]
~~~~^^^^^^^^
KeyError: 'tracks'

<!-- gh-comment-id:3694908596 --> @Cio2566 commented on GitHub (Dec 28, 2025): > [@Cio2566](https://github.com/Cio2566) For this one I don't know... You said you had Spotify Premium so it shouldn't be an issue for the Audio key error, otherwise I would have suggested this issue: [#86](https://github.com/Googolplexed0/zotify/issues/86) > > If you have the occasion to test with a VPN or maybe another network (phone Wi-Fi hotspot), maybe you could try it, I don't know if they block or rate limit IP addresses that call the API too often. No idea at the moment otherwise :/ My friend, then I typed "zotify," it said search, I typed the song title, and on the third attempt it gave an error. Civan $ zotify [∙●∙] Logging in... Enter search: paparazzi ### WARNING: API ERROR (TRY 0) - RETRYING ### ### 429: API rate limit exceeded ### ### WARNING: API ERROR (TRY 1) - RETRYING ### ### 429: API rate limit exceeded ### ### API_ERROR: API ERROR (TRY 2) - RETRY LIMIT EXCEDED ### ### 429: API rate limit exceeded ### Traceback (most recent call last): File "/data/data/com.termux/files/usr/bin/zotify", line 7, in <module> sys.exit(main()) ^^^^^^ File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/__main__.py", line 119, in main args.func(args) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 324, in client search(Printer.get_input('Enter search: ')) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 116, in search tracks = resp[TRACKS][ITEMS] ~~~~^^^^^^^^ KeyError: 'tracks'
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

Hi there @Genkleable Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in config.py is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed!

@rulo226 Were the lyrics for this song available in your previous downloads? Or does it work for the other songs? Maybe it's just not present for this one song?

Or maybe the fork requires adaptation for this, but I already updated the invoke_url method used by get_track_lyrics. The error can be either from no lyrics available or from an added bug, I dunno, see these lines in handle_lyrics in track.py.

    except ValueError:
        Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)')
    return lyrics

Could also be due to the fork update in the metadata lines in track.py - parse_track_metadata, not sure for lyrics, sorry...

    # track_metadata unpack to individual variables
    # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks,
    # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values()
    print(track_metadata)

=>ValueError comes up either if lyrics are not present or if there is an issue in lyrics extraction due to an added bug in the fork update as I understand, don't know for this one mate, might need to be adapted for lyrics maybe. Or just regular good old absent lyrics on the database

<!-- gh-comment-id:3694911113 --> @Genkleable commented on GitHub (Dec 28, 2025): > Hi there [@Genkleable](https://github.com/Genkleable) Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in `config.py` is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed! @rulo226 Were the lyrics for this song available in your previous downloads? Or does it work for the other songs? Maybe it's just not present for this one song? Or maybe the fork requires adaptation for this, but I already updated the invoke_url method used by get_track_lyrics. The error can be either from no lyrics available or from an added bug, I dunno, see these lines in handle_lyrics in track.py. ``` except ValueError: Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)') return lyrics ``` Could also be due to the fork update in the metadata lines in track.py - parse_track_metadata, not sure for lyrics, sorry... ``` # track_metadata unpack to individual variables # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks, # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values() print(track_metadata) ``` =>ValueError comes up either if lyrics are not present or if there is an issue in lyrics extraction due to an added bug in the fork update as I understand, don't know for this one mate, might need to be adapted for lyrics maybe. Or just regular good old absent lyrics on the database
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Merhaba @Genkleable, şu an her şey mükemmel çalışıyor, ancak -cehaletim için özür dilerim- config.pyşarkı sözlerinin indirilmesinde bir değişiklik olmuş gibi görünüyor, bir göz atarsanız görebilirsiniz. Bunun dışında her şey tekrar çalışıyor. Katkıda bulunan herkese teşekkürler!

@rulo226Bu şarkının sözleri önceki indirmelerinizde mevcut muydu? Yoksa diğer şarkılar için mi geçerli? Belki de sadece bu şarkıda yok?

Belki de bu durum için çatalın uyarlanması gerekiyor, ancak get_track_lyrics tarafından kullanılan invoke_url yöntemini zaten güncelledim. Hata, ya şarkı sözlerinin bulunmamasından ya da eklenen bir hatadan kaynaklanıyor olabilir, bilmiyorum, track.py dosyasındaki handle_lyrics'teki şu satırlara bakın.

    except ValueError:
        Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)')
    return lyrics

Bunun nedeni, track.py dosyasındaki meta veri satırlarında (parse_track_metadata) yapılan çatal güncellemesi de olabilir; şarkı sözleri için emin değilim, üzgünüm...

    # track_metadata unpack to individual variables
    # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks,
    # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values()
    print(track_metadata)

=>ValueError hatası, ya şarkı sözleri mevcut değilse ya da çatal güncellemesinde eklenen bir hata nedeniyle şarkı sözü çıkarma işleminde bir sorun varsa ortaya çıkıyor, anladığım kadarıyla bu sürüm için bilmiyorum, belki şarkı sözleri için uyarlanması gerekebilir. Ya da sadece veritabanında şarkı sözlerinin olmaması gibi normal bir durum.

So you're saying my IP address is banned? I'll try with a VPN and let you know.

<!-- gh-comment-id:3694912697 --> @Cio2566 commented on GitHub (Dec 28, 2025): > > Merhaba [@Genkleable,](https://github.com/Genkleable) şu an her şey mükemmel çalışıyor, ancak -cehaletim için özür dilerim- `config.py`şarkı sözlerinin indirilmesinde bir değişiklik olmuş gibi görünüyor, bir göz atarsanız görebilirsiniz. Bunun dışında her şey tekrar çalışıyor. Katkıda bulunan herkese teşekkürler! > > [@rulo226](https://github.com/rulo226)Bu şarkının sözleri önceki indirmelerinizde mevcut muydu? Yoksa diğer şarkılar için mi geçerli? Belki de sadece bu şarkıda yok? > > Belki de bu durum için çatalın uyarlanması gerekiyor, ancak get_track_lyrics tarafından kullanılan invoke_url yöntemini zaten güncelledim. Hata, ya şarkı sözlerinin bulunmamasından ya da eklenen bir hatadan kaynaklanıyor olabilir, bilmiyorum, track.py dosyasındaki handle_lyrics'teki şu satırlara bakın. > > ``` > except ValueError: > Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)') > return lyrics > ``` > > Bunun nedeni, track.py dosyasındaki meta veri satırlarında (parse_track_metadata) yapılan çatal güncellemesi de olabilir; şarkı sözleri için emin değilim, üzgünüm... > > ``` > # track_metadata unpack to individual variables > # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks, > # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values() > print(track_metadata) > ``` > > =>ValueError hatası, ya şarkı sözleri mevcut değilse ya da çatal güncellemesinde eklenen bir hata nedeniyle şarkı sözü çıkarma işleminde bir sorun varsa ortaya çıkıyor, anladığım kadarıyla bu sürüm için bilmiyorum, belki şarkı sözleri için uyarlanması gerekebilir. Ya da sadece veritabanında şarkı sözlerinin olmaması gibi normal bir durum. So you're saying my IP address is banned? I'll try with a VPN and let you know.
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

My friend, then I typed "zotify," it said search, I typed the song title, and on the third attempt it gave an error.
Civan $ zotify

Are your errors systematic? First and second attempt never worked either? Here you're getting the API 429 error again even with the fix... If you're on the same IP address this might confirm the too many connections hypothesis

So you're saying my IP address is banned? I'll try with a VPN and let you know.

Could be, or just blocked with a cooldown time. You could use a VPN or try on another network!
Hope it's not the API you created that is blocked though! But mine is still working and I've downloaded at least 500+ songs now, compared to your single song should be fine (I hope)!

I usually used a VPN for this fork updates, so I would not know on a fixed IP address. In the past it worked fine without VPN but with the Anna's Archive leak they might be monitoring weird IP addresses... My password got reset for unusual activity which never happened in the past, so they might be freaking out and mitigating drastically.

<!-- gh-comment-id:3694914763 --> @Genkleable commented on GitHub (Dec 28, 2025): > My friend, then I typed "zotify," it said search, I typed the song title, and on the third attempt it gave an error. > Civan $ zotify Are your errors systematic? First and second attempt never worked either? Here you're getting the API 429 error again even with the fix... If you're on the same IP address this might confirm the too many connections hypothesis > So you're saying my IP address is banned? I'll try with a VPN and let you know. Could be, or just blocked with a cooldown time. You could use a VPN or try on another network! Hope it's not the API you created that is blocked though! But mine is still working and I've downloaded at least 500+ songs now, compared to your single song should be fine (I hope)! I usually used a VPN for this fork updates, so I would not know on a fixed IP address. In the past it worked fine without VPN but with the Anna's Archive leak they might be monitoring weird IP addresses... My password got reset for unusual activity which never happened in the past, so they might be freaking out and mitigating drastically.
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Arkadaşım, sonra "zotify" yazdım, arama yaptı, şarkı adını yazdım ve üçüncü denemede hata verdi.
Civan $ zotify

Hatalarınız sistematik mi? İlk ve ikinci deneme de işe yaramadı mı? Düzeltmeye rağmen yine API 429 hatası alıyorsunuz... Aynı IP adresini kullanıyorsanız, bu durum çok fazla bağlantı hipotezini doğrulayabilir.

Yani IP adresimin yasaklandığını mı söylüyorsunuz? VPN ile deneyeceğim ve size bildireceğim.

Olabilir, ya da sadece bir bekleme süresiyle engellenmiş olabilir. VPN kullanabilir veya başka bir ağda deneyebilirsiniz! Umarım oluşturduğunuz API engellenmiyordur! Ama benimki hala çalışıyor ve şimdiye kadar en az 500'den fazla şarkı indirdim, sizin tek şarkınızla kıyaslandığında sorun olmamalı (umarım)!

Bu çatal güncellemeleri için genellikle VPN kullanıyordum, bu yüzden sabit bir IP adresinde nasıl çalıştığını bilmiyorum. Geçmişte VPN olmadan sorunsuz çalışıyordu, ancak Anna'nın Arşivi sızıntısından sonra garip IP adreslerini izliyor olabilirler... Daha önce hiç yaşanmamış bir durum olan olağandışı aktivite nedeniyle şifrem sıfırlandı, bu yüzden panikleyip ciddi önlemler alıyor olabilirler.

Dude, I tried a VPN and it was giving the same error.

<!-- gh-comment-id:3694918421 --> @Cio2566 commented on GitHub (Dec 28, 2025): > > Arkadaşım, sonra "zotify" yazdım, arama yaptı, şarkı adını yazdım ve üçüncü denemede hata verdi. > > Civan $ zotify > > Hatalarınız sistematik mi? İlk ve ikinci deneme de işe yaramadı mı? Düzeltmeye rağmen yine API 429 hatası alıyorsunuz... Aynı IP adresini kullanıyorsanız, bu durum çok fazla bağlantı hipotezini doğrulayabilir. > > > Yani IP adresimin yasaklandığını mı söylüyorsunuz? VPN ile deneyeceğim ve size bildireceğim. > > Olabilir, ya da sadece bir bekleme süresiyle engellenmiş olabilir. VPN kullanabilir veya başka bir ağda deneyebilirsiniz! Umarım oluşturduğunuz API engellenmiyordur! Ama benimki hala çalışıyor ve şimdiye kadar en az 500'den fazla şarkı indirdim, sizin tek şarkınızla kıyaslandığında sorun olmamalı (umarım)! > > Bu çatal güncellemeleri için genellikle VPN kullanıyordum, bu yüzden sabit bir IP adresinde nasıl çalıştığını bilmiyorum. Geçmişte VPN olmadan sorunsuz çalışıyordu, ancak Anna'nın Arşivi sızıntısından sonra garip IP adreslerini izliyor olabilirler... Daha önce hiç yaşanmamış bir durum olan olağandışı aktivite nedeniyle şifrem sıfırlandı, bu yüzden panikleyip ciddi önlemler alıyor olabilirler. Dude, I tried a VPN and it was giving the same error.
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips?

It seems the quality is still the same as before the Anna's Archive incident, good old 320kpbs:

Image
  • First waveform is the one I downloaded last night with the fork
  • Second waveform is one I downloaded on 02/2025, that I reversed in Audacity
  • Third waveform is the mix of both songs => it's full silence, so there is no difference, additionally both files are the exact same size.
<!-- gh-comment-id:3694919201 --> @Genkleable commented on GitHub (Dec 28, 2025): > No, my friend, it's not downloading songs. Lately, after the Annas Archive incident, the APIs have become more frequent, and we can't download them.> do I have it correct this workaround doesn't support very high (320 kbps) premium mp3 download rips? It seems the quality is still the same as before the Anna's Archive incident, good old 320kpbs: <img width="1068" height="510" alt="Image" src="https://github.com/user-attachments/assets/0e7536ce-9660-4fea-bcca-ad55468dd165" /> - First waveform is the one I downloaded last night with the fork - Second waveform is one I downloaded on 02/2025, that I reversed in Audacity - Third waveform is the mix of both songs => it's full silence, so there is no difference, additionally both files are the exact same size.
Author
Owner

@Cio2566 commented on GitHub (Dec 28, 2025):

Hayır, dostum, şarkı indirmiyor. Son zamanlarda, Annas Arşivi olayından sonra, API'ler daha sık kullanıma sunuldu ve biz de onları indiremiyoruz. > Doğru mu anladım, bu geçici çözüm çok yüksek (320 kbps) premium mp3 indirme dosyalarını desteklemiyor?

Görünüşe göre kalite, Anna'nın Arşivi olayından öncekiyle aynı, eski güzel 320kbps:

Görüntü * İlk dalga formu, dün gece çatal (fork) ile indirdiğim dalga formu. * İkinci dalga formu, 02/2025 tarihinde indirdiğim ve Audacity'de tersine çevirdiğim bir dalga formudur. * Üçüncü dalga formu, her iki şarkının karışımıdır => tamamen sessizlikten oluşur, bu nedenle hiçbir fark yoktur; ayrıca her iki dosya da tamamen aynı boyuttadır.

I don't think they'll compromise on quality; they're probably just patching up the gaps.

<!-- gh-comment-id:3694920797 --> @Cio2566 commented on GitHub (Dec 28, 2025): > > Hayır, dostum, şarkı indirmiyor. Son zamanlarda, Annas Arşivi olayından sonra, API'ler daha sık kullanıma sunuldu ve biz de onları indiremiyoruz. > Doğru mu anladım, bu geçici çözüm çok yüksek (320 kbps) premium mp3 indirme dosyalarını desteklemiyor? > > Görünüşe göre kalite, Anna'nın Arşivi olayından öncekiyle aynı, eski güzel 320kbps: > > <img alt="Görüntü" width="1068" height="510" src="https://private-user-images.githubusercontent.com/251799281/530566830-0e7536ce-9660-4fea-bcca-ad55468dd165.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NjY5NDQ2MjMsIm5iZiI6MTc2Njk0NDMyMywicGF0aCI6Ii8yNTE3OTkyODEvNTMwNTY2ODMwLTBlNzUzNmNlLTk2NjAtNGZlYS1iY2NhLWFkNTU0NjhkZDE2NS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUxMjI4JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MTIyOFQxNzUyMDNaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1iN2I4YzAxNWM3MDEyOGIzN2ViY2RmNWY0ZWI0OWZiODI3Y2MyNjNlNTAxNmQ5NjkxY2Y1ODU5MmM4ZmM4NjRkJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.FieRAi0jaGYe1DeJ_0FhOXCzna6qlCOztDeqAIr_9wA"> > * İlk dalga formu, dün gece çatal (fork) ile indirdiğim dalga formu. > * İkinci dalga formu, 02/2025 tarihinde indirdiğim ve Audacity'de tersine çevirdiğim bir dalga formudur. > * Üçüncü dalga formu, her iki şarkının karışımıdır => tamamen sessizlikten oluşur, bu nedenle hiçbir fark yoktur; ayrıca her iki dosya da tamamen aynı boyuttadır. I don't think they'll compromise on quality; they're probably just patching up the gaps.
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

@Cio2566 For your issue I don't know man... I would try to rerun several time just to make sure you didn't get several errors in a row out of bad luck, and then if this fails reinstall the fork + redo the changes from scratch again just to make sure... The VPN location may have an impact maybe but it's pretty far-fetched...

The only difference with you is that I'm using the command with the pipx install, but I don't think it changes a thing. I don't have "python -m zotify track ..." but "zotify track ...". I had to update the python files in the C:\Users\USERNAME\scoop\apps\python\current\Lib\site-packages\zotify though.

<!-- gh-comment-id:3694923433 --> @Genkleable commented on GitHub (Dec 28, 2025): @Cio2566 For your issue I don't know man... I would try to rerun several time just to make sure you didn't get several errors in a row out of bad luck, and then if this fails reinstall the fork + redo the changes from scratch again just to make sure... The VPN location may have an impact maybe but it's pretty far-fetched... The only difference with you is that I'm using the command with the pipx install, but I don't think it changes a thing. I don't have "python -m zotify track ..." but "zotify track ...". I had to update the python files in the C:\Users\USERNAME\scoop\apps\python\current\Lib\site-packages\zotify though.
Author
Owner

@rulo226 commented on GitHub (Dec 28, 2025):

Hi there @Genkleable Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in config.py is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed!

@rulo226 Were the lyrics for this song available in your previous downloads? Or does it work for the other songs? Maybe it's just not present for this one song?

Or maybe the fork requires adaptation for this, but I already updated the invoke_url method used by get_track_lyrics. The error can be either from no lyrics available or from an added bug, I dunno, see these lines in handle_lyrics in track.py.

    except ValueError:
        Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)')
    return lyrics

Could also be due to the fork update in the metadata lines in track.py - parse_track_metadata, not sure for lyrics, sorry...

    # track_metadata unpack to individual variables
    # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks,
    # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values()
    print(track_metadata)

=>ValueError comes up either if lyrics are not present or if there is an issue in lyrics extraction due to an added bug in the fork update as I understand, don't know for this one mate, might need to be adapted for lyrics maybe. Or just regular good old absent lyrics on the database

@Genkleable Hi! I tested it first only with the change in tracks.py and lyrics download fine.
Then I added the changes in config.py and playlist.py.

After testing again, that’s when it starts failing — the same songs that were previously downloaded with their .lrc files now fail.

Following the error path to find the first place where something changes, I end up at get_track_lyrics in tracks.py (which has not been modified).
So, without touching config.py it works, but after changing config.py it doesn’t.

This function leads to invoke_url in config.py, which is exactly where changes were made.
So I assume something is missing in that code that prevents the lyrics from being fetched.

I’ve attached an image, and sorry in advance — English is not my native language.
Thanks everyone, cheers!

Image
<!-- gh-comment-id:3694923905 --> @rulo226 commented on GitHub (Dec 28, 2025): > > Hi there [@Genkleable](https://github.com/Genkleable) Everything works perfectly now, except that — sorry for my ignorance — it looks like something changed in `config.py` is interfering with the lyrics download, if you take a look. Apart from that, everything is working again. Thanks to everyone who contributed! > > [@rulo226](https://github.com/rulo226) Were the lyrics for this song available in your previous downloads? Or does it work for the other songs? Maybe it's just not present for this one song? > > Or maybe the fork requires adaptation for this, but I already updated the invoke_url method used by get_track_lyrics. The error can be either from no lyrics available or from an added bug, I dunno, see these lines in handle_lyrics in track.py. > > ``` > except ValueError: > Printer.hashtaged(PrintChannel.SKIPPING, f'LYRICS FOR "{track_label}" (LYRICS NOT AVAILABLE)') > return lyrics > ``` > > Could also be due to the fork update in the metadata lines in track.py - parse_track_metadata, not sure for lyrics, sorry... > > ``` > # track_metadata unpack to individual variables > # (scraped_track_id, track_name, artists, artist_ids, release_date, release_year, track_number, total_tracks, > # album, album_artists, disc_number, compilation, duration_ms, image_url, is_playable) = track_metadata.values() > print(track_metadata) > ``` > > =>ValueError comes up either if lyrics are not present or if there is an issue in lyrics extraction due to an added bug in the fork update as I understand, don't know for this one mate, might need to be adapted for lyrics maybe. Or just regular good old absent lyrics on the database @Genkleable Hi! I tested it first only with the change in tracks.py and lyrics download fine. Then I added the changes in config.py and playlist.py. After testing again, that’s when it starts failing — the same songs that were previously downloaded with their .lrc files now fail. Following the error path to find the first place where something changes, I end up at get_track_lyrics in tracks.py (which has not been modified). So, without touching config.py it works, but after changing config.py it doesn’t. This function leads to invoke_url in config.py, which is exactly where changes were made. So I assume something is missing in that code that prevents the lyrics from being fetched. I’ve attached an image, and sorry in advance — English is not my native language. Thanks everyone, cheers! <img width="1093" height="730" alt="Image" src="https://github.com/user-attachments/assets/506e89c1-2140-4b13-84b1-ef3fa0159f5f" />
Author
Owner

@Genkleable commented on GitHub (Dec 28, 2025):

@rulo226 I tried adding the lines from the initial header but did not work:

    def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]:
        
        token = token_manager.get_token()
        
        headers={
            "Authorization": f"Bearer {token}",
            'Accept-Language': f'{cls.CONFIG.get_language()}',
            'Accept': 'application/json',
            'app-platform': 'WebPlayer',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0'
        }

Maybe the params from the original get_auth_token were important for this...

return cls.SESSION.tokens().get_token(
            USER_READ_EMAIL, PLAYLIST_READ_PRIVATE, USER_LIBRARY_READ, USER_FOLLOW_READ
        )

I did not add them in the tokenmanager as I needed to do it quick (and I'm not 100% sure how this works on this side haha)

For your use case you could revert to the original fork (without my changes, only the track.py ones) and use these steps for your playlist with lyrics use case:

  1. On the Spotify desktop app, go to the desired playlist
  2. Select all
  3. Drag and drop in a new text file => all playlist track links will be copied
  4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
  5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;"
  6. Copy paste in the terminal (I'm using Powershell sigh)

That'll perform sequential track from playlist download.

This should work as this will be a sequential call to zotify track TRACK_URL instead of zotify PLAYLIST_URL.

The song_ids gets updated even with track by track update, so there will be no useless download, but I don't know if you'll be able to sync your playlist with the folder if you ever change its content in Spotify, which was why I updated the files in the first place (and also to avoid time consuming extra steps).

<!-- gh-comment-id:3694936692 --> @Genkleable commented on GitHub (Dec 28, 2025): @rulo226 I tried adding the lines from the initial header but did not work: ``` def invoke_url(cls, url: str, _params: dict | None = None, expectFail: bool = False) -> tuple[str, dict]: token = token_manager.get_token() headers={ "Authorization": f"Bearer {token}", 'Accept-Language': f'{cls.CONFIG.get_language()}', 'Accept': 'application/json', 'app-platform': 'WebPlayer', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0' } ``` Maybe the params from the original get_auth_token were important for this... ``` return cls.SESSION.tokens().get_token( USER_READ_EMAIL, PLAYLIST_READ_PRIVATE, USER_LIBRARY_READ, USER_FOLLOW_READ ) ``` I did not add them in the tokenmanager as I needed to do it quick (and I'm not 100% sure how this works on this side haha) For your use case you could revert to the original fork (without my changes, only the track.py ones) and use these steps for your playlist with lyrics use case: > 1. On the Spotify desktop app, go to the desired playlist > 2. Select all > 3. Drag and drop in a new text file => all playlist track links will be copied > 4. (Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": "YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") > 5. Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" > 6. Copy paste in the terminal (I'm using Powershell _sigh_) > > That'll perform sequential track from playlist download. This should work as this will be a sequential call to zotify track TRACK_URL instead of zotify PLAYLIST_URL. The song_ids gets updated even with track by track update, so there will be no useless download, but I don't know if you'll be able to sync your playlist with the folder if you ever change its content in Spotify, which was why I updated the files in the first place (and also to avoid time consuming extra steps).
Author
Owner

@RGPZ commented on GitHub (Dec 28, 2025):

@Genkleable you also need to add from zotify.termoutput import Loader from zotify.tokenmanager import token_manager import requests to playlist.py so you can download playlists, as none of these values are defined in the file elsewhere, I'm not sure what else may need defining that you haven't already mentioned. I'm mainly just checking as I go through using the script normally.

<!-- gh-comment-id:3695062743 --> @RGPZ commented on GitHub (Dec 28, 2025): @Genkleable you also need to add `from zotify.termoutput import Loader from zotify.tokenmanager import token_manager import requests` to playlist.py so you can download playlists, as none of these values are defined in the file elsewhere, I'm not sure what else may need defining that you haven't already mentioned. I'm mainly just checking as I go through using the script normally.
Author
Owner

@Phlogi commented on GitHub (Dec 29, 2025):

One solution is to use the client credentials auth for retrieving the metadata and oauth only for librespot streaming.

@Googolplexed0

<!-- gh-comment-id:3695957396 --> @Phlogi commented on GitHub (Dec 29, 2025): One solution is to use the client credentials auth for retrieving the metadata and oauth only for librespot streaming. @Googolplexed0
Author
Owner

@cewlp commented on GitHub (Dec 29, 2025):

guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials

(venv) debollekes@des-MacBook-Air zotify % zotify track https://open.spotify.com/track/5jhDeobWatA1XAHWKOWPh2

Username: xxx@xxxmail.com
Password: **********
Traceback (most recent call last):
File "/Users/debollekes/venv/bin/zotify", line 7, in
sys.exit(main())
~~~~^^
File "/Users/debollekes/zotify/zotify/zotify/main.py", line 64, in main
args.func(args)
~~~~~~~~~^^^^^^
File "/Users/debollekes/zotify/zotify/zotify/app.py", line 21, in client
Zotify(args)
~~~~~~^^^^^^
File "/Users/debollekes/zotify/zotify/zotify/zotify.py", line 21, in init
Zotify.login(args)
~~~~~~~~~~~~^^^^^^
File "/Users/debollekes/zotify/zotify/zotify/zotify.py", line 46, in login
cls.SESSION = Session.Builder(conf).user_pass(user_name, password).create()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 1680, in create
session.authenticate(self.login_credentials)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 964, in authenticate
self.__authenticate_partial(credential, False)
~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 1375, in __authenticate_partial
raise Session.SpotifyAuthenticationException(ap_login_failed)
librespot.core.Session.SpotifyAuthenticationException: BadCredentials
(venv) debollekes@des-MacBook-Air zotify % pip show zotify

this was a problem before. I am using below versions

Name: zotify
Version: 0.6.14

and

(venv) debollekes@des-MacBook-Air zotify % brew upgrade librespot

✔︎ JSON API cask.jws.json [Downloaded 15.3MB/ 15.3MB]
✔︎ JSON API formula.jws.json [Downloaded 32.1MB/ 32.1MB]
Warning: librespot 0.8.0 already installed
(venv) debollekes@des-MacBook-Air zotify %

Anything I overlooked? I tried to login with my spotify email and device pw (should be ok normally) . I tried to reset my device pw via spotify but I keep getting There was a problem setting your password. Any help appreciated.

<!-- gh-comment-id:3696064561 --> @cewlp commented on GitHub (Dec 29, 2025): guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials (venv) debollekes@des-MacBook-Air zotify % zotify track https://open.spotify.com/track/5jhDeobWatA1XAHWKOWPh2 Username: xxx@xxxmail.com Password: ********** Traceback (most recent call last): File "/Users/debollekes/venv/bin/zotify", line 7, in <module> sys.exit(main()) ~~~~^^ File "/Users/debollekes/zotify/zotify/zotify/__main__.py", line 64, in main args.func(args) ~~~~~~~~~^^^^^^ File "/Users/debollekes/zotify/zotify/zotify/app.py", line 21, in client Zotify(args) ~~~~~~^^^^^^ File "/Users/debollekes/zotify/zotify/zotify/zotify.py", line 21, in __init__ Zotify.login(args) ~~~~~~~~~~~~^^^^^^ File "/Users/debollekes/zotify/zotify/zotify/zotify.py", line 46, in login cls.SESSION = Session.Builder(conf).user_pass(user_name, password).create() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 1680, in create session.authenticate(self.login_credentials) ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^ File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 964, in authenticate self.__authenticate_partial(credential, False) ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^ File "/Users/debollekes/venv/lib/python3.14/site-packages/librespot/core.py", line 1375, in __authenticate_partial raise Session.SpotifyAuthenticationException(ap_login_failed) librespot.core.Session.SpotifyAuthenticationException: BadCredentials (venv) debollekes@des-MacBook-Air zotify % pip show zotify this was a problem before. I am using below versions Name: zotify Version: 0.6.14 and (venv) debollekes@des-MacBook-Air zotify % brew upgrade librespot ✔︎ JSON API cask.jws.json [Downloaded 15.3MB/ 15.3MB] ✔︎ JSON API formula.jws.json [Downloaded 32.1MB/ 32.1MB] Warning: librespot 0.8.0 already installed (venv) debollekes@des-MacBook-Air zotify % Anything I overlooked? I tried to login with my spotify email and device pw (should be ok normally) . I tried to reset my device pw via spotify but I keep getting There was a problem setting your password. Any help appreciated.
Author
Owner

@Genkleable commented on GitHub (Dec 29, 2025):

guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials

@cewlp I had the same issue because my password had been reset:

Other point: I had a BadCredentials error as Spotify reset my password, to fix it I had to

  • Reset my Spotify password
  • Delete my credentials.json
  • Reconnect through the login link proposed when running zotify

Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks?

If I'm correct you can't just log with your email and PW, try to delete (or move elsewhere) your credentials.json and then run zotify and go the link shown in the terminal and authenticate. If there was a problem setting your password, it may mean your previous one has been reset, thus the credentials.json content would be obsolete - you should first make sure you have correctly set your Spotify password.

I wonder why you get "There was a problem setting your password" however, hope if you try again the error will go, else this would be a Spotify issue.

<!-- gh-comment-id:3696750804 --> @Genkleable commented on GitHub (Dec 29, 2025): > guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials @cewlp I had the same issue because my password had been reset: > **Other point:** I had a BadCredentials error as Spotify reset my password, to fix it I had to > > * Reset my Spotify password > * Delete my credentials.json > * Reconnect through the login link proposed when running zotify > > Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks? If I'm correct you can't just log with your email and PW, try to delete (or move elsewhere) your credentials.json and then run zotify and go the link shown in the terminal and authenticate. If there was a problem setting your password, it may mean your previous one has been reset, thus the credentials.json content would be obsolete - you should first **make sure you have correctly set your Spotify password**. I wonder why you get "There was a problem setting your password" however, hope if you try again the error will go, else this would be a Spotify issue.
Author
Owner

@8a32c5c commented on GitHub (Dec 29, 2025):

It seems like they shut down the ability to create new API credentials. If anyone has burner credentials (client ID/secret) to share I'd be eternally grateful.

<!-- gh-comment-id:3696751823 --> @8a32c5c commented on GitHub (Dec 29, 2025): It seems like they shut down the ability to create new API credentials. If anyone has burner credentials (client ID/secret) to share I'd be eternally grateful.
Author
Owner

@mintyCATy commented on GitHub (Dec 29, 2025):

Throwing my hat into the ring with my experience as someone less technically minded getting this working (hopefully in an easy-to-understand way for less technical people):

  • I installed the fork from Melf11 by running the install command with the new repo's git link
  • Created a new API in the Spotify developer dashboard (working Yesterday)
  • Inputted Client ID/Secret in ~/pipx/venvs/zotify/Lib/site-packages/zotify/tokenmanager.py
  • Made "get_track_genres" in track.py always return null by deleting everything except for the last "return" statement
  • Manually made a download list as described by Genkleable

On the Spotify desktop app, go to the desired playlist
Select all
Drag and drop in a new text file => all playlist track links will be copied
(Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": >"YOUR_PLAYLIST_FOLDER/{artist} - {song_name}")
Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" (use some sort of find and replace)
Copy paste in the terminal

All of the above, with the addition of -rt to the commands because I didn't want my account banned, worked flawlessly and I've had no issues. It's a bit frustrating to have to create the download list by hand, but this looks to be the simplest way to download your music at the moment as someone who doesn't have the skills to modify and debug the program to get playlists working.

<!-- gh-comment-id:3696990315 --> @mintyCATy commented on GitHub (Dec 29, 2025): Throwing my hat into the ring with my experience as someone less technically minded getting this working (hopefully in an easy-to-understand way for less technical people): - I installed [the fork](https://github.com/Melf11/zotify) from Melf11 by running the install command with the new repo's git link - Created a new API in the Spotify developer dashboard (working Yesterday) - Inputted Client ID/Secret in ~/pipx/venvs/zotify/Lib/site-packages/zotify/tokenmanager.py - Made "get_track_genres" in track.py always return null by deleting everything except for the last "return" statement - Manually made a download list as described by Genkleable > On the Spotify desktop app, go to the desired playlist >Select all >Drag and drop in a new text file => all playlist track links will be copied >(Optional: to update an existing playlist, in the config.json file, you can set the "OUTPUT" to ""OUTPUT": >"YOUR_PLAYLIST_FOLDER/{artist} - {song_name}") >Change all the links from the created text file to "zotify track {track_URL} --bulk-wait-time 45;" (use some sort of find and replace) >Copy paste in the terminal All of the above, with the addition of -rt to the commands because I didn't want my account banned, worked flawlessly and I've had no issues. It's a bit frustrating to have to create the download list by hand, but this looks to be the simplest way to download your music at the moment as someone who doesn't have the skills to modify and debug the program to get playlists working.
Author
Owner

@IsaacAgulhas commented on GitHub (Dec 29, 2025):

@Googolplexed0 it seems the spotify API is becoming a bottleneck, spotify has been making quite a lot of changes to their system and as mentioned above they have also disabled the ability to create new API apps. I did some digging into the code of this repo and found that the API is used to get metadata for songs, artists, albums, etc. It mainly uses that metadata to write tags on audio files (artwork, artists name, album name, song name, etc), and this is not really needed since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api. So what I did was I updated the code to remove the writing of the tags, and therefore the need for API metadata and it is working correctly now. This is a work around to the inability to create new API keys. Here is my updated download_track function in track.py, it is not pretty but it does work.

def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None:
    """ Downloads raw song audio from Spotify """

    if extra_keys is None:
        extra_keys = {}

    prepare_download_loader = Loader(PrintChannel.PROGRESS_INFO, "Preparing download...")
    prepare_download_loader.start()

    try:
        output_template = Zotify.CONFIG.get_output(mode)

        # (artists, raw_artists, album_name, name, image_url, release_year, disc_number,
        #  track_number, scraped_song_id, is_playable, duration_ms) = get_song_info(track_id)

        song_name = track_id  # fix_filename(artists[0]) + ' - ' + fix_filename(name)
        is_playable = True

        for k in extra_keys:
            output_template = output_template.replace("{"+k+"}", fix_filename(extra_keys[k]))

        ext = EXT_MAP.get(Zotify.CONFIG.get_download_format().lower())

        # output_template = output_template.replace("{artist}", fix_filename(artists[0]))
        # output_template = output_template.replace("{album}", fix_filename(album_name))
        # output_template = output_template.replace("{song_name}", fix_filename(name))
        # output_template = output_template.replace("{release_year}", fix_filename(release_year))
        # output_template = output_template.replace("{disc_number}", fix_filename(disc_number))
        # output_template = output_template.replace("{track_number}", fix_filename(track_number))
        # output_template = output_template.replace("{id}", fix_filename(scraped_song_id))

        output_template = output_template.replace("{id}", fix_filename(track_id)) # remove to revert
        output_template = output_template.replace("{track_id}", fix_filename(track_id))
        output_template = output_template.replace("{ext}", ext)

        filename = PurePath(Zotify.CONFIG.get_root_path()).joinpath(output_template)
        filedir = PurePath(filename).parent

        filename_temp = filename
        if Zotify.CONFIG.get_temp_download_dir() != '':
            filename_temp = PurePath(Zotify.CONFIG.get_temp_download_dir()).joinpath(f'zotify_{str(uuid.uuid4())}_{track_id}.{ext}')

        check_name = Path(filename).is_file() and Path(filename).stat().st_size
        check_id = track_id in get_directory_song_ids(filedir) # scraped_song_id in get_directory_song_ids(filedir)
        check_all_time = track_id in get_previously_downloaded() # scraped_song_id in get_previously_downloaded()

        # a song with the same name is installed
        if not check_id and check_name:
            c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1

            fname = PurePath(PurePath(filename).name).parent
            ext = PurePath(PurePath(filename).name).suffix

            filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}')

    except Exception as e:
        Printer.print(PrintChannel.ERRORS, '###   SKIPPING SONG - FAILED TO QUERY METADATA   ###')
        Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
        for k in extra_keys:
            Printer.print(PrintChannel.ERRORS, k + ': ' + str(extra_keys[k]))
        Printer.print(PrintChannel.ERRORS, "\n")
        Printer.print(PrintChannel.ERRORS, str(e) + "\n")
        Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n")

    else:
        try:
            if not is_playable:
                prepare_download_loader.stop()
                Printer.print(PrintChannel.SKIPS, '\n###   SKIPPING: ' + song_name + ' (SONG IS UNAVAILABLE)   ###' + "\n")
            else:
                if check_id and check_name and Zotify.CONFIG.get_skip_existing():
                    prepare_download_loader.stop()
                    Printer.print(PrintChannel.SKIPS, '\n###   SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS)   ###' + "\n")

                elif check_all_time and Zotify.CONFIG.get_skip_previously_downloaded():
                    prepare_download_loader.stop()
                    Printer.print(PrintChannel.SKIPS, '\n###   SKIPPING: ' + song_name + ' (SONG ALREADY DOWNLOADED ONCE)   ###' + "\n")

                else:
                    # if track_id != scraped_song_id:
                    #     track_id = scraped_song_id
                    track = TrackId.from_base62(track_id)
                    stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY)
                    create_download_directory(filedir)
                    total_size = stream.input_stream.size

                    prepare_download_loader.stop()

                    time_start = time.time()
                    downloaded = 0
                    with open(filename_temp, 'wb') as file, Printer.progress(
                            desc=song_name,
                            total=total_size,
                            unit='B',
                            unit_scale=True,
                            unit_divisor=1024,
                            disable=disable_progressbar
                    ) as p_bar:
                        b = 0
                        while b < 5:
                        #for _ in range(int(total_size / Zotify.CONFIG.get_chunk_size()) + 2):
                            data = stream.input_stream.stream().read(Zotify.CONFIG.get_chunk_size())
                            p_bar.update(file.write(data))
                            downloaded += len(data)
                            b += 1 if data == b'' else 0
                            # if Zotify.CONFIG.get_download_real_time():
                            #     delta_real = time.time() - time_start
                            #     delta_want = (downloaded / total_size) * (duration_ms/1000)
                            #     if delta_want > delta_real:
                            #         time.sleep(delta_want - delta_real)

                    time_downloaded = time.time()

                    # genres = get_song_genres(raw_artists, name)

                    if(Zotify.CONFIG.get_download_lyrics()):
                        try:
                            get_song_lyrics(track_id, PurePath(str(filename)[:-3] + "lrc"))
                        except ValueError:
                            Printer.print(PrintChannel.SKIPS, f"###   Skipping lyrics for {song_name}: lyrics not available   ###")
                    convert_audio_format(filename_temp)
                    # try:
                    #     set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number)
                    #     set_music_thumbnail(filename_temp, image_url)
                    # except Exception:
                    #     Printer.print(PrintChannel.ERRORS, "Unable to write metadata, ensure ffmpeg is installed and added to your PATH.")

                    if filename_temp != filename:
                        Path(filename_temp).rename(filename)

                    time_finished = time.time()

                    Printer.print(PrintChannel.DOWNLOADS, f'###   Downloaded "{song_name}" to "{Path(filename).relative_to(Zotify.CONFIG.get_root_path())}" in {fmt_seconds(time_downloaded - time_start)} (plus {fmt_seconds(time_finished - time_downloaded)} converting)   ###' + "\n")

                    # add song id to archive file
                    # if Zotify.CONFIG.get_skip_previously_downloaded():
                    #     add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name)
                    # add song id to download directory's .song_ids file
                    # if not check_id:
                    #     add_to_directory_song_ids(filedir, scraped_song_id, PurePath(filename).name, artists[0], name)

                    if Zotify.CONFIG.get_bulk_wait_time():
                        time.sleep(Zotify.CONFIG.get_bulk_wait_time())
        except Exception as e:
            Printer.print(PrintChannel.ERRORS, '###   SKIPPING: ' + song_name + ' (GENERAL DOWNLOAD ERROR)   ###')
            Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id))
            for k in extra_keys:
                Printer.print(PrintChannel.ERRORS, k + ': ' + str(extra_keys[k]))
            Printer.print(PrintChannel.ERRORS, "\n")
            Printer.print(PrintChannel.ERRORS, str(e) + "\n")
            Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n")
            if Path(filename_temp).exists():
                Path(filename_temp).unlink()

    prepare_download_loader.stop()

Again, I post this as a guide and not a complete solution, kudos to @Melf11 for their implementation and fork.

<!-- gh-comment-id:3696994943 --> @IsaacAgulhas commented on GitHub (Dec 29, 2025): @Googolplexed0 it seems the spotify API is becoming a bottleneck, spotify has been making quite a lot of changes to their system and as mentioned above they have also disabled the ability to create new API apps. I did some digging into the code of this repo and found that the API is used to get metadata for songs, artists, albums, etc. It mainly uses that metadata to write tags on audio files (artwork, artists name, album name, song name, etc), and this is not really needed since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api. So what I did was I updated the code to remove the writing of the tags, and therefore the need for API metadata and it is working correctly now. This is a work around to the inability to create new API keys. Here is my updated download_track function in track.py, it is not pretty but it does work. ```python def download_track(mode: str, track_id: str, extra_keys=None, disable_progressbar=False) -> None: """ Downloads raw song audio from Spotify """ if extra_keys is None: extra_keys = {} prepare_download_loader = Loader(PrintChannel.PROGRESS_INFO, "Preparing download...") prepare_download_loader.start() try: output_template = Zotify.CONFIG.get_output(mode) # (artists, raw_artists, album_name, name, image_url, release_year, disc_number, # track_number, scraped_song_id, is_playable, duration_ms) = get_song_info(track_id) song_name = track_id # fix_filename(artists[0]) + ' - ' + fix_filename(name) is_playable = True for k in extra_keys: output_template = output_template.replace("{"+k+"}", fix_filename(extra_keys[k])) ext = EXT_MAP.get(Zotify.CONFIG.get_download_format().lower()) # output_template = output_template.replace("{artist}", fix_filename(artists[0])) # output_template = output_template.replace("{album}", fix_filename(album_name)) # output_template = output_template.replace("{song_name}", fix_filename(name)) # output_template = output_template.replace("{release_year}", fix_filename(release_year)) # output_template = output_template.replace("{disc_number}", fix_filename(disc_number)) # output_template = output_template.replace("{track_number}", fix_filename(track_number)) # output_template = output_template.replace("{id}", fix_filename(scraped_song_id)) output_template = output_template.replace("{id}", fix_filename(track_id)) # remove to revert output_template = output_template.replace("{track_id}", fix_filename(track_id)) output_template = output_template.replace("{ext}", ext) filename = PurePath(Zotify.CONFIG.get_root_path()).joinpath(output_template) filedir = PurePath(filename).parent filename_temp = filename if Zotify.CONFIG.get_temp_download_dir() != '': filename_temp = PurePath(Zotify.CONFIG.get_temp_download_dir()).joinpath(f'zotify_{str(uuid.uuid4())}_{track_id}.{ext}') check_name = Path(filename).is_file() and Path(filename).stat().st_size check_id = track_id in get_directory_song_ids(filedir) # scraped_song_id in get_directory_song_ids(filedir) check_all_time = track_id in get_previously_downloaded() # scraped_song_id in get_previously_downloaded() # a song with the same name is installed if not check_id and check_name: c = len([file for file in Path(filedir).iterdir() if re.search(f'^{filename}_', str(file))]) + 1 fname = PurePath(PurePath(filename).name).parent ext = PurePath(PurePath(filename).name).suffix filename = PurePath(filedir).joinpath(f'{fname}_{c}{ext}') except Exception as e: Printer.print(PrintChannel.ERRORS, '### SKIPPING SONG - FAILED TO QUERY METADATA ###') Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id)) for k in extra_keys: Printer.print(PrintChannel.ERRORS, k + ': ' + str(extra_keys[k])) Printer.print(PrintChannel.ERRORS, "\n") Printer.print(PrintChannel.ERRORS, str(e) + "\n") Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n") else: try: if not is_playable: prepare_download_loader.stop() Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG IS UNAVAILABLE) ###' + "\n") else: if check_id and check_name and Zotify.CONFIG.get_skip_existing(): prepare_download_loader.stop() Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY EXISTS) ###' + "\n") elif check_all_time and Zotify.CONFIG.get_skip_previously_downloaded(): prepare_download_loader.stop() Printer.print(PrintChannel.SKIPS, '\n### SKIPPING: ' + song_name + ' (SONG ALREADY DOWNLOADED ONCE) ###' + "\n") else: # if track_id != scraped_song_id: # track_id = scraped_song_id track = TrackId.from_base62(track_id) stream = Zotify.get_content_stream(track, Zotify.DOWNLOAD_QUALITY) create_download_directory(filedir) total_size = stream.input_stream.size prepare_download_loader.stop() time_start = time.time() downloaded = 0 with open(filename_temp, 'wb') as file, Printer.progress( desc=song_name, total=total_size, unit='B', unit_scale=True, unit_divisor=1024, disable=disable_progressbar ) as p_bar: b = 0 while b < 5: #for _ in range(int(total_size / Zotify.CONFIG.get_chunk_size()) + 2): data = stream.input_stream.stream().read(Zotify.CONFIG.get_chunk_size()) p_bar.update(file.write(data)) downloaded += len(data) b += 1 if data == b'' else 0 # if Zotify.CONFIG.get_download_real_time(): # delta_real = time.time() - time_start # delta_want = (downloaded / total_size) * (duration_ms/1000) # if delta_want > delta_real: # time.sleep(delta_want - delta_real) time_downloaded = time.time() # genres = get_song_genres(raw_artists, name) if(Zotify.CONFIG.get_download_lyrics()): try: get_song_lyrics(track_id, PurePath(str(filename)[:-3] + "lrc")) except ValueError: Printer.print(PrintChannel.SKIPS, f"### Skipping lyrics for {song_name}: lyrics not available ###") convert_audio_format(filename_temp) # try: # set_audio_tags(filename_temp, artists, genres, name, album_name, release_year, disc_number, track_number) # set_music_thumbnail(filename_temp, image_url) # except Exception: # Printer.print(PrintChannel.ERRORS, "Unable to write metadata, ensure ffmpeg is installed and added to your PATH.") if filename_temp != filename: Path(filename_temp).rename(filename) time_finished = time.time() Printer.print(PrintChannel.DOWNLOADS, f'### Downloaded "{song_name}" to "{Path(filename).relative_to(Zotify.CONFIG.get_root_path())}" in {fmt_seconds(time_downloaded - time_start)} (plus {fmt_seconds(time_finished - time_downloaded)} converting) ###' + "\n") # add song id to archive file # if Zotify.CONFIG.get_skip_previously_downloaded(): # add_to_archive(scraped_song_id, PurePath(filename).name, artists[0], name) # add song id to download directory's .song_ids file # if not check_id: # add_to_directory_song_ids(filedir, scraped_song_id, PurePath(filename).name, artists[0], name) if Zotify.CONFIG.get_bulk_wait_time(): time.sleep(Zotify.CONFIG.get_bulk_wait_time()) except Exception as e: Printer.print(PrintChannel.ERRORS, '### SKIPPING: ' + song_name + ' (GENERAL DOWNLOAD ERROR) ###') Printer.print(PrintChannel.ERRORS, 'Track_ID: ' + str(track_id)) for k in extra_keys: Printer.print(PrintChannel.ERRORS, k + ': ' + str(extra_keys[k])) Printer.print(PrintChannel.ERRORS, "\n") Printer.print(PrintChannel.ERRORS, str(e) + "\n") Printer.print(PrintChannel.ERRORS, "".join(traceback.TracebackException.from_exception(e).format()) + "\n") if Path(filename_temp).exists(): Path(filename_temp).unlink() prepare_download_loader.stop() ``` Again, I post this as a guide and not a complete solution, kudos to @Melf11 for their implementation and fork.
Author
Owner

@cewlp commented on GitHub (Dec 29, 2025):

guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials

@cewlp I had the same issue because my password had been reset:

Other point: I had a BadCredentials error as Spotify reset my password, to fix it I had to

  • Reset my Spotify password
  • Delete my credentials.json
  • Reconnect through the login link proposed when running zotify

Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks?

If I'm correct you can't just log with your email and PW, try to delete (or move elsewhere) your credentials.json and then run zotify and go the link shown in the terminal and authenticate. If there was a problem setting your password, it may mean your previous one has been reset, thus the credentials.json content would be obsolete - you should first make sure you have correctly set your Spotify password.

I wonder why you get "There was a problem setting your password" however, hope if you try again the error will go, else this would be a Spotify issue.

@Genkleable good tip thanks, I can download individual songs again

<!-- gh-comment-id:3697152683 --> @cewlp commented on GitHub (Dec 29, 2025): > > guys I tried to reinstall things using above pointers but now again I am confronted with librespot.core.Session.SpotifyAuthenticationException: BadCredentials > > [@cewlp](https://github.com/cewlp) I had the same issue because my password had been reset: > > > **Other point:** I had a BadCredentials error as Spotify reset my password, to fix it I had to > > > > * Reset my Spotify password > > * Delete my credentials.json > > * Reconnect through the login link proposed when running zotify > > > > Works fine now => maybe Spotify is resetting the passwords to mitigate the leaks? > > If I'm correct you can't just log with your email and PW, try to delete (or move elsewhere) your credentials.json and then run zotify and go the link shown in the terminal and authenticate. If there was a problem setting your password, it may mean your previous one has been reset, thus the credentials.json content would be obsolete - you should first **make sure you have correctly set your Spotify password**. > > I wonder why you get "There was a problem setting your password" however, hope if you try again the error will go, else this would be a Spotify issue. @Genkleable good tip thanks, I can download individual songs again
Author
Owner

@yashikada commented on GitHub (Dec 29, 2025):

Here is my updated download_track function in track.py, it is not pretty but it does work.

Maybe it works in your fork, which you don't want to share, but not for me.
Your code is incomplete and calls functions that don't exist.
I understand that you don't want to share your valuable changes.

<!-- gh-comment-id:3697488363 --> @yashikada commented on GitHub (Dec 29, 2025): > Here is my updated download_track function in track.py, it is not pretty but it does work. Maybe it works in your fork, which you don't want to share, but not for me. Your code is incomplete and calls functions that don't exist. I understand that you don't want to share your valuable changes.
Author
Owner

@IsaacAgulhas commented on GitHub (Dec 29, 2025):

Here is my updated download_track function in track.py, it is not pretty but it does work.

Maybe it works in your fork, which you don't want to share, but not for me. Your code is incomplete and calls functions that don't exist. I understand that you don't want to share your valuable changes.

sorry to hear you are experiencing issues, I use a fork of the original zotify, not of this repo so maybe thats where the discrepancy is coming from. And that is also why I share my changes in this way, since I know it needs additional effort to make it conform to this repo, since this repo is a fork of the main zotify and has had many updates in that time.

<!-- gh-comment-id:3697639874 --> @IsaacAgulhas commented on GitHub (Dec 29, 2025): > > Here is my updated download_track function in track.py, it is not pretty but it does work. > > Maybe it works in your fork, which you don't want to share, but not for me. Your code is incomplete and calls functions that don't exist. I understand that you don't want to share your valuable changes. sorry to hear you are experiencing issues, I use a fork of the original zotify, not of this repo so maybe thats where the discrepancy is coming from. And that is also why I share my changes in this way, since I know it needs additional effort to make it conform to this repo, since this repo is a fork of the main zotify and has had many updates in that time.
Author
Owner

@ddxy commented on GitHub (Dec 29, 2025):

@IsaacAgulhas maybe you can also create a fork? I just want to download playlists :-D

<!-- gh-comment-id:3697666669 --> @ddxy commented on GitHub (Dec 29, 2025): @IsaacAgulhas maybe you can also create a fork? I just want to download playlists :-D
Author
Owner

@8a32c5c commented on GitHub (Dec 30, 2025):

[...] its inefficient that the entire thing stops working because it can't access the api. So what I did was I updated the code to remove the writing of the tags, and therefore the need for API metadata and it is working correctly now. This is a work around to the inability to create new API keys [...]

Thanks a lot for this. If you're not getting the metadata from Spotify, how are you tagging and naming your files? Or do you not need that at all? I can't imagine having all these tracks with just IDs used as filenames...

Edit: Maybe https://github.com/Aran404/SpotAPI can be hooked up as an alternative?

<!-- gh-comment-id:3697926263 --> @8a32c5c commented on GitHub (Dec 30, 2025): > [...] its inefficient that the entire thing stops working because it can't access the api. So what I did was I updated the code to remove the writing of the tags, and therefore the need for API metadata and it is working correctly now. This is a work around to the inability to create new API keys [...] Thanks a lot for this. If you're not getting the metadata from Spotify, how are you tagging and naming your files? Or do you not need that at all? I can't imagine having all these tracks with just IDs used as filenames... Edit: Maybe https://github.com/Aran404/SpotAPI can be hooked up as an alternative?
Author
Owner

@SkilletWarez commented on GitHub (Dec 31, 2025):

Here is my updated download_track function in track.py, it is not pretty but it does work.

Maybe it works in your fork, which you don't want to share, but not for me. Your code is incomplete and calls functions that don't exist. I understand that you don't want to share your valuable changes.

sorry to hear you are experiencing issues, I use a fork of the original zotify, not of this repo so maybe thats where the discrepancy is coming from. And that is also why I share my changes in this way, since I know it needs additional effort to make it conform to this repo, since this repo is a fork of the main zotify and has had many updates in that time.

Which one?

<!-- gh-comment-id:3702876173 --> @SkilletWarez commented on GitHub (Dec 31, 2025): > > > Here is my updated download_track function in track.py, it is not pretty but it does work. > > > > > > Maybe it works in your fork, which you don't want to share, but not for me. Your code is incomplete and calls functions that don't exist. I understand that you don't want to share your valuable changes. > > sorry to hear you are experiencing issues, I use a fork of the original zotify, not of this repo so maybe thats where the discrepancy is coming from. And that is also why I share my changes in this way, since I know it needs additional effort to make it conform to this repo, since this repo is a fork of the main zotify and has had many updates in that time. Which one?
Author
Owner

@Googolplexed0 commented on GitHub (Jan 2, 2026):

Should be fixed for efficient-api with github.com/Googolplexed0/zotify@0aa65cd3db.

Delete your credentials.json file, include the --client-id CLI argument with the Client ID for your Developer App (only needs done once, Client ID will be saved in the new credentials.json). Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment).

since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api.

Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality.

<!-- gh-comment-id:3704333576 --> @Googolplexed0 commented on GitHub (Jan 2, 2026): Should be fixed for [efficient-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) with https://github.com/Googolplexed0/zotify/commit/0aa65cd3dbddd73a6296b23e9c7f281d9defa2f5. Delete your credentials.json file, include the `--client-id` CLI argument with the Client ID for your Developer App **(only needs done once, Client ID will be saved in the new credentials.json)**. Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment). > since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api. Might make a new config option `MD_BYPASS_FALLBACK` to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality.
Author
Owner

@SkilletWarez commented on GitHub (Jan 2, 2026):

Should be fixed for efficient-api with 0aa65cd.

Delete your credentials.json file, include the --client-id CLI argument with the Client ID for your Developer App (only needs done once, Client ID will be saved in the new credentials.json). Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment).

since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api.

Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality.

Deleted the credentials.json but problem still persists. Am I missing something?

<!-- gh-comment-id:3704385048 --> @SkilletWarez commented on GitHub (Jan 2, 2026): > Should be fixed for [efficient-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) with [0aa65cd](https://github.com/Googolplexed0/zotify/commit/0aa65cd3dbddd73a6296b23e9c7f281d9defa2f5). > > Delete your credentials.json file, include the `--client-id` CLI argument with the Client ID for your Developer App **(only needs done once, Client ID will be saved in the new credentials.json)**. Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment). > > > since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api. > > Might make a new config option `MD_BYPASS_FALLBACK` to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality. Deleted the credentials.json but problem still persists. Am I missing something?
Author
Owner

@Googolplexed0 commented on GitHub (Jan 2, 2026):

Deleted the credentials.json but problem still persists. Am I missing something?

Are you on the efficeint-api branch? Did you add the new commandline arg --client-id <YOUR CLIENT ID HERE?> to your call? Did it create a new set of credentials for you of type OAUTH_PKCE_TOKEN?

<!-- gh-comment-id:3704399217 --> @Googolplexed0 commented on GitHub (Jan 2, 2026): > Deleted the credentials.json but problem still persists. Am I missing something? Are you on the [efficeint-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) branch? Did you add the new commandline arg `--client-id <YOUR CLIENT ID HERE?>` to your call? Did it create a new set of credentials for you of type `OAUTH_PKCE_TOKEN`?
Author
Owner

@SkilletWarez commented on GitHub (Jan 2, 2026):

Are you on the efficeint-api branch? Did you add the new commandline arg --client-id <YOUR CLIENT ID HERE?> to your call? Did it create a new set of credentials for you of type OAUTH_PKCE_TOKEN?

Yes I am on the efficeint-api branch. It says INVALID_CLIENT: Invalid redirect URI

File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/__main__.py", line 144, in main args.func(args, modes) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/app.py", line 74, in client Zotify(args) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/config.py", line 620, in __init__ Zotify.login(args) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/config.py", line 684, in login session_builder.login_credentials = oauth.flow() ^^^^^^^^^^^^ File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 204, in flow self.run_callback_server() File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 192, in run_callback_server self.__server = self.CallbackServer( ^^^^^^^^^^^^^^^^^^^^ File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 157, in __init__ super().__init__(server_address, RequestHandlerClass) File "/data/data/com.termux/files/usr/lib/python3.12/socketserver.py", line 457, in __init__ self.server_bind() File "/data/data/com.termux/files/usr/lib/python3.12/http/server.py", line 136, in server_bind socketserver.TCPServer.server_bind(self) File "/data/data/com.termux/files/usr/lib/python3.12/socketserver.py", line 478, in server_bind self.socket.bind(self.server_address) OSError: [Errno 98] Address already in use

This is my developer app
Image

Image
<!-- gh-comment-id:3704614204 --> @SkilletWarez commented on GitHub (Jan 2, 2026): > Are you on the [efficeint-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) branch? Did you add the new commandline arg `--client-id <YOUR CLIENT ID HERE?>` to your call? Did it create a new set of credentials for you of type `OAUTH_PKCE_TOKEN`? Yes I am on the [efficeint-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) branch. It says `INVALID_CLIENT: Invalid redirect URI` ` File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/__main__.py", line 144, in main args.func(args, modes) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/app.py", line 74, in client Zotify(args) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/config.py", line 620, in __init__ Zotify.login(args) File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/zotify/config.py", line 684, in login session_builder.login_credentials = oauth.flow() ^^^^^^^^^^^^ File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 204, in flow self.run_callback_server() File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 192, in run_callback_server self.__server = self.CallbackServer( ^^^^^^^^^^^^^^^^^^^^ File "/data/data/com.termux/files/home/.local/share/pipx/venvs/zotify/lib/python3.12/site-packages/librespot/oauth.py", line 157, in __init__ super().__init__(server_address, RequestHandlerClass) File "/data/data/com.termux/files/usr/lib/python3.12/socketserver.py", line 457, in __init__ self.server_bind() File "/data/data/com.termux/files/usr/lib/python3.12/http/server.py", line 136, in server_bind socketserver.TCPServer.server_bind(self) File "/data/data/com.termux/files/usr/lib/python3.12/socketserver.py", line 478, in server_bind self.socket.bind(self.server_address) OSError: [Errno 98] Address already in use` This is my developer app ![Image](https://github.com/user-attachments/assets/d4d9544e-003f-42c7-9fb6-a319c705afd6) <img width="716" height="164" alt="Image" src="https://github.com/user-attachments/assets/e5d90307-15f0-4751-ad5b-548d7ee839a3" />
Author
Owner

@ddxy commented on GitHub (Jan 2, 2026):

same here with the same redirect uri as above: INVALID_CLIENT: Invalid redirect URI

<!-- gh-comment-id:3704809776 --> @ddxy commented on GitHub (Jan 2, 2026): same here with the same redirect uri as above: INVALID_CLIENT: Invalid redirect URI
Author
Owner

@agenbite commented on GitHub (Jan 2, 2026):

You need to edit the url and make sure that the redirect uri is exactly the same as configured in the spotify app.

<!-- gh-comment-id:3704931782 --> @agenbite commented on GitHub (Jan 2, 2026): You need to edit the url and make sure that the redirect uri is exactly the same as configured in the spotify app.
Author
Owner

@ddxy commented on GitHub (Jan 2, 2026):

@agenbite where do I find the redirect url in the app? Can you add a screenshot?

<!-- gh-comment-id:3705011327 --> @ddxy commented on GitHub (Jan 2, 2026): @agenbite where do I find the redirect url in the app? Can you add a screenshot?
Author
Owner

@agenbite commented on GitHub (Jan 2, 2026):

It's shown in @SkilletWarez's captures!

<!-- gh-comment-id:3705082140 --> @agenbite commented on GitHub (Jan 2, 2026): It's shown in @SkilletWarez's captures!
Author
Owner

@ddxy commented on GitHub (Jan 2, 2026):

@agenbite thanks i found the solution. @SkilletWarez you need to replace callback with login.
So the uri is http://127.0.0.1/login as seen also in your log output

<!-- gh-comment-id:3705148853 --> @ddxy commented on GitHub (Jan 2, 2026): @agenbite thanks i found the solution. @SkilletWarez you need to replace callback with login. So the uri is http://127.0.0.1/login as seen also in your log output
Author
Owner

@rulo226 commented on GitHub (Jan 2, 2026):

http://127.0.0.1:8888/callback

this works for me

<!-- gh-comment-id:3705191372 --> @rulo226 commented on GitHub (Jan 2, 2026): http://127.0.0.1:8888/callback this works for me
Author
Owner

@SkilletWarez commented on GitHub (Jan 2, 2026):

@agenbite thanks i found the solution. @SkilletWarez you need to replace callback with login. So the uri is http://127.0.0.1/login as seen also in your log output

This also worked for me. Then after logged in, new problem arises.

<!-- gh-comment-id:3705208239 --> @SkilletWarez commented on GitHub (Jan 2, 2026): > [@agenbite](https://github.com/agenbite) thanks i found the solution. [@SkilletWarez](https://github.com/SkilletWarez) you need to replace callback with login. So the uri is http://127.0.0.1/login as seen also in your log output This also worked for me. Then after logged in, new problem arises.
Author
Owner

@cewlp commented on GitHub (Jan 2, 2026):

the efficeint-api branch works for me.

before zotify login I added http://127.0.0.1:4381/login as redirect uri in https://developer.spotify.com/dashboard

then I logged in with: zotify --client-id xxxxxxxxxxxxx

then I pasted the url into my browser for approval allowing access and I got authenticated

my created client ID and secret I added in my config.json, the uri in my config.json is "redirect_uri": "http://localhost:8080/callback" so not matching with http://127.0.0.1:4381/login but the callback one is also added as extra redirect uri in https://developer.spotify.com/dashboard, I left it this way given it works

my version Zotify 0.11.0

<!-- gh-comment-id:3705229448 --> @cewlp commented on GitHub (Jan 2, 2026): the [efficeint-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) branch works for me. before zotify login I added http://127.0.0.1:4381/login as redirect uri in https://developer.spotify.com/dashboard then I logged in with: zotify --client-id xxxxxxxxxxxxx then I pasted the url into my browser for approval allowing access and I got authenticated my created client ID and secret I added in my config.json, the uri in my config.json is "redirect_uri": "http://localhost:8080/callback" so not matching with http://127.0.0.1:4381/login but the callback one is also added as extra redirect uri in https://developer.spotify.com/dashboard, I left it this way given it works my version Zotify 0.11.0
Author
Owner

@growingcow-design commented on GitHub (Jan 2, 2026):

Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality.

Should be fixed for efficient-api with 0aa65cd.

Delete your credentials.json file, include the --client-id CLI argument with the Client ID for your Developer App (only needs done once, Client ID will be saved in the new credentials.json). Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment).

since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api.

Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality.

Can you make a new configure option to bypass because right now spotify isn't allowing new develop apps to be made. Not having metadata would suck but it beats not having the song at the end of the day. And i'm only looking at individual songs

<!-- gh-comment-id:3705317307 --> @growingcow-design commented on GitHub (Jan 2, 2026): Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality. > Should be fixed for [efficient-api](https://github.com/Googolplexed0/zotify/tree/efficient-api) with [0aa65cd](https://github.com/Googolplexed0/zotify/commit/0aa65cd3dbddd73a6296b23e9c7f281d9defa2f5). > > Delete your credentials.json file, include the `--client-id` CLI argument with the Client ID for your Developer App **(only needs done once, Client ID will be saved in the new credentials.json)**. Metadata should then pull from your Developer App API instead of using Login5 API tokens (what is permanently rate-limited at the moment). > > > since the audio file itself is the only thing we really care about, and therefore its inefficient that the entire thing stops working because it can't access the api. > > Might make a new config option `MD_BYPASS_FALLBACK` to bypass the metadata dependency for those without a Developer App API. Depends on if enough people require this functionality. Can you make a new configure option to bypass because right now spotify isn't allowing new develop apps to be made. Not having metadata would suck but it beats not having the song at the end of the day. And i'm only looking at individual songs
Author
Owner

@SkilletWarez commented on GitHub (Jan 2, 2026):

ok.. everything is now working fine on my end.. I installed everything on my pc. I see some bugs using my phone but flawlessly working on my pc.. cheers.. another patch successful. thank you everyone. especially to @Googolplexed0 for maintaining this repo.

<!-- gh-comment-id:3706001681 --> @SkilletWarez commented on GitHub (Jan 2, 2026): ok.. everything is now working fine on my end.. I installed everything on my pc. I see some bugs using my phone but flawlessly working on my pc.. cheers.. another patch successful. thank you everyone. especially to @Googolplexed0 for maintaining this repo.
Author
Owner

@cewlp commented on GitHub (Jan 2, 2026):

Hail to @Googolplexed0 indeed

<!-- gh-comment-id:3706143361 --> @cewlp commented on GitHub (Jan 2, 2026): Hail to @Googolplexed0 indeed
Author
Owner

@fafamobile commented on GitHub (Jan 3, 2026):

@Googolplexed0 :(

Image
<!-- gh-comment-id:3706595075 --> @fafamobile commented on GitHub (Jan 3, 2026): @Googolplexed0 :( <img width="1485" height="386" alt="Image" src="https://github.com/user-attachments/assets/b05cc4af-764b-46b7-8c45-107408d6171f" />
Author
Owner

@CommitCodeicide commented on GitHub (Jan 3, 2026):

I know this has been closed but anything that can be done about the lyrics not being pulled anymore? Pretty much everything works again using the efficient api, except for the lyrics.

<!-- gh-comment-id:3706689061 --> @CommitCodeicide commented on GitHub (Jan 3, 2026): I know this has been closed but anything that can be done about the lyrics not being pulled anymore? Pretty much everything works again using the efficient api, except for the lyrics.
Author
Owner

@growingcow-design commented on GitHub (Jan 3, 2026):

Googoleplexed0 do you think you can "Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API"?

<!-- gh-comment-id:3706808742 --> @growingcow-design commented on GitHub (Jan 3, 2026): Googoleplexed0 do you think you can "Might make a new config option MD_BYPASS_FALLBACK to bypass the metadata dependency for those without a Developer App API"?
Author
Owner

@vel342 commented on GitHub (Jan 3, 2026):

I'm also unable to make the developer App, I get the same message as @fafamobile :(((((

<!-- gh-comment-id:3706811920 --> @vel342 commented on GitHub (Jan 3, 2026): I'm also unable to make the developer App, I get the same message as @fafamobile :(((((
Author
Owner

@juvannx commented on GitHub (Jan 3, 2026):

I don't really like the solution proposed with development app, wouldn't it have been better to use the new API as suggested here?
Here there is an example which seems to work.

<!-- gh-comment-id:3706858763 --> @juvannx commented on GitHub (Jan 3, 2026): I don't really like the solution proposed with development app, wouldn't it have been better to use the new API as suggested [here](https://github.com/glomatico/votify/issues/79#issuecomment-3698269540)? [Here](https://github.com/glomatico/votify/issues/79#issuecomment-3701765787) there is an example which seems to work.
Author
Owner

@juvannx commented on GitHub (Jan 3, 2026):

I'm also unable to make the developer App, I get the same message as @fafamobile :(((((

This issue is not related to this project.
It's pointless to keep repeating that Spotify has blocked the creation of new apps.
The solution that requires the creation of a development app is wrong.

<!-- gh-comment-id:3706860218 --> @juvannx commented on GitHub (Jan 3, 2026): > I'm also unable to make the developer App, I get the same message as [@fafamobile](https://github.com/fafamobile) :((((( This issue is not related to this project. It's pointless to keep repeating that Spotify has blocked the creation of new apps. The solution that requires the creation of a development app is wrong.
Author
Owner

@SkilletWarez commented on GitHub (Jan 3, 2026):

I don't really like the solution proposed with development app, wouldn't it have been better to use the new API as suggested here? Here there is an example which seems to work.

This is to individualize as to use your own API as to not exceed the API rate limit than congest a single API for the whole world to use it.

<!-- gh-comment-id:3706878601 --> @SkilletWarez commented on GitHub (Jan 3, 2026): > I don't really like the solution proposed with development app, wouldn't it have been better to use the new API as suggested [here](https://github.com/glomatico/votify/issues/79#issuecomment-3698269540)? [Here](https://github.com/glomatico/votify/issues/79#issuecomment-3701765787) there is an example which seems to work. This is to individualize as to use your own API as to not exceed the API rate limit than congest a single API for the whole world to use it.
Author
Owner

@fafamobile commented on GitHub (Jan 3, 2026):

I'm also unable to make the developer App, I get the same message as @fafamobile :(((((

This issue is not related to this project. It's pointless to keep repeating that Spotify has blocked the creation of new apps. The solution that requires the creation of a development app is wrong.

It's not pointless since this whole solution revolves around requiring the creation of a development app...

<!-- gh-comment-id:3706958906 --> @fafamobile commented on GitHub (Jan 3, 2026): > > I'm also unable to make the developer App, I get the same message as [@fafamobile](https://github.com/fafamobile) :((((( > > This issue is not related to this project. It's pointless to keep repeating that Spotify has blocked the creation of new apps. The solution that requires the creation of a development app is wrong. It's not pointless since this whole solution revolves around requiring the creation of a development app...
Author
Owner

@Googolplexed0 commented on GitHub (Jan 3, 2026):

Can you make a new configure option to bypass because right now spotify isn't allowing new develop apps to be made. Not having metadata would suck but it beats not having the song at the end of the day. And i'm only looking at individual songs

Yes, I'll figure this out shortly.

wouldn't it have been better to use the new API as suggested here?

Honestly, I don't want to push the envelope and get a reaction from the parent company. I will do some investigating on this new API, but I make no promises. Hopefully, this will all be temporary anyways.

<!-- gh-comment-id:3707324372 --> @Googolplexed0 commented on GitHub (Jan 3, 2026): > Can you make a new configure option to bypass because right now spotify isn't allowing new develop apps to be made. Not having metadata would suck but it beats not having the song at the end of the day. And i'm only looking at individual songs Yes, I'll figure this out shortly. > wouldn't it have been better to use the new API as suggested [here](https://github.com/glomatico/votify/issues/79#issuecomment-3698269540)? Honestly, I don't want to push the envelope and get a reaction from the parent company. I will do some investigating on this new API, but I make no promises. Hopefully, this will all be temporary anyways.
Author
Owner

@DieselMane2006 commented on GitHub (Jan 3, 2026):

@Googolplexed0 I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api.

Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction.

tokenmanager.py

import base64
import requests
import time

SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"

class SpotifyTokenManager:
def init(self, client_id, client_secret):
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.expires_at = 0

def get_token(self):
    if self.access_token and time.time() < self.expires_at:
        return self.access_token

    auth_header = base64.b64encode(
        f"{self.client_id}:{self.client_secret}".encode()
    ).decode()

    response = requests.post(
        SPOTIFY_TOKEN_URL,
        headers={
            "Authorization": f"Basic {auth_header}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={"grant_type": "client_credentials"},
    )

    response.raise_for_status()
    data = response.json()

    self.access_token = data["access_token"]
    self.expires_at = time.time() + data["expires_in"] - 30
    return self.access_token

get_song_info function in track.py

from zotify.tokenmanager import SpotifyTokenManager

token_manager = SpotifyTokenManager(
client_id="",
client_secret=""
)

def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]:
""" Retrieves metadata for downloaded songs using own Spotify API credentials """

with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."):
    token = token_manager.get_token()

    response = requests.get(
        f"https://api.spotify.com/v1/tracks",
        params={"ids": song_id, "market": "US"},
        headers={
            "Authorization": f"Bearer {token}"
        }
    )

if response.status_code == 429:
    raise RuntimeError("Spotify API rate limit exceeded (your app)")

response.raise_for_status()
info = response.json()

if TRACKS not in info:
    raise ValueError(f'Invalid response from TRACKS_URL:\n{info}')

try:
    track = info[TRACKS][0]

    artists = [a[NAME] for a in track[ARTISTS]]
    album = track[ALBUM]

    album_name = album[NAME]
    name = track[NAME]
    release_year = album[RELEASE_DATE].split('-')[0]
    disc_number = track[DISC_NUMBER]
    track_number = track[TRACK_NUMBER]
    scraped_song_id = track[ID]
    is_playable = track[IS_PLAYABLE]
    duration_ms = track[DURATION_MS]

    image = max(album[IMAGES], key=lambda i: i[WIDTH])
    image_url = image[URL]

    return (
        artists,
        track[ARTISTS],
        album_name,
        name,
        image_url,
        release_year,
        disc_number,
        track_number,
        scraped_song_id,
        is_playable,
        duration_ms,
    )

except Exception as e:
    raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}')

I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well.

Im sorry, i dont understand anything that is written in that comment, is this a fix to the API issue? And if yes, how do i implement this? do i need to add it as a .py file into the zotify directory? It says one needs to sign up for the spotify developer portal, so i would need to connect it with my API credentials via the developer portal or something? Im very confused and dont even know where to begin to get a grasp on how to implement the fix.. Maybe someone can help me understand how to use this fix, i would be very thankful!

<!-- gh-comment-id:3707431687 --> @DieselMane2006 commented on GitHub (Jan 3, 2026): > [@Googolplexed0](https://github.com/Googolplexed0) I have a working solution on my fork of the main zotify, the issue is that zotify uses a shared api credential, and this is exceeding the limit, the solution of course for everyone to use their own api credentials, this of course does require everyone to sign up to the spotify developer portal and create an api app (which will then generate the client id and client secret). From there that can be used for accessing the spotify api. > > Below is my patch, this implements a new file called tokenmananger.py which has the token manager class which will perform the request for a token as well as manage the lifecycle of that token (since api tokens expire). The patch also has a replacement for the get_song_info function in the track.py file. Since my uses are only for downloading songs I have only made a patch for this directly, to do it system wide (since many feature of this repo perform api calls for metadata) and more indepth implementation is required, I provide this mainly as a starting point to guide you in the right direction. > > tokenmanager.py > > import base64 > import requests > import time > > SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token" > > class SpotifyTokenManager: > def __init__(self, client_id, client_secret): > self.client_id = client_id > self.client_secret = client_secret > self.access_token = None > self.expires_at = 0 > > def get_token(self): > if self.access_token and time.time() < self.expires_at: > return self.access_token > > auth_header = base64.b64encode( > f"{self.client_id}:{self.client_secret}".encode() > ).decode() > > response = requests.post( > SPOTIFY_TOKEN_URL, > headers={ > "Authorization": f"Basic {auth_header}", > "Content-Type": "application/x-www-form-urlencoded", > }, > data={"grant_type": "client_credentials"}, > ) > > response.raise_for_status() > data = response.json() > > self.access_token = data["access_token"] > self.expires_at = time.time() + data["expires_in"] - 30 > return self.access_token > > get_song_info function in track.py > > from zotify.tokenmanager import SpotifyTokenManager > > token_manager = SpotifyTokenManager( > client_id="", > client_secret="" > ) > > def get_song_info(song_id) -> Tuple[List[str], List[Any], str, str, Any, Any, Any, Any, Any, Any, int]: > """ Retrieves metadata for downloaded songs using own Spotify API credentials """ > > with Loader(PrintChannel.PROGRESS_INFO, "Fetching track information..."): > token = token_manager.get_token() > > response = requests.get( > f"https://api.spotify.com/v1/tracks", > params={"ids": song_id, "market": "US"}, > headers={ > "Authorization": f"Bearer {token}" > } > ) > > if response.status_code == 429: > raise RuntimeError("Spotify API rate limit exceeded (your app)") > > response.raise_for_status() > info = response.json() > > if TRACKS not in info: > raise ValueError(f'Invalid response from TRACKS_URL:\n{info}') > > try: > track = info[TRACKS][0] > > artists = [a[NAME] for a in track[ARTISTS]] > album = track[ALBUM] > > album_name = album[NAME] > name = track[NAME] > release_year = album[RELEASE_DATE].split('-')[0] > disc_number = track[DISC_NUMBER] > track_number = track[TRACK_NUMBER] > scraped_song_id = track[ID] > is_playable = track[IS_PLAYABLE] > duration_ms = track[DURATION_MS] > > image = max(album[IMAGES], key=lambda i: i[WIDTH]) > image_url = image[URL] > > return ( > artists, > track[ARTISTS], > album_name, > name, > image_url, > release_year, > disc_number, > track_number, > scraped_song_id, > is_playable, > duration_ms, > ) > > except Exception as e: > raise ValueError(f'Failed to parse TRACKS_URL response: {str(e)}\n{info}') > > I hope this helps to solve the issue as well as improve this repo going forward, I admire the hard work you guys put in to keep this repo working and up to date, and I have gotten many fixes from here is the past as well so I'm happy to give back as well. Im sorry, i dont understand anything that is written in that comment, is this a fix to the API issue? And if yes, how do i implement this? do i need to add it as a .py file into the zotify directory? It says one needs to sign up for the spotify developer portal, so i would need to connect it with my API credentials via the developer portal or something? Im very confused and dont even know where to begin to get a grasp on how to implement the fix.. Maybe someone can help me understand how to use this fix, i would be very thankful!
Author
Owner

@Googolplexed0 commented on GitHub (Jan 4, 2026):

@8a32c5c @IsaacAgulhas @growingcow-design @vel342
efficient-api v0.11.10 adds the BYPASS_MD_API config, which allows for downloading individual tracks/episodes without a Developer App API

<!-- gh-comment-id:3707581072 --> @Googolplexed0 commented on GitHub (Jan 4, 2026): @8a32c5c @IsaacAgulhas @growingcow-design @vel342 [efficient-api v0.11.10](https://github.com/Googolplexed0/zotify/commit/903c6375878462632299666fa7fa03b770ba5919) adds the `BYPASS_MD_API` config, which allows for downloading individual tracks/episodes without a Developer App API
Author
Owner

@growingcow-design commented on GitHub (Jan 4, 2026):

That's awssome, but I'm a little dumb, how do i download the newer efficient-api.V0.11.10?

<!-- gh-comment-id:3708401796 --> @growingcow-design commented on GitHub (Jan 4, 2026): That's awssome, but I'm a little dumb, how do i download the newer efficient-api.V0.11.10?
Author
Owner

@SkilletWarez commented on GitHub (Jan 4, 2026):

That's awssome, but I'm a little dumb, how do i download the newer efficient-api.V0.11.10?

If Executable (pipx): pipx install -f git+https://github.com/Googolplexed0/zotify.git@efficient-api

If Module: python -m pip install --force-reinstall git+https://github.com/Googolplexed0/zotify.git@efficient-api

<!-- gh-comment-id:3708405787 --> @SkilletWarez commented on GitHub (Jan 4, 2026): > That's awssome, but I'm a little dumb, how do i download the newer efficient-api.V0.11.10? If Executable (pipx): `pipx install -f git+https://github.com/Googolplexed0/zotify.git@efficient-api` If Module: `python -m pip install --force-reinstall git+https://github.com/Googolplexed0/zotify.git@efficient-api`
Author
Owner

@growingcow-design commented on GitHub (Jan 4, 2026):

thanks, I just figured out and saw your message hahaha

<!-- gh-comment-id:3708418098 --> @growingcow-design commented on GitHub (Jan 4, 2026): thanks, I just figured out and saw your message hahaha
Author
Owner

@growingcow-design commented on GitHub (Jan 4, 2026):

Yeah it's working wooohoo. No metadata and that's going to be some work but individual songs work.

<!-- gh-comment-id:3708428707 --> @growingcow-design commented on GitHub (Jan 4, 2026): Yeah it's working wooohoo. No metadata and that's going to be some work but individual songs work.
Author
Owner

@CommitCodeicide commented on GitHub (Jan 4, 2026):

i updated zotify efficient api and i'm not able to download anymore:

happens with playlists or individual songs, any fix?

[∙∙∙] Parsing playlist information...                                                                                   
[∙∙∙] Fetching bulk genre information...                                                                                
[●∙∙] Fetching bulk track/disc total information...                                                                     
[●∙∙] Preparing Download...                                                                                     
[∙∙●] Fetching lyrics...                                                                                        

###   ERROR:  UNEXPECTED ERROR DURING DOWNLOADS   ###
###   ATTEMPTING TO CLEAN UP   ###

###   ERROR:  CLEAN UP COMPLETE   ###
###   LOGGING ERROR AND TRACEBACK   ###


Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "c:\users\npp002\.local\bin\zotify.exe\__main__.py", line 6, in <module>
    sys.exit(main())
             ~~~~^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 143, in main
    client(args, modes)
    ~~~~~~^^^^^^^^^^^^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 85, in client
    perform_query(args)
    ~~~~~~~~~~~~~^^^^^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 77, in perform_query
    raise e
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 48, in perform_query
    Query(Zotify.DATETIME_LAUNCH).request(urls).execute()
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1746, in execute
    self.download()
    ~~~~~~~~~~~~~^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1739, in download
    raise interrupt
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1708, in download
    super().download(pbar_stack=None)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1218, in download
    child.download(pbar_stack)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 979, in download
    self.fetch_lyrics()
    ~~~~~~~~~~~~~~~~~^^
  File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 785, in fetch_lyrics
    file.writelines(lyrics)
                    ^^^^^^
UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value
<!-- gh-comment-id:3708488545 --> @CommitCodeicide commented on GitHub (Jan 4, 2026): i updated zotify efficient api and i'm not able to download anymore: happens with playlists or individual songs, any fix? ```[∙∙∙] Fetching playlist information... [∙∙∙] Parsing playlist information... [∙∙∙] Fetching bulk genre information... [●∙∙] Fetching bulk track/disc total information... [●∙∙] Preparing Download... [∙∙●] Fetching lyrics... ### ERROR: UNEXPECTED ERROR DURING DOWNLOADS ### ### ATTEMPTING TO CLEAN UP ### ### ERROR: CLEAN UP COMPLETE ### ### LOGGING ERROR AND TRACEBACK ### Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "c:\users\npp002\.local\bin\zotify.exe\__main__.py", line 6, in <module> sys.exit(main()) ~~~~^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\__main__.py", line 143, in main client(args, modes) ~~~~~~^^^^^^^^^^^^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 85, in client perform_query(args) ~~~~~~~~~~~~~^^^^^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 77, in perform_query raise e File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1746, in execute self.download() ~~~~~~~~~~~~~^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1739, in download raise interrupt File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1708, in download super().download(pbar_stack=None) ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 1218, in download child.download(pbar_stack) ~~~~~~~~~~~~~~^^^^^^^^^^^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 979, in download self.fetch_lyrics() ~~~~~~~~~~~~~~~~~^^ File "C:\Users\NPP002\pipx\venvs\zotify\Lib\site-packages\zotify\api.py", line 785, in fetch_lyrics file.writelines(lyrics) ^^^^^^ UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value
Author
Owner

@Googolplexed0 commented on GitHub (Jan 4, 2026):

file.writelines(lyrics)
UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value

Good catch, fixed with v0.11.14 github.com/Googolplexed0/zotify@e6a15ab9b1

<!-- gh-comment-id:3708515059 --> @Googolplexed0 commented on GitHub (Jan 4, 2026): > file.writelines(lyrics) > UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value Good catch, fixed with v0.11.14 https://github.com/Googolplexed0/zotify/commit/e6a15ab9b1ba9a13283f0e5d544e8d72c836bf92
Author
Owner

@CommitCodeicide commented on GitHub (Jan 4, 2026):

file.writelines(lyrics)
UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value

Good catch, fixed with v0.11.14 e6a15ab

thanks! downloading works again, with metadata too.
Is there a way to get lyrics to work again as well? it says LYRICS NOT AVAILABLE on all the songs

<!-- gh-comment-id:3708521813 --> @CommitCodeicide commented on GitHub (Jan 4, 2026): > > file.writelines(lyrics) > > UnboundLocalError: cannot access local variable 'lyrics' where it is not associated with a value > > Good catch, fixed with v0.11.14 [e6a15ab](https://github.com/Googolplexed0/zotify/commit/e6a15ab9b1ba9a13283f0e5d544e8d72c836bf92) thanks! downloading works again, with metadata too. Is there a way to get lyrics to work again as well? it says LYRICS NOT AVAILABLE on all the songs
Author
Owner

@Cio2566 commented on GitHub (Jan 4, 2026):

❯ zotify https://open.spotify.com/track/1cQTT0WyAvD4LL0m42J8M4?si=2BhAGL-RSeGio1YL9a1jRw

    [∙∙●] Logging in...                                   [∙∙∙] Fetching track information...           

WARNING: API ERROR (TRY 0) - RETRYING

429: API rate limit exceeded

    [∙∙∙] Fetching track information...           

WARNING: API ERROR (TRY 1) - RETRYING

429: API rate limit exceeded

    [∙●∙] Fetching track information...           

API_ERROR: RETRY LIMIT EXCEDED

RESPONSE TEXT: {

"error": {

"status": 429,

"message": "API rate limit exceeded"

}

}

URL: https://api.spotify.com/v1/tracks/

1cQTT0WyAvD4LL0m42J8M4?market=from_token

    [∙∙∙] Fetching track information...                   [∙∙∙] Parsing track information...            

Traceback (most recent call last):
File "/data/data/com.termux/files/usr/bin/zotify", line 7, in
sys.exit(main())
^^^^^^
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/main.py", line 143, in main
client(args, modes)
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 85, in client
perform_query(args)
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 77, in perform_query
raise e
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 48, in perform_query
Query(Zotify.DATETIME_LAUNCH).request(urls).execute()
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1725, in execute
self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs))
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1538, in parse_direct_metadata
obj.parse_metadata(item_resp)
File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 547, in parse_metadata
self.update_id(track_resp[ID])
~~~~~~~~~~^^^^
KeyError: 'id'. I received this error.

<!-- gh-comment-id:3708523248 --> @Cio2566 commented on GitHub (Jan 4, 2026): ❯ zotify https://open.spotify.com/track/1cQTT0WyAvD4LL0m42J8M4?si=2BhAGL-RSeGio1YL9a1jRw [∙∙●] Logging in... [∙∙∙] Fetching track information... ### WARNING: API ERROR (TRY 0) - RETRYING ### ### 429: API rate limit exceeded ### [∙∙∙] Fetching track information... ### WARNING: API ERROR (TRY 1) - RETRYING ### ### 429: API rate limit exceeded ### [∙●∙] Fetching track information... ### API_ERROR: RETRY LIMIT EXCEDED ### ### RESPONSE TEXT: { ### ### "error": { ### ### "status": 429, ### ### "message": "API rate limit exceeded" ### ### } ### ### } ### ### URL: https://api.spotify.com/v1/tracks/ ### ### 1cQTT0WyAvD4LL0m42J8M4?market=from_token ### [∙∙∙] Fetching track information... [∙∙∙] Parsing track information... Traceback (most recent call last): File "/data/data/com.termux/files/usr/bin/zotify", line 7, in <module> sys.exit(main()) ^^^^^^ File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/__main__.py", line 143, in main client(args, modes) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 85, in client perform_query(args) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 77, in perform_query raise e File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1725, in execute self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs)) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1538, in parse_direct_metadata obj.parse_metadata(item_resp) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 547, in parse_metadata self.update_id(track_resp[ID]) ~~~~~~~~~~^^^^ KeyError: 'id'. I received this error.
Author
Owner

@vel342 commented on GitHub (Jan 5, 2026):

It works for me, I'm able to download without the metadata. :) However, I am unable to do real time downloads. It seems to be ignoring the real time flag, and just downloads super quickly - makes me a little nervous that it'll get noticed by spotify. but either way, it works!

<!-- gh-comment-id:3708681744 --> @vel342 commented on GitHub (Jan 5, 2026): It works for me, I'm able to download without the metadata. :) However, I am unable to do real time downloads. It seems to be ignoring the real time flag, and just downloads super quickly - makes me a little nervous that it'll get noticed by spotify. but either way, it works!
Author
Owner

@carlyd95 commented on GitHub (Jan 5, 2026):

@Googolplexed0

When downloading most playlists via a file (-f $FILENAME) I receive one of the 2 following errors:

ERROR 1:

`[∙∙∙] Logging in...
[∙∙∙] Fetching playlist information...
[●∙∙] Parsing playlist information...
[∙∙∙] Fetching bulk genre information...
[●∙∙] Fetching bulk track/disc total information...

Traceback (most recent call last):
File "/home/pi/.local/bin/zotify", line 7, in
sys.exit(main())
^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/main.py", line 143, in main
client(args, modes)
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 85, in client
perform_query(args)
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 77, in perform_query
raise e
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 48, in perform_query
Query(Zotify.DATETIME_LAUNCH).request(urls).execute()
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1728, in execute
self.download()
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in download
skipped = {d for d in self.downloadables if d.check_skippable()}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in
skipped = {d for d in self.downloadables if d.check_skippable()}
^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 911, in check_skippable
if super().check_skippable():
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 370, in check_skippable
path = self.fill_output_template()
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 699, in fill_output_template
playlist_number = str(self.parent.tracks_or_eps.index(self) + 1).zfill(2)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: <zotify.api.Track object at 0x7f7ad17290> is not in list`

ERROR 2:

`### SKIPPING: "Tonic Walter - SIGN" (TRACK ALREADY EXISTS) ###

SKIPPING: "Tiësto - Everlight" (TRACK ALREADY EXISTS)

SKIPPING: "Sub Focus - Fade (feat. Inéz)" (TRACK ALREADY EXISTS)

Traceback (most recent call last):
File "/home/pi/.local/bin/zotify", line 7, in
sys.exit(main())
^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/main.py", line 143, in main
client(args, modes)
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 85, in client
perform_query(args)
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 77, in perform_query
raise e
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 48, in perform_query
Query(Zotify.DATETIME_LAUNCH).request(urls).execute()
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1728, in execute
self.download()
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in download
skipped = {d for d in self.downloadables if d.check_skippable()}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in
skipped = {d for d in self.downloadables if d.check_skippable()}
^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable
self.skippable = self.album.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable
return self.parent.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable
self.skippable = self.album.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable
return self.parent.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable
self.skippable = self.album.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable
return self.parent.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...

~~ 2945 LINES REDACTED (Same alternating logs) ~~

...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable
self.skippable = self.album.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable
return self.parent.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable
self.skippable = self.album.check_skippable()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1323, in check_skippable
if isinstance(self.parent, Artist) and self.album_group:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RecursionError: maximum recursion depth exceeded while calling a Python object`

Any ideas on the cause? or what else I can test to help troubleshoot the issue?

I am invoking zotify by running the following command (sudo is used for a reason and has always worked in the past):

sudo -u pi /home/pi/.local/bin/zotify -f zotifylist.txt --download-format mp3 --download-quality very_high --root-path /media/pi/X9/DJ/Music --output "{artist} - {song_name}" --download-real-time True --md-save-genres True --client-ID XXXXX-REDACTED-XXXXXXX

Any help is greatly appreciated! Thanks to all who have helped thus far! :D

<!-- gh-comment-id:3708779032 --> @carlyd95 commented on GitHub (Jan 5, 2026): @Googolplexed0 When downloading most playlists via a file (`-f $FILENAME`) I receive one of the 2 following errors: # ERROR 1: `[∙∙∙] Logging in... [∙∙∙] Fetching playlist information... [●∙∙] Parsing playlist information... [∙∙∙] Fetching bulk genre information... [●∙∙] Fetching bulk track/disc total information... Traceback (most recent call last): File "/home/pi/.local/bin/zotify", line 7, in <module> sys.exit(main()) ^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/__main__.py", line 143, in main client(args, modes) File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 85, in client perform_query(args) File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 77, in perform_query raise e File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1728, in execute self.download() File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in download skipped = {d for d in self.downloadables if d.check_skippable()} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in <setcomp> skipped = {d for d in self.downloadables if d.check_skippable()} ^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 911, in check_skippable if super().check_skippable(): ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 370, in check_skippable path = self.fill_output_template() ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 699, in fill_output_template playlist_number = str(self.parent.tracks_or_eps.index(self) + 1).zfill(2) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ValueError: <zotify.api.Track object at 0x7f7ad17290> is not in list` # ERROR 2: `### SKIPPING: "Tonic Walter - SIGN" (TRACK ALREADY EXISTS) ### ### SKIPPING: "Tiësto - Everlight" (TRACK ALREADY EXISTS) ### ### SKIPPING: "Sub Focus - Fade (feat. Inéz)" (TRACK ALREADY EXISTS) ### Traceback (most recent call last): File "/home/pi/.local/bin/zotify", line 7, in <module> sys.exit(main()) ^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/__main__.py", line 143, in main client(args, modes) File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 85, in client perform_query(args) File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 77, in perform_query raise e File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1728, in execute self.download() File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in download skipped = {d for d in self.downloadables if d.check_skippable()} ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1662, in <setcomp> skipped = {d for d in self.downloadables if d.check_skippable()} ^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... ~~ 2945 LINES REDACTED (Same alternating logs) ~~ ... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1323, in check_skippable if isinstance(self.parent, Artist) and self.album_group: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RecursionError: maximum recursion depth exceeded while calling a Python object` Any ideas on the cause? or what else I can test to help troubleshoot the issue? I am invoking zotify by running the following command (sudo is used for a reason and has always worked in the past): `sudo -u pi /home/pi/.local/bin/zotify -f zotifylist.txt --download-format mp3 --download-quality very_high --root-path /media/pi/X9/DJ/Music --output "{artist} - {song_name}" --download-real-time True --md-save-genres True --client-ID XXXXX-REDACTED-XXXXXXX` Any help is greatly appreciated! Thanks to all who have helped thus far! :D
Author
Owner

@Googolplexed0 commented on GitHub (Jan 5, 2026):

RecursionError: maximum recursion depth exceeded while calling a Python object`

Just fixed this one in v0.11.15, forgot that Tracks could be the parents of Albums.

playlist_number = str(self.parent.tracks_or_eps.index(self) + 1).zfill(2)
ValueError: <zotify.api.Track object at 0x7f7ad17290> is not in list`

At a glance this seems like another error with parental rights between a Playlist, Track, and Query. These kind of issues keep popping up out of nowhere after seemingly unrelated changes, I'm taking it as a sign that the current hierarchy schema might need to be reworked (read: simplified from first principles). Please break this out into a separate bug report and include some extra details with --debug enabled and I will take a look.

<!-- gh-comment-id:3708910486 --> @Googolplexed0 commented on GitHub (Jan 5, 2026): > RecursionError: maximum recursion depth exceeded while calling a Python object` Just fixed this one in v0.11.15, forgot that Tracks could be the parents of Albums. > playlist_number = str(self.parent.tracks_or_eps.index(self) + 1).zfill(2) > ValueError: <zotify.api.Track object at 0x7f7ad17290> is not in list` At a glance this seems like another error with parental rights between a Playlist, Track, and Query. These kind of issues keep popping up out of nowhere after seemingly unrelated changes, I'm taking it as a sign that the current hierarchy schema might need to be reworked (read: simplified from first principles). Please break this out into a separate bug report and include some extra details with `--debug` enabled and I will take a look.
Author
Owner

@SkilletWarez commented on GitHub (Jan 5, 2026):

... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1323, in check_skippable if isinstance(self.parent, Artist) and self.album_group: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RecursionError: maximum recursion depth exceeded while calling a Python object`

When I too experienced this on the update ZOTIFY 0.11.14, I reverted back to ZOTIFY 0.11.12

<!-- gh-comment-id:3709120358 --> @SkilletWarez commented on GitHub (Jan 5, 2026): > ... ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1347, in check_skippable return self.parent.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 914, in check_skippable self.skippable = self.album.check_skippable() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pi/.local/share/pipx/venvs/zotify/lib/python3.11/site-packages/zotify/api.py", line 1323, in check_skippable if isinstance(self.parent, Artist) and self.album_group: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RecursionError: maximum recursion depth exceeded while calling a Python object` When I too experienced this on the update ZOTIFY 0.11.14, I reverted back to ZOTIFY 0.11.12
Author
Owner

@DieselMane2006 commented on GitHub (Jan 5, 2026):

i am very new to this and have no idea what this is all about, but the problem is still the api rate limit exceeded thing, right? can some people still download stuff by configuring code in the files or is this a problem on spotifys side? im new to coding so it would love to at least get a tiny grasp on whats going on lol

<!-- gh-comment-id:3712500261 --> @DieselMane2006 commented on GitHub (Jan 5, 2026): i am very new to this and have no idea what this is all about, but the problem is still the api rate limit exceeded thing, right? can some people still download stuff by configuring code in the files or is this a problem on spotifys side? im new to coding so it would love to at least get a tiny grasp on whats going on lol
Author
Owner

@Scranox commented on GitHub (Jan 6, 2026):

❯ zotify https://open.spotify.com/track/1cQTT0WyAvD4LL0m42J8M4?si=2BhAGL-RSeGio1YL9a1jRw

    [∙∙●] Logging in...                                   [∙∙∙] Fetching track information...           

WARNING: API ERROR (TRY 0) - RETRYING

429: API rate limit exceeded

    [∙∙∙] Fetching track information...           

WARNING: API ERROR (TRY 1) - RETRYING

429: API rate limit exceeded

    [∙●∙] Fetching track information...           

API_ERROR: RETRY LIMIT EXCEDED

RESPONSE TEXT: {

"error": {

"status": 429,

"message": "API rate limit exceeded"

}

}

URL: https://api.spotify.com/v1/tracks/

1cQTT0WyAvD4LL0m42J8M4?market=from_token

    [∙∙∙] Fetching track information...                   [∙∙∙] Parsing track information...            

Traceback (most recent call last): File "/data/data/com.termux/files/usr/bin/zotify", line 7, in sys.exit(main()) ^^^^^^ File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/main.py", line 143, in main client(args, modes) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 85, in client perform_query(args) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 77, in perform_query raise e File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1725, in execute self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs)) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1538, in parse_direct_metadata obj.parse_metadata(item_resp) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 547, in parse_metadata self.update_id(track_resp[ID]) ~~~~~~~~~~^^^^ KeyError: 'id'. I received this error.

Got the same error, after reinstalling efficient-api with python -m pip install --force-reinstall git+https://github.com/Googolplexed0/zotify.git@efficient-api

<!-- gh-comment-id:3713215083 --> @Scranox commented on GitHub (Jan 6, 2026): > ❯ zotify https://open.spotify.com/track/1cQTT0WyAvD4LL0m42J8M4?si=2BhAGL-RSeGio1YL9a1jRw > > ``` > [∙∙●] Logging in... [∙∙∙] Fetching track information... > ``` > > ### WARNING: API ERROR (TRY 0) - RETRYING > ### 429: API rate limit exceeded > ``` > [∙∙∙] Fetching track information... > ``` > > ### WARNING: API ERROR (TRY 1) - RETRYING > ### 429: API rate limit exceeded > ``` > [∙●∙] Fetching track information... > ``` > > ### API_ERROR: RETRY LIMIT EXCEDED > ### RESPONSE TEXT: { > ### "error": { > ### "status": 429, > ### "message": "API rate limit exceeded" > ### } > ### } > ### URL: https://api.spotify.com/v1/tracks/ > ### 1cQTT0WyAvD4LL0m42J8M4?market=from_token > ``` > [∙∙∙] Fetching track information... [∙∙∙] Parsing track information... > ``` > > Traceback (most recent call last): File "/data/data/com.termux/files/usr/bin/zotify", line 7, in sys.exit(main()) ^^^^^^ File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/**main**.py", line 143, in main client(args, modes) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 85, in client perform_query(args) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 77, in perform_query raise e File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/app.py", line 48, in perform_query Query(Zotify.DATETIME_LAUNCH).request(urls).execute() File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1725, in execute self.parse_direct_metadata(*self.fetch_direct_metadata(direct_reqs_objs)) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 1538, in parse_direct_metadata obj.parse_metadata(item_resp) File "/data/data/com.termux/files/usr/lib/python3.12/site-packages/zotify/api.py", line 547, in parse_metadata self.update_id(track_resp[ID]) ~~~~~~~~~~^^^^ KeyError: 'id'. I received this error. Got the same error, after reinstalling efficient-api with `python -m pip install --force-reinstall git+https://github.com/Googolplexed0/zotify.git@efficient-api`
Author
Owner

@Zwei7778 commented on GitHub (Jan 6, 2026):

@Googolplexed0 sorry can you give new full step guide from start? I have tried from separate post at this thread, but still not working, maybe miss at some step

<!-- gh-comment-id:3714104759 --> @Zwei7778 commented on GitHub (Jan 6, 2026): @Googolplexed0 sorry can you give new full step guide from start? I have tried from separate post at this thread, but still not working, maybe miss at some step
Author
Owner

@2uu7 commented on GitHub (Jan 12, 2026):

Apparently, spotify has temporarily disabled their "Create Spotify app" feature to get client ID and Client secret for your own API requests..

Image
<!-- gh-comment-id:3740886640 --> @2uu7 commented on GitHub (Jan 12, 2026): Apparently, spotify has temporarily disabled their "Create Spotify app" feature to get client ID and Client secret for your own API requests.. <img width="541" height="298" alt="Image" src="https://github.com/user-attachments/assets/711f9a51-e067-437e-8ed4-45fdcc53feaf" />
Author
Owner

@Googolplexed0 commented on GitHub (Jan 13, 2026):

@Googolplexed0 sorry can you give new full step guide from start? I have tried from separate post at this thread, but still not working, maybe miss at some step

  • Install the latest version of the efficient-api branch (see INSTALLATION if you do not yet have Python / pipx installed)
  • If you have a pre-existing Developer App / Web API token:
    • Ensure your developer API accepts the Redirect URI http://127.0.0.1:4381/login and enables the use of both the Web API & Web Playback SDK.
    • Add --client-id <INSERT CLIENT ID HERE> to the command line arguments the first time you run
      • Your client-id will be saved in your credentials.json (if enabled)
    • Everything should work as normal
    • If you fail to login correctly, delete your credentials.json and add --client-id <INSERT CLIENT ID HERE> to the command line arguments again
  • If you do not have a Developer App / Web API token:
    • You cannot currently get one
    • Add --bypass-metadata-api True to your command line arguments (or set BYPASS_MD_API to True in your config)
    • You can still download some URLs, but these will have no metadata (track name, artists, album, etc.)
<!-- gh-comment-id:3742078424 --> @Googolplexed0 commented on GitHub (Jan 13, 2026): > [@Googolplexed0](https://github.com/Googolplexed0) sorry can you give new full step guide from start? I have tried from separate post at this thread, but still not working, maybe miss at some step - Install the **latest version** of the [efficient-api](https://github.com/Googolplexed0/zotify/tree/efficient-api?tab=readme-ov-file#installation-and-updating) branch (see [INSTALLATION](https://github.com/Googolplexed0/zotify/blob/efficient-api/INSTALLATION.md) if you do not yet have Python / pipx installed) - If you have a pre-existing Developer App / Web API token: - Ensure your developer API accepts the Redirect URI `http://127.0.0.1:4381/login` and enables the use of both the Web API & Web Playback SDK. - Add `--client-id <INSERT CLIENT ID HERE>` to the command line arguments the first time you run - Your client-id will be saved in your credentials.json (if enabled) - Everything should work as normal - If you fail to login correctly, delete your credentials.json and add `--client-id <INSERT CLIENT ID HERE>` to the command line arguments again - If you do not have a Developer App / Web API token: - You cannot currently get one - Add `--bypass-metadata-api True` to your command line arguments (or set `BYPASS_MD_API` to `True` in your config) - You can still download _some_ URLs, but these will have no metadata (track name, artists, album, etc.)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/zotify#118
No description provided.