Cache endpoints
Admin endpoints for inspecting and managing the cache.
All endpoints except /cache/help and /cache require the Authorization header
with the cache access token, sent as is with no Bearer prefix. A missing or wrong token returns
401 with the plain text body Unauthorized.
Authentication
curl -H "Authorization: your-cache-token" \
"https://api.betterlyrics.org/cache/keys"const res = await fetch("https://api.betterlyrics.org/cache/keys", {
headers: { "Authorization": "your-cache-token" },
});
const data = await res.json();import requests
res = requests.get(
"https://api.betterlyrics.org/cache/keys",
headers={"Authorization": "your-cache-token"},
)
data = res.json()package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/keys", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "your-cache-token")
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))
} Endpoints overview
| Endpoint | Description | Auth required |
|---|---|---|
GET /cache/help | Documentation for cache endpoints | No |
GET /cache | Removed. Returns 410 Gone with a list of alternatives | No |
GET /cache/lookup | Check if a song is cached | Yes |
GET /cache/keys | List and search cache keys | Yes |
GET /cache/debug | Inspect a specific cache entry | Yes |
GET /cache/clear | Clear all cached lyrics and negative entries | Yes |
GET /cache/clear/{provider} | Clear cached lyrics and negative entries for one provider | Yes |
GET /cache/backup, /cache/backups, /cache/restore,
/cache/dump, /cache/migrate, /cache/migrate/status | Removed. Return 410 Gone. See Removed endpoints | Yes |
GET /cache/lookup
Check if lyrics are cached for a specific song. This checks the cache keys that /getLyrics uses,
with the exact duration (no duration tolerance).
Parameters
Name Type Required Description s, song, songName string Always send Song name. a, artist, artistName string Always send Artist name. al, album, albumName string Always send Album name. Improves matching. d, duration string of digits Always send Track duration in whole seconds. Improves matching.
Send all four: the cache keys include album and duration, so a lookup without them checks a different key
than the one your lyrics requests use. The API rejects a request with 400 only when both song and
artist are missing:
{
"error": "Provide at least song (s) or artist (a) parameter"
}
Example
curl -H "Authorization: token" \
"https://api.betterlyrics.org/cache/lookup?s=E85&a=Don%20Toliver&al=OCTANE&d=154"
const res = await fetch("https://api.betterlyrics.org/cache/lookup?s=E85&a=Don%20Toliver&al=OCTANE&d=154", {
headers: { "Authorization": "token" },
});
const data = await res.json();
import requests
res = requests.get(
"https://api.betterlyrics.org/cache/lookup?s=E85&a=Don%20Toliver&al=OCTANE&d=154",
headers={"Authorization": "token"},
)
data = res.json()
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/lookup?s=E85&a=Don%20Toliver&al=OCTANE&d=154", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "token")
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))
}
{
"query": {
"song": "E85",
"artist": "Don Toliver",
"album": "OCTANE",
"duration": "154"
},
"normalized_key": "ttml_lyrics:e85 don toliver octane 154s",
"legacy_key": "ttml_lyrics:E85 Don Toliver OCTANE 154s",
"keys_differ": true,
"found": true,
"found_in": "normalized",
"track_duration_ms": 153181,
"score": 0.97,
"language": "en",
"isRTL": false,
"ttml_length": 17060,
"ttml_preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
}
Response fields
Field Type Always present Description query object Yes The song, artist, album and duration values as received. query.song string Yes As received. query.artist string Yes As received. query.album string Yes As received. query.duration string Yes As received. normalized_key string Yes Cache key in the current format (lowercase, trimmed). legacy_key string Yes Cache key in the older format (original case, not trimmed). keys_differ boolean Yes Whether the two keys are different. found boolean Yes Whether lyrics are cached under either key. found_in string No Only when found is true. One of normalized, legacy. note string No Set when the entry was found under the legacy key. track_duration_ms integer No Stored metadata of the entry. Only when found is true. score number No Match confidence. language string No Stored metadata of the entry. Only when found is true. isRTL boolean No Stored metadata of the entry. Only when found is true. ttml_length integer No Length of the stored lyrics. Only when found is true. ttml_preview string No First 200 characters. negative_cache boolean No Set when no lyrics are cached but a negative entry exists. negative_cache_in string No Set when the negative entry was found under the legacy key. One of legacy. negative_reason string No Set when no lyrics are cached but a negative entry exists.
GET /cache/keys
List and search cache keys.
Parameters
Name Type Required Description prefix string No Only keys starting with this string. contains string No Only keys containing this string. limit integer No Maximum keys to return. Values above 1000 are capped at 1000.
Example
curl -H "Authorization: token" \
"https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10"
const res = await fetch("https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10", {
headers: { "Authorization": "token" },
});
const data = await res.json();
import requests
res = requests.get(
"https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10",
headers={"Authorization": "token"},
)
data = res.json()
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "token")
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))
}
{
"total_keys": 42,
"matched_keys": 10,
"limit": 10,
"keys": [
{
"key": "ttml_lyrics:love story taylor swift",
"size": 38211,
"provider": "ttml"
}
]
}
Response fields
Field Type Always present Description total_keys integer Yes Number of cached lyrics keys that match the filters. matched_keys integer Yes Number of keys in this response (at most limit). limit integer Yes The limit that was applied. keys object[] Yes Keys in alphabetical order. keys[].key string Yes The cache key. keys[].size integer Yes Size of the stored lyrics in bytes. keys[].provider string Yes Provider of the entry.
Negative cache entries are not listed.
GET /cache/debug
Inspect a specific cache entry.
Parameters
Name Type Required Description key string Yes Full cache key, for example ttml_lyrics:song artist.
Example
curl -H "Authorization: token" \
"https://api.betterlyrics.org/cache/debug?key=ttml_lyrics:e85%20don%20toliver%20octane%20154s"
const res = await fetch("https://api.betterlyrics.org/cache/debug?key=ttml_lyrics:e85%20don%20toliver%20octane%20154s", {
headers: { "Authorization": "token" },
});
const data = await res.json();
import requests
res = requests.get(
"https://api.betterlyrics.org/cache/debug?key=ttml_lyrics:e85%20don%20toliver%20octane%20154s",
headers={"Authorization": "token"},
)
data = res.json()
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/debug?key=ttml_lyrics:e85%20don%20toliver%20octane%20154s", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "token")
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))
}
{
"key": "ttml_lyrics:e85 don toliver octane 154s",
"found": true,
"type": "lyrics",
"track_duration_ms": 153181,
"score": 0.97,
"language": "en",
"isRTL": false,
"ttml_length": 17060,
"ttml_preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
}
Response fields
Field Type Always present Description key string Yes The key that was inspected. found boolean Yes Whether an entry exists for the key. type string No no_lyrics_sentinel is a stored "no lyrics" marker. Only when found is true. One of lyrics, no_lyrics_sentinel, negative_cache. reason string No Present when type is negative_cache. track_duration_ms integer No Stored metadata. Only when type is lyrics. score number No Match confidence. language string No Stored metadata. Only when type is lyrics. isRTL boolean No Stored metadata. Only when type is lyrics. ttml_length integer No Length of the stored lyrics. Only when type is lyrics. ttml_preview string No First 300 characters.
Without the key parameter, the response is 400 with {"error": "Provide 'key' parameter"}.
GET /cache/clear
Delete all cached lyrics and negative cache entries, for every provider. This cannot be undone through the API.
Example
curl -H "Authorization: token" \
"https://api.betterlyrics.org/cache/clear"
const res = await fetch("https://api.betterlyrics.org/cache/clear", {
headers: { "Authorization": "token" },
});
const data = await res.json();
import requests
res = requests.get(
"https://api.betterlyrics.org/cache/clear",
headers={"Authorization": "token"},
)
data = res.json()
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/clear", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "token")
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))
}
{
"message": "Cache cleared successfully"
}
GET /cache/clear/{provider}
Delete cached lyrics and negative cache entries for one provider. provider is one of
ttml, kugou, qq or legacy.
Example
curl -H "Authorization: token" \
"https://api.betterlyrics.org/cache/clear/kugou"
const res = await fetch("https://api.betterlyrics.org/cache/clear/kugou", {
headers: { "Authorization": "token" },
});
const data = await res.json();
import requests
res = requests.get(
"https://api.betterlyrics.org/cache/clear/kugou",
headers={"Authorization": "token"},
)
data = res.json()
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodGet, "https://api.betterlyrics.org/cache/clear/kugou", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "token")
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))
}
{
"message": "Cleared cache for provider: kugou",
"provider": "kugou",
"keys_deleted": 1234
}
keys_deleted counts the deleted lyrics entries. An unknown provider returns 400:
{
"error": "Unknown provider: foo",
"valid_providers": ["ttml", "kugou", "qq", "legacy"]
}
A database error on either clear endpoint returns 500 with an error field.
Removed endpoints
/cache/backup, /cache/backups, /cache/restore, /cache/dump,
/cache/migrate and /cache/migrate/status are removed. Backups are handled by the
database platform. With a valid token they return 410 Gone:
{
"error": "Endpoint removed",
"message": "This operation is not available on the managed Postgres backend. Backups and point-in-time recovery are handled by the platform."
}
/cache is also removed. It needs no token and returns 410 Gone with error,
message and an alternatives object.
Notes
- These endpoints are for administrative use only
- Lyrics cache entries do not expire. They stay until they are cleared.
- The cache access token is different from the API key