Dashboard API: System and updates

Part of the Dashboard API documentation. The fundamentals there (base URL, response format, error codes) apply to every endpoint.

In the Gate column, – means a session is enough. Otherwise the column names the role or other requirements.

Diagnostics and health

Method Path Gate Purpose
GET /api/v1/health – Overall status: ok or degraded
GET /api/v1/health/storage – State of the storage medium
GET /api/v1/runtime-cache – Status of the runtime cache
GET /api/v1/diagnostics – All warnings
GET /api/v1/diagnostics/rules – IDs of the diagnostic rules
GET /api/v1/diagnostics/health – Health score

Each warning names its rule and the text variant it uses:

{
  "rule_id": "Offline",
  "key": "offline",
  "severity": "critical",
  "device_id": "node",
  "entity_id": "temp",
  "message": "Gerät meldet sich als offline.",
  "hint": "Stromversorgung, Netzwerk und Bridge prüfen"
}

key selects the catalog texts diagnostics.rule.<key>.message and .hint, which the dashboard shows in the page language. message and hint are the German fallback for clients that do not know the key. key is not rule_id, because one rule can produce more than one text.

GET /api/v1/health combines several sources:

{
  "status": "ok",
  "uptime_seconds": 86400,
  "runtime_cache": { "…": "…" },
  "mqtt": { "connected": true, "broker_address": "localhost:1883" },
  "storage": { "available": true, "medium": "…", "mode": "…", "confidence": "…" },
  "node": {
    "telemetry": { "cpu_temp_c": 47.8, "cpu_load_pct": 12.5, "ram_used_pct": 63.1, "undervoltage_now": false },
    "services": [
      { "id": "shelly", "state": "active" },
      { "id": "trucki", "state": "configured" }
    ]
  }
}

status becomes degraded when the runtime cache is degraded, the MQTT connection is missing or the storage check fails.

node reports what the dashboard’s node agent (internal/nodeagent) publishes about the Pi. It is only present when the agent is wired in. node.telemetry is missing until the first reading arrives and then carries cpu_temp_c, cpu_load_pct (one minute load average per core, capped at 100) and ram_used_pct (each a number or null) and the undervoltage_now flag. node.services has one entry per configured device service, { "id": "<service_id>", "state": … }. state is active when the service’s outstation/<id>/status/online is 1 and the last_update in its outstation/<id>/settings/status is no older than poll_interval_s * diagnostic_poll_multiplier + 60 s. Otherwise it is configured.

System actions

Method Path Gate Purpose
POST /api/v1/system/restart-dashboard system_actions + CSRF Restart the dashboard service
POST /api/v1/system/reboot system_actions + CSRF Reboot the Pi
POST /api/v1/system/poweroff system_actions + CSRF Shut down the Pi
GET /api/v1/system/logs/units view_logs Allowlisted units, since and line presets, and the defaults
GET /api/v1/system/logs view_logs Journal entries of one allowlisted unit (unit, since, lines, cursor, q)

The response is 202 Accepted with {"action":"…","status":"accepted"}. If a system action is already running, the answer is 409 with system_action_busy.

The service log endpoints only read. They need the view_logs role but no CSRF token. unit is required and must be one of the allowlisted units that /logs/units lists. since is in minutes (default 60) and lines is the number of entries (default 200), both limited to the presets from /logs/units. cursor continues an earlier page and q filters the lines by substring. Errors: log_query_invalid (400), journal_not_permitted (503), journal_failed (502, message holds the detail), journal_timeout (504) and journal_busy (429, two queries already run). Responses carry Cache-Control: no-store.

Updates

Method Path Gate Purpose
POST /api/v1/updates/install install_updates + CSRF Install the update the dashboard currently offers
GET /api/v1/updates/progress signed-in session How far a staged update is, or how the last install failed

POST /api/v1/updates/install takes no parameters. The body and the query are ignored, so a client cannot name a version: the request installs exactly the offered release, which is signed and is the only one the dashboard offers. The route is not HTTPS-only, because the role is not HTTPS-only either. The answer is 202 Accepted with {"version":"v0.10.9"}, the version being installed.

Errors: install_updates_forbidden (403), csrf_failed (403), no_update_available (409, nothing is offered), update_in_progress (409, an install or an updater job is already running), install_failed (500) and install_unavailable (501, this device cannot install directly).

GET /api/v1/updates/progress reports {"state":"preparing","version":"…"} while the installer prepares the offered update (download and staging). When the preparation fails, it reports {"state":"fail","version":"…","code":"…", "finished_at":"…"} until a newer job replaces it. After the staging run the job shows the usual pending, running and ok or fail states. The endpoint answers Cache-Control: no-store.

Versions and changelog

Method Path Gate Purpose
GET /api/v1/versions signed-in session The installed bundle and the version of every component
GET /api/v1/changelog signed-in session The installed changelog.json, ?component=<id> (repeatable) keeps only those components

Both read the installer’s state directory (/var/lib/energy-node-installer: installed-manifest.json, selection.json, changelog.json) and never write. On a node the installer never touched, versions answers 200 with "bundle": null and "components": []. changelog answers 404 with code no_changelog (also for a bundle from before changelog.json existed).

versions response:

{
  "bundle": {"version": "v0.7.5", "built_at": "2026-09-21T10:00:00+02:00", "arch": "armv6"},
  "running": {"dashboard": "v0.7.5"},
  "has_changelog": true,
  "components": [
    {"id": "dashboard", "label": "Dashboard", "kind": "app", "version": "v0.7.5", "installed": true},
    {"id": "service:shelly", "label": "Shelly", "kind": "service", "version": "v0.4.2", "installed": false}
  ]
}

installed is false for an optional service the operator deselected (selection.json). running.dashboard is the build version of the running binary, which can differ from bundle.version after the dashboard updated itself. Without a changelog.json, components falls back to what the manifest knows, with labels and kinds from a table in internal/versions that mirrors scripts/version/components.json (a test keeps them in step). ENERGY_NODE_INSTALLER_STATE_DIR overrides the directory. Only the local smoke test uses it.

Written for Energy Node v0.11.0.

Edit on GitHub