11# Health and Readiness Endpoints
22
3- The health module exposes three endpoints, each with a different contract.
3+ The health module exposes four endpoints with different contracts and intended consumers.
4+
5+ ## Liveness vs Readiness
6+
7+ | Endpoint | Purpose | Consumer |
8+ | ----------------------------- | ------------------------------------------------------------ | ----------------------------------------- |
9+ | ` GET /api/v1/health ` | Liveness — confirms the process is alive | Load balancers, uptime monitors |
10+ | ` GET /api/v1/health/ready ` | Readiness — confirms all critical dependencies are reachable | Kubernetes ` readinessProbe ` , deploy gates |
11+ | ` GET /api/v1/health/detailed ` | Diagnostics — full system snapshot | Operators, dashboards |
12+ | ` GET /api/v1/health/indexer ` | Worker heartbeat — confirms the indexer is running | Alerting, internal monitoring |
13+
14+ ** Liveness** never probes dependencies. It responds ` 200 ` as long as the event loop is processing requests. Use it wherever a dependency failure should not pull the instance from rotation (e.g., when a downstream DB blip should not make the server appear down).
15+
16+ ** Readiness** actively pings each critical dependency. A single ` fail ` result flips the response to ` 503 ` , signalling to the orchestrator that this instance should stop receiving traffic until the dependency recovers.
17+
18+ ---
419
520## ` GET /api/v1/health ` — liveness
621
7- A minimal "the process is up" check. Always ` 200 ` while the event loop is
8- healthy. Safe for load balancers and uptime monitors that should not fan out
9- to dependencies.
22+ A minimal "the process is up" check. Always ` 200 ` while the event loop is healthy. No dependency probing.
23+
24+ ### Response shape
25+
26+ ``` json
27+ {
28+ "success" : true ,
29+ "message" : " OK" ,
30+ "timestamp" : " 2026-04-28T16:00:00.000Z"
31+ }
32+ ```
33+
34+ ### Fields
35+
36+ | Field | Type | Description |
37+ | ----------- | ----------------- | -------------------------------------------------- |
38+ | ` success ` | boolean | Always ` true ` . |
39+ | ` message ` | string | Human-readable confirmation string. Always ` "OK" ` . |
40+ | ` timestamp ` | string (ISO-8601) | When the response was built. |
41+
42+ ### HTTP status codes
43+
44+ | Code | Condition |
45+ | ----- | ---------------------------------------- |
46+ | ` 200 ` | Always — liveness never returns non-200. |
47+
48+ ---
1049
1150## ` GET /api/v1/health/ready ` — readiness
1251
13- Probes critical dependencies (database, cache config). Returns ` 200 ` when
14- every probe passes and ` 503 ` otherwise.
52+ Probes critical dependencies (database, cache config). Returns ` 200 ` when every probe passes and ` 503 ` otherwise.
1553
1654### Response shape
1755
@@ -29,27 +67,172 @@ every probe passes and `503` otherwise.
2967
3068### Fields
3169
32- | Field | Type | Notes |
33- | ----------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
34- | ` ready ` | boolean | ` true ` only when every check is ` ok ` . Maps directly to the HTTP status (` 200 ` vs ` 503 ` ). |
35- | ` timestamp ` | string (ISO-8601) | When the response was built. |
36- | ` latencyMs ` | number | ** Total** wall-clock duration of the readiness probe — sum of every check plus orchestration overhead. Useful for dashboards and SLO tracking. |
37- | ` checks ` | array | Per-dependency results, each with its own ` name ` , ` status ` , optional ` latencyMs ` , and optional ` error ` . |
70+ | Field | Type | Description |
71+ | ----------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
72+ | ` ready ` | boolean | ` true ` only when every check returns ` "ok" ` . ** This field determines the HTTP status** — ` true ` → ` 200 ` , ` false ` → ` 503 ` . |
73+ | ` timestamp ` | string (ISO-8601) | When the response was built. |
74+ | ` latencyMs ` | number | Total wall-clock duration of the entire readiness probe in milliseconds. Useful for dashboards and SLO tracking. |
75+ | ` checks ` | array | Per-dependency probe results. See the checks table below. |
76+
77+ #### ` checks ` array entry fields
78+
79+ | Field | Type | Present when | Description |
80+ | ----------- | ------ | ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
81+ | ` name ` | string | Always | Dependency name. Current values: ` "database" ` , ` "cache" ` . |
82+ | ` status ` | string | Always | ` "ok" ` — probe passed; ` "fail" ` — probe failed. |
83+ | ` latencyMs ` | number | ` status: "ok" ` and check measured a latency | Round-trip time for this probe in milliseconds. |
84+ | ` error ` | string | ` status: "fail" ` | Human-readable reason for the failure. No internal hostnames, connection strings, or stack traces. |
3885
39- The payload is intentionally public-safe: no internal hostnames,
40- connection strings, or stack traces are included even when a check fails.
86+ #### Probe descriptions
87+
88+ | Probe name | What is checked | Healthy value | Unhealthy value |
89+ | ---------- | --------------------------------------------------------------------- | ---------------------------- | -------------------------- |
90+ | ` database ` | Issues ` SELECT 1 ` against the primary database. | ` "ok" ` + ` latencyMs ` present | ` "fail" ` + ` error ` present |
91+ | ` cache ` | Verifies that the HTTP cache layer config constant is a valid number. | ` "ok" ` | ` "fail" ` + ` error ` present |
92+
93+ ### Non-200 trigger
94+
95+ The ` ready ` field is the sole trigger. When ** any** check in the ` checks ` array has ` status: "fail" ` , ` ready ` is ` false ` and the HTTP status is ` 503 ` .
96+
97+ The payload is intentionally public-safe: no internal hostnames, connection strings, or stack traces are included even when a check fails.
98+
99+ ---
41100
42101## ` GET /api/v1/health/detailed ` — diagnostics
43102
44- Full system snapshot including memory, uptime, system info, database
45- response time, chain-sync lag and per-service health flags. Intended for
46- operators rather than load balancers — cheaper liveness/readiness paths
47- should be preferred for automated probing.
103+ Full system snapshot including memory, uptime, system info, database response time, chain-sync lag, and per-service health flags. Intended for operators and dashboards — use the cheaper liveness and readiness paths for automated probing.
104+
105+ ### Response shape
106+
107+ ``` json
108+ {
109+ "success" : true ,
110+ "message" : " Access Layer server is running" ,
111+ "timestamp" : " 2026-04-28T16:00:00.000Z" ,
112+ "version" : " 1.0.0" ,
113+ "environment" : " production" ,
114+ "uptime" : 3600.5 ,
115+ "memory" : {
116+ "used" : 48.32 ,
117+ "total" : 64.0
118+ },
119+ "system" : {
120+ "platform" : " linux" ,
121+ "nodeVersion" : " v20.11.0"
122+ },
123+ "timeouts" : {
124+ "database_timeout_ms" : 5000 ,
125+ "cache_timeout_ms" : 300000
126+ },
127+ "database" : {
128+ "status" : " connected" ,
129+ "responseTime" : 4
130+ },
131+ "syncing" : {
132+ "status" : " in-sync" ,
133+ "latestIndexedLedger" : 12345 ,
134+ "observedHeadLedger" : 12400 ,
135+ "syncLagLedgers" : 55
136+ },
137+ "services" : [
138+ { "name" : " API Server" , "status" : " healthy" },
139+ { "name" : " Database" , "status" : " healthy" },
140+ { "name" : " Chain Sync" , "status" : " healthy" }
141+ ]
142+ }
143+ ```
144+
145+ ### Top-level fields
146+
147+ | Field | Type | Description |
148+ | ------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
149+ | ` success ` | boolean | Always ` true ` in a normal response. ` false ` only on an unexpected exception. |
150+ | ` message ` | string | Human-readable status string. ` "Access Layer server is running" ` when healthy. |
151+ | ` timestamp ` | string (ISO-8601) | When the response was built. |
152+ | ` version ` | string | Application version string. |
153+ | ` environment ` | string | Deployment environment. Mirrors ` MODE ` env var (e.g. ` "production" ` , ` "staging" ` , ` "development" ` ). |
154+ | ` uptime ` | number (seconds) | Seconds since the Node.js process started (` process.uptime() ` ). Always ≥ 0. |
155+ | ` memory ` | object | JVM heap snapshot. See below. |
156+ | ` system ` | object | Runtime identity. See below. |
157+ | ` timeouts ` | object | Public-safe dependency timeout values. See below. |
158+ | ` database ` | object | Database connectivity status. See below. |
159+ | ` syncing ` | object \| absent | Chain indexer sync lag. Absent when the sync status check itself errors. |
160+ | ` services ` | array | Rolled-up health flags for each major component. See below. |
161+
162+ ### ` memory ` fields
163+
164+ | Field | Type | Description |
165+ | ------- | ------ | ----------------------------------------------------------------------- |
166+ | ` used ` | number | Node.js heap memory currently used, in ** megabytes** (rounded to 2 dp). |
167+ | ` total ` | number | Node.js heap memory allocated, in ** megabytes** (rounded to 2 dp). |
168+
169+ ### ` system ` fields
170+
171+ | Field | Type | Description |
172+ | ------------- | ------ | -------------------------------------------------------------- |
173+ | ` platform ` | string | Operating system platform string (e.g. ` "linux" ` , ` "darwin" ` ). |
174+ | ` nodeVersion ` | string | Node.js version string (e.g. ` "v20.11.0" ` ). |
48175
49- ### Field order and grouping
176+ ### ` timeouts ` fields
50177
51- The detailed health payload should preserve this top-level order to keep
52- JSON snapshots predictable for contributors and dashboards:
178+ Public-safe dependency timeout configuration. No connection strings, hostnames, or credentials are included.
179+
180+ | Field | Type | Description |
181+ | --------------------- | ------ | -------------------------------------------------- |
182+ | ` database_timeout_ms ` | number | Configured database query timeout in milliseconds. |
183+ | ` cache_timeout_ms ` | number | Configured public HTTP cache TTL in milliseconds. |
184+
185+ ### ` database ` fields
186+
187+ | Field | Type | Present when | Description |
188+ | -------------- | ------ | --------------------- | ---------------------------------------------------------------------- |
189+ | ` status ` | string | Always | ` "connected" ` — ` SELECT 1 ` succeeded; ` "disconnected" ` — query failed. |
190+ | ` responseTime ` | number | ` status: "connected" ` | Round-trip time for the DB probe in milliseconds. |
191+
192+ #### Database states
193+
194+ | ` status ` | Meaning | HTTP impact |
195+ | ---------------- | ------------------------------------------- | ----------------------------------------------------- |
196+ | ` "connected" ` | Database is reachable and responding. | Healthy. |
197+ | ` "disconnected" ` | Database probe threw an error or timed out. | ** 503** in ` production ` ; ` 200 ` in other environments. |
198+
199+ ### ` syncing ` fields
200+
201+ Present when the chain indexer sync status is available. Absent if the sync check itself errors.
202+
203+ | Field | Type | Description |
204+ | --------------------- | ------ | ----------------------------------------------------------------------------------- |
205+ | ` status ` | string | ` "in-sync" ` — lag is within threshold; ` "degraded" ` — lag exceeds the threshold. |
206+ | ` latestIndexedLedger ` | number | Most recent ledger the indexer has processed. |
207+ | ` observedHeadLedger ` | number | Latest ledger observed on the chain. |
208+ | ` syncLagLedgers ` | number | Difference: ` observedHeadLedger − latestIndexedLedger ` . Threshold: ** 100 ledgers** . |
209+
210+ #### Sync states
211+
212+ | ` status ` | Condition | ` syncLagLedgers ` |
213+ | ------------ | ---------------------------------- | ---------------- |
214+ | ` "in-sync" ` | Lag is within the 100-ledger limit | 0 – 100 |
215+ | ` "degraded" ` | Lag exceeds 100 ledgers | > 100 |
216+
217+ The sync state does ** not** affect the HTTP status code of this endpoint. Use ` GET /api/v1/health/indexer ` for a dedicated alerting-friendly probe.
218+
219+ ### ` services ` array
220+
221+ A rolled-up health summary of each major component.
222+
223+ | ` name ` | ` status: "healthy" ` condition | ` status: "unhealthy" ` condition |
224+ | -------------- | ------------------------------------------------- | ------------------------------------- |
225+ | ` "API Server" ` | Always healthy (present means the server is up). | Never unhealthy in normal operation. |
226+ | ` "Database" ` | ` database.status === "connected" ` . | ` database.status === "disconnected" ` . |
227+ | ` "Chain Sync" ` | ` syncing.status !== "degraded" ` (or sync absent). | ` syncing.status === "degraded" ` . |
228+
229+ ### Non-200 trigger
230+
231+ ` database.status === "disconnected" ` ** and** ` environment === "production" ` triggers a ` 503 ` . In non-production environments the endpoint always returns ` 200 ` regardless of database connectivity.
232+
233+ ### Field order
234+
235+ The top-level JSON keys are guaranteed to appear in this order:
53236
542371 . ` success `
552382 . ` message `
@@ -59,24 +242,75 @@ JSON snapshots predictable for contributors and dashboards:
592426 . ` uptime `
602437 . ` memory `
612448 . ` system `
62- 9 . ` database `
63- 10 . ` syncing `
64- 11 . ` services `
245+ 9 . ` timeouts `
246+ 10 . ` database `
247+ 11 . ` syncing `
248+ 12 . ` services `
249+
250+ ---
251+
252+ ## ` GET /api/v1/health/indexer ` — worker heartbeat
65253
66- Nested grouping follows the same convention:
254+ Reports the liveness state of the indexer background worker. The indexer calls ` POST /api/v1/health/indexer/heartbeat ` after each successful run; this endpoint exposes the resulting state.
67255
68- - ` memory ` : ` used ` , ` total `
69- - ` system ` : ` platform ` , ` nodeVersion `
70- - ` database ` : ` status ` , ` responseTime ` (when connected)
71- - ` syncing ` : ` status ` , ` latestIndexedLedger ` , ` observedHeadLedger ` , ` syncLagLedgers `
72- - ` services ` : ordered as ` API Server ` , ` Database ` , ` Chain Sync `
73- - ` timeouts ` : ` database_timeout_ms ` , ` cache_timeout_ms `
256+ ### Response shape
257+
258+ ``` json
259+ {
260+ "success" : true ,
261+ "data" : {
262+ "service" : " indexer" ,
263+ "status" : " healthy" ,
264+ "lastSuccessfulRun" : " 2026-04-28T15:59:00.000Z" ,
265+ "staleSinceMs" : null
266+ }
267+ }
268+ ```
269+
270+ ### Fields
271+
272+ | Field | Type | Description |
273+ | ------------------------ | -------------- | ---------------------------------------------------------------------------- |
274+ | ` success ` | boolean | Always ` true ` . |
275+ | ` data.service ` | string | Always ` "indexer" ` . |
276+ | ` data.status ` | string | Current worker state. See status table below. |
277+ | ` data.lastSuccessfulRun ` | string \| null | ISO-8601 timestamp of the most recent heartbeat, or ` null ` if none recorded. |
278+ | ` data.staleSinceMs ` | number \| null | Milliseconds since the heartbeat became stale, or ` null ` when not stale. |
279+
280+ #### Worker status values
74281
75- ### Detailed health fields
282+ | ` data.status ` | Condition | ` lastSuccessfulRun ` | ` staleSinceMs ` | HTTP status |
283+ | ------------- | ------------------------------------------------------------------------------------- | ------------------- | --------------- | ----------- |
284+ | ` "unknown" ` | No heartbeat has ever been recorded (fresh deploy or restart). | ` null ` | ` null ` | ` 200 ` |
285+ | ` "healthy" ` | Last heartbeat is within the stale threshold. | ISO-8601 string | ` null ` | ` 200 ` |
286+ | ` "degraded" ` | Last heartbeat exceeded the stale threshold (` INDEXER_HEARTBEAT_STALE_THRESHOLD_MS ` ). | ISO-8601 string | positive number | ` 503 ` |
76287
77- The ` /api/v1/health/detailed ` response also includes an explicit ` timeouts ` object with public-safe dependency timeout values:
288+ ### Non-200 trigger
78289
79- - ` database_timeout_ms ` (number) — configured database query timeout in milliseconds.
80- - ` cache_timeout_ms ` (number) — configured public cache timeout in milliseconds.
290+ ` data.status === "degraded" ` is the sole trigger for ` 503 ` . Both ` "unknown" ` and ` "healthy" ` return ` 200 ` .
291+
292+ ---
293+
294+ ## ` POST /api/v1/health/indexer/heartbeat ` — record worker run
295+
296+ Called by the indexer worker after each successful run to reset the stale timer.
297+
298+ ### Response shape
299+
300+ ``` json
301+ {
302+ "success" : true ,
303+ "data" : {
304+ "recorded" : true ,
305+ "timestamp" : " 2026-04-28T16:00:00.000Z"
306+ },
307+ "message" : " Heartbeat recorded"
308+ }
309+ ```
81310
82- These values are intentionally numeric-only and do not include connection strings, hostnames, credentials, or other internal topology details.
311+ | Field | Type | Description |
312+ | ---------------- | ----------------- | -------------------------------------------- |
313+ | ` success ` | boolean | Always ` true ` . |
314+ | ` data.recorded ` | boolean | Always ` true ` when the heartbeat was stored. |
315+ | ` data.timestamp ` | string (ISO-8601) | The time the heartbeat was recorded. |
316+ | ` message ` | string | Always ` "Heartbeat recorded" ` . |
0 commit comments