# 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

   

```bash
curl -H "Authorization: your-cache-token" \
  "https://api.betterlyrics.org/cache/keys"
```

```javascript
const res = await fetch("https://api.betterlyrics.org/cache/keys", {
  headers: { "Authorization": "your-cache-token" },
});
const data = await res.json();
```

```python
import requests

res = requests.get(
    "https://api.betterlyrics.org/cache/keys",
    headers={"Authorization": "your-cache-token"},
)
data = res.json()
```

```go
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:  ```json {   "error": "Provide at least song (s) or artist (a) parameter" } ```  ### Example       ```bash curl -H "Authorization: token" \   "https://api.betterlyrics.org/cache/lookup?s=E85&a=Don%20Toliver&al=OCTANE&d=154" ```  ```javascript 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(); ```  ```python 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() ```  ```go 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)) } ```  ```json {   "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       ```bash curl -H "Authorization: token" \   "https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10" ```  ```javascript const res = await fetch("https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10", {   headers: { "Authorization": "token" }, }); const data = await res.json(); ```  ```python import requests  res = requests.get(     "https://api.betterlyrics.org/cache/keys?contains=taylor&limit=10",     headers={"Authorization": "token"}, ) data = res.json() ```  ```go 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)) } ```  ```json {   "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       ```bash curl -H "Authorization: token" \   "https://api.betterlyrics.org/cache/debug?key=ttml_lyrics:e85%20don%20toliver%20octane%20154s" ```  ```javascript 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(); ```  ```python 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() ```  ```go 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)) } ```  ```json {   "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       ```bash curl -H "Authorization: token" \   "https://api.betterlyrics.org/cache/clear" ```  ```javascript const res = await fetch("https://api.betterlyrics.org/cache/clear", {   headers: { "Authorization": "token" }, }); const data = await res.json(); ```  ```python import requests  res = requests.get(     "https://api.betterlyrics.org/cache/clear",     headers={"Authorization": "token"}, ) data = res.json() ```  ```go 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)) } ```  ```json {   "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       ```bash curl -H "Authorization: token" \   "https://api.betterlyrics.org/cache/clear/kugou" ```  ```javascript const res = await fetch("https://api.betterlyrics.org/cache/clear/kugou", {   headers: { "Authorization": "token" }, }); const data = await res.json(); ```  ```python import requests  res = requests.get(     "https://api.betterlyrics.org/cache/clear/kugou",     headers={"Authorization": "token"}, ) data = res.json() ```  ```go 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)) } ```  ```json {   "message": "Cleared cache for provider: kugou",   "provider": "kugou",   "keys_deleted": 1234 } ```  `keys_deleted` counts the deleted lyrics entries. An unknown provider returns `400`:  ```json {   "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`:  ```json {   "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   ``

Source: https://docs.betterlyrics.org/reference/cache-endpoints
