# GET /health

GET `/health`

[Open /health](https://api.betterlyrics.org/health)

Check whether the API is healthy. Always returns `200 OK`, so read the `status` field for the result. With the cache access token, the response also lists the status of each upstream token.

## Headers (optional)

| Name | Required | Description |
| --- | --- | --- |
| `Authorization` | No | Cache access token for detailed status |

## Example request (basic)

   

```bash
curl "https://api.betterlyrics.org/health"
```

```javascript
const res = await fetch("https://api.betterlyrics.org/health");
const data = await res.json();
```

```python
import requests

res = requests.get("https://api.betterlyrics.org/health")
data = res.json()
```

```go
package main

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

func main() {
	res, err := http.Get("https://api.betterlyrics.org/health")
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

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

## Basic response

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

```json
{
  "status": "ok",
  "accounts": 2,
  "accounts_active": 2,
  "accounts_out_of_service": 0,
  "circuit_breaker": "CLOSED",
  "cache_ready": true
}
```

## Response fields

| Field | Type | Always present | Description |
| --- | --- | --- | --- |
| `status` | string | Yes | One of `ok`, `degraded`, `unhealthy`. |
| `accounts` | integer | Yes | Number of configured upstream accounts. |
| `accounts_active` | integer | Yes | Accounts that are in service. |
| `accounts_out_of_service` | integer | Yes | Accounts that are configured but out of service. |
| `circuit_breaker` | string | Yes | One of `CLOSED`, `OPEN`, `HALF-OPEN`, `UNINITIALIZED`, `UNKNOWN`. |
| `circuit_breaker_retry_in` | string | No | Go duration until the breaker retries. Present only when it is `OPEN`. |
| `cache_ready` | boolean | Yes | Whether the cache is ready. |
| `error` | string | No | Present when `status` is `unhealthy`. |
| `circuit_breaker_failures` | integer | No | Admin token only. |
| `tokens` | object[] | No | Admin token only. |
| `tokens[].name` | string | Yes | `shared_bearer_token` for the shared token, or the account name. |
| `tokens[].type` | string | Yes | `bearer` for the shared token, `mut` for an account token. One of `bearer`, `mut`. |
| `tokens[].status` | string | Yes | See the token status values below. One of `healthy`, `unhealthy`, `refreshing_soon`, `not_initialized`, `out_of_service`, `unknown`. |
| `tokens[].expires` | string | No | Expiry time as `YYYY-MM-DD HH:MM:SS`. `bearer` only, when initialized. |
| `tokens[].remaining_minutes` | integer | No | Minutes until expiry. `bearer` only, when initialized. |
| `tokens[].last_checked` | string | No | Time of the last health check (RFC 3339). `mut` only, after a check has run. |
| `tokens[].last_error` | string | No | Error from the last health check. Only when `status` is `unhealthy`. |
| `tokens[].reason` | string | No | Why the account is out of service. Only when `status` is `out_of_service`. |
| `tokens[].note` | string | No | Only when `status` is `unknown`. |

## Status values

| Status | Meaning |
| --- | --- |
| `ok` | All systems operational |
| `degraded` | The circuit breaker is `OPEN`. With the cache access token, also when an account token is `unhealthy`. |
| `unhealthy` | No account is in service (none configured, or all out of service). See `error`. |

Token checks affect `status` only when the request has the cache access token, so an authenticated request can report `degraded` while a basic request reports `ok`.

## Authenticated response

With the `Authorization` header, the response includes more detail:

   

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

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

```python
import requests

res = requests.get(
    "https://api.betterlyrics.org/health",
    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/health", 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))
}
```

```json
{
  "status": "ok",
  "accounts": 2,
  "accounts_active": 2,
  "accounts_out_of_service": 0,
  "circuit_breaker": "CLOSED",
  "cache_ready": true,
  "circuit_breaker_failures": 0,
  "tokens": [
    {
      "name": "shared_bearer_token",
      "type": "bearer",
      "status": "healthy",
      "expires": "2026-09-30 12:00:00",
      "remaining_minutes": 10080
    },
    {
      "name": "account-1",
      "type": "mut",
      "status": "healthy",
      "last_checked": "2026-09-23T11:00:00Z"
    },
    {
      "name": "account-2",
      "type": "mut",
      "status": "unknown",
      "note": "health check not yet run"
    }
  ]
}
```

## Token status values

| Status | Type | Meaning |
| --- | --- | --- |
| `healthy` | `bearer`, `mut` | Token is valid |
| `refreshing_soon` | `bearer` | Token is valid and will be refreshed soon |
| `not_initialized` | `bearer` | Token has not been fetched yet |
| `unhealthy` | `mut` | The last health check failed. See `last_error`. |
| `out_of_service` | `mut` | Account has no credentials and is not used |
| `unknown` | `mut` | No health check has run yet |

## Use cases

-   Health checks in your own monitoring
-   Checking that the API is reachable and working while you debug
-   Showing API status on a status page or in your app

Source: https://docs.betterlyrics.org/reference/health
