Health & Status API

Platform health, status, metrics, and the read-only provider/model inventory.

Endpoints

MethodPathActionDescription
GET/healthnone (public)Health report — bypasses auth entirely
GET/statusAgentListDetailed daemon status
GET/metricsnone*Prometheus metrics
GET/providersConfigReadBoot-time provider inventory (read-only)
GET/modelsConfigReadBoot-time model inventory (read-only)
GET/models/{key}ConfigReadSingle model entry, by model id
GET/gateway/poolsConfigReadLocal/Cloud pool composition + live reachability
GET/gateway/traffic?window=<secs>ConfigReadPer-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

ValueMeaning
healthyAll subsystems operating normally
degradedAt least one subsystem degraded, none unavailable
unavailableAt 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 paramDescriptionDefault
windowTrailing window in seconds86400 (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.


  • 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