# Error handling

The API uses standard HTTP status codes. Most error responses are JSON, and a few are plain text. This page covers what each one means and how to respond.

## Status codes

| Code | Meaning | Action |
| --- | --- | --- |
| `200` | Success | Parse the response body |
| `401` | Unauthorized | Check your API key, or the request was a cache miss |
| `404` | Not found | Lyrics not available for this track |
| `422` | Unprocessable | Neither song nor artist was sent |
| `429` | Too many requests | Rate limited. Check the Retry-After header |
| `500` | Server error | Upstream failure. Retry with backoff |
| `503` | Service unavailable | The server is serving only cached lyrics and the query is not cached. Retry later |

## Error response format

JSON error responses always have `error`. Some also have `message`:

```json
{
  "error": "Short error description",
  "message": "Longer explanation with suggested action"
}
```

Two errors are plain text (`Content-Type: text/plain`) instead: `422`, and the `429` sent when both rate limit tiers are exhausted. Check the `Content-Type` header before you parse the body as JSON.

## Common errors

### Missing parameters (422)

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

Plain text. Sent only when both `s` (song) and `a` (artist) are missing. Always send all four parameters 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`.

### Lyrics not found (404)

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

No lyrics exist for this song. The result is cached for 7 days (negative cache). Check `X-Cache-Status: NEGATIVE_HIT` to know if this is a cached result.

### API key required (401)

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

The requested lyrics are not cached and the server requires an API key for fresh fetches. First check that the request sends album and duration: the cache key includes both, so a request without them often misses an entry that is cached. Keys aren't being issued right now (see [Authentication](https://docs.betterlyrics.org/docs/authentication)). If you have a key, pass it via `X-API-Key`. Otherwise, play the song once through the [Better Lyrics](https://better-lyrics.boidu.dev/) extension to prime the cache, then retry.

### Rate limited (429)

When both rate limit tiers are exhausted, the body is plain text and `Retry-After` is `1`:

```
Too Many Requests
```

When only the cached tier is left and the query is not cached, the body is JSON and `Retry-After` is `60`:

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

Check the `Retry-After` header and `X-RateLimit-Type` to see which case you hit. See [Rate limiting](https://docs.betterlyrics.org/docs/rate-limiting).

## Circuit breaker

The API puts a circuit breaker in front of the upstream provider. When the upstream fails repeatedly, the breaker "opens" so the failures do not cascade.

### Circuit breaker states

| State | Behavior |
| --- | --- |
| `CLOSED` | Normal operation. Requests go through |
| `OPEN` | Tripped. Requests fail fast, and stale cache is served if available |
| `HALF-OPEN` | Testing recovery. A limited number of requests go through |

### While the breaker is open

-   Cached responses are still served (both positive and stale cache)
-   Fresh fetch requests fail fast instead of timing out
-   Every so often the breaker lets a test request through to check whether upstream has recovered

## Retry strategies

### For 429 (rate limited)

1.  Read the `Retry-After` header
2.  Wait the specified number of seconds
3.  Retry the request

### For 500 (server error)

1.  Use exponential backoff
2.  Start with a 1 second delay
3.  Double the delay on each retry (1s, 2s, 4s, 8s...)
4.  Cap at a maximum delay (e.g., 60 seconds)
5.  Give up after a maximum number of retries (e.g., 5)

### For 404 (not found)

Do not retry immediately. The negative result is cached for 7 days. If you believe lyrics should exist, use the `/revalidate` endpoint with an API key to force a fresh lookup.

## Example error handling (JavaScript)

```javascript
async function fetchLyrics(song, artist, album, duration) {
  const url = `https://api.betterlyrics.org/getLyrics?s=${encodeURIComponent(song)}&a=${encodeURIComponent(artist)}&al=${encodeURIComponent(album)}&d=${duration}`;

  const response = await fetch(url);

  if (response.ok) {
    return await response.json();
  }

  const isJson = response.headers.get('Content-Type')?.includes('application/json');
  const error = isJson ? await response.json() : { error: await response.text() };

  switch (response.status) {
    case 404:
      // Lyrics not available - cache this locally
      return { notFound: true, cached: response.headers.get('X-Cache-Status') === 'NEGATIVE_HIT' };

    case 429:
      // Rate limited - get retry time
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
      throw new Error(`Rate limited. Retry after ${retryAfter} seconds.`);

    case 401:
      // Auth required for cache miss
      throw new Error('API key required for uncached queries');

    default:
      throw new Error(error.message || error.error || 'Unknown error');
  }
}
```

Source: https://docs.betterlyrics.org/docs/error-handling
