Health & Status API
Platform health, status, metrics, and the read-only provider/model inventory.
Endpoints
| Method | Path | Action | Description |
|---|---|---|---|
| GET | /health | none (public) | Health report — bypasses auth entirely |
| GET | /status | AgentList | Detailed daemon status |
| GET | /metrics | none* | Prometheus metrics |
| GET | /providers | ConfigRead | Boot-time provider inventory (read-only) |
| GET | /models | ConfigRead | Boot-time model inventory (read-only) |
| GET | /models/{key} | ConfigRead | Single model entry, by model id |
| GET | /gateway/pools | ConfigRead | Local/Cloud pool composition + live reachability |
| GET | /gateway/traffic?window=<secs> | ConfigRead | Per-model routing traffic over a trailing window (Issue #766) |
* /metrics is deliberately not on the public-path bypass list (issue #514 — it exposes provider health and request/spend volume, a reconnaissance surface) even though /health is. When auth is enabled, /metrics requires the same bearer credential as everything else.
GET /health
Always accessible regardless of auth mode (the one genuinely public path — orchestrator probes must always work).
curl http://localhost:4200/health
Response (200 OK)
{
"status": "healthy",
"uptime_secs": 51735,
"latency_p95_ms": 340,
"version": "0.42.0",
"agents": {"running": 5, "suspended": 1, "total": 6},
"providers": {"healthy": 3, "degraded": 0, "unavailable": 0},
"memory": {"state": "healthy"},
"audit_export": {
"running": true,
"pending_entries": 0,
"last_exported_seq": 3,
"last_exported_id": 1247,
"batches_exported": 12,
"entries_exported": 1247,
"consecutive_failures": 0,
"target": "s3://carrier-audit-bucket/hoziron"
}
}
latency_p95_ms is null until at least one request has completed since boot, or when metrics are disabled entirely. audit_export is present only when an audit-export consumer is actually running.
last_exported_seq is the segment sequence number the export cursor last confirmed drained, and last_exported_id the last-drained row id within that segment — together they identify the cursor's exact position across segment rotations. Prior to this pair, last_exported_id alone could not distinguish "caught up" from "an older, never-drained segment was silently skipped," since row ids reset to 1 on every segment rotation.
Status Values
| Value | Meaning |
|---|---|
healthy | All subsystems operating normally |
degraded | At least one subsystem degraded, none unavailable |
unavailable | At least one subsystem unavailable |
Health Check Example
curl -f http://localhost:4200/health || exit 1
Point any process supervisor's liveness/readiness check at GET /health on this same path and port.
GET /status
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
http://localhost:4200/status
Response (200 OK)
{
"uptime_secs": 51735,
"health": {"overall": "Healthy", "monitoring_enabled": true},
"providers": [
{"id": "ollama.west", "display_name": "Ollama (West)", "auth_status": "Configured", "model_count": 3}
]
}
auth_status is "Configured", "Missing", or "NotRequired" (local drivers typically need no API key).
GET /metrics
Prometheus exposition format. Returns 404 if metrics are not enabled.
curl -H "Authorization: Bearer hzn_sk_..." http://localhost:4200/metrics
Response (200 OK, text/plain; version=0.0.4)
# HELP hoziron_uptime_seconds Platform uptime in seconds
# TYPE hoziron_uptime_seconds gauge
hoziron_uptime_seconds 51735
# HELP hoziron_agents_total Number of agents by state
# TYPE hoziron_agents_total gauge
hoziron_agents_total{state="running"} 5
hoziron_agents_total{state="suspended"} 1
hoziron_agents_total{state="total"} 6
Response (404 — metrics not enabled)
{"error": {"category": "NotFound", "message": "Metrics not enabled"}}
Prometheus Scrape Config
scrape_configs:
- job_name: 'hoziron'
bearer_token: 'hzn_sk_...'
static_configs:
- targets: ['hoziron.internal:4200']
metrics_path: '/metrics'
scrape_interval: 30s
GET /providers
Read-only, boot-time provider inventory sourced from config.toml's [[provider]] array (ADR-053: providers/models are a closed, operator-authored set — there is no runtime mutation API for this).
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
http://localhost:4200/providers
Response (200) — ProviderSummary[]
{
"providers": [
{"id": "ollama.west", "display_name": "Ollama (West)", "auth_status": "Configured", "model_count": 3, "enabled": true}
]
}
GET /models
Read-only, decorated from the compiled-in model catalog. Only includes models whose owning provider has Configured/NotRequired auth status.
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
http://localhost:4200/models
Response (200) — ModelInfo[]
{
"models": [
{
"id": "llama3.2",
"display_name": "Llama 3.2",
"provider": "ollama.west",
"tier": "local",
"context_window": 128000,
"supports_tools": true,
"aliases": []
}
]
}
GET /models/{key}
Looks up a single model entry directly from the parsed config.toml (not the decorated available_models() list) — a different, narrower data source than /models.
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
http://localhost:4200/models/llama3.2
Response (200)
{
"id": "llama3.2",
"provider": "ollama.west",
"driver": "ollama",
"max_tokens": 2048,
"temperature": 0.1,
"context_window": null
}
Response (404 — not found in config, or no config file)
{"error": {"category": "NotFound", "message": "model 'gpt-9' not found in [[provider.model]] config"}}
GET /gateway/pools
Local/Cloud pool composition for the routing gateway (issue #634 Phase 2) — the exact split the gateway itself dispatches against, including each provider's driver-declared sovereignty disposition (ADR-053: AlwaysLocal/AlwaysCloud/Ambiguous) and live circuit-breaker reachability.
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
http://localhost:4200/gateway/pools
Response (200)
{
"local": [
{
"id": "ollama.west",
"driver": "ollama",
"disposition": "AlwaysLocal",
"sovereignty_reason": "native-local",
"auth_status": "Configured",
"reachable": true,
"models": ["llama3.2", "qwen2.5"]
}
],
"cloud": [
{
"id": "big.licence",
"driver": "anthropic",
"disposition": "AlwaysCloud",
"sovereignty_reason": "cloud",
"auth_status": "Configured",
"reachable": true,
"models": ["claude-sonnet-4-5-20260929"]
}
]
}
sovereignty_reason is one of native-local, attested-local, defaulted-cloud, cloud (ADR-053 §3) — this is the audit-relevant field distinguishing "structurally local" from "operator attested in-boundary," which the collapsed Local/Cloud split alone can't express.
GET /gateway/traffic
Per-model routing traffic over a trailing window, for the dashboard Routing
Gateway card's 24h/7d toggle (Issue #766). Counts are derived from the
audit log's routing-decision entries (routing:routing.local,
routing:routing.regional, routing:routing.decision_cloud), not a
separate metrics store.
curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
"http://localhost:4200/gateway/traffic?window=604800"
| Query param | Description | Default |
|---|---|---|
window | Trailing window in seconds | 86400 (24h) |
Response (200)
{
"window_secs": 604800,
"since": "2026-07-06T12:00:00Z",
"until": "2026-07-13T12:00:00Z",
"by_model": {
"anthropic/claude-sonnet-4-5-20250929": 342,
"ollama/llama3.2": 118
},
"total": 460
}
If no audit store is attached, by_model is always {} and total is 0 — this endpoint reads from the audit log, not a live counter, so it reports nothing on a build with auditing disabled.
Related
- usage-budget.md —
/licence/status, the routing gateway's licence/transaction-budget data - ADR-053 (
docs/decisions/053-provider-model-inventory-and-driver-disposition-sovereignty.md) — the provider/model inventory and sovereignty model these endpoints expose