| title | RADAR REST API |
|---|---|
| description | Read-only JSON over the latest stored inventory report and assessment report. Base URL in local dev: http://127.0.0.1:8080. No authentication (cluster-internal service; expose via Sunbeam Proxy if... |
Read-only JSON over the latest stored inventory report and assessment report. Base URL in local dev:
http://127.0.0.1:8080. No authentication (cluster-internal service; expose via Sunbeam Proxy if
needed — see docs/KUBERNETES.md).
When RADAR_DASHBOARD_ENABLED is true (the default), the API serves a human-readable landing page
at the root path. It renders each tracked component, its current and latest versions, risk
assessment, dry-run status, and an emoji status indicator, styled with the Beam dark theme. The page
fetches /api/v1/components, /api/v1/assessments, and /api/v1/dryruns client-side.
Each table header has a dropdown menu for sorting. The default sort is assessment severity
(most-urgent first), then component name; select ascending/descending order or a specific value from
the dropdown. Components with a dry-run status show a show output button that opens a modal
preview of the namespace's dry-run output; the preview includes a link to the full
/output?namespace=<ns> page.
If RADAR_GRAFANA_URL is set, a Grafana icon appears in the page header and links to that URL.
When the dashboard is disabled, GET / returns 404 {"error":"not found"} like any unknown path.
| Endpoint | Purpose | Success | Failure |
|---|---|---|---|
GET /__lbheartbeat__ |
Liveness (cluster contract) | 200 ok (text) |
— |
GET /__heartbeat__ |
Readiness — checks store reachability | 200 ok (text) |
503 unavailable |
GET /health |
JSON health summary | 200 {"status":"ok",…} |
503 {"status":"error",…} |
GET /metrics |
Prometheus text metrics | 200 (text/plain) |
— |
The full latest report.
{
"generated_at": "2026-08-22 01:00:38 UTC",
"components": [{ "name": "Gateway API CRDs", "…": "…" }]
}404 {"error":"no inventory yet — run the radar job first"} before the first run.
The latest report's component records as a flat array (record shape: docs/SCHEMA.md).
One component record. name is URL-encoded and matched exactly (e.g.
/api/v1/components/Scaleway%20cert-manager%20webhook).
{
"name": "Cert-manager",
"namespace": "cert-manager",
"current": "1.19.4",
"latest": "v1.21.1",
"source": "helm_chart",
"upstream": "https://charts.jetstack.io::cert-manager",
"link_template": "https://github.com/cert-manager/cert-manager/releases/tag/{app_version}",
"notes": "Helm chart 1.19.4",
"update_available": true,
"chart_version": "1.19.4",
"track_app_version": true
}404 {"error":"component not found: {name}"} when absent.
The assess job (pipeline step 2 — see docs/ARCHITECTURE.md) attaches one assessment report to the latest inventory run; these endpoints serve it (record shape: docs/SCHEMA.md).
The full latest assessment report. Optional ?risk_level= narrows assessments to one risk level.
{
"generated_at": "2026-08-22 01:30:12 UTC",
"inventory_generated_at": "2026-08-22 01:00:38 UTC",
"assessments": [{ "name": "Cert-manager", "risk_level": "breaking", "…": "…" }]
}inventory_generated_at is the generated_at of the inventory run the assessments were computed
from. 404 {"error":"no assessments yet — run the radar assess job first"} before the first assess
run; 400 {"error":"invalid risk_level: … (expected one of …)"} when the filter value isn't a known
risk level.
Risk levels: breaking, deprecated, eol_warning, false_positive, floating_tag,
custom_fork, review, unknown, likely_safe, non_applicable. Assessments are sorted
most-urgent-first in exactly that order.
One assessment record. name is URL-encoded and matched exactly, like the component endpoints.
{
"name": "Cert-manager",
"current": "v1.19.4",
"latest": "v2.0.0",
"risk_level": "breaking",
"reason": "Major version bump: 1.19.4 → 2.0.0",
"action": "Read migration guide before upgrading",
"layer": "layer_0_precheck",
"details": { "from": "v1.19.4", "to": "v2.0.0" }
}layer names the analysis layer that produced the verdict; details carries layer-specific
evidence. 404 {"error":"assessment not found: {name}"} when absent.
The dry-run job (pipeline step 3 — see docs/ARCHITECTURE.md) attaches one dry-run report to the latest assessed inventory run; these endpoints serve it.
Dry-runs are grouped by Kubernetes namespace: all drifted likely_safe Helm components in a
namespace are bumped to latest together, the namespace is rendered once, and
kubectl apply --dry-run=server is run once. The resulting record covers every component whose
chart was mutated in that namespace, and all of them share the same status, stdout, and
stderr.
The full latest dry-run report. Optional ?status= narrows dry_runs to one status, and optional
?namespace= returns only the dry-run for that namespace (404 when not found).
{
"generated_at": "2026-08-22 02:00:15 UTC",
"inventory_generated_at": "2026-08-22 01:00:38 UTC",
"assessment_generated_at": "2026-08-22 01:30:12 UTC",
"dry_runs": [
{
"namespace": "longhorn-system",
"components": ["Longhorn"],
"status": "success",
"stdout": "namespace/longhorn-system created (dry-run)",
"stderr": "",
"duration_ms": 1234,
"details": {
"work_dir": "/tmp/radar-dryrun-xxx",
"namespace_base": "/tmp/radar-base-xxx/base/longhorn",
"component_count": 1,
"mutated_versions": { "Longhorn": "1.12.0" },
"kubectl_exit_code": 0,
"sunbeam_exit_code": 0
}
}
]
}Statuses: success, build_failed, dryrun_failed, skipped_no_mapping,
skipped_unsupported_source. 404 {"error":"no dry-runs yet — run the radar dry-run job first"}
before the first dry-run job; 400 {"error":"invalid status: … (expected one of …)"} when the
filter value isn't a known status.
One dry-run result, looked up by Kubernetes namespace (URL-encoded).
{
"namespace": "longhorn-system",
"components": ["Longhorn"],
"status": "success",
"stdout": "namespace/longhorn-system created (dry-run)",
"stderr": "",
"duration_ms": 1234,
"details": {
"work_dir": "/tmp/radar-dryrun-xxx",
"namespace_base": "/tmp/radar-base-xxx/base/longhorn",
"component_count": 1,
"mutated_versions": { "Longhorn": "1.12.0" },
"kubectl_exit_code": 0,
"sunbeam_exit_code": 0
}
}404 {"error":"dry-run not found: {namespace}"} when absent.
When the dashboard is enabled, this returns a human-readable HTML page for a single namespace's
dry-run output. It shows the namespace, status badge, and collapsible sections for kubectl stdout,
kubectl stderr, Sunbeam logs, and the raw details JSON.
400 {"error":"namespace query param required"} if namespace is missing.
404 {"error":"dry-run not found: {namespace}"} when the namespace has no dry-run record.
| Status | Body | When |
|---|---|---|
| 400 | {"error":"invalid risk_level: … (expected one of …)"} |
Bad ?risk_level= filter |
| 404 | {"error":"not found"} |
Unknown path |
| 404 | {"error":"no inventory yet — run the radar job first"} |
Store empty |
| 404 | {"error":"no assessments yet — run the radar assess job first"} |
No assessment run yet |
| 404 | {"error":"no dry-runs yet — run the radar dry-run job first"} |
No dry-run job yet |
| 404 | {"error":"component not found: …"} |
Unknown component name |
| 404 | {"error":"assessment not found: …"} |
Unknown component name |
| 404 | {"error":"dry-run not found: …"} |
Unknown component name |
| 400 | {"error":"invalid status: … (expected one of …)"} |
Bad ?status= filter |
| 405 | {"error":"method not allowed"} |
Non-GET on data endpoints |
| 503 | probe bodies above | Store unreachable |
- There are no write endpoints by design. Refresh happens by running the job (Kubernetes Job/CronJob, later wfe); the API always serves the latest stored run.
- Responses are pretty-printed JSON with
content-type: application/json; charset=utf-8. - Component names are not unique across runs but are unique within one report (postgres enforces
PRIMARY KEY (run_id, name)).