GuidesMUSIC APIS
Free lyrics APIs compared
LRCLIB is the free lyrics API to call from browser JavaScript: no key, Access-Control-Allow-Origin: *, plain and time-synced (LRC) lyrics, and about 45 requests a minute per IP for unidentified clients. lyrics.ovh also sends CORS headers and returns plain lyrics with no key. The Genius API returns song metadata but not the full lyrics, its public web search sends no CORS header, so a browser needs a proxy such as the free proxy.cors.dev (no signup, GET and HEAD, about 600 requests a minute per IP), and Musixmatch lists no free plan: Basic costs $49 a month for 500 lyrics calls a day.
Lyrics APIs checked for browser CORS
We sent browser-style requests with an Origin header to 5 lyrics endpoints on 28 October 2026. Only LRCLIB and lyrics.ovh return lyrics text without a key and send Access-Control-Allow-Origin: *, so call both directly. Genius returns metadata and annotated fragments only, and Musixmatch has no free plan.
| API | Key | Lyrics text | Browser CORS | Through cors.dev |
|---|---|---|---|---|
| LRCLIB | none | plain and time-synced | yes | 429 on the shared IP so call directly |
| lyrics.ovh | none | plain | yes | works but not needed |
| Genius API | access token | metadata and annotated fragments | GET yes, preflight 403 | not needed with access_token in the URL |
| Genius web search | none | search results only | no header | 429 from Genius |
| Musixmatch | secret key, paid plans only | static on Basic, synced from Grow | no header | works, but the key is visible |
For other keyless APIs and their CORS status, see free public APIs.
LRCLIB: free synced lyrics with no key
LRCLIB (lrclib.net) is a free lyrics database with user-contributed lyrics and no API key. It sends Access-Control-Allow-Origin: *, so fetch() works straight from the browser, and the LRCLIB API documentation lists three read endpoints:
/api/get?artist_name=<artist>&track_name=<title>returns one record, or404when nothing matches. Optionalalbum_nameandduration(seconds, matched within 2 seconds either way) tighten the match./api/get/<id>returns one record by ID./api/search?q=<text>returns matching records:daft punk one more timegave 20 records in 303,707 bytes.
Each record has plainLyrics, syncedLyrics (LRC, one line per timestamp, like [00:35.66] Look at the stars), instrumental, duration and albumName. Coldplay's "Yellow" with duration=269 returned a 267-second record with 994 characters of plain lyrics and 49 synced lines, and Daft Punk's "One More Time" returned 15,381 bytes.
Limits depend on how your client identifies itself. Browsers don't let JavaScript set User-Agent, so LRCLIB also reads X-User-Agent or Lrclib-Client, and its preflight allows both plus lrclib-app-id and lrclib-api-key. A generic client (no User-Agent, a browser, curl or a common HTTP library) gets about 45 requests a minute per IP, an identified client about 90, and a registered client-side app sending Lrclib-App-Id about 180 per IP plus an app-wide allowance of about 6,000 a minute.
Over the limit, LRCLIB answers 429 with a Retry-After header and a JSON body naming the ip or application scope; in our burst test the 429 carried Access-Control-Allow-Origin: * and exposed Retry-After to JavaScript. Its docs require waiting at least that long plus up to 30% random jitter, and warn that ignoring Retry-After may get you temporarily banned. Through https://proxy.cors.dev/ our test got a 238-byte 429 RateLimitExceeded, because the proxy's shared IP was already over the per-IP limit, so call LRCLIB directly and each visitor uses their own allowance.
Show lyrics in time with playback
The helper calls LRCLIB directly with an Lrclib-Client header, parses the LRC timestamps into {seconds, text} lines next to the plain text, and reports Retry-After on a 429.
async function syncedLyrics(artist, track, durationSeconds) {
const params = new URLSearchParams({ artist_name: artist, track_name: track });
if (durationSeconds) params.set('duration', String(Math.round(durationSeconds)));
// LRCLIB sends CORS headers and reads Lrclib-Client because browsers cannot set User-Agent
const res = await fetch(`https://lrclib.net/api/get?${params}`, {
headers: { 'Lrclib-Client': 'my-lyrics-app/1.0 (https://example.com)' },
});
if (res.status === 404) return null;
if (res.status === 429) throw new Error(`LRCLIB rate limit, retry in ${res.headers.get('Retry-After')} s`);
if (!res.ok) throw new Error(`LRCLIB answered ${res.status}`);
const record = await res.json();
const lines = (record.syncedLyrics ?? '').split('\n').flatMap((row) => {
const match = row.match(/^\[(\d+):(\d+(?:\.\d+)?)\]\s?(.*)$/);
return match ? [{ seconds: Number(match[1]) * 60 + Number(match[2]), text: match[3] }] : [];
});
return { plain: record.plainLyrics ?? '', lines, instrumental: record.instrumental };
}
console.log(await syncedLyrics('Coldplay', 'Yellow', 269));To sync the parsed lines with an <audio> element in your page:
- Fetch the track
Call
syncedLyrics(artist, track, duration)when a song loads and keep the returned array of{seconds, text}lines. - Listen for playback updates
Attach a listener to the
<audio>element'stimeupdateevent. - Highlight the current line
On each
timeupdateevent, compareaudio.currentTimewith each line'ssecondsvalue and highlight the last line whose time has passed.
lyrics.ovh, Genius and Musixmatch
For static text only, https://api.lyrics.ovh/v1/<artist>/<title> returns JSON with a lyrics field and sends Access-Control-Allow-Origin: *, so no key or proxy is needed: Coldplay/Yellow returned 1,153 bytes. Its /suggest/<query> endpoint returned 30,566 bytes of song matches in Deezer's search format; the Deezer API guide covers that API.
The official Genius API (api.genius.com) returns song metadata, annotations and the URL of the song's page, where the lyrics are shown; it does not return the full lyrics, only the lyric fragments that annotations are attached to. It needs an access token from a registered API client and answered 401 (113 bytes) with Access-Control-Allow-Origin: * without one. A browser can't send that token in an Authorization header, because the OPTIONS preflight answers 403; the access_token query parameter skips the preflight but shows the token to anyone using the page.
Genius's undocumented web search at https://genius.com/api/search/multi?q=<text> can change without notice and returns search results, not lyrics; genius.com/api/songs/<id> through the proxy returned 322,486 bytes with a lyrics_state field and no lyrics text. The search answered a 403 Cloudflare challenge to a datacenter IP and 200 JSON without Access-Control-Allow-Origin to residential IPs, so a browser can't read it either way. Prefixed with https://proxy.cors.dev/ it answered 429 from Genius in 4 of 4 re-tests; the 25,780 bytes of JSON for yellow coldplay came from a residential IP. The free tier needs no signup or key and allows about 600 requests a minute per IP, answering 429 with Retry-After past that.
Musixmatch (api.musixmatch.com/ws/1.1/) sends no Access-Control-Allow-Origin header. Without a valid apikey it answers HTTP 200 with content type text/plain and a JSON body whose message.header.status_code is 401, so check that field rather than the HTTP status. Its pricing page lists no free plan:
| Plan | Price a month | Calls a day | Time-synced lyrics | Lyrics caching |
|---|---|---|---|---|
| Basic | $49 | 5,000 (500 lyrics) | no | not included |
| Grow | $199 | 20,000 (2,000 lyrics) | yes | not included |
| Scale | $499 | 100,000 (10,000 lyrics) | yes | not included |
| Enterprise | from $2,000 | custom | yes | included |
Through https://proxy.cors.dev/ Musixmatch returned 200 with the caller's origin allowed, but the apikey sits in the URL where any visitor can read it, and Musixmatch says the key must be kept secret, so call it from your own server.
Lyrics licensing
Lyrics are copyrighted by their songwriters and publishers, and an API that answers does not grant display rights. LRCLIB's lyrics are contributed by its users, and lyrics.ovh is an unofficial service.
Genius's API docs say commercial use is not allowed without a license, and Musixmatch sells licensed lyrics, with lyrics caching only on its Enterprise plan. Check the rights you need before showing lyrics in a public or paid product.
Good questions.
Does the Genius API return song lyrics?
No. The official API returns song metadata, annotations and the URL of the song's page on Genius, where the lyrics are shown. Genius's undocumented genius.com/api/songs/<id> returned 322,486 bytes for a song on 28 October 2026, with a lyrics_state field but no lyrics text.
Is there a free plan for the Musixmatch API?
No. As of October 2026 Musixmatch's pricing page lists no free plan; the cheapest is Basic at $49 a month for 5,000 calls and 500 lyrics calls a day, with static lyrics only and no caching. Time-synced lyrics start on Grow at $199 a month.
Why did LRCLIB return 429 when called through cors.dev?
LRCLIB limits requests per IP address, starting at about 45 requests a minute for generic unidentified clients. When you route requests through https://proxy.cors.dev/, you share an outbound IP with other proxy users, which pushes that IP over LRCLIB's limit. Because LRCLIB sends Access-Control-Allow-Origin: *, you should call it directly from the browser so each user gets their own per-IP allowance.
How can I double my LRCLIB rate limit from browser JavaScript?
Browsers block JavaScript from changing the User-Agent header, so browser fetch() calls fall into LRCLIB's generic bucket of about 45 requests a minute per IP. Send an Lrclib-Client or X-User-Agent header with your request, both of which LRCLIB allows in its CORS preflight. Identifying your client raises the limit to about 90 requests a minute per IP, or about 180 per IP if you register a client-side app and send Lrclib-App-Id.
Can I use cors.dev to hide my Musixmatch or Genius API key?
No. The free tier refuses Authorization and other credential headers with 400 header_not_allowed, and query parameters like apikey or access_token are forwarded as-is, so they stay visible in your frontend code and the network tab. cors.dev Pro ($5 a month for 500,000 requests) forwards Authorization, but a key sent from the browser is still readable by visitors, so keep secret keys on your own server.