Skip to main content

Stand With Ukraine Made in Ukraine Stand With Ukraine Russian Warship Go Fuck Yourself

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.

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 release
  • nr - New album release
  • p - Fan purchase
  • fp - 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. None when 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 on album.artist.name when present. Consumers that relied on that must read album.tralbum_artist instead. The same applies to BCTrack.

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.
    lyrics = await client.get_lyrics(1994024535, "a")

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.

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 client
  • search(query: str) - Search Bandcamp
  • get_album(artist_id, album_id, *, with_lyrics=False) - Get album details
  • get_track(artist_id, track_id, *, with_lyrics=False) - Get track details
  • get_lyrics(tralbum_id, tralbum_type) - Get lyrics as a track ID to text map
  • get_artist(artist_id) - Get artist details
  • get_collection_summary() - Get collection overview
  • get_collection_items(collection_type, older_than_token, count, fan_id) - Get collection/wishlist/following items with pagination
  • get_artist_discography(artist_id) - Get artist's complete discography
  • get_feed(older_than) - Get personalized music feed with pagination support

Data Models

  • SearchResultItem - Base search result
  • BCAlbum - Album with tracks and metadata
  • BCTrack - Individual track information
  • BCArtist - Artist/band profile
  • CollectionSummary - User's collection data
  • CollectionItem - Individual collection item
  • FollowingItem - Band/artist from following list
  • FanItem - Fan/user from following_fans or followers
  • FeedResponse - User's music feed with stories and tracks
  • FeedStory - Individual feed story (new release, fan purchase, etc.)
  • FeedTrack - Track from feed with streaming URL
  • FeedBandInfo - Band information referenced in feed
  • FeedFanInfo - Fan information referenced in feed

Exceptions

  • BandcampAPIError - Base API error
  • BandcampNotFoundError - Resource not found
  • BandcampBadQueryError - Invalid search query
  • BandcampRateLimitError - Rate limit exceeded (includes retry_after attribute)

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:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

This project is built based on data from:

Release files for bandcamp-async-api 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bandcamp-async-api 0.2.3
File Size Uploaded
bandcamp_async_api-0.2.3.tar.gz 15.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bandcamp-async-api 0.2.3
File Interpreter ABI Platform
bandcamp_async_api-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 32.7 kB

Release files / bandcamp_async_api-0.2.3.tar.gz

Download URL bandcamp_async_api-0.2.3.tar.gz
Size 15.6 kB
Tags Source
SHA-256 checksum
How to use checksums
c88b0f7a9bfd65c4980696e71c8c752a28f426a36606c19628a33338c62465e9
BLAKE2b-256 checksum
How to use checksums
aad0c140a0dd1dca6597352ca530be149643c4c1a087e3d67937629af0332bdf
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.3-py3-none-any.whl

Download URL bandcamp_async_api-0.2.3-py3-none-any.whl
Size 17.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
be5914a44bab8f4072ea5c252966f433eb76677f7486f6f9670dec05fcc58998
BLAKE2b-256 checksum
How to use checksums
81ffadac3ffc1d05c72677de915f8753e58c5245aecdde28a0c0fa343589d26e
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 history Release notifications | RSS feed

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page