> ## Documentation Index
> Fetch the complete documentation index at: https://docs.minerva.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Liveness

> Probe Minerva API endpoints via `mc.status.health()`. Auto-upgrades to an authenticated metrics view when the client has an API key. Never raises — safe to call in a polling loop.

`mc.status.health()` is a **starter / sanity check** — confirm Minerva is
reachable, see if the API is currently healthy, debug "is my key wrong or
is the service down?". The same method calls one of two endpoints
depending on whether the client has an API key configured:

| Client state | Endpoint hit          | Auth        | What you get back                                                                                                                                                                                              |
| ------------ | --------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No API key   | `GET /health`         | none        | Liveness: `ok`, `latency_ms`, `status_code`, `status`, `message`                                                                                                                                               |
| API key set  | `GET /health/metrics` | `x-api-key` | Same fields **plus** `refreshed_at` (server-side aggregation timestamp). The `message` rolls up to text like `"all systems normal"` or `"elevated latency on 2 endpoint(s)"` — counts, not per-route numerics. |

The method **never raises** on probe failure. Each endpoint's outcome
lives in its `HealthStatus` (`ok=False` with `error` set), so it's safe
to call in a polling loop without `try/except`.

```python theme={null}
from minerva import Minerva

mc = Minerva()                            # MINERVA_API_KEY from env
report = mc.status.health()               # auto-picks /health or /health/metrics

for name, h in report.items():
    if not h.ok:
        alert(f"{name} {h.status}: {h.message} (HTTP {h.status_code})")
    else:
        log(f"{name} ok — {h.message} ({h.latency_ms:.0f} ms client-perceived)")
```

## When (and when NOT) to call it

<Card title="Use as a checkpoint, not a per-call gate">
  Every `mc.status.health()` is a real HTTP round-trip. Gating every
  `mc.api.enrich(...)` on a health probe doubles your latency for no
  useful signal — the next `mc.api.*` call itself raises `MinervaAPIError`
  if something's wrong.
</Card>

✅ **Good use cases**

* A dashboard widget polled every 30-60s
* A monitor / synthetic that fires every few minutes
* A one-shot call at process startup
* Debugging "is the API down or is my key wrong?" (works without a key)

❌ **Avoid**

* Wrapping every `mc.api.enrich(...)` / `mc.api.resolve(...)` in a health check
* Tight loops — the authed endpoint's data is up to \~60 s old by design;
  polling faster doesn't get you fresher numbers
* Treating it as a substitute for client-side error handling

## Auto-upgrade behavior

When the client has an API key, `health()` automatically hits the
authenticated `/health/metrics` endpoint and returns the richer payload.
You can override either way:

```python theme={null}
mc.status.health()                # auto: /health if no key, /health/metrics if key
mc.status.health(detailed=False)  # force /health (basic, skip the metrics overhead)
mc.status.health(detailed=True)   # force /health/metrics (raises MinervaAuthError if no key)
```

## Return shape

Always a `dict[str, HealthStatus]` keyed by endpoint name. One entry today
(`"api"`); the dict grows as Minerva exposes more endpoints. Iterating
today's code keeps working as endpoints come online.

### `HealthStatus`

<ParamField path="ok" type="bool">
  `True` when the endpoint is reachable and the gateway reports healthy
  (HTTP 2xx and `status == "ok"` in the body, when present).
</ParamField>

<ParamField path="latency_ms" type="float">
  Client-perceived round-trip time, in milliseconds.
</ParamField>

<ParamField path="status_code" type="int | None">
  HTTP status code returned by the endpoint. `None` on network-level
  failure (DNS, TCP, TLS, timeout).
</ParamField>

<ParamField path="status" type="str | None">
  Value of the `status` field in the gateway's response body, when
  present (e.g. `"ok"`, `"degraded"`, `"warming"`). `None` if absent.
</ParamField>

<ParamField path="message" type="str | None">
  Human-readable detail. On the unauthenticated endpoint, often `None`
  or a short free-text note from the gateway. On the authenticated
  endpoint, rolls up to phrases like `"all systems normal"` or
  `"elevated latency on 2 endpoint(s)"` — **counts only, no per-route
  numerics**.
