[GH-ISSUE #763] 405 Client Error when using "playlist_remove_all_occurrences_of_items" #463

Closed
opened 2026-02-27 23:22:47 +03:00 by kerem · 9 comments
Owner

Originally created by @Ovcheric on GitHub (Dec 31, 2021).
Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/763

Trying to remove tracks from a playlist from a list that has all the URI's of the songs I want removed.
When calling the "playlist_remove_all_occurrences_of_items" I get the following error:

HTTP Error for DELETE to https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks with Params: {} returned 405 due to None
Traceback (most recent call last):
  File "C:\Users\ovche\AppData\Local\Programs\Python\Python310\lib\site-packages\spotipy\client.py", line 245, in _internal_call
    response.raise_for_status()
  File "C:\Users\ovche\AppData\Local\Programs\Python\Python310\lib\site-packages\requests\models.py", line 953, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks

I am calling the function in this way:
sp.playlist_remove_all_occurrences_of_items(targetPlaylistID, savedURIs)
Where targetPlaylistID is the full ID of the playlist from which I want the songs removed from, and savedURIs is a list of all the URI's I want removed from the target playlist.

For example,

targetPlaylistID = "50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a"
savedURIs = ["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"]

At the start of my program, I do have the playlist-modify-public and playlist-modify-private scopes. I have also used the same playlistID to save all tracks URI from the playlist to a local text file, so I know I have the authorization flow correct.

Why am I getting the 405 error? All I want to do is remove every occurrence of the tracks saved in the savedURIs from the target playlist.

Originally created by @Ovcheric on GitHub (Dec 31, 2021). Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/763 Trying to remove tracks from a playlist from a list that has all the URI's of the songs I want removed. When calling the "playlist_remove_all_occurrences_of_items" I get the following error: ``` HTTP Error for DELETE to https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks with Params: {} returned 405 due to None Traceback (most recent call last): File "C:\Users\ovche\AppData\Local\Programs\Python\Python310\lib\site-packages\spotipy\client.py", line 245, in _internal_call response.raise_for_status() File "C:\Users\ovche\AppData\Local\Programs\Python\Python310\lib\site-packages\requests\models.py", line 953, in raise_for_status raise HTTPError(http_error_msg, response=self) requests.exceptions.HTTPError: 405 Client Error: Method Not Allowed for url: https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks ``` I am calling the function in this way: `sp.playlist_remove_all_occurrences_of_items(targetPlaylistID, savedURIs)` Where `targetPlaylistID` is the full ID of the playlist from which I want the songs removed from, and `savedURIs` is a list of all the URI's I want removed from the target playlist. For example, ``` targetPlaylistID = "50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a" savedURIs = ["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"] ``` At the start of my program, I do have the `playlist-modify-public` and `playlist-modify-private` scopes. I have also used the same playlistID to save all tracks URI from the playlist to a local text file, so I know I have the authorization flow correct. Why am I getting the 405 error? All I want to do is remove every occurrence of the tracks saved in the `savedURIs` from the target playlist.
kerem closed this issue 2026-02-27 23:22:47 +03:00
Author
Owner

@Peter-Schorn commented on GitHub (Dec 31, 2021):

50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a is not a valid id. It should be 50uWPcNFdJElMVZWo0IebB. A ? character denotes the beginning of the query string in a URL, which is why you are getting this error:

405 Client Error: Method Not Allowed for url: https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks

["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"] are not URIs. These are URIs: ["spotify:track:2Vc6NJ9PW9gD9q343XFRKx", "spotify:track:1s70cjkrdj9lpEeQQlmS9l"].

<!-- gh-comment-id:1003414331 --> @Peter-Schorn commented on GitHub (Dec 31, 2021): `50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a` is not a valid id. It should be `50uWPcNFdJElMVZWo0IebB`. A `?` character denotes the beginning of the query string in a URL, which is why you are getting this error: > 405 Client Error: Method Not Allowed for url: https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks `["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"]` are not URIs. These are URIs: `["spotify:track:2Vc6NJ9PW9gD9q343XFRKx", "spotify:track:1s70cjkrdj9lpEeQQlmS9l"]`.
Author
Owner

@Ovcheric commented on GitHub (Dec 31, 2021):

Thanks for the quick response. It worked!

If the first ID is not a valid ID, then how come it worked when I passed it to playlist_items(playlistID)? Also even if I dont have the spotify:track: at the beginning of the URI, it still removes the correct songs.

If I remove everything past the "?" in the playlist ID, and pass it to the playlist_items(playlistID) then I get a key error. This does not happen if I keep everything after the "?". Its not a big deal because I can just use a different ID for different purposes, but from a conceptual POV, do you know why that is?

<!-- gh-comment-id:1003434461 --> @Ovcheric commented on GitHub (Dec 31, 2021): Thanks for the quick response. It worked! If the first ID is not a valid ID, then how come it worked when I passed it to `playlist_items(playlistID)`? Also even if I dont have the `spotify:track:` at the beginning of the URI, it still removes the correct songs. If I remove everything past the "?" in the playlist ID, and pass it to the `playlist_items(playlistID)` then I get a key error. This does not happen if I keep everything after the "?". Its not a big deal because I can just use a different ID for different purposes, but from a conceptual POV, do you know why that is?
Author
Owner

@Peter-Schorn commented on GitHub (Dec 31, 2021):

If the first ID is not a valid ID, then how come it worked when I passed it to playlist_items(playlistID)?

The playlist_items method uses the /playlists/{playlist_id}/tracks endpoint. Endpoints are specified by the path components of a URL.

When you insert the invalid ID 50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a, the full URL becomes https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks. Everything after the ? is part of the query string, including /tracks. Read about the structure of a URL here. Therefore, you're actually calling the /playlist/{playlist_id} endpoint, which corresponds to the Spotify.playlist method in spotipy. This is why adding/removing ?si=fe98d76d56314a7a changes the structure of the JSON response and can result in a key error in your code.

Decide which endpoint you really want to use: the /playlists/{playlist_id}/tracks endpoint or the /playlist/{playlist_id} endpoint, and then pass a valid ID into the playlist_items or playlist method of spotipy, respectively.

Its not a big deal because I can just use a different ID for different purposes

If the string has a ? in it, then it's not a valid ID. IDs can only have alphanumeric characters in them. I can't be any more clear about it than that. See here.

Also even if I dont have the spotify:track: at the beginning of the URI, it still removes the correct songs.

spotipy will automatically prefix IDs with spotify:track: when you pass them in to certain methods. However, it's still a bad idea to have a variable called savedURIs that actually contains IDs.

<!-- gh-comment-id:1003441199 --> @Peter-Schorn commented on GitHub (Dec 31, 2021): > If the first ID is not a valid ID, then how come it worked when I passed it to playlist_items(playlistID)? The `playlist_items` method uses the `/playlists/{playlist_id}/tracks` [endpoint](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-playlists-tracks). Endpoints are specified by the *path* components of a URL. When you insert the invalid ID `50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a`, the full URL becomes `https://api.spotify.com/v1/playlists/50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a/tracks`. Everything after the `?` is part of the query string, including `/tracks`. Read about the structure of a URL [here](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL). Therefore, you're actually calling the `/playlist/{playlist_id}` [endpoint](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-playlist), which corresponds to the `Spotify.playlist` method in spotipy. This is why adding/removing `?si=fe98d76d56314a7a` changes the structure of the JSON response and can result in a key error in your code. Decide which endpoint you really want to use: the `/playlists/{playlist_id}/tracks` [endpoint](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-playlists-tracks) or the `/playlist/{playlist_id}` [endpoint](https://developer.spotify.com/documentation/web-api/reference/#/operations/get-playlist), and then pass a valid ID into the `playlist_items` or `playlist` method of spotipy, respectively. > Its not a big deal because I can just use a different ID for different purposes If the string has a `?` in it, **then it's not a valid ID**. IDs can only have alphanumeric characters in them. I can't be any more clear about it than that. See [here](https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids). > Also even if I dont have the spotify:track: at the beginning of the URI, it still removes the correct songs. spotipy will automatically prefix IDs with `spotify:track:` when you pass them in to certain methods. However, it's still a bad idea to have a variable called `savedURIs` that actually contains IDs.
Author
Owner

@Ovcheric commented on GitHub (Dec 31, 2021):

# User function that is called to scrape for track name, artist, and URI, and save to file.
# Input is the playlistID that it gets the tracks from
def save_track_names(playlistID):
    # Getting playlist items from the passed Playlist ID
    results = sp.playlist_items(playlistID)

    # Isolating the JSON dump for only the tracks
    tracks = results['tracks']

    # Calling function that will isolate for track info
    # Passing the isolated tracks from JSON dump
    show_tracks(tracks)

    # If there is a next page in the JSON dump (if more than 100 songs)...
    # then go to the next page and call the function that fills in the main list
    while tracks['next']:
        tracks = sp.next(tracks)
        show_tracks(tracks)

# Non-user function that is called by "save_track_names" to scrape the JSON dump for the name, artist, and URI
def show_tracks(results):
    # Loop through the JSON dump of the tracks
    for i, item in enumerate(results['items']):
        # Isolate the current 'track' from the JSON dump
        track = item['track']

        # Get the name of the track and put it into the 'title' variable as a string
        title = str(track['name'])

        # Get the first artist of the track and put it into the 'artist' variable as a string
        artist = str(track['artists'][0]['name'])

        # Get the URI of the track and put it into the 'uri' variable as a string
        uri = str(track['uri'])

        uri = uri[14:]

        write_to_file(title, artist, uri)

# Function that writes all songs in the passed list to the song archive file
def write_to_file(trackName, trackArtist, trackURI):
    file = open('Songs Archive.txt', "a", encoding='utf-8')
    file.write(trackName + ' - ' + trackArtist + ' - ' + trackURI + '\n')
    file.close()

Im not sure if "JSON dump" is the correct term for the huge nested dictionary/list/array that you get from playlist_items().

Also its not really a bug, but you said that ["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"] are not URIs. But when I pass this list to a function it still works.

For example,

# Function that removes all songs that are saved in the text file from a playlist.
# Pass in the target playlist ID (from where you want songs to be removed from)
# and the array with all saved URI's.
# Return is how many songs were removed.
def remove_songs(targetPlaylistID, savedURIs):
    # For each URI in the passed savedURI's...
    for track in savedURIs:
        # Create an empty list
        uri = []

        # Insert the current URI into the first position on the empty list
        uri.insert(0, track)

        # Pass the playlist from which to remove the song from, and the list with a single URI
        # If the URI is found in the playlist, it will be removed.
        sp.playlist_remove_all_occurrences_of_items(targetPlaylistID, uri)

In this case, savedURIs = ["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"].

As of right now, everything works exactly as I want it to, except that when calling the save_track_names function I need to pass the ID with the "?" such as 50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a otherwise I get a Key Error, and when calling the remove_songs function, I need to pass the ID without the "?" such as 50uWPcNFdJElMVZWo0IebB

<!-- gh-comment-id:1003447123 --> @Ovcheric commented on GitHub (Dec 31, 2021): ``` # User function that is called to scrape for track name, artist, and URI, and save to file. # Input is the playlistID that it gets the tracks from def save_track_names(playlistID): # Getting playlist items from the passed Playlist ID results = sp.playlist_items(playlistID) # Isolating the JSON dump for only the tracks tracks = results['tracks'] # Calling function that will isolate for track info # Passing the isolated tracks from JSON dump show_tracks(tracks) # If there is a next page in the JSON dump (if more than 100 songs)... # then go to the next page and call the function that fills in the main list while tracks['next']: tracks = sp.next(tracks) show_tracks(tracks) # Non-user function that is called by "save_track_names" to scrape the JSON dump for the name, artist, and URI def show_tracks(results): # Loop through the JSON dump of the tracks for i, item in enumerate(results['items']): # Isolate the current 'track' from the JSON dump track = item['track'] # Get the name of the track and put it into the 'title' variable as a string title = str(track['name']) # Get the first artist of the track and put it into the 'artist' variable as a string artist = str(track['artists'][0]['name']) # Get the URI of the track and put it into the 'uri' variable as a string uri = str(track['uri']) uri = uri[14:] write_to_file(title, artist, uri) # Function that writes all songs in the passed list to the song archive file def write_to_file(trackName, trackArtist, trackURI): file = open('Songs Archive.txt', "a", encoding='utf-8') file.write(trackName + ' - ' + trackArtist + ' - ' + trackURI + '\n') file.close() ``` Im not sure if "JSON dump" is the correct term for the huge nested dictionary/list/array that you get from `playlist_items()`. Also its not really a bug, but you said that `["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"]` are not URIs. But when I pass this list to a function it still works. For example, ``` # Function that removes all songs that are saved in the text file from a playlist. # Pass in the target playlist ID (from where you want songs to be removed from) # and the array with all saved URI's. # Return is how many songs were removed. def remove_songs(targetPlaylistID, savedURIs): # For each URI in the passed savedURI's... for track in savedURIs: # Create an empty list uri = [] # Insert the current URI into the first position on the empty list uri.insert(0, track) # Pass the playlist from which to remove the song from, and the list with a single URI # If the URI is found in the playlist, it will be removed. sp.playlist_remove_all_occurrences_of_items(targetPlaylistID, uri) ``` In this case, `savedURIs = ["2Vc6NJ9PW9gD9q343XFRKx","1s70cjkrdj9lpEeQQlmS9l"]`. As of right now, everything works exactly as I want it to, except that when calling the `save_track_names` function I need to pass the ID with the "?" such as `50uWPcNFdJElMVZWo0IebB?si=fe98d76d56314a7a` otherwise I get a `Key Error`, and when calling the `remove_songs` function, I need to pass the ID without the "?" such as `50uWPcNFdJElMVZWo0IebB`
Author
Owner

@Peter-Schorn commented on GitHub (Dec 31, 2021):

Your remove_songs function is extremely bizarre. If you want to create a list with one item, then just write: uri = [track]. It never makes sense to use the insert method on an empty list. More importantly, though, why are you removing the tracks one at a time? You're slowing down your code by several orders of magnitude by making a separate API call for each track. the playlist_remove_all_occurrences_of_items method accepts a list of items for a reason. The maximum length is 100. Trying to remove an item that does not exist in the playlist will NOT cause an error.

<!-- gh-comment-id:1003448542 --> @Peter-Schorn commented on GitHub (Dec 31, 2021): Your `remove_songs` function is extremely bizarre. If you want to create a list with one item, then just write: `uri = [track]`. It never makes sense to use the `insert` method on an empty list. More importantly, though, why are you removing the tracks one at a time? You're slowing down your code by several orders of magnitude by making a separate API call for each track. the `playlist_remove_all_occurrences_of_items` method accepts a list of items for a reason. The maximum length is 100. Trying to remove an item that does not exist in the playlist will **NOT** cause an error.
Author
Owner

@Ovcheric commented on GitHub (Dec 31, 2021):

Yeah my lack of python experience is showing haha... I'm very proficient in C++ so I had to learn python from scratch for this program.

I'll definitely change my code to speed it up. I was passing a single uri because the text file contains thousands of tracks at this point. I'll update it to pass in uris in increments of 100.

Thanks for you help!

<!-- gh-comment-id:1003449239 --> @Ovcheric commented on GitHub (Dec 31, 2021): Yeah my lack of python experience is showing haha... I'm very proficient in C++ so I had to learn python from scratch for this program. I'll definitely change my code to speed it up. I was passing a single uri because the text file contains thousands of tracks at this point. I'll update it to pass in uris in increments of 100. Thanks for you help!
Author
Owner

@Peter-Schorn commented on GitHub (Dec 31, 2021):

uri = uri[14:]

I strongly discourage you from using incorrect variable names. With this line, you've just changed the URI to an ID.

title = str(track['name'])

track['name'] is already a string and so is track['artists'][0]['name'] and track['uri']. Passing these into the str initializer has no effect.

<!-- gh-comment-id:1003449559 --> @Peter-Schorn commented on GitHub (Dec 31, 2021): > uri = uri[14:] I strongly discourage you from using incorrect variable names. With this line, you've just changed the URI to an ID. > title = str(track['name']) `track['name']` is already a string and so is `track['artists'][0]['name']` and `track['uri']`. Passing these into the `str` initializer has no effect.
Author
Owner

@Peter-Schorn commented on GitHub (Dec 31, 2021):

# Getting playlist items from the passed Playlist ID
results = sp.playlist_items(playlistID)

Change sp.playlist_items to sp.playlist, and then make sure you pass in valid IDs.

<!-- gh-comment-id:1003450078 --> @Peter-Schorn commented on GitHub (Dec 31, 2021): > ```python > # Getting playlist items from the passed Playlist ID > results = sp.playlist_items(playlistID) > ``` Change `sp.playlist_items` to `sp.playlist`, and then make sure you pass in valid IDs.
Author
Owner

@stephanebruckert commented on GitHub (Jul 9, 2024):

This seems resolved

<!-- gh-comment-id:2218377476 --> @stephanebruckert commented on GitHub (Jul 9, 2024): This seems resolved
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/spotipy#463
No description provided.