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

# Health Check

> Public health endpoint for uptime monitors, host load balancers, and CI smoke tests.

The health endpoint returns the runtime state of GetMCP and its dependencies. **It's public** — no API key, nonce, or login required — so you can wire it into UptimeRobot, Pingdom, status pages, Kubernetes liveness probes, or whatever monitoring tool you use.

It's rate-limited to **60 requests per minute per IP** to discourage abuse, and emits `Cache-Control: no-store` so monitors always read fresh state.

***

## Authentication

None. The endpoint deliberately exposes only non-sensitive runtime data — no license keys, no API tokens, no server credentials.

***

## Response Body

```json theme={null}
{
  "status":      "ok",
  "version":     "1.0.0",
  "db_version":  "1.12",
  "checks": {
    "db":              { "status": "ok",   "latency_ms": 3 },
    "sodium":          { "status": "ok" },
    "rest":            { "status": "ok" },
    "active_servers":  { "status": "ok",   "count": 3 },
    "license":         { "status": "ok",   "tier": "pro" }
  },
  "uptime_seconds": 1845200,
  "checked_at":     "2027-05-13T10:42:18Z"
}
```

<ResponseField name="status" type="string">
  Overall health: `ok`, `degraded`, or `fail`. Aggregated from the per-check statuses below.
</ResponseField>

<ResponseField name="version" type="string">
  GetMCP plugin version (matches `getmcp.php` header).
</ResponseField>

<ResponseField name="db_version" type="string">
  Internal schema version. Bumped on each migration.
</ResponseField>

<ResponseField name="checks" type="object">
  Per-dependency status. Each check has its own `status` (`ok` | `warn` | `fail`) plus check-specific extras like `latency_ms` or `count`.
</ResponseField>

<ResponseField name="uptime_seconds" type="integer">
  Seconds since the plugin was first activated on this site (or since the install was first observed by the health endpoint, whichever is earlier).
</ResponseField>

<ResponseField name="checked_at" type="string">
  ISO 8601 timestamp when the response was generated.
</ResponseField>

***

## Status Aggregation

The top-level `status` rolls up from the per-check statuses:

| Per-check states    | Top-level `status` | HTTP status |
| ------------------- | ------------------ | ----------- |
| All `ok`            | `ok`               | `200 OK`    |
| At least one `warn` | `degraded`         | `200 OK`    |
| At least one `fail` | `fail`             | `503`       |
| Rate limit exceeded | (no body)          | `429`       |

Pick the right HTTP-status interpretation based on your monitor:

* **Strict probes** (Kubernetes, AWS ALB) — treat `200` as healthy, anything else as down. `degraded` will still show as up, which is usually correct because the API works but something deserves attention.
* **Status pages** (Statuspage, Better Uptime) — parse the JSON body and surface `status` directly so you can show "degraded" without flipping to red.

***

## Status Codes

| Code | Meaning                                                                 |
| ---- | ----------------------------------------------------------------------- |
| 200  | All checks passed (`ok`) or the system is up but degraded (`degraded`). |
| 429  | Rate limit exceeded for this IP — wait one minute.                      |
| 503  | At least one check failed. The body still includes the check details.   |

***

## Built-in Checks

| Check            | What it verifies                                         | Pass when                                         |
| ---------------- | -------------------------------------------------------- | ------------------------------------------------- |
| `db`             | DB is reachable and responsive (`SELECT 1` round-trip)   | Query completes within \~100ms                    |
| `sodium`         | libsodium PHP extension is loaded                        | `extension_loaded( 'sodium' )`                    |
| `rest`           | REST API is dispatching requests                         | Always — implicitly true if you got this response |
| `active_servers` | At least one MCP server is set to **Active**             | `count > 0`                                       |
| `license`        | License is in good standing (paid, trial, or unlicensed) | Not expired (paid). `warn` for expired trial.     |

***

## Rate Limiting

60 requests per minute per IP, evaluated against a sliding-window transient. Exceeded requests get:

