# minim: API Reference ## API Type minim is a **Python library**, not an HTTP API. This document describes the public Python API surface for each module. **Import Pattern:** ```python from minim import spotify, tidal, qobuz, discogs, itunes from minim.audio import Audio ``` ## audio Module ### Audio Class **Purpose:** Read, write, and convert audio file metadata. **Constructor:** ```python Audio(filepath: str) ``` **Parameters:** - `filepath`: Path to audio file (FLAC, MP3, MP4/M4A, Ogg Vorbis, WAVE) **Returns:** Subclass instance (`FLAC`, `MP3`, `MP4`, `OggVorbis`, `WAVE`) based on file format. **Raises:** - `FileNotFoundError`: File does not exist - `ValueError`: Unsupported audio format **Example:** ```python audio = Audio("track.flac") # Returns FLAC instance print(audio.title, audio.artist, audio.album) ``` ### Attributes All `Audio` instances expose these attributes: | Attribute | Type | Description | |-----------|------|-------------| | `filepath` | `str` | Path to audio file | | `title` | `str` | Track title | | `artist` | `str` | Primary artist(s), comma-separated | | `album` | `str` | Album title | | `album_artist` | `str` | Album artist (for compilations) | | `date` | `str` | Release date (YYYY-MM-DD or YYYY) | | `genre` | `str` | Genre classification | | `track_number` | `int` | Track position in album | | `disc_number` | `int` | Disc number (for multi-disc albums) | | `isrc` | `str` | International Standard Recording Code | | `upc` | `str` | Universal Product Code (album barcode) | | `artwork` | `bytes` | Album cover image (JPEG or PNG) | | `lyrics` | `str` | Song lyrics | | `comment` | `str` | Freeform comment field | | `copyright` | `str` | Copyright notice | | `duration` | `float` | Track duration in seconds | | `bitrate` | `int` | Bitrate in bits per second | | `sample_rate` | `int` | Sample rate in Hz | | `channels` | `int` | Number of audio channels (1=mono, 2=stereo) | ### Methods #### read_metadata() ```python audio.read_metadata() -> None ``` Read metadata from file and populate attributes. Called automatically by constructor. #### write_metadata() ```python audio.write_metadata() -> None ``` Write current attribute values to file tags. **Example:** ```python audio = Audio("track.flac") audio.title = "New Title" audio.artist = "New Artist" audio.write_metadata() ``` #### convert() ```python audio.convert(output_path: str, format: str, **ffmpeg_options) -> Audio ``` Convert audio file to different format using FFmpeg. **Parameters:** - `output_path`: Destination file path - `format`: Target format (`"flac"`, `"mp3"`, `"m4a"`, `"ogg"`, `"wav"`) - `**ffmpeg_options`: Additional FFmpeg arguments (e.g., `bitrate="320k"`, `sample_rate=48000`) **Returns:** New `Audio` instance for converted file with metadata copied. **Requires:** FFmpeg installed and in PATH. **Example:** ```python audio = Audio("track.flac") mp3_audio = audio.convert("track.mp3", "mp3", bitrate="320k") ``` #### set_metadata_using_itunes() ```python audio.set_metadata_using_itunes(data: dict) -> None ``` Map iTunes Search API track object to audio metadata. **Parameters:** - `data`: Track object from `itunes.SearchAPI.lookup()` or `search()` **Example:** ```python itunes_api = itunes.SearchAPI() results = itunes_api.search("Radiohead Creep", media="music", entity="song") track = results["results"][0] audio = Audio("track.mp3") audio.set_metadata_using_itunes(track) audio.write_metadata() ``` #### set_metadata_using_qobuz() ```python audio.set_metadata_using_qobuz(data: dict) -> None ``` Map Qobuz API track object to audio metadata. **Parameters:** - `data`: Track object from `qobuz.PrivateAPI.get_track()` #### set_metadata_using_spotify() ```python audio.set_metadata_using_spotify(data: dict) -> None ``` Map Spotify Web API track object to audio metadata. **Parameters:** - `data`: Track object from `spotify.WebAPI.get_track()` or `search()` **Example:** ```python spotify_api = spotify.WebAPI(client_id="...", client_secret="...") spotify_api.set_flow("client_credentials") spotify_api.set_access_token() track = spotify_api.get_track("3n3Ppam7vgaVa1iaRUc9Lp") # Radiohead - Creep audio = Audio("track.flac") audio.set_metadata_using_spotify(track) audio.write_metadata() ``` #### set_metadata_using_tidal() ```python audio.set_metadata_using_tidal(data: dict) -> None ``` Map TIDAL API track object to audio metadata. **Parameters:** - `data`: Track object from `tidal.API.get_track()` or `tidal.PrivateAPI.get_track()` ## discogs Module ### API Class **Purpose:** Interact with Discogs database, marketplace, collection, and wantlist. **Constructor:** ```python discogs.API( consumer_key: str = None, consumer_secret: str = None, access_token: str = None, access_token_secret: str = None, personal_access_token: str = None ) ``` **Parameters:** - `consumer_key`: OAuth consumer key (from Discogs app settings) - `consumer_secret`: OAuth consumer secret - `access_token`: OAuth access token (obtained via `set_access_token()`) - `access_token_secret`: OAuth access token secret - `personal_access_token`: Alternative to OAuth (from Discogs settings) **Authentication:** OAuth 1.0a or personal access token. OAuth required for write operations (collection, wantlist). ### Methods #### search() ```python api.search( query: str = None, type: str = None, title: str = None, artist: str = None, label: str = None, genre: str = None, year: str = None, format: str = None, country: str = None, per_page: int = 50, page: int = 1 ) -> dict ``` Search Discogs database. **Parameters:** - `query`: General search query - `type`: Result type (`"release"`, `"master"`, `"artist"`, `"label"`) - `title`: Filter by release title - `artist`: Filter by artist name - `label`: Filter by label name - `genre`: Filter by genre - `year`: Filter by release year - `format`: Filter by format (e.g., `"Vinyl"`, `"CD"`) - `country`: Filter by release country - `per_page`: Results per page (max 100) - `page`: Page number **Returns:** Dict with `results` array and pagination info. **Example:** ```python api = discogs.API(personal_access_token="...") results = api.search(artist="Radiohead", type="release", format="Vinyl") for release in results["results"]: print(release["title"], release["year"]) ``` #### get_artist() ```python api.get_artist(artist_id: int) -> dict ``` Get artist details. **Parameters:** - `artist_id`: Discogs artist ID **Returns:** Artist object with name, profile, images, members, URLs. #### get_release() ```python api.get_release(release_id: int) -> dict ``` Get release details. **Parameters:** - `release_id`: Discogs release ID **Returns:** Release object with tracklist, artists, labels, formats, identifiers (barcode, catalog number). #### get_master_release() ```python api.get_master_release(master_id: int) -> dict ``` Get master release details (canonical version of a release with multiple pressings). **Parameters:** - `master_id`: Discogs master release ID **Returns:** Master release object with main release info and versions list. #### get_label() ```python api.get_label(label_id: int) -> dict ``` Get label details. **Parameters:** - `label_id`: Discogs label ID **Returns:** Label object with name, profile, images, sublabels, releases. #### get_collection_folders() ```python api.get_collection_folders(username: str) -> dict ``` Get user's collection folders. **Parameters:** - `username`: Discogs username **Returns:** Array of folder objects with ID, name, count. #### get_collection_items() ```python api.get_collection_items( username: str, folder_id: int = 0, per_page: int = 50, page: int = 1 ) -> dict ``` Get items in collection folder. **Parameters:** - `username`: Discogs username - `folder_id`: Folder ID (0 = "All" folder) - `per_page`: Results per page - `page`: Page number **Returns:** Array of collection items with release info, notes, rating. #### add_to_collection() ```python api.add_to_collection( username: str, folder_id: int, release_id: int ) -> dict ``` Add release to collection. **Requires:** OAuth authentication. #### remove_from_collection() ```python api.remove_from_collection( username: str, folder_id: int, release_id: int, instance_id: int ) -> None ``` Remove release from collection. **Requires:** OAuth authentication. #### get_wantlist() ```python api.get_wantlist( username: str, per_page: int = 50, page: int = 1 ) -> dict ``` Get user's wantlist. #### add_to_wantlist() ```python api.add_to_wantlist(username: str, release_id: int) -> dict ``` Add release to wantlist. **Requires:** OAuth authentication. #### remove_from_wantlist() ```python api.remove_from_wantlist(username: str, release_id: int) -> None ``` Remove release from wantlist. **Requires:** OAuth authentication. ## itunes Module ### SearchAPI Class **Purpose:** Search and lookup items in iTunes catalog. **Constructor:** ```python itunes.SearchAPI() ``` **Authentication:** None required (public API). ### Methods #### search() ```python api.search( term: str, country: str = "US", media: str = "all", entity: str = None, attribute: str = None, limit: int = 50, lang: str = "en_us", explicit: str = "Yes" ) -> dict ``` Search iTunes catalog. **Parameters:** - `term`: Search query - `country`: Two-letter country code (e.g., `"US"`, `"GB"`, `"JP"`) - `media`: Media type (`"all"`, `"music"`, `"movie"`, `"podcast"`, `"audiobook"`, etc.) - `entity`: Entity type (`"song"`, `"album"`, `"musicArtist"`, `"movie"`, etc.) - `attribute`: Search attribute (`"artistTerm"`, `"albumTerm"`, `"songTerm"`, etc.) - `limit`: Max results (max 200) - `lang`: Language code - `explicit`: Include explicit content (`"Yes"` or `"No"`) **Returns:** Dict with `resultCount` and `results` array. **Example:** ```python api = itunes.SearchAPI() results = api.search("Radiohead", media="music", entity="musicArtist", limit=10) for artist in results["results"]: print(artist["artistName"], artist["artistId"]) ``` #### lookup() ```python api.lookup( id: int = None, amg_artist_id: int = None, amg_album_id: int = None, upc: str = None, isbn: str = None, entity: str = None, limit: int = 50 ) -> dict ``` Lookup item by ID or identifier. **Parameters:** - `id`: iTunes ID (artist, album, track, etc.) - `amg_artist_id`: All Music Guide artist ID - `amg_album_id`: All Music Guide album ID - `upc`: Universal Product Code (album barcode) - `isbn`: ISBN (for books) - `entity`: Entity type to return - `limit`: Max results **Returns:** Dict with `resultCount` and `results` array. **Example:** ```python # Lookup by iTunes ID results = api.lookup(id=1419227, entity="album") # Lookup by UPC results = api.lookup(upc="724384260910") ``` ## qobuz Module ### PrivateAPI Class **Purpose:** Interact with Qobuz catalog and streaming service. **Note:** Uses undocumented private API endpoints. May break without notice. **Constructor:** ```python qobuz.PrivateAPI( app_id: str = None, app_secret: str = None, email: str = None, password: str = None, access_token: str = None ) ``` **Parameters:** - `app_id`: Qobuz app ID (auto-extracted from web player if not provided) - `app_secret`: Qobuz app secret (auto-extracted) - `email`: User email for password grant - `password`: User password - `access_token`: Existing access token **Authentication:** Password grant OAuth. Requires Qobuz subscription for streaming URLs. ### Methods #### search() ```python api.search( query: str, type: str = "tracks", limit: int = 50, offset: int = 0 ) -> dict ``` Search Qobuz catalog. **Parameters:** - `query`: Search query - `type`: Result type (`"tracks"`, `"albums"`, `"artists"`, `"playlists"`) - `limit`: Max results - `offset`: Pagination offset **Returns:** Dict with type-specific results array. #### get_artist() ```python api.get_artist(artist_id: int) -> dict ``` Get artist details. #### get_album() ```python api.get_album(album_id: str) -> dict ``` Get album details with tracklist. **Parameters:** - `album_id`: Qobuz album ID (string, not int) **Returns:** Album object with tracks, artists, label, release date, UPC. #### get_track() ```python api.get_track(track_id: int) -> dict ``` Get track details. #### get_track_file_url() ```python api.get_track_file_url( track_id: int, quality: int = 27 ) -> dict ``` Get streaming URL for track. **Parameters:** - `track_id`: Qobuz track ID - `quality`: Audio quality (5=MP3 320kbps, 6=FLAC 16/44.1, 7=FLAC 24/96, 27=FLAC Hi-Res) **Returns:** Dict with `url`, `format_id`, `mime_type`, `sampling_rate`, `bit_depth`. **Requires:** Active Qobuz subscription. Quality availability depends on subscription tier. **Example:** ```python api = qobuz.PrivateAPI(email="user@example.com", password="...") api.set_access_token() track = api.get_track(12345678) file_url = api.get_track_file_url(track["id"], quality=27) # Download track import requests audio_data = requests.get(file_url["url"]).content with open("track.flac", "wb") as f: f.write(audio_data) ``` #### get_user_playlists() ```python api.get_user_playlists(limit: int = 50, offset: int = 0) -> dict ``` Get user's playlists. #### get_playlist() ```python api.get_playlist(playlist_id: str) -> dict ``` Get playlist details with tracks. #### create_playlist() ```python api.create_playlist( name: str, description: str = "", is_public: bool = False ) -> dict ``` Create new playlist. #### add_playlist_tracks() ```python api.add_playlist_tracks( playlist_id: str, track_ids: list[int] ) -> dict ``` Add tracks to playlist. #### get_favorites() ```python api.get_favorites( type: str = "tracks", limit: int = 50, offset: int = 0 ) -> dict ``` Get user's favorites. **Parameters:** - `type`: Favorite type (`"tracks"`, `"albums"`, `"artists"`) #### add_favorite() ```python api.add_favorite(item_id: int, type: str = "tracks") -> dict ``` Add item to favorites. #### remove_favorite() ```python api.remove_favorite(item_id: int, type: str = "tracks") -> dict ``` Remove item from favorites. ## spotify Module ### WebAPI Class **Purpose:** Interact with Spotify Web API. **Constructor:** ```python spotify.WebAPI( client_id: str = None, client_secret: str = None, redirect_uri: str = "http://localhost:8888", access_token: str = None, refresh_token: str = None ) ``` **Parameters:** - `client_id`: Spotify app client ID - `client_secret`: Spotify app client secret - `redirect_uri`: OAuth redirect URI (must match app settings) - `access_token`: Existing access token - `refresh_token`: Existing refresh token **Authentication:** OAuth 2.0 with four flow types (set via `set_flow()`). ### Authentication Methods #### set_flow() ```python api.set_flow( flow_type: str = "authorization_code", scopes: list[str] = None ) ``` Configure OAuth flow. **Parameters:** - `flow_type`: Flow type (`"authorization_code"`, `"pkce"`, `"client_credentials"`, `"web_player"`) - `scopes`: Permission scopes (see Spotify documentation for available scopes) **Common Scopes:** - `user-read-private`: Read user profile - `user-read-email`: Read user email - `user-library-read`: Read saved tracks/albums - `user-library-modify`: Modify saved tracks/albums - `playlist-read-private`: Read private playlists - `playlist-modify-public`: Modify public playlists - `playlist-modify-private`: Modify private playlists - `user-read-playback-state`: Read playback state - `user-modify-playback-state`: Control playback #### set_access_token() ```python api.set_access_token(method: str = "http.server") ``` Obtain access token via OAuth flow. **Parameters:** - `method`: Callback method (`"http.server"`, `"flask"`, `"playwright"`) **Example:** ```python api = spotify.WebAPI(client_id="...", client_secret="...") api.set_flow("authorization_code", scopes=["user-library-read", "playlist-read-private"]) api.set_access_token() # Opens browser for user login ``` ### Catalog Methods #### get_album() ```python api.get_album(album_id: str, market: str = None) -> dict ``` Get album details. **Parameters:** - `album_id`: Spotify album ID - `market`: ISO 3166-1 alpha-2 country code (affects track availability) #### get_artist() ```python api.get_artist(artist_id: str) -> dict ``` Get artist details. #### get_track() ```python api.get_track(track_id: str, market: str = None) -> dict ``` Get track details. #### get_playlist() ```python api.get_playlist( playlist_id: str, market: str = None, fields: str = None ) -> dict ``` Get playlist details. **Parameters:** - `playlist_id`: Spotify playlist ID - `market`: Country code - `fields`: Comma-separated list of fields to return (filters response) #### search() ```python api.search( query: str, types: list[str] = ["track"], market: str = None, limit: int = 20, offset: int = 0 ) -> dict ``` Search Spotify catalog. **Parameters:** - `query`: Search query (supports field filters like `artist:Radiohead track:Creep`) - `types`: Result types (`["track"]`, `["album"]`, `["artist"]`, `["playlist"]`, or combinations) - `market`: Country code - `limit`: Max results per type (max 50) - `offset`: Pagination offset **Returns:** Dict with keys for each type (`tracks`, `albums`, `artists`, `playlists`), each containing `items` array and pagination info. **Example:** ```python results = api.search("Radiohead Creep", types=["track", "album"], limit=10) for track in results["tracks"]["items"]: print(track["name"], track["artists"][0]["name"]) ``` ### Library Methods #### get_saved_tracks() ```python api.get_saved_tracks(limit: int = 20, offset: int = 0, market: str = None) -> dict ``` Get user's saved tracks. **Requires:** `user-library-read` scope. #### save_tracks() ```python api.save_tracks(track_ids: list[str]) -> None ``` Save tracks to user's library. **Requires:** `user-library-modify` scope. #### remove_saved_tracks() ```python api.remove_saved_tracks(track_ids: list[str]) -> None ``` Remove tracks from user's library. **Requires:** `user-library-modify` scope. #### check_saved_tracks() ```python api.check_saved_tracks(track_ids: list[str]) -> list[bool] ``` Check if tracks are in user's library. **Returns:** Array of booleans corresponding to input track IDs. ### Playlist Methods #### get_user_playlists() ```python api.get_user_playlists(limit: int = 20, offset: int = 0) -> dict ``` Get current user's playlists. **Requires:** `playlist-read-private` scope. #### create_playlist() ```python api.create_playlist( user_id: str, name: str, description: str = "", public: bool = True ) -> dict ``` Create playlist. **Requires:** `playlist-modify-public` or `playlist-modify-private` scope. #### add_playlist_items() ```python api.add_playlist_items( playlist_id: str, uris: list[str], position: int = None ) -> dict ``` Add tracks to playlist. **Parameters:** - `playlist_id`: Spotify playlist ID - `uris`: Track URIs (format: `spotify:track:{id}`) - `position`: Insert position (0-based, None = append) #### remove_playlist_items() ```python api.remove_playlist_items( playlist_id: str, uris: list[str] ) -> dict ``` Remove tracks from playlist. ### Playback Methods #### get_playback_state() ```python api.get_playback_state(market: str = None) -> dict ``` Get current playback state. **Requires:** `user-read-playback-state` scope. **Returns:** Dict with device, track, progress, shuffle/repeat state, or `None` if nothing playing. #### start_playback() ```python api.start_playback( device_id: str = None, context_uri: str = None, uris: list[str] = None, offset: dict = None, position_ms: int = None ) -> None ``` Start or resume playback. **Requires:** `user-modify-playback-state` scope. #### pause_playback() ```python api.pause_playback(device_id: str = None) -> None ``` Pause playback. #### skip_to_next() ```python api.skip_to_next(device_id: str = None) -> None ``` Skip to next track. #### skip_to_previous() ```python api.skip_to_previous(device_id: str = None) -> None ``` Skip to previous track. ### Audio Features #### get_audio_features() ```python api.get_audio_features(track_id: str) -> dict ``` Get audio features for track. **Returns:** Dict with: - `danceability`: 0.0 to 1.0 - `energy`: 0.0 to 1.0 - `key`: Pitch class (0=C, 1=C#, etc.) - `loudness`: dB - `mode`: Major (1) or minor (0) - `speechiness`: 0.0 to 1.0 - `acousticness`: 0.0 to 1.0 - `instrumentalness`: 0.0 to 1.0 - `liveness`: 0.0 to 1.0 - `valence`: 0.0 to 1.0 (positivity) - `tempo`: BPM - `duration_ms`: Track length - `time_signature`: 3 to 7 ### PrivateLyricsService Class **Purpose:** Get lyrics via Spotify's Musixmatch integration. **Note:** Undocumented private API. May break without notice. **Constructor:** ```python spotify.PrivateLyricsService(sp_dc: str = None) ``` **Parameters:** - `sp_dc`: Spotify `sp_dc` cookie value (from browser) #### get_lyrics() ```python service.get_lyrics(track_id: str) -> dict ``` Get lyrics for track. **Returns:** Dict with `lyrics` object containing `lines` array. Each line has `startTimeMs`, `words`, `syllables`. **Example:** ```python service = spotify.PrivateLyricsService(sp_dc="...") lyrics = service.get_lyrics("3n3Ppam7vgaVa1iaRUc9Lp") for line in lyrics["lyrics"]["lines"]: print(f"[{line['startTimeMs']}ms] {line['words']}") ``` ## tidal Module ### API Class **Purpose:** Interact with TIDAL public API. **Constructor:** ```python tidal.API( client_id: str = None, client_secret: str = None, access_token: str = None, refresh_token: str = None ) ``` **Authentication:** OAuth 2.0 (client credentials or PKCE). ### PrivateAPI Class **Purpose:** Interact with TIDAL private API (streaming URLs, lyrics, credits). **Note:** Uses undocumented endpoints. May break without notice. **Constructor:** ```python tidal.PrivateAPI( client_id: str = None, client_secret: str = None, access_token: str = None, refresh_token: str = None, user_id: int = None, country_code: str = "US" ) ``` ### Methods (Common to Both Classes) #### search() ```python api.search( query: str, type: str = "TRACKS", limit: int = 50, offset: int = 0 ) -> dict ``` Search TIDAL catalog. **Parameters:** - `query`: Search query - `type`: Result type (`"TRACKS"`, `"ALBUMS"`, `"ARTISTS"`, `"PLAYLISTS"`) - `limit`: Max results - `offset`: Pagination offset #### get_artist() ```python api.get_artist(artist_id: int) -> dict ``` Get artist details. #### get_album() ```python api.get_album(album_id: int) -> dict ``` Get album details. #### get_track() ```python api.get_track(track_id: int) -> dict ``` Get track details. ### PrivateAPI-Specific Methods #### get_track_stream_url() ```python api.get_track_stream_url( track_id: int, quality: str = "LOSSLESS" ) -> str ``` Get streaming URL for track. **Parameters:** - `track_id`: TIDAL track ID - `quality`: Audio quality (`"LOW"`, `"HIGH"`, `"LOSSLESS"`, `"HI_RES"`, `"HI_RES_LOSSLESS"`) **Returns:** Direct streaming URL (time-limited). **Quality Levels:** - `LOW`: 96 kbps AAC - `HIGH`: 320 kbps AAC - `LOSSLESS`: FLAC 16-bit/44.1kHz (CD quality) - `HI_RES`: FLAC 24-bit/96kHz or higher - `HI_RES_LOSSLESS`: MQA (Master Quality Authenticated) **Requires:** Active TIDAL subscription. Quality availability depends on subscription tier (HiFi, HiFi Plus). **Example:** ```python api = tidal.PrivateAPI(client_id="...", client_secret="...") api.set_flow("pkce") api.set_access_token() url = api.get_track_stream_url(12345678, quality="HI_RES") import requests audio_data = requests.get(url).content with open("track.flac", "wb") as f: f.write(audio_data) ``` #### get_track_lyrics() ```python api.get_track_lyrics(track_id: int) -> dict ``` Get lyrics for track. **Returns:** Dict with `lyrics` (plain text) and `subtitles` (LRC format with timestamps). #### get_track_credits() ```python api.get_track_credits(track_id: int) -> dict ``` Get credits for track (producers, engineers, musicians, etc.). **Returns:** Dict with `credits` array containing `type` and `contributors`. #### get_user_playlists() ```python api.get_user_playlists(user_id: int = None, limit: int = 50, offset: int = 0) -> dict ``` Get user's playlists. #### get_playlist() ```python api.get_playlist(playlist_id: str) -> dict ``` Get playlist details with tracks. #### create_playlist() ```python api.create_playlist(title: str, description: str = "") -> dict ``` Create playlist. #### add_playlist_items() ```python api.add_playlist_items(playlist_id: str, track_ids: list[int]) -> dict ``` Add tracks to playlist. ## Error Handling All API methods raise `RuntimeError` on HTTP errors: ```python try: track = api.get_track(12345) except RuntimeError as e: print(f"API error: {e}") ``` **Error Message Format:** `"{METHOD} {URL} failed: {status_code} {response_text}"` **No Typed Exceptions:** All errors are generic `RuntimeError`. Parse error message to determine cause. ## Rate Limiting **Not Implemented:** minim does not enforce rate limits. Caller must respect service limits: - **Discogs:** 60 req/min (authenticated), 25 req/min (unauthenticated) - **iTunes:** ~20 req/min (undocumented) - **Qobuz:** Unknown (private API) - **Spotify:** Varies by endpoint, typically 180 req/30sec - **TIDAL:** Unknown (private API) **Recommendation:** Implement exponential backoff and respect `Retry-After` headers. ## Summary minim provides comprehensive Python APIs for five music services: - **discogs:** Database, marketplace, collection, wantlist - **itunes:** Public search and lookup - **qobuz:** Catalog, streaming, playlists, favorites - **spotify:** Full Web API coverage, playback control, audio features, lyrics - **tidal:** Catalog, high-resolution streaming, lyrics, credits All APIs follow consistent patterns (authentication, request handling, error raising) while exposing service-specific features. The `Audio` class bridges API responses to audio file metadata, enabling automated tagging workflows.