Skip to main content

Fully async Python client for Spotify's public & private API. No API key. No Premium.

Project description

alt text

SpotAPI v2

The fully async Python client for Spotify's public & private API. No API key. No Premium. No rate-limit babysitting.

PyPI Python Async Typed License Stars

⚠️ This is the async-v2 beta branch. It is under active development and the API may change before the stable spotapi.v2.0.0 release. For the stable v1 library see main.


What's new in v2

v1 v2
I/O model Synchronous Fully async (asyncio)
Pagination for page in gen: async for page in gen:
Type safety Partial Full — py.typed, typed data wrappers
HTTP tls-client wreq with connection pooling & retry
Auth CAPTCHA solver required TOTP-based (no solver needed)
Session Manual refresh Auto-refresh background task
Codebase Flat structure Layered: connection/, datastruct/, specialized/

Installation

# Latest beta
pip install "spotapi==2.0.0b1"

# Or directly from this branch
pip install "git+https://github.com/Aran404/SpotAPI@async-v2"

Quick start

Unauthenticated — public search

import asyncio
import spotapi

async def main() -> None:
    async for page in spotapi.sync.Public.search_tracks("Radiohead"):
        for track in page:
            duration_s = track.duration.total_milliseconds // 1000
            print(f"{track.name}  ({duration_s}s)  —  {track.uri}")

asyncio.run(main())

Search artists with pagination control

import asyncio
import spotapi

async def main() -> None:
    # Yields one page (up to 100 results) at a time
    async for page in spotapi.sync.Public.search_artists("Tame Impala"):
        for artist in page:
            verified = artist.on_platform_reputation_trait.verification.is_verified
            print(f"{artist.profile.name}  verified={verified}")

asyncio.run(main())

Search albums

import asyncio
import spotapi

async def main() -> None:
    async for page in spotapi.sync.Public.search_albums("OK Computer"):
        for album in page:
            artists = ", ".join(a.profile.name for a in album.artists.items)
            print(f"{album.name} ({album.date.year}) — {artists}")

asyncio.run(main())

Search podcasts

import asyncio
import spotapi

async def main() -> None:
    async for page in spotapi.sync.Public.search_podcasts("Lex Fridman"):
        for podcast in page:
            print(f"{podcast.name} by {podcast.publisher.name}")

asyncio.run(main())

Architecture overview

v2/
├── base.py                  # BaseClient — pathfinder query + pagination
├── client.py                # AsyncClient — thin HTTP facade + context manager
├── session.py               # BundleSession + AuthSession — tokens, TOTP, auto-refresh
├── public.py                # Public — typed search generators
│
├── connection/
│   ├── http.py              # HTTPClient — wreq wrapper, retry + backoff, browser emulation
│   ├── websocket.py         # WebSocketClient — heartbeat, event dispatch
│   └── types.py             # ResponseSuccess / ResponseFailure, backoff types
│
├── datastruct/
│   ├── object_dict.py       # ObjectDict — attribute-access dict for raw JSON
│   ├── pool.py              # Pool[T] — generic async object pool
│   └── event_handler.py     # EventDispatcher — async event system
│
├── specialized/
│   ├── totp.py              # TOTP generation — no CAPTCHA solver required
│   └── data_wrappers/
│       ├── _common.py       # Shared color/visual dataclasses
│       ├── track.py         # Track
│       ├── artist.py        # Artist
│       ├── album.py         # Album
│       ├── playlist.py      # Playlist
│       └── podcast.py       # Podcast
│
├── types/
│   ├── logger.py            # LoggerProtocol + 5 concrete loggers
│   └── exceptions.py        # HTTPError, WebsocketError, BaseClientError
│
└── utils/
    ├── cache.py             # timed_cache — TTL-based async/sync cache decorator
    ├── random.py            # Weighted random selection
    └── strings.py           # JS bundle parsing, hash extraction

Logging

v2 ships five ready-to-use loggers. All implement LoggerProtocol so you can drop in your own.

from spotapi.v2.types import LoggerColour, JsonLogger, NoopLogger, MultiLogger, Level

# Pretty coloured output to stderr (default)
logger = LoggerColour(min_level=Level.INFO)

# Structured JSON — good for log aggregation pipelines
json_logger = JsonLogger(min_level=Level.WARNING)

# Fan-out to multiple loggers at once
combined = MultiLogger(logger, json_logger)

# Silence everything
noop = NoopLogger()

# Bind extra context fields that appear on every subsequent record
scoped = logger.bind(request_id="abc123")
scoped.info("Token refreshed", token_version=2)

Connection pool & browser emulation

Every HTTPClient automatically selects a randomised browser profile (Chrome, Edge, Firefox, Opera) weighted towards modern versions, and a randomised OS (Windows 30×, macOS 6×, Linux 2×). A shared ClientPool reuses connections across requests.

from spotapi.v2.connection import HTTPClient, ClientPool

# Get a pooled client (shared across BaseClient instances)
http = await ClientPool.get()

# Or create a dedicated client with a proxy
async with HTTPClient(proxies=[wreq.Proxy.http("http://user:pass@host:port")]) as http:
    response = await http.get("https://open.spotify.com/")

Migration from v1

For a full side-by-side comparison of the v1 and v2 APIs, see MIGRATION.md.

# v1
from spotapi import Song
song = Song()
for batch in song.paginate_songs("weezer"):
    for item in batch:
        print(item["item"]["data"]["name"])

# v2
import spotapi
async for page in spotapi.sync.Public.search_tracks("weezer"):
    for track in page:
        print(track.name)

Contributing

All contributions are welcome. The async rewrite is still in progress and there are plenty of well-scoped tasks available — see CONTRIBUTING.md for the full guide and the open good first issues.

git clone https://github.com/Aran404/SpotAPI
cd SpotAPI
git checkout async-v2
pip install -e ".[dev]"

Legal notice

Disclaimer: This repository is provided for educational purposes only. The author accepts no responsibility for misuse. Accessing private Spotify endpoints may violate Spotify's Terms of Service. Use responsibly. See LEGAL_NOTICE.md for full details.


License

GPL-3.0 © Aran404

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spotapi-2.0.0b1.tar.gz (219.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spotapi-2.0.0b1-py3-none-any.whl (29.6 kB view details)

Uploaded Python 3

File details

Details for the file spotapi-2.0.0b1.tar.gz.

File metadata

  • Download URL: spotapi-2.0.0b1.tar.gz
  • Upload date:
  • Size: 219.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for spotapi-2.0.0b1.tar.gz
Algorithm Hash digest
SHA256 c3a355b880b078657d6cc5549b2fa09259c5ae27872f6782713e87d75ade83be
MD5 bc1eccda3e832e76e8fec1d4bf731796
BLAKE2b-256 3b8822f9be34b46bab4fe1db5dcd6016dba087f03242b5d079edb16d7a6c118c

See more details on using hashes here.

File details

Details for the file spotapi-2.0.0b1-py3-none-any.whl.

File metadata

  • Download URL: spotapi-2.0.0b1-py3-none-any.whl
  • Upload date:
  • Size: 29.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.2

File hashes

Hashes for spotapi-2.0.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 48220a83716567490c7b95d5b0f18aa3409c60db63d086b1da04dcfb07c3071e
MD5 67b40186a51622fbce15c791019a6204
BLAKE2b-256 19fc1aa6618179033bd0b921346be741ffcfd6b13e4ca7bfb42df491f9fadb21

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page