health
Returns the node's operational status and the typed condition catalog that explains it.
Method: GET /health
Parameters: None
Returns: JSON object containing the overall status, network/version information, validator identity (validator mode), and the full condition catalog
HTTP status: 503 when status is unavailable; 200 for every other state.
Example Request:
curl -X GET http://localhost:9153/healthExample Response:
{
"status": "healthy",
"network": "",
"node_version": "v0.6.0",
"protocol_version": "0.3.0",
"genvm_version": "v0.0.18",
"validator_address": "0x1234567890abcdef1234567890abcdef12345678",
"validator_moniker": "my-validator",
"conditions": {
"chain.rpc": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"chain.websocket": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"chain.sync": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"consensus.ban": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"consensus.stake": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"consensus.operator_funds": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"consensus.prime": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"genvm.llm": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" },
"genvm.web": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" },
"node.disk": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
"node.snapshot_age": { "state": "healthy", "severity": "info", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }
}
}Response Fields:
status(string): the most urgent state across the catalog —unknown,healthy,suspect,warning,degraded, orunavailablenetwork(string): The network the node is connected tonode_version,protocol_version,genvm_version(string): build versionsvalidator_address(string, validator mode only): The validator's addressvalidator_moniker(string, validator mode only): The validator's on-chain moniker; reported alongside the address even when emptyconditions(object): the full catalog. Every entry is always present — this is not a list of what is broken.state(string): current stateseverity(string): the catalog's fixed class —info,warning, orcritical. Not the state: acriticalcondition can behealthy.occurrences(number): accepted failed observations since process startconsecutive_failures(number): current failure streak; zero after recoverysince,updated_at,last_ok(string, optional): ISO 8601 lifecycle timestampsreason(string, present only when not healthy): stable, low-cardinality cause codeimpact(string, present only when not healthy): what the fault does to this nodeaction(string, present only when not healthy): what the operator should dodetails(object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected.
States
| State | Meaning | Alerted |
|---|---|---|
unknown | The node never observed this condition. | no |
healthy | The last observation succeeded. | no |
suspect | One failure, not yet confirmed. It may clear by itself. | no |
warning | The capability works, and it is confirmed sub-optimal. | yes |
degraded | The failure is confirmed. The capability does not work. | yes |
unavailable | The node cannot do its duty at all. | yes |
Roll-up precedence, most urgent first: unavailable, degraded, warning, suspect, unknown, healthy.
unknown is not on that ladder. It is only ever the state a condition starts in, so it means "not observed yet", never "faulty". A GenVM module rests there until a transaction needs one; node.snapshot_age rests there on a node that takes no snapshots. Letting either gate the endpoint would report unknown for a node where nothing is wrong.
If no condition has been observed at all, status is unknown — nothing observed is not the same as nothing wrong.
Only unavailable returns 503.
A condition can never exceed the liveness cap its catalog entry declares. The GenVM conditions are capped at degraded, so a dead LLM provider returns 200: that node is still booted, synced and voting — it votes Timeout on the transactions it cannot execute, which is the designed behaviour rather than an outage. Failing the endpoint would pull a working validator out of a load balancer.
consensus.ban, consensus.stake, chain.rpc, chain.websocket and chain.sync are capped at unavailable, because each of them stops the node doing its job.
Example: the LLM provider is dead — HTTP 200
{
"status": "degraded",
"conditions": {
"genvm.llm": {
"state": "degraded",
"severity": "critical",
"reason": "llm_request_failed",
"impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.",
"action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.",
"occurrences": 2,
"consecutive_failures": 2,
"since": "2026-09-01T10:30:45Z",
"updated_at": "2026-09-01T10:31:12Z",
"last_ok": "2026-09-01T04:12:03Z"
}
}
}Example: the validator is banned — HTTP 503
{
"status": "unavailable",
"conditions": {
"consensus.ban": {
"state": "unavailable",
"severity": "critical",
"reason": "validator_banned",
"impact": "The consensus contracts do not select this validator for a round. The node earns nothing.",
"action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.",
"details": { "until_epoch": "4182", "permanent": "false" },
"occurrences": 1,
"consecutive_failures": 1,
"since": "2026-09-01T09:14:02Z",
"updated_at": "2026-09-01T10:31:12Z",
"last_ok": "2026-09-01T09:13:52Z"
}
}
}chain.sync — 503 only when the node stops syncing
Depth is not a fault. A node syncing from genesis is millions of blocks behind and perfectly healthy. What matters is whether the stored synced block advances.
| Reason | State | HTTP |
|---|---|---|
sync_lagging | warning | 200 |
sync_catching_up | degraded | 200 |
sync_stalled | unavailable | 503 |
sync_feed_frozen | unavailable | 503 |
The node never skips a duty because it is behind. It votes Timeout only on a transaction that reads state it has not synced; a new contract deploy still runs.
cURL Example with Headers:
curl -X GET http://localhost:9153/health \
-H "Accept: application/json" \
-v