</ParamField>

<ParamField path="refreshed_at" type="int | None">
  Unix timestamp of the last server-side aggregation (authenticated
  endpoint only). Data can be up to \~60 s old by design.
</ParamField>

<ParamField path="error" type="str | None">
  SDK-side reason for `ok=False` — the HTTP code (e.g. `"HTTP 503"`)
  or the underlying network exception class (e.g. `"ConnectError: ..."`).
  `None` when the probe succeeded. Distinct from `message`: `error` is
  the SDK's verdict; `message` is whatever the gateway returned.
</ParamField>

## Parameters

<ParamField path="endpoints" type="str | list[str] | None">
  Which endpoint(s) to probe. Omit (or pass `None`) to probe every
  known endpoint. Pass a single name or a list to filter. Today the
  only known name is `"api"`; unknown names raise `ValueError`.
</ParamField>

<ParamField path="timeout" type="float" default="5.0">
  Per-endpoint timeout in seconds. Default 5 — for a liveness check,
  "no answer in 5 seconds" is itself a useful answer.
</ParamField>

<ParamField path="detailed" type="bool | None">
  `None` (default) auto-picks the endpoint based on API-key presence.
  `True` forces `/health/metrics` (raises `MinervaAuthError` if no key).
  `False` forces `/health` (skip the metrics call even when a key is
  configured).
</ParamField>

## Examples

**Probe everything (default):**

```python theme={null}
mc.status.health()
# without API key  → {"api": HealthStatus(ok=True, status="ok", refreshed_at=None, ...)}
# with API key     → {"api": HealthStatus(ok=True, status="ok", message="all systems normal", refreshed_at=1780617600, ...)}
```

**Force the basic call (skip the metrics overhead):**

```python theme={null}
mc.status.health(detailed=False)
```

**Polling loop in a dashboard:**

```python theme={null}
import time
from minerva import Minerva

mc = Minerva()
while True:
    for name, h in mc.status.health().items():
        if not h.ok:
            page_oncall(f"Minerva {name} {h.status}: {h.message}")
    time.sleep(30)
```

**Prometheus exporter:**

```python theme={null}
from prometheus_client import Gauge

up = Gauge("minerva_endpoint_up", "1 if up", ["endpoint"])
latency = Gauge("minerva_endpoint_latency_ms", "Probe latency", ["endpoint"])

def collect():
    for name, h in mc.status.health().items():
        up.labels(endpoint=name).set(1 if h.ok else 0)
        latency.labels(endpoint=name).set(h.latency_ms)
```

**Distinguish "API down" from "key invalid":**

```python theme={null}
report = mc.status.health(detailed=False)   # no key needed; hits /health
if not report["api"].ok:
    print("API is down — try again later")
else:
    try:
        mc.api.enrich([{"record_id": "1", "linkedin_url": "..."}])
    except MinervaAuthError:
        print("API is fine — your key is the problem")
```

## What the authed view tells you

The `/health/metrics` payload is intentionally **minimal**: an overall
`status` + a generic `message` + a `refreshed_at` timestamp. By design
it does **not** expose:

* Per-route latency or error-rate numbers
* Internal service / Lambda names
* Endpoints you're not entitled to use

Engineers needing the raw view query their observability stack
(DataDog / Grafana) directly. The SDK's view is a quick "is everything
behaving?" signal.

## Authentication

`GET /health` is unauthenticated. `GET /health/metrics` requires the
same `x-api-key` you'd use for any data-API call. Both run on
`api.minerva.io` so there's no DNS or TLS overhead beyond the
data-plane connection your client already uses.

## Errors

`health()` never raises for probe failures — the outcome is encoded
in the returned `HealthStatus`. The only things that raise are misuses
of the call itself:

| Condition                                 | Raises                                  |
| ----------------------------------------- | --------------------------------------- |
| Unknown name in `endpoints=`              | `ValueError`                            |
| `detailed=True` but no API key configured | `MinervaAuthError`                      |
| Bad `timeout=` (e.g. negative)            | `TypeError` / `ValueError` (from httpx) |
