Spotipy: "401 error: The access token expired" when using SpotifyClientCredentials

Created on 6 Mar 2016  路  17Comments  路  Source: plamere/spotipy

I am creating my Spotipy object like this, after reading this code snippet:

scc = SpotifyClientCredentials(client_id = SPOTIPY_CLIENT_ID,
                                   client_secret = SPOTIPY_CLIENT_SECRET)
sp = spotipy.Spotify(client_credentials_manager = scc)

However, after a number of queries, I obtain the following error:

http status:401                                                                 
Traceback (most recent call last):                                              
  File "retriever2.py", line 114, in <module>                                   
    store_songs(terms, dbfile, total_number)                                    
  File "retriever2.py", line 97, in store_songs                                 
    songs = get_songs(sp, playlist)                                             
  File "retriever2.py", line 36, in get_songs                                   
    track_results = sp.next(track_results)                                      
  File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 172, in next
    return self._get(result['next'])                                            
  File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 123, in _get
    return self._internal_call('GET', url, payload, kwargs)                     
  File "/home/chema/.local/lib/python2.7/site-packages/spotipy/client.py", line 104, in _internal_call
    -1, '%s:\n %s' % (r.url, r.json()['error']['message']))                     
spotipy.client.SpotifyException: http status: 401, code:-1 - https://api.spotify.com/v1/users/1248788010/playlists/4wEVX1M28mBIpiHttM6PrP/tracks?offset=100&limit=100:
 The access token expired      

I was under the impression that SpotifyClientCredentials was used to update the access token accordingly. Right now, I have my queries inside a try - except block and I refresh it manually (I basically recreate the sp object every time I get an exception). Am I using this correctly or is there any other way?

Most helpful comment

@ritiek Now it works, thanks for the help.

All 17 comments

Hi, I am having the same problem as you and I was wondering how you got the try - except to work? Right now it just seems to not catch the error for me.

@Biggerandreas You can use something like this to catch exception raised when the token expires:

try:
    do_something()
except spotipy.client.SpotifyException:
    # re-authenticate when token expires
    token = ...
    sp = spotipy.Spotify(auth=token)
    do_something()

@ritiek I have already tried that but it still gives me this error:

Traceback (most recent call last):
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 121, in _internal_call
File "/usr/local/lib/python3.5/dist-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: https://api.spotify.com/v1/me/player/currently-playing
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/home/pi/python/musicplayer3.py", line 101, in
run_loop()
File "/home/pi/python/musicplayer3.py", line 87, in run_loop
new_track = main.get_song()
File "/home/pi/python/musicplayer3.py", line 41, in get_song
info = self.sp.currently_playing()
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 899, in currently_playing
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 148, in _get
File "/usr/local/lib/python3.5/dist-packages/spotipy-2.4.4-py3.5.egg/spotipy/client.py", line 126, in _internal_call
spotipy.client.SpotifyException: http status: 401, code:-1 - https://api.spotify.com/v1/me/player/currently-playing:
The access token expired

Thanks in advance.

@Biggerandreas That should not be happening, I've been using something similar without problems. Do you mind sharing relevant code from your script here? (removing any credentials of course)

@ritiek Sorry for the mess of a code but here it is:

import spotipy
import spotipy.oauth2 as oauth
import re
import time
from RPLCD.i2c import CharLCD
lcd = CharLCD(
    i2c_expander='PCF8574', address=0x27, port=1,
    cols=20, rows=4, dotsize=8,
    charmap='A02',
    auto_linebreaks=True,
    backlight_enabled=True)

lcd.clear()

username = ''
scope = 'playlist-read-private,' \
        ' playlist-read-collaborative,' \
        ' user-read-playback-state,' \
        ' user-modify-playback-state,' \
        ' user-read-currently-playing'
redirect_uri = 'http://localhost/'
client_id = ''
client_secret = ''

_re_compiled = re.compile("'name':\s'(.*?)'|'name':\s\"(.*?)\"")
_auth_finder = re.compile("code=(.*?)$", re.MULTILINE)


