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
- 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
- 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
Install from PyPI:
pip install bandcamp-async-apiOr using uv:
uv add bandcamp-async-apiimport 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())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")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())The feed contains different story types:
np- New track/track releasenr- New album releasep- Fan purchasefp- Fan pick
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")BandcampAPIClient()- Main API clientsearch(query: str)- Search Bandcampget_album(artist_id, album_id)- Get album detailsget_track(artist_id, track_id)- Get track detailsget_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
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
BandcampAPIError- Base API errorBandcampNotFoundError- Resource not foundBandcampBadQueryError- Invalid search queryBandcampRateLimitError- Rate limit exceeded (includesretry_afterattribute)
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 NoneWhen 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:
raiseFor 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)# 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 checkThe 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/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: