Bandcamp Async API
A modern, asynchronous Python client for the Bandcamp API.
This project was created to implement a Bandcamp music provider for Music Assistant, enabling seamless integration of Bandcamp's music catalog into home audio systems.
- Repository: https://github.com/ALERTua/bandcamp_async_api
- Changelog: https://github.com/ALERTua/bandcamp_async_api/releases
- PyPI: https://pypi.org/project/bandcamp_async_api/
- Music Assistant: https://github.com/music-assistant
Features
- Search: Search for artists, albums, and tracks across Bandcamp
- Albums: Retrieve detailed album information including track listings
- Tracks: Get individual track details and streaming information
- Lyrics: Fetch song lyrics on request, at the cost of one extra request
- Artists: Access artist profiles, discographies, and metadata
- Collections: Browse user collections and wishlists (auth required for private data)
- Following: Access following bands, following fans, and followers
- Feed: Get personalized music feed with new releases from followed artists
- Async: Fully asynchronous API using aiohttp
- Type-safe: Complete type hints for all models and methods
- Well-tested: Comprehensive test suite with real API data
Installation
Install from PyPI:
pip install bandcamp-async-api
Or using uv:
uv add bandcamp-async-api
Quick Start
import asyncio
from bandcamp_async_api import BandcampAPIClient
async def main():
async with BandcampAPIClient(
identity_token='7%09optional_identity_token%7D'
) as client:
# Search for music
results = await client.search("radiohead")
print(f"Found {len(results)} results")
# Get album details
if results:
album_result = next(r for r in results if r.type == "album")
album = await client.get_album(album_result.artist_id, album_result.id)
print(f"Album: {album.title} by {album.artist.name}")
# Get artist information
artist_result = next(r for r in results if r.type == "artist")
artist = await client.get_artist(artist_result.id)
print(f"Artist: {artist.name} - {artist.bio}")
if __name__ == '__main__':
asyncio.run(main())
Authentication
For accessing user collections, you need to obtain an identity token from Bandcamp cookies:
from bandcamp_async_api import BandcampAPIClient
client = BandcampAPIClient(identity_token="your_identity_token")
Music Feed
The get_feed() method retrieves a personalized music feed containing new releases from followed artists, fan purchases, and fan picks. This endpoint requires authentication - you must provide an identity token.
import asyncio
from bandcamp_async_api import BandcampAPIClient, BandcampMustBeLoggedInError
async def main():
async with BandcampAPIClient(identity_token='your_identity_token') as client:
# Get your music feed
feed = await client.get_feed()
print(f"New stories: {len(feed.stories)}")
print(f"Has more: {feed.has_more}")
# Iterate through feed stories
for story in feed.stories:
print(f" - {story.story_type}: {story.item_title} by {story.band_name}")
# Access tracks with streaming URLs
for track in feed.track_list:
print(f" Track: {track.title} - {track.streaming_url}")
# Paginate through older stories
if feed.has_more and feed.oldest_story_date:
older_feed = await client.get_feed(older_than=feed.oldest_story_date)
print(f"Older stories: {len(older_feed.stories)}")
if __name__ == '__main__':
asyncio.run(main())
Feed Story Types
The feed contains different story types:
np- New track/track releasenr- New album releasep- Fan purchasefp- Fan pick
Error Handling
The feed endpoint requires authentication. If you try to access it without an identity token, you'll receive a BandcampMustBeLoggedInError:
from bandcamp_async_api import BandcampAPIClient, BandcampMustBeLoggedInError
async def safe_get_feed():
client = BandcampAPIClient() # No identity token
try:
feed = await client.get_feed()
except BandcampMustBeLoggedInError:
print("Feed requires authentication - provide an identity token")
Artist vs. performer credit
Bandcamp distinguishes between the page owner (the band whose bandcamp.com page hosts a release) and the performer credit for a specific release. They usually match, but on label-style pages they diverge — e.g. Mortaja's "Combined Minds" is published on audiophob.bandcamp.com, so the page owner is audiophob and the performer is Mortaja.
BCAlbum and BCTrack expose both:
album.artist(BCArtist) — always the page-owning band. Has a Bandcamp profile, follows/following counts, etc.album.tralbum_artist(str | None) — the explicit performer credit from the API.Nonewhen the API didn't set one (the album is by the band itself).
album = await client.get_album(artist_id, album_id)
# Display name — prefer the performer credit, fall back to the page owner:
display_artist = album.tralbum_artist or album.artist.name
# Detect a label release:
is_label_release = (
album.tralbum_artist is not None and album.tralbum_artist != album.artist.name
)
Note (breaking change in
<version>): prior versions returned the performer credit onalbum.artist.namewhen present. Consumers that relied on that must readalbum.tralbum_artistinstead. The same applies toBCTrack.
Lyrics
Bandcamp does not send the song text together with the track details. It sends a has_lyrics flag only. The text lives behind a second request, so this library never fetches it unless you ask for it.
async with BandcampAPIClient() as client:
# One request. The flag arrives, the text does not.
track = await client.get_track(2437326710, 178646676)
print(track.has_lyrics, track.lyrics) # True None
# Two requests. The text is filled in.
track = await client.get_track(2437326710, 178646676, with_lyrics=True)
print(track.lyrics)
# One extra request fills every track of the album.
album = await client.get_album(2437326710, 1994024535, with_lyrics=True)
# Or ask for the map yourself: track ID to text.
album_lyrics = await client.get_album_lyrics(1994024535)
track_lyrics = await client.get_track_lyrics(178646676)
with_lyrics costs one extra request per call. The client skips that request when no track reports lyrics, so an album without lyrics costs nothing.
get_album_lyrics answers for every track of the album with one request. When the id is really a standalone track, the album request answers an empty map, and the client asks again as a track. That costs one more request and covers the same ids that get_album resolves through its track fallback.
A failed lyrics request never breaks the call. The track comes back with an empty lyrics field, and the client writes a warning to the log.
Bandcamp serves plain text only. There is no timed variant.
API Reference
Core Client
BandcampAPIClient()- Main API clientsearch(query: str)- Search Bandcampget_album(artist_id, album_id, *, with_lyrics=False)- Get album detailsget_track(artist_id, track_id, *, with_lyrics=False)- Get track detailsget_lyrics(tralbum_id, tralbum_type)- Get lyrics as a track ID to text map; the type constantsTRALBUM_TYPE_ALBUMandTRALBUM_TYPE_TRACKare exportedget_album_lyrics(album_id)- Get the lyrics of every album track in one requestget_track_lyrics(track_id)- Get the lyrics of a standalone trackget_artist(artist_id)- Get artist detailsget_collection_summary()- Get collection overviewget_collection_items(collection_type, older_than_token, count, fan_id)- Get collection/wishlist/following items with paginationget_artist_discography(artist_id)- Get artist's complete discographyget_feed(older_than)- Get personalized music feed with pagination support
Data Models
SearchResultItem- Base search resultBCAlbum- Album with tracks and metadataBCTrack- Individual track informationBCArtist- Artist/band profileCollectionSummary- User's collection dataCollectionItem- Individual collection itemFollowingItem- Band/artist from following listFanItem- Fan/user from following_fans or followersFeedResponse- User's music feed with stories and tracksFeedStory- Individual feed story (new release, fan purchase, etc.)FeedTrack- Track from feed with streaming URLFeedBandInfo- Band information referenced in feedFeedFanInfo- Fan information referenced in feed
Exceptions
BandcampAPIError- Base API errorBandcampNotFoundError- Resource not foundBandcampBadQueryError- Invalid search queryBandcampRateLimitError- Rate limit exceeded (includesretry_afterattribute)
Error Handling
The client provides specific exception types for different error conditions:
from bandcamp_async_api import (
BandcampAPIClient,
BandcampNotFoundError,
BandcampAPIError,
)
async def safe_get_album(client, artist_id, album_id):
try:
return await client.get_album(artist_id, album_id)
except BandcampNotFoundError:
print("Album not found")
return None
except BandcampAPIError as e:
print(f"API error: {e}")
return None
Rate Limiting
When Bandcamp's API rate limit is exceeded, a BandcampRateLimitError is raised with a retry_after attribute indicating how many seconds to wait before retrying:
import asyncio
from bandcamp_async_api import BandcampAPIClient, BandcampRateLimitError
async def get_album_with_retry(client, artist_id, album_id, max_retries=3):
for attempt in range(max_retries):
try:
return await client.get_album(artist_id, album_id)
except BandcampRateLimitError as e:
if attempt < max_retries - 1:
wait_time = e.retry_after or 30
print(f"Rate limited. Waiting {wait_time} seconds...")
await asyncio.sleep(wait_time)
else:
raise
For automatic retries with exponential backoff, you can use the tenacity library:
from tenacity import retry, retry_if_exception_type, wait_exponential
from bandcamp_async_api import BandcampAPIClient, BandcampRateLimitError
@retry(
retry=retry_if_exception_type(BandcampRateLimitError),
wait=wait_exponential(multiplier=1, min=30, max=300),
)
async def get_album(client, artist_id, album_id):
return await client.get_album(artist_id, album_id)
Development
Setup
# Clone the repository
git clone https://github.com/ALERTua/bandcamp_async_api.git
cd bandcamp_async_api
# Install dependencies
uv sync --dev
# Run tests
uv run pytest
# Run linting
uv run ruff check
Testing
The project includes comprehensive tests:
# Run all tests
uv run pytest
# Run integration tests (requires real API access)
echo "BANDCAMP_IDENTITY_TOKEN=7%09identity_token%7D" > .env
uv run pytest tests/real_data/
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is built based on data from:
Release files for bandcamp-async-api 0.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bandcamp_async_api-0.2.4.tar.gz | 16.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| bandcamp_async_api-0.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.8 kB
Release files / bandcamp_async_api-0.2.4.tar.gz
| Download URL | bandcamp_async_api-0.2.4.tar.gz |
|---|---|
| Size | 16.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1cc064e8c629dd60bfe8ecd39800306dee1da61f2ac9aeaf6a6ece85b432e27f
|
|
BLAKE2b-256 checksum How to use checksums |
f2c839aecd1befd151cb7854521fae49573a4239027644192a9780164a302107
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / bandcamp_async_api-0.2.4-py3-none-any.whl
| Download URL | bandcamp_async_api-0.2.4-py3-none-any.whl |
|---|---|
| Size | 17.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a88cdbc1c02476ab5e806a0f728cda01df2c1b244dceebcdc12a7da5e0fc12a9
|
|
BLAKE2b-256 checksum How to use checksums |
091a1779c327e28767f157eb443bd5e2c97a4b1c881713188441076a31d9433b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.8 {"installer":{"name":"uv","version":"0.12.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|