class Main:
    def __init__(self):
        self.spo = oauth.SpotifyOAuth(
            client_id=client_id,
            client_secret=client_secret,
            redirect_uri=redirect_uri,
            scope=scope,
            cache_path=".cache-{}".format(username))

        self.sp = spotipy.Spotify(auth=self.get_token())
        self.get_song()

    def spot(self, token):
        self.token = token
        self.sp = spotipy.Spotify(auth=token)
        return self.sp

    def refresh_token(self):
        cached_token = self.spo.get_cached_token()
        is_it_expired = self.spo.is_token_expired(cached_token)
        if is_it_expired is True:
            refreshed_token = cached_token['refresh_token']
            new_token = self.spo.refresh_access_token(refreshed_token)
            self.sp = spotipy.Spotify(auth=new_token)
            return new_token

    def get_token(self):
        token_info = self.spo.get_cached_token()
        if token_info:
            access_token = token_info['access_token']
            return access_token
        else:
            auth = self.spo.get_authorize_url()
            print(auth)
            auth_url = input('Click the link above and copy and paste the url here: ')
            _re_auth = re.findall(_auth_finder, auth_url)
            access_token = self.spo.get_access_token(_re_auth[0])
            return access_token

    def get_song(self):
        info = self.sp.currently_playing()
        self.finder = re.findall(_re_compiled, str(info))
        self.song = info['item']['name']
        _re_artist = re.findall(_re_compiled, str(info['item']['artists']))

        if not _re_artist[0][0]:
            self.artist = _re_artist[0][1]
        else:
            self.artist = _re_artist[0][0]

        return "Song: {} Artist: {}".format(self.song, self.artist)


time_started = time.strftime("%Y-%m-%d %H:%M:%S")
main = Main()
main.refresh_token()

def run_loop():
    lcd.clear()
    lcd.cursor_pos = (0, 0)
    lcd.write_string(main.song)
    lcd.cursor_pos = (3, 0)
    lcd.write_string(main.artist)
    track_count = 0
    old_track = main.get_song()
    while True:
        new_track = main.get_song()
        time.sleep(0.1)
        if new_track != old_track:
            print(main.finder)
            track_count += 1
            print(track_count)
            lcd.clear()
            lcd.cursor_pos = (0, 0)
            lcd.write_string(main.song)
            lcd.cursor_pos = (3, 0)
            lcd.write_string(main.artist)
            old_track = new_track

lcd.clear()
lcd.cursor_pos = (0, 0)
lcd.write_string(main.song)
lcd.cursor_pos = (3, 0)
lcd.write_string(main.artist)
track_count = 0
old_track = main.get_song()
while True:
    new_track = main.get_song()
    time.sleep(0.1)
    try:
        if new_track != old_track:
            print(main.finder)
            track_count += 1
            print(track_count)
            lcd.clear()
            lcd.cursor_pos = (0, 0)
            lcd.write_string(main.song)
            lcd.cursor_pos = (3, 0)
            lcd.write_string(main.artist)
            old_track = new_track
            main.refresh_token()

    except spotipy.client.SpotifyException:
        #main = Main
        token = main.refresh_token()
        main.spot(token)

        current_time = time.strftime("%Y-%m-%d %H:%M:%S")
        print(time_started - current_time)
        print(current_time)
        print(time_started)
        print("Got an exception ")
        with open('logs.txt', 'a') as the_file:
            the_file.write(current_time)

@Biggerandreas Actually, there are couple of things you need to fix with your code. I'll try to explain my best. :)


  • You are calling Main.refresh_token() unnecessarily at multiple places, which would slow down your script. We only need to call it when a spotipy.client.SpotifyException is raised. You should remove calling it from any other place.
  • As spotipy.client.SpotifyException is raised when the token expires, we don't need to check it again in Main.refresh_token(). So, we can reduce Main.refresh_token() to something below:
    def refresh_token(self):
        cached_token = self.spo.get_cached_token()
        refreshed_token = cached_token['refresh_token']
        new_token = self.spo.refresh_access_token(refreshed_token)
        # also we need to specifically pass `auth=new_token['access_token']`
        self.sp = spotipy.Spotify(auth=new_token['access_token'])
        return new_token

You could then just call main.refresh_token() in your except block which should refresh token.

  • In your main loop, you should place main.get_song() under try block otherwise we cannot catch any exception it raises (in our case, we want to catch spotipy.client.SpotifyException).
  • You are also trying to subtract 2 different times in str which is not possible. Consider:
import time
time_started = time.strftime("%Y-%m-%d %H:%M:%S")
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
print(time_started - current_time)

```
Traceback (most recent call last):
File "timi.py", line 4, in
print(time_started - current_time)
TypeError: unsupported operand type(s) for -: 'str' and 'str'

As a workaround, we can convert them to `datetime.datetime` objects and then subtract them:
```python
from datetime import datetime
import time
fmt = "%Y-%m-d %H:%M:%S"
time_started = time.strftime(fmt)
current_time = time.strftime(fmt)
# also i think you meant `current_time - time_started` and not other way round
print(datetime.strptime(current_time, fmt) - datetime.strptime(time_started, fmt))

