uclogserver
Log in

Health & status

Endpoints for load balancers, uptime monitors and dashboards. None of them needs a token except the metrics endpoint. A human-readable status page with 90 days of history is at /health. All health endpoints allow cross-origin requests.

Use it for Endpoint
Container / process liveness GET /healthz
Load-balancer readiness, uptime monitors GET /readyz
Dashboards, detailed checks GET /status
Availability history GET /health/history

Liveness

GET /healthz

No authentication

200 ok whenever the process is running. It doesn't check dependencies, so a database outage doesn't make an orchestrator restart healthy containers.

GET /healthz
curl "https://fw.unitcircle.ca/healthz"
http GET "https://fw.unitcircle.ca/healthz"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/healthz",
)
print(r.status_code, r.text)
Response
ok

Readiness

GET /readyz

No authentication

Checks the database and the object store (with short timeouts). 503 if either fails — point external uptime monitors here.

GET /readyz
curl "https://fw.unitcircle.ca/readyz"
http GET "https://fw.unitcircle.ca/readyz"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/readyz",
)
print(r.status_code, r.json())
Response
{
  "checks": {
    "db": {"ok": true, "latency_ms": 1},
    "storage": {"ok": true, "latency_ms": 7}
  },
  "ready": true
}

Retrieve service status

GET /status

No authentication

Overall status (ok, degraded or down), version, uptime, connection and request counters, database pool statistics, and the latest result of each check: database, object storage, email relay, and backup freshness. degraded means the service works but a check such as backups needs attention; down (HTTP 503) means the database is unreachable.

GET /status
curl "https://fw.unitcircle.ca/status"
http GET "https://fw.unitcircle.ca/status"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/status",
)
print(r.status_code, r.json())
Response
{
  "active_connections": 1,
  "checks": {
    "backup": {"age_hours": 6, "backend": "tarsnap", "last_success": "2026-09-25T18:23:18.83603Z", "ok": true},
    "db": {"latency_ms": 1, "ok": true},
    "mail": {"backend": "smtp", "checked_at": "2026-09-26T00:58:38.77972Z", "ok": true},
    "storage": {"latency_ms": 5, "ok": true}
  },
  "db_pool": {"acquire_wait_ms_total": 10, "idle": 1, "max": 10, "size": 1},
  "git_sha": "6238f4b",
  "requests_in_flight": 1,
  "requests_total": 75,
  "started_at": "2026-09-26T00:58:38Z",
  "status": "ok",
  "time": "2026-09-26T01:04:31Z",
  "uptime_seconds": 352,
  "version": "1.0.0"
}

Retrieve health history

GET /health/history

No authentication

Daily availability per component (api, db, storage, smtp, backup), from checks the service runs every minute, plus overall uptime per component for the period. Today's figures are live.

Query parameters

days integer

1–400, default 90.

GET /health/history
curl "https://fw.unitcircle.ca/health/history?days=2"
http GET "https://fw.unitcircle.ca/health/history?days=2"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/health/history",
    params={
        "days": "2"
    },
)
print(r.status_code, r.json())
Response
{
  "days": 2,
  "uptime_pct": {"api": 100, "backup": 100, "db": 100, "mail": 100, "storage": 100},
  "daily": [
    {"day": "2026-09-25", "component": "api", "samples": 339, "ok_samples": 339, "uptime_pct": 100, "p50_ms": 4, "p95_ms": 6, "incidents": 0},
    {"day": "2026-09-25", "component": "db", "samples": 339, "ok_samples": 339, "uptime_pct": 100, "p50_ms": 1, "p95_ms": 2, "incidents": 0},
    {"day": "2026-09-25", "component": "mail", "samples": 33, "ok_samples": 33, "uptime_pct": 100, "p50_ms": 9, "p95_ms": 55, "incidents": 0},
    {"…": "one entry per component per day"}
  ]
}

Retrieve metrics

GET /metrics

Admin token

Prometheus text format: request counts and durations per route, uploads, downloads, update checks, rate-limit rejections, database pool and backup age. Requires the operator's metrics token (Authorization: Bearer <METRICS_TOKEN>) or an administrator token.

GET /metrics
curl "https://fw.unitcircle.ca/metrics" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"
http -A bearer -a YOUR_ADMIN_TOKEN GET "https://fw.unitcircle.ca/metrics"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/metrics",
    headers={"Authorization": "Bearer YOUR_ADMIN_TOKEN"},
)
print(r.status_code, r.text)
Response
# TYPE http_requests_total counter
http_requests_total{route="/devices",method="GET",status="200"} 1
http_requests_total{route="/firmware/{domain}/{hw}/{hwver}/{fw}/{fwver}",method="GET",status="200"} 24
# TYPE http_request_duration_seconds summary
http_request_duration_seconds_sum{route="/devices",method="GET"} 0.004210
http_request_duration_seconds_count{route="/devices",method="GET"} 1
update_checks_total 24
uclogserver_uptime_seconds 352.4
backup_last_success_timestamp 1.790360598e+09

Retrieve signing keys

GET /.well-known/jwks.json

No authentication

The public keys that sign API tokens, as a JSON Web Key Set (Ed25519, EdDSA). Use them to check a token offline; the server still decides whether a token is revoked.

GET /.well-known/jwks.json
curl "https://fw.unitcircle.ca/.well-known/jwks.json"
http GET "https://fw.unitcircle.ca/.well-known/jwks.json"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/.well-known/jwks.json",
)
print(r.status_code, r.json())
Response
{
  "keys": [
    {"alg": "EdDSA", "crv": "Ed25519", "kid": "2026-09-01-a1B2", "kty": "OKP", "use": "sig", "x": "njhynTM5__vh7w8s7KYd-_P_1iqFbr46TrvrdgUlfiM"}
  ]
}

Retrieve the OpenAPI description

GET /openapi.json

No authentication

A machine-readable OpenAPI 3.1 description of every endpoint on these pages, for code generators and API clients.

GET /openapi.json
curl "https://fw.unitcircle.ca/openapi.json"
http GET "https://fw.unitcircle.ca/openapi.json"
import requests

r = requests.get(
    "https://fw.unitcircle.ca/openapi.json",
)
print(r.status_code, r.json())
Response
{
  "openapi": "3.1.0",
  "info": {"title": "uclogserver API", "version": "1.0.0", "…": "…"},
  "paths": {"…": "…"}
}