Rate limiting
The API uses two-tier rate limiting. When you go over the normal limit, cached responses still come through, up to a second, higher limit.
How it works
Standard rate limit for all requests. Can fetch fresh lyrics or serve from cache.
Higher rate limit, but only serves cached responses. Fresh fetches return 429.
All requests return 429 Too Many Requests.
Limits are per IP address. The limits can change, so do not hardcode them.
Read X-RateLimit-Limit and X-RateLimit-Remaining on each response instead.
Response headers
The API reports rate limit, cache and authentication state in these headers:
| Header | Values | When it is sent |
|---|---|---|
X-RateLimit-Limit | Burst size of the current tier. On exceeded it is the burst size of the cached tier. | Every request without a valid API key |
X-RateLimit-Remaining | Requests left in the current tier, 0 when both tiers are exceeded | Every request without a valid API key |
X-RateLimit-Type | normal, cached, exceeded, or bypass | Every request without a valid API key. With a valid API key, bypass is sent on JSON responses from the lyrics endpoints, /revalidate and the /cache/* endpoints |
X-RateLimit-Bypass | true | Requests with a valid X-API-Key header |
Retry-After | 1 when both tiers are exceeded, 60 when the cached tier has no cache entry for the query | Only on 429 responses |
X-Cache-Status | HIT, MISS, NEGATIVE_HIT, or STALE (STALE only on /getLyrics) | Lyrics endpoints: /getLyrics and the provider-specific endpoints. See Caching |
X-Provider | ttml, kugou, or qq | Provider-specific endpoints only, for example /kugou/getLyrics |
X-Auth-Mode | authenticated, invalid, or cache | /getLyrics, /ttml/getLyrics and /revalidate, when the API requires a key for uncached queries. See Authentication |
Browsers cannot read these headers from cross-origin JavaScript. The API does not expose them through CORS on purpose, so they are meant for server-side callers and tools like curl.
Rate limit types
normal
Request processed normally. Can fetch fresh lyrics or serve from cache.
cached
Normal tier exceeded. The request is served only if the lyrics are cached. Otherwise it returns 429.
exceeded
Both tiers exceeded. All requests return 429 Too Many Requests.
bypass
The request has a valid API key, so rate limits do not apply.
API key bypass
Requests with a valid X-API-Key header bypass all rate limits.
This is useful for production applications that need guaranteed access.
curl -H "X-API-Key: your-api-key" \
"https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154"const res = await fetch("https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154", {
headers: { "X-API-Key": "your-api-key" },
});
const data = await res.json();import requests
res = requests.get(
"https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154",
headers={"X-API-Key": "your-api-key"},
)
data = res.json()package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154", nil)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Key", "your-api-key")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
When bypass is active, every response includes X-RateLimit-Bypass: true, and has no
X-RateLimit-Limit or X-RateLimit-Remaining. The lyrics endpoints,
/revalidate and the /cache/* JSON responses also include X-RateLimit-Type: bypass:
X-RateLimit-Bypass: true
X-RateLimit-Type: bypass Handling rate limits
When you receive a 429 response:
- Check the
Retry-Afterheader for when to retry - Check
X-RateLimit-Typeto see which tier was exceeded - If
cached, the lyrics are not in the cache yet, so wait before you retry - Use exponential backoff for retries
Rate limit responses
When both tiers are exhausted, any endpoint returns 429 Too Many Requests with
Content-Type: text/plain, Retry-After: 1 and X-RateLimit-Type: exceeded:
Too Many Requests
When the cached tier serves a lyrics request that is not cached, the response is 429 with a JSON body,
Retry-After: 60 and X-RateLimit-Type: cached:
{
"error": "Rate limit exceeded. This request requires cached data, but no cache is available for this query.",
"message": "Please try again later or reduce your request rate."
}
The provider-specific endpoints return {"error": "Rate limit exceeded. No cached data available.", "provider": "..."} in this case.