|
| 1 | +--- |
| 2 | +title: "Operational Endpoints API" |
| 3 | +doc-type: reference |
| 4 | +status: current |
| 5 | +version: "1.0.0" |
| 6 | +base-url: "/" |
| 7 | +last-updated: 2026-02-28 |
| 8 | +updated-by: "api-docs-writer (AYG-68)" |
| 9 | +related-code: |
| 10 | + - backend/app/api/routes/health.py |
| 11 | + - backend/app/main.py |
| 12 | + - backend/app/core/config.py |
| 13 | +related-docs: |
| 14 | + - docs/api/overview.md |
| 15 | + - docs/architecture/overview.md |
| 16 | +tags: [api, rest, health, operations, liveness, readiness, version] |
| 17 | +--- |
| 18 | + |
| 19 | +# Operational Endpoints API |
| 20 | + |
| 21 | +## Overview |
| 22 | + |
| 23 | +The operational endpoints provide container-orchestrator-compatible probes and |
| 24 | +build metadata for this service. They are mounted at the **application root** |
| 25 | +(not under `/api/v1`) so that Kubernetes, AWS ECS, and similar platforms can |
| 26 | +reach them without API-version routing logic. |
| 27 | + |
| 28 | +**Base URL:** `/` (root — no `/api/v1` prefix) |
| 29 | +**Authentication:** None — all three endpoints are fully public |
| 30 | + |
| 31 | +> These endpoints do **not** appear in the `/api/v1/openapi.json` spec and are |
| 32 | +> not shown in the Swagger UI at `/docs`. They are intentionally excluded to |
| 33 | +> keep the versioned API spec clean. |
| 34 | +
|
| 35 | +## Quick Start |
| 36 | + |
| 37 | +```bash |
| 38 | +# Liveness probe |
| 39 | +curl http://localhost:8000/healthz |
| 40 | + |
| 41 | +# Readiness probe |
| 42 | +curl http://localhost:8000/readyz |
| 43 | + |
| 44 | +# Build metadata |
| 45 | +curl http://localhost:8000/version |
| 46 | +``` |
| 47 | + |
| 48 | +--- |
| 49 | + |
| 50 | +## Endpoints |
| 51 | + |
| 52 | +### GET /healthz |
| 53 | + |
| 54 | +Liveness probe. Returns `200 OK` immediately with no dependency checks. Use |
| 55 | +this endpoint to tell the orchestrator that the process is alive and the event |
| 56 | +loop is running. It never contacts Supabase or any other external service. |
| 57 | + |
| 58 | +**Authentication:** None |
| 59 | +**Authorization:** Public |
| 60 | + |
| 61 | +**Parameters:** None |
| 62 | + |
| 63 | +**Response (200):** |
| 64 | + |
| 65 | +| Field | Type | Description | |
| 66 | +|-------|------|-------------| |
| 67 | +| `status` | string | Always `"ok"` | |
| 68 | + |
| 69 | +**Example Request:** |
| 70 | + |
| 71 | +```bash |
| 72 | +curl -X GET "http://localhost:8000/healthz" |
| 73 | +``` |
| 74 | + |
| 75 | +**Example Response:** |
| 76 | + |
| 77 | +```json |
| 78 | +{"status": "ok"} |
| 79 | +``` |
| 80 | + |
| 81 | +**Error Responses:** |
| 82 | + |
| 83 | +This endpoint has no application-level error responses. A non-`200` reply |
| 84 | +indicates a process crash or network-level failure, not an API error. |
| 85 | + |
| 86 | +--- |
| 87 | + |
| 88 | +### GET /readyz |
| 89 | + |
| 90 | +Readiness probe. Verifies that the service can accept traffic by checking |
| 91 | +connectivity to Supabase. Returns `200` when all checks pass; returns `503` |
| 92 | +when any dependency is unreachable. |
| 93 | + |
| 94 | +Container orchestrators use the `200`/`503` distinction to gate traffic: |
| 95 | +a pod that is alive (`/healthz` = 200) but not ready (`/readyz` = 503) is |
| 96 | +kept out of the load-balancer rotation until dependencies recover. |
| 97 | + |
| 98 | +**Authentication:** None |
| 99 | +**Authorization:** Public |
| 100 | + |
| 101 | +**Parameters:** None |
| 102 | + |
| 103 | +**Supabase check logic:** |
| 104 | + |
| 105 | +The check issues a lightweight `HEAD` request against a probe table via the |
| 106 | +Supabase PostgREST client. It considers the server **reachable** even if the |
| 107 | +table does not exist (PostgREST returns an HTTP-level `APIError` in that case, |
| 108 | +which still proves connectivity). Only a connection-level failure — or a |
| 109 | +missing Supabase client on `app.state` — is treated as `"error"`. |
| 110 | + |
| 111 | +**Response (200 — ready):** |
| 112 | + |
| 113 | +| Field | Type | Description | |
| 114 | +|-------|------|-------------| |
| 115 | +| `status` | string | `"ready"` | |
| 116 | +| `checks` | object | Per-dependency check results | |
| 117 | +| `checks.supabase` | string | `"ok"` when Supabase is reachable | |
| 118 | + |
| 119 | +**Example Request:** |
| 120 | + |
| 121 | +```bash |
| 122 | +curl -X GET "http://localhost:8000/readyz" |
| 123 | +``` |
| 124 | + |
| 125 | +**Example Response (200 — ready):** |
| 126 | + |
| 127 | +```json |
| 128 | +{ |
| 129 | + "status": "ready", |
| 130 | + "checks": { |
| 131 | + "supabase": "ok" |
| 132 | + } |
| 133 | +} |
| 134 | +``` |
| 135 | + |
| 136 | +**Example Response (503 — not ready):** |
| 137 | + |
| 138 | +```json |
| 139 | +{ |
| 140 | + "status": "not_ready", |
| 141 | + "checks": { |
| 142 | + "supabase": "error" |
| 143 | + } |
| 144 | +} |
| 145 | +``` |
| 146 | + |
| 147 | +**Error Responses:** |
| 148 | + |
| 149 | +| Status | When | |
| 150 | +|--------|------| |
| 151 | +| `200 OK` | Supabase is reachable (or PostgREST returned a table-level error, which still proves connectivity) | |
| 152 | +| `503 Service Unavailable` | Supabase connection failed, timed out, or `app.state.supabase` is not initialised | |
| 153 | + |
| 154 | +> **Note:** Unlike most API errors, the `503` response from `/readyz` does NOT |
| 155 | +> use the [standard error shape](../overview.md#standard-error-responses). It |
| 156 | +> returns the plain `{"status": "not_ready", "checks": {...}}` body shown above, |
| 157 | +> because this endpoint is designed for machine consumption by orchestrators, not |
| 158 | +> API clients. |
| 159 | +
|
| 160 | +--- |
| 161 | + |
| 162 | +### GET /version |
| 163 | + |
| 164 | +Returns build metadata injected at container image build time via environment |
| 165 | +variables. API gateways use this endpoint for service registration and |
| 166 | +canary-deployment tracking. |
| 167 | + |
| 168 | +**Authentication:** None |
| 169 | +**Authorization:** Public |
| 170 | + |
| 171 | +**Parameters:** None |
| 172 | + |
| 173 | +**Response (200):** |
| 174 | + |
| 175 | +| Field | Type | Description | |
| 176 | +|-------|------|-------------| |
| 177 | +| `service_name` | string | Service identifier (from `SERVICE_NAME` env var; default `"my-service"`) | |
| 178 | +| `version` | string | Semantic version string (from `SERVICE_VERSION` env var; default `"0.1.0"`) | |
| 179 | +| `commit` | string | Git commit SHA of the deployed image (from `GIT_COMMIT` env var; default `"unknown"`) | |
| 180 | +| `build_time` | string | ISO 8601 timestamp of the image build (from `BUILD_TIME` env var; default `"unknown"`) | |
| 181 | +| `environment` | string | Active deployment environment (from `ENVIRONMENT` env var; e.g. `"local"`, `"staging"`, `"production"`) | |
| 182 | + |
| 183 | +**Example Request:** |
| 184 | + |
| 185 | +```bash |
| 186 | +curl -X GET "http://localhost:8000/version" |
| 187 | +``` |
| 188 | + |
| 189 | +**Example Response:** |
| 190 | + |
| 191 | +```json |
| 192 | +{ |
| 193 | + "service_name": "my-service", |
| 194 | + "version": "1.2.0", |
| 195 | + "commit": "a3f1c2d", |
| 196 | + "build_time": "2026-02-28T08:00:00Z", |
| 197 | + "environment": "production" |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +**Example Response (unset build vars — local development):** |
| 202 | + |
| 203 | +```json |
| 204 | +{ |
| 205 | + "service_name": "my-service", |
| 206 | + "version": "0.1.0", |
| 207 | + "commit": "unknown", |
| 208 | + "build_time": "unknown", |
| 209 | + "environment": "local" |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +**Error Responses:** |
| 214 | + |
| 215 | +This endpoint has no application-level error responses. It reads from |
| 216 | +in-process settings and cannot fail at the application layer. |
| 217 | + |
| 218 | +--- |
| 219 | + |
| 220 | +## Kubernetes Probe Configuration |
| 221 | + |
| 222 | +Typical Kubernetes deployment snippet using these endpoints: |
| 223 | + |
| 224 | +```yaml |
| 225 | +livenessProbe: |
| 226 | + httpGet: |
| 227 | + path: /healthz |
| 228 | + port: 8000 |
| 229 | + initialDelaySeconds: 5 |
| 230 | + periodSeconds: 10 |
| 231 | + |
| 232 | +readinessProbe: |
| 233 | + httpGet: |
| 234 | + path: /readyz |
| 235 | + port: 8000 |
| 236 | + initialDelaySeconds: 10 |
| 237 | + periodSeconds: 15 |
| 238 | + failureThreshold: 3 |
| 239 | +``` |
| 240 | +
|
| 241 | +--- |
| 242 | +
|
| 243 | +## Changelog |
| 244 | +
|
| 245 | +| Version | Date | Change | |
| 246 | +|---------|------|--------| |
| 247 | +| 1.0.0 | 2026-02-28 | AYG-68: Initial release — `/healthz`, `/readyz`, `/version` | |
0 commit comments