[GH-ISSUE #878] Anonymous access to API #537

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

Originally created by @dieser-niko on GitHub (Nov 2, 2022).
Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/878

Is your feature request related to a problem? Please describe.
This feature would allow the script to make anonymous API calls without login or registered application.

Describe the solution you'd like
It's actually very easy to obtain an anonymous token: https://open.spotify.com/get_access_token

I wrote a little script, but it may not be the best implementation (basically copied SpotifyClientCredentials and changed the class to my liking):

spotipy_anon.py

import warnings
import logging

import requests
from spotipy.oauth2 import SpotifyAuthBase
from spotipy.cache_handler import CacheFileHandler, CacheHandler

logger = logging.getLogger(__name__)


class SpotifyAnon(SpotifyAuthBase):
    """
    Implements anonymous access to the Spotify API.
    """
    client_id: str  # was suggested by PyCharm

    TOKEN_URL = "https://open.spotify.com/get_access_token"

    def __init__(
            self,
            proxies=None,
            requests_session=True,
            requests_timeout=None,
            cache_handler=None
    ):
        """
        Creates a SpotifyAnon object
        
        Parameters:
        * proxies: Optional, interpreted as boolean
        * requests_session: A Requests session
        * requests_timeout: Optional, tell Requests to stop waiting for a response after
                            a given number of seconds
        * cache_handler: An instance of the `CacheHandler` class to handle
                         getting and saving cached authorization tokens.
                         Optional, will otherwise use `CacheFileHandler`.
                         (takes precedence over `cache_path` and `username`)
        """

        super(SpotifyAnon, self).__init__(requests_session)

        self.proxies = proxies
        self.requests_session = requests_session
        self.requests_timeout = requests_timeout
        self.cache_handler = cache_handler

        if cache_handler:
            assert issubclass(cache_handler.__class__, CacheHandler), \
                "cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
                + " != " + str(CacheHandler)
            self.cache_handler = cache_handler
        else:
            self.cache_handler = CacheFileHandler()

    def get_access_token(self, as_dict=False, check_cache=True):
        """
        If a valid access token is in memory, returns it
        Else fetches a new token and returns it
            Parameters:
            - as_dict - a boolean indicating if returning the access token
                as a token_info dictionary, otherwise it will be returned
                as a string.
        """
        if as_dict:
            warnings.warn(
                "You're using 'as_dict = True'."
                "get_access_token will return the token string directly in future "
                "versions. Please adjust your code accordingly, or use "
                "get_cached_token instead.",
                DeprecationWarning,
                stacklevel=2,
            )

        if check_cache:
            token_info = self.cache_handler.get_cached_token()
            if token_info and not self.is_token_expired(token_info):
                return token_info if as_dict else token_info["access_token"]

        token_info = self._request_access_token()
        # token_info = self._add_custom_values_to_token_info(token_info)
        self.cache_handler.save_token_to_cache(token_info)
        self.client_id = token_info["client_id"]
        return token_info if as_dict else token_info["access_token"]

    def _request_access_token(self):
        """Gets client credentials access token """
        logger.debug("sending GET request to %s", self.TOKEN_URL)

        try:
            response = self._session.get(
                self.TOKEN_URL,
                verify=True,
                proxies=self.proxies,
                timeout=self.requests_timeout,
            )
            response.raise_for_status()
            token_info = response.json()
            return {"client_id": token_info["clientId"],
                    "access_token": token_info["accessToken"],
                    "expires_at": int(token_info["accessTokenExpirationTimestampMs"] / 1000)
                    }
        except requests.exceptions.HTTPError as http_error:
            self._handle_oauth_error(http_error)

main.py

import spotipy, spotipy_anon
sp = spotipy.Spotify(auth_manager=spotipy_anon.SpotifyAnon())

results = sp.search(q='weezer', limit=20)
for idx, track in enumerate(results['tracks']['items']):
    print(idx, track['name'])

Describe alternatives you've considered
None.

Additional context
Not really relevant for this suggestion or even for this repo, but there's also the endpoint api-partner.spotify.com which uses the same token, but much harder to implement. This is how I found the anonymous token. It is possible to get even more stuff like background colors and things relevant to the front end, but you need the hash of the definition, which can be changed (and was already changed while I was testing it)

Originally created by @dieser-niko on GitHub (Nov 2, 2022). Original GitHub issue: https://github.com/spotipy-dev/spotipy/issues/878 **Is your feature request related to a problem? Please describe.** This feature would allow the script to make anonymous API calls without login or registered application. **Describe the solution you'd like** It's actually very easy to obtain an anonymous token: [https://open.spotify.com/get_access_token](https://open.spotify.com/get_access_token) I wrote a little script, but it may not be the best implementation (basically copied SpotifyClientCredentials and changed the class to my liking): **spotipy_anon.py** ```python3 import warnings import logging import requests from spotipy.oauth2 import SpotifyAuthBase from spotipy.cache_handler import CacheFileHandler, CacheHandler logger = logging.getLogger(__name__) class SpotifyAnon(SpotifyAuthBase): """ Implements anonymous access to the Spotify API. """ client_id: str # was suggested by PyCharm TOKEN_URL = "https://open.spotify.com/get_access_token" def __init__( self, proxies=None, requests_session=True, requests_timeout=None, cache_handler=None ): """ Creates a SpotifyAnon object Parameters: * proxies: Optional, interpreted as boolean * requests_session: A Requests session * requests_timeout: Optional, tell Requests to stop waiting for a response after a given number of seconds * cache_handler: An instance of the `CacheHandler` class to handle getting and saving cached authorization tokens. Optional, will otherwise use `CacheFileHandler`. (takes precedence over `cache_path` and `username`) """ super(SpotifyAnon, self).__init__(requests_session) self.proxies = proxies self.requests_session = requests_session self.requests_timeout = requests_timeout self.cache_handler = cache_handler if cache_handler: assert issubclass(cache_handler.__class__, CacheHandler), \ "cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \ + " != " + str(CacheHandler) self.cache_handler = cache_handler else: self.cache_handler = CacheFileHandler() def get_access_token(self, as_dict=False, check_cache=True): """ If a valid access token is in memory, returns it Else fetches a new token and returns it Parameters: - as_dict - a boolean indicating if returning the access token as a token_info dictionary, otherwise it will be returned as a string. """ if as_dict: warnings.warn( "You're using 'as_dict = True'." "get_access_token will return the token string directly in future " "versions. Please adjust your code accordingly, or use " "get_cached_token instead.", DeprecationWarning, stacklevel=2, ) if check_cache: token_info = self.cache_handler.get_cached_token() if token_info and not self.is_token_expired(token_info): return token_info if as_dict else token_info["access_token"] token_info = self._request_access_token() # token_info = self._add_custom_values_to_token_info(token_info) self.cache_handler.save_token_to_cache(token_info) self.client_id = token_info["client_id"] return token_info if as_dict else token_info["access_token"] def _request_access_token(self): """Gets client credentials access token """ logger.debug("sending GET request to %s", self.TOKEN_URL) try: response = self._session.get( self.TOKEN_URL, verify=True, proxies=self.proxies, timeout=self.requests_timeout, ) response.raise_for_status() token_info = response.json() return {"client_id": token_info["clientId"], "access_token": token_info["accessToken"], "expires_at": int(token_info["accessTokenExpirationTimestampMs"] / 1000) } except requests.exceptions.HTTPError as http_error: self._handle_oauth_error(http_error) ``` **main.py** ```python3 import spotipy, spotipy_anon sp = spotipy.Spotify(auth_manager=spotipy_anon.SpotifyAnon()) results = sp.search(q='weezer', limit=20) for idx, track in enumerate(results['tracks']['items']): print(idx, track['name']) ``` **Describe alternatives you've considered** None. **Additional context** Not really relevant for this suggestion or even for this repo, but there's also the endpoint `api-partner.spotify.com` which uses the same token, but much harder to implement. This is how I found the anonymous token. It is possible to get even more stuff like background colors and things relevant to the front end, but you need the hash of the definition, which can be changed (and was already changed while I was testing it)
kerem 2026-02-27 23:23:12 +03:00
Author
Owner

@stephanebruckert commented on GitHub (Nov 2, 2022):

Really cool discovery. It's not part of the public/documented API so we will want to think of whether/how we could incorporate this into spotipy. The API has a lot of other hidden/private endpoints, for example getting/setting the user playlists order https://github.com/mirrorfm/spotify-private-api. The problem is they could change without warning and versioning could become difficult. But definitely something to explore more.

<!-- gh-comment-id:1299996552 --> @stephanebruckert commented on GitHub (Nov 2, 2022): Really cool discovery. It's not part of the public/documented API so we will want to think of whether/how we could incorporate this into spotipy. The API has a lot of other hidden/private endpoints, for example getting/setting the user playlists order https://github.com/mirrorfm/spotify-private-api. The problem is they could change without warning and versioning could become difficult. But definitely something to explore more.
Author
Owner

@dieser-niko commented on GitHub (Nov 2, 2022):

That's true. After researching for a little bit, the only "official" thing I could find was an issue in the old spotify web api repo. So at least it looks like they are aware that people use it.
A quick look into the WaybackMachine also reveals that it didn't change for the last 2 1/2 years when it first appeared, but I don't think it matters that much.

If you think the risk is too high that it will change again, then I would suggest mentioning this endpoint and/or class in the documentation.

<!-- gh-comment-id:1300481896 --> @dieser-niko commented on GitHub (Nov 2, 2022): That's true. After researching for a little bit, the only "official" thing I could find was an [issue](https://github.com/spotify/web-api/issues/1531) in the old spotify web api repo. So at least it looks like they are aware that people use it. A quick look into the WaybackMachine also reveals that it didn't change for the last 2 1/2 years when it first appeared, but I don't think it matters that much. If you think the risk is too high that it will change again, then I would suggest mentioning this endpoint and/or class in the documentation.
Author
Owner

@danihodovic commented on GitHub (Apr 7, 2023):

Great find. I'd love to see this merged so I don't have to create a developer app and inject environment variables for smaller projects.

<!-- gh-comment-id:1500445586 --> @danihodovic commented on GitHub (Apr 7, 2023): Great find. I'd love to see this merged so I don't have to create a developer app and inject environment variables for smaller projects.
Author
Owner

@dieser-niko commented on GitHub (Apr 7, 2023):

Great find. I'd love to see this merged so I don't have to create a developer app and inject environment variables for smaller projects.

As stephanebruckert already mentioned, this isn't part of the public API, I don't think, it will be included in spotipy at all. So you still have to copy the python script yourself in order to use it.

<!-- gh-comment-id:1500523329 --> @dieser-niko commented on GitHub (Apr 7, 2023): > Great find. I'd love to see this merged so I don't have to create a developer app and inject environment variables for smaller projects. As stephanebruckert already mentioned, this isn't part of the public API, I don't think, it will be included in spotipy at all. So you still have to copy the python script yourself in order to use it.
Author
Owner

@nleroy917 commented on GitHub (May 5, 2023):

You could just make a separate, very tiny, spotipy-anon package that people can optionally install that does this for them and just interfaces with the core of spotipy. Then its implemented just like your example:

pip install spotipy spotipy-anon
import spotipy
from spotipy_anon import SpotifyAnon

sp = spotipy.Spotify(auth_manager=SpotifyAnon())
results = sp.search(q='weezer', limit=20)
for idx, track in enumerate(results['tracks']['items']):
    print(idx, track['name'])
<!-- gh-comment-id:1536462791 --> @nleroy917 commented on GitHub (May 5, 2023): You could just make a separate, very tiny, `spotipy-anon` package that people can optionally install that does this for them and just interfaces with the core of `spotipy`. Then its implemented just like your example: ```bash pip install spotipy spotipy-anon ``` ```python import spotipy from spotipy_anon import SpotifyAnon sp = spotipy.Spotify(auth_manager=SpotifyAnon()) results = sp.search(q='weezer', limit=20) for idx, track in enumerate(results['tracks']['items']): print(idx, track['name']) ```
Author
Owner

@dieser-niko commented on GitHub (May 9, 2023):

Well then, first ever release.
spotipy-anon is now available on PyPI and GitHub.
I guess this is now finished, closing the issue.

<!-- gh-comment-id:1540416293 --> @dieser-niko commented on GitHub (May 9, 2023): Well then, first ever release. spotipy-anon is now available on [PyPI](https://pypi.org/project/spotipy-anon/) and [GitHub](https://github.com/dieser-niko/spotipy-anon). I guess this is now finished, closing the issue.
Author
Owner

@nleroy917 commented on GitHub (May 9, 2023):

Cool! Would love to contribute if needed!

<!-- gh-comment-id:1541001789 --> @nleroy917 commented on GitHub (May 9, 2023): Cool! Would love to contribute if needed!
Author
Owner

@dieser-niko commented on GitHub (May 15, 2023):

Help is always appreciated, but this is a relatively small repository, so I'm not sure where you can contribute. At the moment, I only have one open (self made) issue, which is about testing the endpoints. I wanted to do this myself, but you can try it too if you like.

<!-- gh-comment-id:1547904958 --> @dieser-niko commented on GitHub (May 15, 2023): Help is always appreciated, but this is a relatively small repository, so I'm not sure where you can contribute. At the moment, I only have one open (self made) issue, which is about testing the endpoints. I wanted to do this myself, but you can try it too if you like.
Author
Owner

@dieser-niko commented on GitHub (Jul 25, 2023):

I just want to add this little bit here because it is related. According to the ToS in section IV 2.b.i. it is forbidden to provide unauthorised access to a Spotify service.

Do not facilitate unauthorized access to the Spotify Service or Spotify Content, including
enabling access to, or use of, the Spotify Service or Spotify Content in violation of the Developer Agreement

Even though this library does not require a registered application, it still uses the API service and, if I understand correctly, has to obey the ToS. So basically use it at your own risk, I won't take it down unless Spotify notices.

I actually stumbled across this when I finished reverse engineering the api-partner.spotify.com endpoint (which, surprise, is also against the ToS). So, sadly, I probably won't be publishing my findings.

<!-- gh-comment-id:1650102831 --> @dieser-niko commented on GitHub (Jul 25, 2023): I just want to add this little bit here because it is related. According to the ToS in section **IV 2.b.i.** it is forbidden to provide unauthorised access to a Spotify service. > Do not facilitate unauthorized access to the Spotify Service or Spotify Content, including > enabling access to, or use of, the Spotify Service or Spotify Content in violation of the Developer Agreement Even though this library does not require a registered application, it still uses the API service and, if I understand correctly, has to obey the ToS. So basically use it at your own risk, I won't take it down unless Spotify notices. I actually stumbled across this when I finished reverse engineering the api-partner.spotify.com endpoint (which, surprise, is also against the ToS). So, sadly, I probably won't be publishing my findings.
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#537
No description provided.