# GET /getLyrics

GET `/getLyrics`

[Try it in the playground](https://docs.betterlyrics.org/playground?s=APT.&a=ROS%C3%89%2C+Bruno+Mars&al=APT.&d=170)

Fetch synced lyrics for a song, with syllable-level timing in TTML format.

## 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. |
| `videoId`, `v` | string | No | Video ID. Links the video to the song so downstream caches can be revalidated. |

The API rejects a request (`422`) only when both song and artist are missing. Send all four anyway: the cache key includes album and duration, so a request without them often misses the cached entry and, without an API key, returns `401`.

If you send more than one alias of a parameter (for example `s` and `song`), the values are joined together. Send only one alias of each.

## Headers

| Name | Required | Description |
| --- | --- | --- |
| `X-API-Key` | Conditional | Needed for cache misses when the server enforces API keys. A valid key also skips rate limiting. |

## Example request

   

```bash
curl "https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154"
```

```javascript
const res = await fetch("https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154");
const { ttml } = await res.json();
```

```python
import requests

res = requests.get("https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154")
ttml = res.json()["ttml"]
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	res, err := http.Get("https://api.betterlyrics.org/getLyrics?s=E85&a=Don%20Toliver&al=OCTANE&d=154")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(body))
}
```

## Success response

Status: `200 OK`, `Content-Type: application/json`

```json
{
  "ttml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<tt xmlns=\"http://www.w3.org/ns/ttml\">\n  <body>\n    <div>\n      <p begin=\"00:00:18.234\" end=\"00:00:21.891\">\n        <span begin=\"00:00:18.234\" end=\"00:00:18.567\">The </span>\n        ...\n      </p>\n    </div>\n  </body>\n</tt>",
  "score": 0.97
}
```

## Response fields

| Field | Type | Always present | Description |
| --- | --- | --- | --- |
| `ttml` | string | Yes | TTML document with syllable or line timing. |
| `score` | number | No | Match confidence. Present only on fresh fetches. |

## Response headers

| Header | Description |
| --- | --- |
| `X-Cache-Status` | `HIT` cached lyrics. `MISS` fetched from upstream (or nothing found). `STALE` upstream failed and an older entry was served. `NEGATIVE_HIT` a cached "no lyrics" result. |
| `X-RateLimit-Type` | `normal` full service. `cached` normal tier exhausted, only cached lyrics are served. `exceeded` both tiers exhausted. `bypass` valid API key. |
| `X-RateLimit-Limit` | Burst size of the current rate limit tier. Absent with a valid API key. |
| `X-RateLimit-Remaining` | Requests left in the current tier. Absent with a valid API key. |
| `X-RateLimit-Bypass` | `true` when a valid API key skipped rate limiting. |
| `X-Auth-Mode` | Set only when the server enforces API keys on this path. `authenticated` valid key, `invalid` wrong key, `cache` no key (cache hits only). |
| `Retry-After` | Seconds to wait before retrying. |

`/getLyrics` does not send `X-Provider`. See [Provider-specific endpoints](https://docs.betterlyrics.org/reference/get-lyrics#provider-specific-endpoints) for the endpoints that do.

## Error responses

All error responses are JSON, except `422` and the `429` sent when both rate limit tiers are exhausted, which are plain text.

### Missing parameters (422)

Sent when neither song nor artist is given. `Content-Type: text/plain`:

```
Song name or artist name not provided
```

### API key required (401)

The query is not cached, the server enforces API keys, and the request has no key:

```json
{
  "error": "API key required",
  "message": "Uncached queries require a valid API key via X-API-Key header"
}
```

With a wrong key:

```json
{
  "error": "Invalid API key",
  "message": "The provided API key is not valid"
}
```

### Not found (404)

```json
{
  "error": "Lyrics not available for this track"
}
```

The `error` text depends on why no lyrics were found. This result is cached, and later requests for the same query get `X-Cache-Status: NEGATIVE_HIT`.

### Rate limited (429)

Both rate limit tiers are exhausted. `Content-Type: text/plain`, `Retry-After: 1`, `X-RateLimit-Type: exceeded`:

```
Too Many Requests
```

The normal tier is exhausted and the query is not cached, so the cached tier cannot serve it. JSON, `Retry-After: 60`, `X-RateLimit-Type: cached`:

```json
{
  "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."
}
```

### Upstream error (500)

The upstream fetch failed and no cached fallback exists. The `error` text is the upstream error:

```json
{
  "error": "<upstream error message>"
}
```

### Cache-only mode (503)

The server is temporarily serving only cached lyrics and the query is not cached:

```json
{
  "error": "Service running in cache-only mode. No cached lyrics available for this query."
}
```

## Provider-specific endpoints

These endpoints select one provider. They take the same `s`, `a`, `al` and `d` parameters (but not `videoId`), and return the same `422` and rate limit responses.

| Endpoint | Format | Timing | API key for cache misses |
| --- | --- | --- | --- |
| `/ttml/getLyrics` | TTML | Syllable-level | When the server enforces API keys |
| `/kugou/getLyrics` | LRC-style text | Line-level | Never |
| `/qq/getLyrics` | QRC | Word-level | Never |

```json
{
  "lyrics": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>...",
  "provider": "ttml"
}
```

They differ from `/getLyrics` in these ways:

-   The body is `lyrics` and `provider`, not `ttml`. There is no `score`.
-   Every JSON response, errors included, has an `X-Provider` header with the provider name. JSON error bodies also have a `provider` field.
-   Cache lookups use the exact cache key. There is no duration tolerance and no `STALE` fallback.
-   `/ttml/getLyrics` builds its cache keys in a different format from `/getLyrics`. When you send an album or a duration, the two endpoints do not share cache entries.
-   `X-Auth-Mode` is sent only by `/ttml/getLyrics`, and only when the server enforces API keys.

Source: https://docs.betterlyrics.org/reference/get-lyrics
