- gRPC service with MusicBrainz provider - PostgreSQL schema with migrations - Service layer with database-first caching - Repository pattern for data access - YAML configuration support - Research documentation for 17 music metadata projects
25 KiB
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:
- Create app at https://www.discogs.com/settings/developers
- Obtain consumer key and consumer secret
- Implement OAuth 1.0a flow (request token → user authorization → access token)
Implementation:
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:
- Generate token at https://www.discogs.com/settings/developers
- Use token directly (no OAuth flow)
Implementation:
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:
{
"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:
{
"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 minuteX-Discogs-Ratelimit-Remaining: Requests remaining in current windowX-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
- Metadata Enrichment: Get detailed release information (catalog numbers, barcodes, formats)
- Collection Management: Sync local library with Discogs collection
- Credits Extraction: Get producer, engineer, musician credits from tracklist
- 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:
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:
{
"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:
{
"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
- Quick Metadata Lookup: Get basic track/album info without authentication
- UPC Lookup: Find albums by barcode
- Preview URLs: Get 30-second preview clips (M4A format)
- 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:
- Qobuz account with active subscription
- App ID and secret auto-extracted from web player JavaScript
- Email and password for authentication
Implementation:
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:
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'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d+/bundle\.js)"', html)
bundle_url = "https://play.qobuz.com" + bundle_url_match.group(1)
# Fetch bundle JavaScript
bundle_js = requests.get(bundle_url).text
# Extract app_id and secrets array
app_id = re.search(r'production:{api:{appId:"(\d+)"', bundle_js).group(1)
secrets = re.findall(r'[a-f0-9]{32}', bundle_js)
# Test secrets to find valid one
for secret in secrets:
if self._test_secret(app_id, secret):
return app_id, secret
Security Note: This method violates Qobuz terms of service. Use at your own risk.
API Coverage
Catalog:
- Search tracks, albums, artists, playlists
- Get detailed information
- Browse by genre, new releases, charts
Streaming:
- Get streaming URLs with quality selection
- Quality levels: MP3 320kbps, FLAC 16/44.1, FLAC 24/96, FLAC Hi-Res (up to 24/192)
- Download tracks (within subscription terms)
User Library:
- Get user playlists
- Create, update, delete playlists
- Add/remove tracks from playlists
- Get favorites (tracks, albums, artists)
- Add/remove favorites
Not Available:
- Lyrics
- Credits (producer, engineer, etc.)
- User playback history
Data Model
Track Object:
{
"id": 12345678,
"title": "Creep",
"duration": 238,
"track_number": 2,
"media_number": 1,
"isrc": "GBAYE9200070",
"performer": {"name": "Radiohead", "id": 12345},
"album": {
"id": "0060254734729",
"title": "Pablo Honey",
"release_date_original": "1993-02-22",
"upc": "0060254734729",
"image": {
"small": "https://.../230x230.jpg",
"large": "https://.../600x600.jpg"
},
"label": {"name": "XL Recordings"}
},
"maximum_bit_depth": 16,
"maximum_sampling_rate": 44.1
}
Streaming URL Response:
{
"url": "https://streaming.qobuz.com/...",
"format_id": 27,
"mime_type": "audio/flac",
"sampling_rate": 44.1,
"bit_depth": 16,
"restrictions": []
}
Quality Levels
| Format ID | Quality | Codec | Bitrate/Depth | Subscription Tier |
|---|---|---|---|---|
| 5 | MP3 | MP3 | 320 kbps | Studio |
| 6 | CD | FLAC | 16-bit/44.1kHz | Studio |
| 7 | Hi-Res | FLAC | 24-bit/96kHz | Studio Sublime |
| 27 | Hi-Res | FLAC | Up to 24-bit/192kHz | Studio Sublime |
Availability: Quality depends on:
- User's subscription tier
- Album's available formats
- Geographic restrictions
Rate Limiting
Unknown. Private API does not document rate limits.
Observation: Aggressive usage (>100 requests/minute) may trigger temporary blocks.
Recommendation: Implement conservative rate limiting (10-20 requests/minute).
Use Cases
- High-Resolution Downloads: Get FLAC files up to 24-bit/192kHz
- Metadata Enrichment: Get detailed album info (label, UPC, release date)
- Playlist Management: Sync playlists between services
- Favorites Sync: Export/import favorite tracks
Limitations
- Private API (may break without notice)
- Requires active subscription
- No lyrics or credits
- Geographic restrictions on content
- Terms of service violations (use at own risk)
Spotify Integration
Service Overview
Purpose: Music streaming, discovery, and social features
Website: https://www.spotify.com
API Documentation: https://developer.spotify.com/documentation/web-api
API Type: Public (Web API) + Private (Lyrics)
Authentication
Method: OAuth 2.0 with four flow types
Flow 1: Authorization Code
- Full user access with refresh token
- Requires user login via browser
- Best for web applications
Flow 2: PKCE (Proof Key for Code Exchange)
- For mobile and desktop apps
- No client secret required
- Enhanced security for public clients
Flow 3: Client Credentials
- App-only access (no user context)
- No user login required
- Limited to catalog endpoints (no user library, playlists, playback)
Flow 4: Web Player (Undocumented)
- Extract
sp_dccookie from browser - Access private endpoints (lyrics)
- Violates terms of service
Implementation:
from minim import spotify
# Authorization Code flow
api = spotify.WebAPI(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="http://localhost:8888"
)
api.set_flow("authorization_code", scopes=[
"user-library-read",
"playlist-read-private",
"user-read-playback-state"
])
api.set_access_token() # Opens browser
# Client Credentials flow (no user login)
api = spotify.WebAPI(client_id="...", client_secret="...")
api.set_flow("client_credentials")
api.set_access_token()
API Coverage
Catalog:
- Search tracks, albums, artists, playlists, shows, episodes
- Get detailed information
- Get related artists
- Get artist top tracks
- Get album tracks
- Get audio features (danceability, energy, tempo, etc.)
- Get audio analysis (detailed beat/bar/section analysis)
User Library:
- Get saved tracks, albums, shows, episodes
- Save/remove items
- Check if items are saved
Playlists:
- Get user playlists
- Get playlist details and tracks
- Create, update, delete playlists
- Add/remove tracks
- Reorder tracks
- Upload custom cover image
Playback:
- Get current playback state
- Get available devices
- Start/pause/skip playback
- Seek to position
- Set volume
- Toggle shuffle/repeat
- Transfer playback between devices
Personalization:
- Get top artists and tracks
- Get recently played tracks
- Get recommendations based on seeds
Follow:
- Follow/unfollow artists, users, playlists
- Check if following
- Get followed artists
Browse:
- Get featured playlists
- Get new releases
- Get categories
- Get category playlists
Lyrics (Private API):
- Get synchronized lyrics via Musixmatch integration
- Requires
sp_dccookie
Data Model
Track Object:
{
"id": "3n3Ppam7vgaVa1iaRUc9Lp",
"name": "Creep",
"artists": [
{"name": "Radiohead", "id": "4Z8W4fKeB5YxbusRsdQVPb"}
],
"album": {
"name": "Pablo Honey",
"id": "6AZv3m27uyRxi8KyJSfUxL",
"release_date": "1993-02-22",
"images": [
{"url": "https://.../640x640.jpg", "width": 640, "height": 640}
]
},
"duration_ms": 238640,
"track_number": 2,
"disc_number": 1,
"explicit": false,
"external_ids": {"isrc": "GBAYE9200070"},
"popularity": 82,
"preview_url": "https://.../preview.mp3"
}
Audio Features Object:
{
"id": "3n3Ppam7vgaVa1iaRUc9Lp",
"danceability": 0.456,
"energy": 0.789,
"key": 7,
"loudness": -6.234,
"mode": 1,
"speechiness": 0.034,
"acousticness": 0.123,
"instrumentalness": 0.000012,
"liveness": 0.089,
"valence": 0.234,
"tempo": 92.456,
"duration_ms": 238640,
"time_signature": 4
}
Lyrics Object (Private API):
{
"lyrics": {
"syncType": "LINE_SYNCED",
"lines": [
{"startTimeMs": "0", "words": "When you were here before", "syllables": []},
{"startTimeMs": "5230", "words": "Couldn't look you in the eye", "syllables": []}
],
"language": "en"
}
}
Scopes
Spotify uses OAuth scopes to control API access. Common scopes:
Library:
user-library-read: Read saved tracks/albumsuser-library-modify: Save/remove tracks/albums
Playlists:
playlist-read-private: Read private playlistsplaylist-read-collaborative: Read collaborative playlistsplaylist-modify-public: Modify public playlistsplaylist-modify-private: Modify private playlists
Playback:
user-read-playback-state: Read playback stateuser-modify-playback-state: Control playbackuser-read-currently-playing: Read currently playing track
Personalization:
user-top-read: Read top artists and tracksuser-read-recently-played: Read recently played tracks
Follow:
user-follow-read: Read followed artists/usersuser-follow-modify: Follow/unfollow artists/users
User:
user-read-private: Read user profile (country, product)user-read-email: Read user email
Rate Limiting
Limit: Varies by endpoint, typically 180 requests per 30 seconds
Enforcement: HTTP 429 (Too Many Requests)
Headers:
Retry-After: Seconds to wait before retrying
Recommendation: Implement exponential backoff and respect Retry-After header.
Use Cases
- Comprehensive Metadata: Get detailed track info, audio features, related artists
- Playlist Management: Create, sync, and manage playlists
- Playback Control: Build custom music players
- Music Discovery: Get recommendations, browse new releases
- Lyrics Integration: Display synchronized lyrics (via private API)
Limitations
- No streaming URLs (only 30-second previews)
- No download capability
- Lyrics require private API (terms of service violation)
- Rate limiting varies by endpoint
- Some features require premium subscription
TIDAL Integration
Service Overview
Purpose: High-fidelity music streaming with MQA support
Website: https://www.tidal.com
API Documentation: Limited (mostly undocumented)
API Type: Public (limited) + Private (extensive)
Authentication
Method: OAuth 2.0 (PKCE or Client Credentials)
Public API:
- Client credentials flow
- Limited endpoints (catalog search, basic info)
Private API:
- PKCE flow with user login
- Full access (streaming URLs, lyrics, credits)
Implementation:
from minim import tidal
# Private API (full access)
api = tidal.PrivateAPI(
client_id="your_client_id",
client_secret="your_client_secret"
)
api.set_flow("pkce")
api.set_access_token() # Opens browser for login
Client ID/Secret:
- Extracted from TIDAL desktop/mobile apps
- Not officially provided by TIDAL
- Use at own risk (terms of service violation)
API Coverage
Catalog:
- Search tracks, albums, artists, playlists, videos
- Get detailed information
- Get artist top tracks and albums
- Get similar artists
- Get album review and credits
Streaming:
- Get streaming URLs with quality selection
- Quality levels: LOW (96kbps AAC), HIGH (320kbps AAC), LOSSLESS (FLAC 16/44.1), HI_RES (FLAC 24/96+), HI_RES_LOSSLESS (MQA)
- Manifest decryption for protected streams
Lyrics:
- Get synchronized lyrics (LRC format)
- Get plain text lyrics
Credits:
- Get detailed credits (producers, engineers, musicians, composers, etc.)
- Role-based organization
User Library:
- Get user playlists
- Create, update, delete playlists
- Add/remove tracks
- Get favorites (tracks, albums, artists, videos)
- Add/remove favorites
Not Available:
- Playback control (no remote control API)
- Audio features/analysis
- Recommendations (limited)
Data Model
Track Object:
{
"id": 12345678,
"title": "Creep",
"duration": 238,
"trackNumber": 2,
"volumeNumber": 1,
"isrc": "GBAYE9200070",
"explicit": false,
"audioQuality": "HI_RES",
"artists": [
{"name": "Radiohead", "id": 4050}
],
"album": {
"id": 1234567,
"title": "Pablo Honey",
"releaseDate": "1993-02-22",
"cover": "01234567-89ab-cdef-0123-456789abcdef",
"upc": "0060254734729",
"numberOfTracks": 12,
"audioQuality": "HI_RES"
},
"streamStartDate": "1993-02-22T00:00:00.000Z"
}
Streaming Manifest:
{
"mimeType": "audio/flac",
"codecs": "flac",
"encryptionType": "NONE",
"urls": ["https://streaming.tidal.com/..."],
"soundQuality": "HI_RES",
"bitDepth": 24,
"sampleRate": 96000
}
Lyrics Object:
{
"trackId": 12345678,
"lyricsProvider": "Musixmatch",
"providerLyricsId": "12345",
"lyrics": "When you were here before\nCouldn't look you in the eye...",
"subtitles": "[00:00.00] When you were here before\n[00:05.23] Couldn't look you in the eye..."
}
Credits Object:
{
"credits": [
{
"type": "Producers",
"contributors": [
{"name": "Sean Slade", "id": 12345},
{"name": "Paul Q. Kolderie", "id": 67890}
]
},
{
"type": "Performers",
"contributors": [
{"name": "Thom Yorke", "id": 111, "role": "Vocals"},
{"name": "Jonny Greenwood", "id": 222, "role": "Guitar"}
]
}
]
}
Quality Levels
| Quality | Codec | Bitrate/Depth | Subscription Tier |
|---|---|---|---|
| LOW | AAC | 96 kbps | Free (trial) |
| HIGH | AAC | 320 kbps | HiFi |
| LOSSLESS | FLAC | 16-bit/44.1kHz | HiFi |
| HI_RES | FLAC | 24-bit/96kHz+ | HiFi Plus |
| HI_RES_LOSSLESS | MQA | 24-bit/96kHz+ (MQA) | HiFi Plus |
MQA (Master Quality Authenticated):
- Proprietary format by Meridian Audio
- Requires MQA-compatible DAC for full unfolding
- Software decoding provides 24-bit/96kHz
Manifest Decryption
Some streaming URLs are encrypted. minim handles decryption:
def _decrypt_manifest(self, manifest: dict) -> str:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import base64
# Extract encrypted URL
encrypted_url = manifest["urls"][0]
# Decrypt using AES-128-CTR
key = base64.b64decode(manifest["encryptionKey"])
nonce = base64.b64decode(manifest["nonce"])
cipher = Cipher(
algorithms.AES(key),
modes.CTR(nonce),
backend=default_backend()
)
decryptor = cipher.decryptor()
decrypted = decryptor.update(base64.b64decode(encrypted_url)) + decryptor.finalize()
return decrypted.decode("utf-8")
Rate Limiting
Unknown. Private API does not document rate limits.
Observation: Moderate usage (<50 requests/minute) appears safe.
Recommendation: Implement conservative rate limiting and exponential backoff on errors.
Use Cases
- High-Fidelity Streaming: Get FLAC files up to 24-bit/192kHz and MQA
- Comprehensive Credits: Get detailed production credits
- Synchronized Lyrics: Display time-synced lyrics
- Metadata Enrichment: Get authoritative release dates, ISRCs, UPCs
- Playlist Management: Sync playlists between services
Limitations
- Private API (may break without notice)
- Requires active subscription (HiFi or HiFi Plus for lossless)
- Client ID/secret extraction violates terms of service
- No playback control API
- No audio features/analysis
- Geographic restrictions on content
Integration Comparison
Authentication Complexity
Simplest: iTunes (no auth)
Simple: Discogs (personal token), Spotify (client credentials)
Moderate: Spotify (authorization code), TIDAL (PKCE)
Complex: Qobuz (app_id extraction + password grant)
API Documentation Quality
Best: Spotify (comprehensive, well-maintained)
Good: Discogs, iTunes
Poor: TIDAL (limited public docs)
None: Qobuz (fully reverse-engineered)
Streaming Capability
High-Resolution: Qobuz (up to 24/192 FLAC), TIDAL (up to 24/192 FLAC + MQA)
Lossless: TIDAL (16/44.1 FLAC)
Lossy: TIDAL (AAC), Qobuz (MP3)
Preview Only: Spotify (30sec MP3), iTunes (30sec M4A)
None: Discogs
Metadata Richness
Credits: TIDAL (excellent), Discogs (good), others (none)
Lyrics: TIDAL (synced), Spotify (synced, private API), others (none)
Audio Features: Spotify (excellent), others (none)
Catalog Info: All services (good)
Terms of Service Compliance
Compliant: Discogs (public API), iTunes (public API), Spotify (public Web API)
Questionable: Spotify (private lyrics API)
Violation: Qobuz (app_id extraction, private API), TIDAL (client_id extraction, private API)
Summary
minim provides comprehensive integration with five music services, each serving different use cases:
- Discogs: Best for credits, catalog numbers, and collection management
- iTunes: Best for quick, unauthenticated metadata lookup
- Qobuz: Best for high-resolution downloads (within subscription terms)
- Spotify: Best for comprehensive metadata, audio features, and playlist management
- TIDAL: Best for high-fidelity streaming, credits, and synchronized lyrics
All integrations follow consistent patterns (authentication, request handling, error raising) while exposing service-specific features. The private API usage (Qobuz, Spotify lyrics, TIDAL) provides powerful capabilities but carries legal and stability risks.
For a metadata aggregator project, prioritize public APIs (Spotify Web API, Discogs, iTunes) for production use, and use private APIs only for research or personal projects.