# minim: Service Integrations ## Overview minim integrates with five music services, each with different authentication methods, API coverage, and capabilities: | Service | Auth Method | API Type | Streaming | Lyrics | Credits | Rate Limit | |---------|-------------|----------|-----------|--------|---------|------------| | Discogs | OAuth 1.0a, Personal Token | Public | No | No | Yes | 60/min (auth), 25/min (unauth) | | iTunes | None | Public | No | No | No | ~20/min | | Qobuz | Password Grant | Private | Yes | No | No | Unknown | | Spotify | OAuth 2.0 (4 flows) | Public + Private | No | Yes | No | 180/30sec | | TIDAL | OAuth 2.0 (PKCE, Client Creds) | Public + Private | Yes | Yes | Yes | Unknown | ## Discogs Integration ### Service Overview **Purpose:** Music database, marketplace, collection management **Website:** https://www.discogs.com **API Documentation:** https://www.discogs.com/developers **API Type:** Public, documented, RESTful ### Authentication **Method 1: OAuth 1.0a** **Setup:** 1. Create app at https://www.discogs.com/settings/developers 2. Obtain consumer key and consumer secret 3. Implement OAuth 1.0a flow (request token → user authorization → access token) **Implementation:** ```python from minim import discogs api = discogs.API( consumer_key="Abcd1234Efgh5678", consumer_secret="IjklMnopQrstUvwx" ) # OAuth flow (opens browser) api.set_access_token() # Tokens saved to ~/minim.cfg ``` **Method 2: Personal Access Token** **Setup:** 1. Generate token at https://www.discogs.com/settings/developers 2. Use token directly (no OAuth flow) **Implementation:** ```python api = discogs.API(personal_access_token="YourPersonalToken") ``` **Comparison:** - OAuth 1.0a: Required for write operations (collection, wantlist), higher rate limit - Personal Token: Read-only, simpler setup, same rate limit as OAuth ### API Coverage **Database:** - Search releases, artists, labels, masters - Get detailed information (tracklist, credits, images, identifiers) - Browse by genre, style, format, year, country **Marketplace:** - Search listings - Get listing details (price, condition, seller) - Not implemented in minim (read-only marketplace access) **Collection:** - Get user's collection folders - List items in folder - Add/remove releases - Update notes and rating **Wantlist:** - Get user's wantlist - Add/remove releases - Not implemented: Update notes **User:** - Get user profile - Get user submissions (releases, artists, labels) - Not implemented in minim ### Data Model **Release Object:** ```json { "id": 249504, "title": "OK Computer", "artists": [{"name": "Radiohead", "id": 3840}], "labels": [{"name": "Parlophone", "catno": "7243 8 55229 2 5"}], "formats": [{"name": "CD", "qty": "1", "descriptions": ["Album"]}], "year": 1997, "country": "UK", "genres": ["Electronic", "Rock"], "styles": ["Alternative Rock", "Experimental"], "tracklist": [ {"position": "1", "title": "Airbag", "duration": "4:44"}, {"position": "2", "title": "Paranoid Android", "duration": "6:23"} ], "identifiers": [ {"type": "Barcode", "value": "724385522925"} ], "images": [ {"type": "primary", "uri": "https://...", "width": 600, "height": 600} ] } ``` **Artist Object:** ```json { "id": 3840, "name": "Radiohead", "profile": "English rock band formed in 1985...", "members": [ {"name": "Thom Yorke", "id": 239}, {"name": "Jonny Greenwood", "id": 240} ], "urls": ["https://www.radiohead.com"], "images": [...] } ``` ### Rate Limiting **Authenticated:** 60 requests per minute **Unauthenticated:** 25 requests per minute **Headers:** - `X-Discogs-Ratelimit`: Total requests allowed per minute - `X-Discogs-Ratelimit-Remaining`: Requests remaining in current window - `X-Discogs-Ratelimit-Used`: Requests used in current window **Enforcement:** HTTP 429 (Too Many Requests) when limit exceeded. **minim Implementation:** Does not check rate limit headers. Caller responsible for tracking. ### Use Cases 1. **Metadata Enrichment:** Get detailed release information (catalog numbers, barcodes, formats) 2. **Collection Management:** Sync local library with Discogs collection 3. **Credits Extraction:** Get producer, engineer, musician credits from tracklist 4. **Format Identification:** Determine pressing details (country, year, label, catalog number) ### Limitations - No streaming or preview URLs - No lyrics - Marketplace write operations not implemented - User-submitted data (quality varies) - Rate limiting requires manual tracking ## iTunes Integration ### Service Overview **Purpose:** Public music catalog search and lookup **Website:** https://www.apple.com/itunes **API Documentation:** https://developer.apple.com/library/archive/documentation/AudioVideo/Conceptual/iTuneSearchAPI **API Type:** Public, documented, RESTful ### Authentication **None required.** iTunes Search API is completely public. **Implementation:** ```python from minim import itunes api = itunes.SearchAPI() results = api.search("Radiohead", media="music", entity="musicArtist") ``` ### API Coverage **Search:** - Search by term across all media types - Filter by media (music, movie, podcast, audiobook, etc.) - Filter by entity (song, album, artist, etc.) - Filter by attribute (artist name, album name, song name, etc.) **Lookup:** - Lookup by iTunes ID - Lookup by UPC (album barcode) - Lookup by ISBN (books) - Lookup by AMG (All Music Guide) ID **Not Available:** - Streaming URLs - Lyrics - User library access - Playlist management ### Data Model **Track Object:** ```json { "trackId": 1109731797, "trackName": "Creep", "artistName": "Radiohead", "collectionName": "Pablo Honey", "collectionId": 1109731533, "artistId": 657515, "trackNumber": 2, "trackCount": 12, "discNumber": 1, "discCount": 1, "releaseDate": "1993-02-22T08:00:00Z", "primaryGenreName": "Alternative", "trackTimeMillis": 238640, "country": "USA", "isrc": "GBAYE9200070", "artworkUrl30": "https://.../30x30bb.jpg", "artworkUrl60": "https://.../60x60bb.jpg", "artworkUrl100": "https://.../100x100bb.jpg", "previewUrl": "https://.../preview.m4a", "trackViewUrl": "https://music.apple.com/us/album/creep/1109731533?i=1109731797" } ``` **Album Object:** ```json { "collectionId": 1109731533, "collectionName": "Pablo Honey", "artistName": "Radiohead", "artistId": 657515, "trackCount": 12, "releaseDate": "1993-02-22T08:00:00Z", "primaryGenreName": "Alternative", "copyright": "℗ 1993 XL Recordings Ltd.", "country": "USA", "artworkUrl100": "https://.../100x100bb.jpg", "collectionViewUrl": "https://music.apple.com/us/album/pablo-honey/1109731533" } ``` ### Rate Limiting **Limit:** Approximately 20 requests per minute (undocumented) **Enforcement:** HTTP 403 (Forbidden) when limit exceeded **Headers:** No rate limit headers provided **Recommendation:** Implement exponential backoff on 403 errors. ### Use Cases 1. **Quick Metadata Lookup:** Get basic track/album info without authentication 2. **UPC Lookup:** Find albums by barcode 3. **Preview URLs:** Get 30-second preview clips (M4A format) 4. **Artwork:** Get album artwork in multiple sizes (30x30, 60x60, 100x100) ### Limitations - No high-resolution artwork (max 100x100 pixels, can be scaled to 600x600 by changing URL) - No streaming URLs (only 30-second previews) - No lyrics - No user-specific data - Rate limit is undocumented and may change - Search results limited to 200 items ## Qobuz Integration ### Service Overview **Purpose:** High-resolution music streaming and downloads **Website:** https://www.qobuz.com **API Documentation:** None (private API) **API Type:** Private, undocumented, reverse-engineered ### Authentication **Method:** Password Grant OAuth 2.0 **Setup:** 1. Qobuz account with active subscription 2. App ID and secret auto-extracted from web player JavaScript 3. Email and password for authentication **Implementation:** ```python from minim import qobuz api = qobuz.PrivateAPI( email="user@example.com", password="YourPassword" ) # Automatic app_id/secret extraction and token acquisition api.set_access_token() ``` **App ID/Secret Extraction:** ```python def _get_app_credentials(self): # Fetch Qobuz web player response = requests.get("https://play.qobuz.com") html = response.text # Extract bundle URL from HTML bundle_url_match = re.search(r'