MusixMatch lyrics provider broken: all requests blocked with HTTP 403 (TLS fingerprint bot detection)
#2,741 opened on Jul 18, 2026
Repository metrics
- Stars
- (14,703 stars)
- PR merge metrics
- (Avg merge 38d 15h) (1 merged PR in 30d)
Description
System OS
Windows
Python Version
3.13 (CPython)
Install Source
GitHub
Install version / commit hash
v4.5.0 (also reproduced on latest master)
Expected Behavior vs Actual Behavior
Expected: The MusixMatch lyrics provider returns lyrics for songs that exist on musixmatch.com.
Actual: MusixMatch returns HTTP 403 for every request made by spotdl, so the provider never returns any lyrics. On current master this failure is completely silent — get_results() has no status-code check, so the 403 error page is parsed as HTML, no /lyrics/ links are found, and the provider quietly falls through to returning no results. (#2697 adds a status check that at least surfaces the error in debug logs.)
Steps to reproduce - Ensure to include actual links!
spotdl download https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT --lyrics musixmatch- Observe no lyrics are embedded, with no error reported.
Or reproduce the raw failure directly:
import requests
from spotdl.providers.lyrics.base import LyricsProvider
resp = requests.get(
"https://www.musixmatch.com/search/test",
headers=LyricsProvider().headers,
timeout=10,
)
print(resp.status_code) # 403
Traceback
(With the status-code check from #2697 applied; on master the failure is silent)
DEBUG spotdl.providers.lyrics.base: MusixMatch: Failed to get results for Never Gonna Give You Up - Rick Astley: Received HTTP 403 from https://www.musixmatch.com/search/Never%20Gonna%20Give%20You%20Up%20-%20Rick%20Astley
Other details
I dug into why the 403 happens. MusixMatch has deployed bot detection at their edge, and it classifies clients by more than headers:
| Client | Result | Served by |
|---|---|---|
| python-requests + Chrome User-Agent (what spotdl sends) | 403 Forbidden | awselb/2.0 (AWS WAF/ELB) |
python-requests + default python-requests/x.y UA |
404 "Not Allowed" on every path, incl. homepage | Varnish |
| curl (either UA) | 403 | edge |
| Real browsers | work | — |
Key observations:
- Headers don't matter. Sending a complete modern browser header set (Chrome 126 UA,
Accept,Accept-Language,sec-ch-uaclient hints) still gets 403. - It appears to be TLS fingerprinting (JA3). spotdl's requests claim to be Chrome in the UA, but Python's OpenSSL handshake looks nothing like Chrome's BoringSSL handshake (cipher ordering, extensions, no GREASE, HTTP/1.1 instead of h2). A "Chrome" UA paired with a non-Chrome TLS fingerprint is a classic bot signal, and the request is rejected at the WAF before reaching the app.
- It is not IP-based — different clients from the same IP get different responses.
- The 403 response even sets a
mxm_bab=AAcookie with a ~10-year expiry (a bot flag).
Suggested fix: switch the MusixMatch provider (or all LyricsProvider HTTP calls) from requests to curl_cffi, which impersonates real browser TLS/JA3 + HTTP/2 fingerprints (from curl_cffi import requests; requests.get(url, impersonate="chrome")). Its requests-compatible API would keep the change small. The trade-offs are a new binary dependency and the usual arms race with a site that is actively blocking scrapers — the alternative is the official (paid) MusixMatch API, or retiring the provider.