```
HTTP/1.1 429 Too Many Requests
Cache-Control: no-store

{ "error": "rate_limited" }
```

The IP is resolved in this order:

1. `CF-Connecting-IP` (set by Cloudflare)
2. `X-Forwarded-For` (set by reverse proxies)
3. `X-Real-IP`
4. `REMOTE_ADDR`

If none of these resolve to a valid IP the rate limit fails open (the request is allowed) so requests behind aggressive proxies aren't blackholed.

***

<RequestExample>
  ```bash cURL theme={null}
  curl --silent https://yoursite.com/wp-json/getmcp/v1/health
  ```

  ```python Python theme={null}
  import requests
  r = requests.get("https://yoursite.com/wp-json/getmcp/v1/health", timeout=5)
  data = r.json()
  assert data["status"] == "ok", data
  ```

  ```javascript Node theme={null}
  const res = await fetch("https://yoursite.com/wp-json/getmcp/v1/health");
  const data = await res.json();
  console.log(`GetMCP status: ${data.status} (HTTP ${res.status})`);
  ```

  ```bash UptimeRobot keyword test theme={null}
  # Monitor type: HTTP(s) keyword
  # URL:          https://yoursite.com/wp-json/getmcp/v1/health
  # Keyword:      "status":"ok"
  # Alert when:   keyword NOT exists
  ```

  ```yaml Kubernetes liveness probe theme={null}
  livenessProbe:
    httpGet:
      path: /wp-json/getmcp/v1/health
      port: 80
    initialDelaySeconds: 30
    periodSeconds: 30
    timeoutSeconds: 5
    failureThreshold: 3
  ```
</RequestExample>

<ResponseExample>
  ```json 200 — Healthy theme={null}
  {
    "status":      "ok",
    "version":     "1.0.0",
    "db_version":  "1.12",
    "checks": {
      "db":             { "status": "ok", "latency_ms": 2 },
      "sodium":         { "status": "ok" },
      "rest":           { "status": "ok" },
      "active_servers": { "status": "ok", "count": 3 },
      "license":        { "status": "ok", "tier": "pro" }
    },
    "uptime_seconds": 1845200,
    "checked_at":     "2027-05-13T10:42:18Z"
  }
  ```

  ```json 200 — Degraded (license expired in trial) theme={null}
  {
    "status":      "degraded",
    "version":     "1.0.0",
    "db_version":  "1.12",
    "checks": {
      "db":             { "status": "ok", "latency_ms": 4 },
      "sodium":         { "status": "ok" },
      "rest":           { "status": "ok" },
      "active_servers": { "status": "ok", "count": 1 },
      "license":        { "status": "warn", "tier": "unlicensed", "message": "Trial expired" }
    },
    "uptime_seconds": 1209600,
    "checked_at":     "2027-05-13T10:42:18Z"
  }
  ```

  ```json 503 — Failed (sodium missing) theme={null}
  {
    "status":      "fail",
    "version":     "1.0.0",
    "db_version":  "1.12",
    "checks": {
      "db":             { "status": "ok", "latency_ms": 3 },
      "sodium":         { "status": "fail", "message": "libsodium extension not loaded" },
      "rest":           { "status": "ok" },
      "active_servers": { "status": "ok", "count": 3 },
      "license":        { "status": "ok", "tier": "pro" }
    },
    "uptime_seconds": 1845200,
    "checked_at":     "2027-05-13T10:42:18Z"
  }
  ```

  ```json 429 — Rate limited theme={null}
  { "error": "rate_limited" }
  ```
</ResponseExample>

***

<Note>
  This endpoint is intentionally lightweight — it does not run the deeper "is every active server's MCP endpoint reachable" probe that lives behind `POST /wp-json/getmcp/v1/settings/health-check` (which is admin-only and runs parallel pings). Use `health` for liveness / external monitoring, and `settings/health-check` from the admin UI for an in-depth diagnostic.
</Note>