That should clear most stuff. Let me know if anything is still not right. :)

Thanks for the help, will try to see if it works!

@ritiek Sadly it did not work, maybe I am just doing it wrong.
You said I should put main.get_song() in my main loop with a try block, but how would I do that?

Is there a way to shorten the timeout? So I don't have to wait an hour for the error?

Thanks in advance.

My bad, that wasn't clear enough. You have this code:

while True:
    new_track = main.get_song()
    time.sleep(0.1)
    try:
        if new_track != old_track:
            print(main.finder)
            ...

You should put Main.get_song() under try block like this so we could catch exceptions:

while True:
    time.sleep(0.1)
    try:
        new_track = main.get_song()
        if new_track != old_track:
            print(main.finder)
            ...

Is there a way to shorten the timeout?

Yep, kind of. For testing purposes, you could add raise spotipy.client.SpotifyError(401, 401, 'ouch!') under try block as well:

while True:
    time.sleep(0.1)
    try:
        raise spotipy.client.SpotifyError(401, 401, 'ouch!')
        new_track = main.get_song()
        if new_track != old_track:
            print(main.finder)

Of course, this won't let rest of the code in try block (after raise ...) run, you could just check out if it is generating a new token correctly or not by adding a simple print in Main.refresh_token():

    def refresh_token(self):
        cached_token = self.spo.get_cached_token()
        refreshed_token = cached_token['refresh_token']
        new_token = self.spo.refresh_access_token(refreshed_token)
        print(new_token['access_token'])  # <--
        # also we need to specifically pass `auth=new_token['access_token']`
        self.sp = spotipy.Spotify(auth=new_token['access_token'])
        return new_token

If it gives you a new token every time then you're probably good to go.

I can't actually test your complete code as I don't have any LCD display for my Pi.

@ritiek Thanks! Now it catches it.

@ritiek Now it catches the error but It does not seem to refresh the token because it just keeps printing, should I do something else in the try block?

while True:
    time.sleep(0.1)
    try:
        new_track = main.get_song()
        if new_track != old_track:
            print(main.finder)
            track_count += 1
            print(track_count)
            lcd.clear()
            lcd.cursor_pos = (0, 0)
            lcd.write_string(main.song)
            lcd.cursor_pos = (3, 0)
            lcd.write_string(main.artist)
            old_track = new_track

    except spotipy.client.SpotifyException:
        token = main.refresh_token()
        main.spot(token)
        print("Got an exception ")

@Biggerandreas I went back to the code I was writing when I reported this issue. In the end it seems that what I did was simply to authenticate at the beginning of each loop I was doing (which retrieved some songs, but not so many that the auth would expire). This: https://github.com/rinze/spotify-moods/blob/847cfcbcf1b463e50705b772821bf48acbb309fd/retriever.py#L78

while n_playlists < total_playlists:
    sp = spotipy.Spotify(client_credentials_manager = scc)
    [...]

In any case, shouldn't spotipy handle this internally?

@Biggerandreas You don't need to feed your refreshed token to Main.spot(). Main.refresh_token() automatically take cares of that with self.sp = spotipy.Spotify(auth=new_token['access_token']).

You could just reduce your except block to:

except spotipy.client.SpotifyException:
    main.refresh_token()
    print("Got an exception ")

When your token expires (after 1 hour), your code will raise an exception spotipy.client.SpotifyException which will cause Main.refresh_token() to execute which should refresh your instance of spotipy.Spotify() with your newly refreshed token, so the script should keep working fine.

In any case, shouldn't spotipy handle this internally?

@rinze Yep, it would nice to have an optional argument to spotipy.Spotify(auto_refresh=True) which would automatically refresh token once it expires. I don't see any active development on spotipy, so can't say how long before anything like this happens.

@ritiek Now it works, thanks for the help.

Thanks @ritiek. You've been very helpful and active on this repo over the years. I'm currently the only maintainer on the repo, would you be interested to join me? If yes I will tweet your name to @plamere and see if he's ok. I see no reason why not, because Paul initially invited 2 more people but they aren't as available as expected.

Sure, thanks! That would be very nice.

Great, thank you! You should have received the invite from @plamere

The goal is to automate RTD + PyPi deployments from a single account, but if needed we can still request it later

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fortyTwo102 picture fortyTwo102  路  4Comments

DylanMeeus picture DylanMeeus  路  4Comments

JessicaMartini212887 picture JessicaMartini212887  路  5Comments

gawaineo picture gawaineo  路  3Comments

basophobic picture basophobic  路  5Comments