Pulse/docs/API.md
rcourtman 2fc54a5a1f Merge pending maintenance repairs with current main
Preserve selected memory freshness and the VM/LXC interpretation.
Retain both native tool contracts and current API concurrency controls.
Qualify this source separately from release and installed recovery.

Contract-Neutral: Integrate already-governed parents without a new contract change. Incoming verification files already match main. Fresh integration tests and source-bound browser proof cover the combined memory and installer surfaces.
2026-10-18 15:11:28 +01:00

97 KiB
Raw Permalink Blame History

🔌 Pulse API Reference

Pulse provides a comprehensive REST API for automation and integration.

Base URL: http://<your-pulse-ip>:7655/api

🔐 Authentication

Most API requests require authentication via one of the following methods:

Create an API token in Pulse's API Access settings with only the scopes needed for your task. The read-only example below requires monitoring:read. Pass the token in the X-API-Token header, using a private header file so the secret does not appear in shell history or process arguments.

On a trusted machine, as the account running curl, prepare the file and open it in an editor. This preserves an existing file, restricts its directory and stops before editing if preparation fails or a credential path is symlinked or not a regular file:

(
  set -eu
  umask 077
  auth_dir="$HOME/.config/pulse"
  auth_file="$auth_dir/api-header"
  if [ -L "$HOME/.config" ] || [ -L "$auth_dir" ] || [ -L "$auth_file" ]; then
    printf 'Refusing a symlinked credential path.\n' >&2
    exit 1
  fi
  if [ -e "$auth_file" ] && [ ! -f "$auth_file" ]; then
    printf 'Credential file must be a regular file.\n' >&2
    exit 1
  fi
  mkdir -p "$auth_dir"
  chmod 700 "$auth_dir"
  touch "$auth_file"
  chmod 600 "$auth_file"
  vi "$auth_file"
)

In the editor, save just this line, replacing <token> with the API token:

X-API-Token: <token>

Define this helper in the same Bash session (curl 7.76 or later). The examples on this page use it to make one request, print only the HTTP status and save each response to a new owner-only file. This keeps resource names, audit details and credential-bearing error bodies out of terminal output and recordings.

pulse_api() (
  set -eu
  umask 077
  if [ "$#" -ne 2 ]; then
    printf 'Usage: pulse_api GET|POST|PUT|DELETE /api/path\n' >&2
    exit 2
  fi
  method=$1
  api_path=$2
  case "$api_path" in
    /api/*) ;;
    *) printf 'Use an API path on this Pulse instance, not a full URL.\n' >&2; exit 2 ;;
  esac
  case "$method" in
    GET) set -- ;;
    POST) set -- --header 'Content-Type: application/json' --request POST --data-binary @- ;;
    PUT) set -- --header 'Content-Type: application/json' --request PUT --data-binary @- ;;
    DELETE) set -- --request DELETE ;;
    *) printf 'This example helper accepts only GET, POST, PUT and DELETE.\n' >&2; exit 2 ;;
  esac
  auth_dir="$HOME/.config/pulse"
  auth_file="$auth_dir/api-header"
  if [ -L "$HOME/.config" ] || [ -L "$auth_dir" ] || [ -L "$auth_file" ] || [ ! -f "$auth_file" ]; then
    printf 'Prepare a regular, private header file first.\n' >&2
    exit 1
  fi
  chmod 700 "$auth_dir" || exit "$?"
  chmod 600 "$auth_file" || exit "$?"
  result_file=$(mktemp "$auth_dir/api-response.XXXXXX") || exit "$?"
  curl_exit=0
  status=$(curl --disable --fail-with-body --silent --show-error --globoff \
    --proto '=http' --noproxy 127.0.0.1 --connect-timeout 5 --max-time 20 \
    --header "@$auth_file" --output "$result_file" --write-out '%{http_code}' \
    "$@" "http://127.0.0.1:7655$api_path") || curl_exit=$?
  printf 'HTTP %s\nPrivate response: %s\n' "$status" "$result_file"
  [ "$curl_exit" -eq 0 ] || exit "$curl_exit"
  case "$status" in
    2??) ;;
    *) exit 1 ;;
  esac
)

Then make a read-only request with a monitoring:read token:

pulse_api GET /api/state/summary

The default loopback origin applies only when curl runs on the Pulse host and explicitly bypasses proxy environment settings. For remote access, replace http://127.0.0.1:7655 in the helper with your Pulse HTTPS origin, and replace --proto '=http' --noproxy 127.0.0.1 with --proto '=https'. Keep certificate verification enabled; for a private CA, add --cacert with a CA file you verified separately, never --insecure. Do not put credentials in the URL.

Open the reported response file privately to inspect it, including on failure; share only the relevant redacted error, not the whole response or file. Each call preserves earlier responses. HTTP 401 and 403 return curl's non-zero exit and retain the error body; redirects are not followed and are not success. The connection limit is five seconds and the whole request limit is twenty seconds. A partial file after a transport error is not a complete result. No request is retried automatically, especially a POST whose outcome is uncertain: inspect the existing action and target before deciding what to do. A 2xx response is only HTTP success, not proof that an action completed.

The helper also supports the existing RBAC administration examples. POST and PUT read their JSON body from standard input; GET and DELETE send no body. A method being supported is not permission to change a resource. Choose each administrative change separately and check its current state afterwards. A timeout or incomplete response can occur after a change was applied; do not repeat a POST, PUT or DELETE merely because the response was lost.

Keep --globoff: shell quotes alone do not disable curl's URL globbing. Without it, a path containing a brace list such as {review,ops} or a range such as [1-3] can send several requests, including several POST, PUT or DELETE requests. The helper treats these characters literally, not as a batch of targets. Still quote the whole path in the shell, and URL-encode dynamic resource IDs and query values; --globoff does not encode them for you.

Keep --disable first: it ignores local curl configuration that could enable trace output, bypass TLS or follow redirects. On an authentication-enabled instance, the protected summary checks token access; /api/health is public and does not verify authentication. The helper is local example code, not an installed Pulse command; define it again in a new shell before using these examples. Dispose of response files under your normal private-data policy.

Do not paste tokens into command lines, URLs or issue reports. Keep the header file private and outside shared repositories and diagnostics; do not use curl verbose/trace output when sharing a result. Share only the relevant redacted error, not the whole infrastructure response. Revoke tokens that are no longer needed in Pulse's API Access settings.

Bearer Token

The same API token can use a Bearer header instead. Replace the header file's line in the editor with the following, then use the same helper above; do not send both authentication headers:

Authorization: Bearer <token>

For one-off read-only diagnostics, use your signed-in Pulse browser to open the API path on that same instance. Do not extract or paste its session cookie into a command or report; use the scoped token-file method above for automation. State-changing session requests also require Pulse's CSRF protection, which the UI handles.

Session endpoints:

  • POST /api/login (sets the session cookie, pulse_session over plain HTTP or __Host-pulse_session over HTTPS, plus pulse_csrf)
  • POST /api/logout (clears session)

Login body:

{ "username": "admin", "password": "secret", "rememberMe": true }

Public endpoints include:

  • GET /api/health
  • GET /api/version
  • GET /api/agent/version (agent update checks)
  • GET /api/setup-script (public; the generated script prompts for its setup token at runtime and skips auto-registration if none is supplied)

🔏 Scopes and Admin Access

Some endpoints require admin privileges and/or scopes. Common scopes include:

  • monitoring:read
  • monitoring:write
  • settings:read
  • settings:write
  • agent:config:read
  • agent:manage
  • actions:plan, actions:approve, actions:execute
  • audit:read

Endpoints that require admin access are noted below.

Admin endpoints are instance administration unless they act only on the request organization. On instance administration an API token must not be bound to any organization other than the default one: a token bound to a tenant organization gets 403 whatever its scopes, as that organization's owner does. Endpoints that serve only the request organization, such as nodes and platform connections, agents, install commands, configuration transfer and reports, accept tokens bound to that organization with the listed scope.


📡 Core Endpoints

System Health

GET /api/health Check if Pulse is running.

{
  "status": "healthy",
  "timestamp": 1700000000,
  "uptime": 3600,
  "devModeSSH": false
}

System State

GET /api/state Returns the complete state of your infrastructure (Nodes, VMs, Containers, Storage, Alerts). This is the main endpoint used by the dashboard.

GET /api/state/summary Returns a lightweight integration summary for external dashboards and checks. Requires monitoring:read.

{
  "activeAlerts": 1,
  "nodes": 2,
  "vms": 8,
  "containers": 12,
  "dockerHosts": [
    {
      "name": "Docker Host",
      "containers": 5,
      "uptimeSeconds": 86400,
      "cpuUsagePercent": 12.5
    }
  ],
  "verdicts": {
    "ok": 17,
    "attention": 1,
    "critical": 1,
    "stale": 1,
    "off": 2,
    "unknown": 0
  },
  "attention": [
    {
      "id": "node-1",
      "name": "PVE Node 1",
      "type": "agent",
      "platformType": "proxmox",
      "verdict": "critical",
      "topReason": { "code": "offline" }
    }
  ],
  "lastUpdate": "2026-05-24T10:11:12Z"
}

verdicts uses the canonical ok, attention, critical, stale, off, and unknown fleet-health vocabulary. attention is severity ordered and capped at 30 entries. Powered-off workloads are neutral (off), and stale or missing telemetry is never reported as ok.

Simple Stats (HTML)

GET /simple-stats Lightweight HTML status page for quick checks.

Unified Resources

GET /api/resources Returns the unified resource list with pagination + aggregations. Requires monitoring:read.

Query params:

  • type: comma-separated list (e.g., agent, vm, system-container, app-container, docker-service, storage, pbs, pmg, k8s-cluster, k8s-node, pod, k8s-deployment, physical_disk, ceph)
  • source: comma-separated list (e.g., proxmox, agent, docker, pbs, pmg, kubernetes)
  • excludeSource: comma-separated list of sources that must not be present on a matching resource
  • status: comma-separated list (online, offline, warning, unknown)
  • parent: parent resource ID
  • cluster: cluster name
  • namespace: Kubernetes namespace (filters Kubernetes resources only)
  • q: name search (contains match)
  • tags: comma-separated tags
  • page: page number (default 1)
  • limit: page size (default 50, max 100)
  • sort: name (default), status, type, lastSeen
  • order: asc (default) or desc

Note: GET /api/resources is optimized for list views. Some large, platform-specific fields may be omitted from the list response and are only returned by GET /api/resources/{id}.

Each list resource can include a backend-owned health envelope with a canonical verdict and stable reasons (code plus optional compact detail). Consumers should use this envelope for cross-platform posture instead of deriving health from provider-specific status strings.

Guest readings: availability and age

In legacy VM payloads, disk.usage: -1 means unavailable, not negative usage. In GET /api/resources and GET /api/resources/{id}, an unavailable disk reading is omitted as metrics.disk. Do not replace an absent metric with zero; a genuinely observed zero is valid. Allocated virtual-disk capacity alone does not establish used space inside the guest.

Read proxmox.diskStatusReason alongside the value. A prev- reason means retained disk evidence, not a fresh reading. agent-not-running can also represent a general guest-agent HTTP 500 error: it does not establish that the guest's service is absent or stopped. An absent reason alone does not prove freshness either.

For memory, use metrics.memory.observation for the selected metric's source and age, not a different source's retained proxmox.memory facet:

Observation state Meaning for the consumer
current Accepted source observation; a normal cache hit can keep the original observedAt. Check its age for your use.
last-known Retained evidence, not a new measurement. Keep its original age visible; do not record it as a fresh sample.
unavailable No usable usage observation; do not treat retained numbers or zero-valued fields as a measurement.
Missing or unrecognised Freshness is unknown, including older payloads without an observation. Do not infer current.

observedAt belongs to that source observation. If it is absent or in the future, its age is unknown; do not substitute the resource's lastSeen or updatedAt. In the raw proxmox.memory facet, usageUnavailable: true can preserve known capacity without measured usage. An absent metrics.memory is not 0% usage. Conversely, an independently observed PVE or Pulse-agent memory reading can remain current while QEMU disk collection is deferred; disk state is not memory provenance.

A running VM, an advancing row timestamp or an OK backup does not prove fresh guest readings or successful thaw. Do not install or restart an agent or send live guest-agent probes merely to fill these gaps, especially during a backup, freeze/thaw or an unresponsive-guest incident. Follow Backup safety and missing-reading guidance. Recovery needs independent thaw, fresh successful writes to every covered filesystem and workload liveness; retained API values cannot establish it.

Availability is an additive resource facet. availability is the compatibility summary used by existing clients; availabilityChecks contains every check attached to the resource. Each check can include correlationState (attached, standalone, ambiguous, or unresolved), its correlation rule/reason/candidate count, and an evidence envelope with observation and validity timestamps. Attached targets also add a checks relationship and do not appear as separate network-endpoint rows.

GET /api/resources/stats Returns aggregations (counts + health rollups).

GET /api/resources/k8s/namespaces?cluster=<clusterName> Returns namespace-level rollups (pods + deployments) for a Kubernetes cluster. Requires monitoring:read.

{
  "cluster": "prod-k8s",
  "data": [
    {
      "namespace": "default",
      "pods": { "total": 12, "online": 10, "warning": 2, "offline": 0, "unknown": 0 },
      "deployments": { "total": 3, "online": 3, "warning": 0, "offline": 0, "unknown": 0 }
    }
  ]
}

GET /api/resources/{id} Fetch a single resource by ID.

GET /api/resources/{id}/children Returns child resources for the parent ID.

GET /api/resources/{id}/metrics Returns the resource metrics payload.

POST /api/resources/{id}/link (monitoring:write) Manually link two resources.

{ "targetId": "resource-id", "reason": "optional note" }

POST /api/resources/{id}/unlink (monitoring:write) Manually unlink two resources.

{ "targetId": "resource-id", "reason": "optional note" }

POST /api/resources/{id}/report-merge (monitoring:write) Report an incorrect merge (creates exclusions).

{ "sources": ["proxmox", "agent"], "notes": "optional note" }

Resource Maintenance and Operator State

Use a timed maintenance window to suppress alerts during planned work on a resource. This API, including descendant scope, is available in stable v6.4.1. Choose the resource's id from GET /api/resources and URL-encode it in the request path. Do not substitute a display name or a Proxmox VM number.

Endpoint Required scope Result
GET /api/resources/{id}/operator-state monitoring:read Returns the saved state, or HTTP 404 with operator_state_not_set when no state is saved.
PUT /api/resources/{id}/operator-state monitoring:write Replaces the entire state and returns the saved record.
DELETE /api/resources/{id}/operator-state monitoring:write Removes the entire state, returning HTTP 204 even if it was already absent.

PUT replaces the whole record, not just the fields supplied. Read the current state first and preserve unrelated settings, such as monitoring mode, lifecycle, remediation policy, criticality and notes. Treat only operator_state_not_set as an empty record, and stop on other read errors. Coordinate concurrent writers so a read followed by a PUT does not overwrite someone else's changes.

For a one-time window, merge these fields into that record, replacing the example times with your intended start and end:

{
  "maintenanceStartAt": "2026-10-15T20:00:00Z",
  "maintenanceEndAt": "2026-10-15T20:30:00Z",
  "maintenanceScope": "resource_and_descendants",
  "maintenanceReason": "Proxmox node maintenance"
}

This is a set of fields to merge, not a complete replacement for an existing record. Remove any maintenanceRecurrence when replacing a recurring schedule with this one-time window. The API rejects a record containing both.

Field Meaning
maintenanceStartAt, maintenanceEndAt RFC3339 timestamps. Supply both, with the end strictly after the start. The window is active from the start, inclusive, until the end, exclusive.
maintenanceScope resource applies only to this resource and is the default. resource_and_descendants also covers descendants in Pulse's resource hierarchy, such as a node's guests.
maintenanceReason Optional explanation of the planned work.

The response includes maintenanceWindowActive and, while active, maintenanceActiveStartAt and maintenanceActiveEndAt. The server sets the record's setAt and setBy fields from the request, ignoring client values.

During an active window, matching new alerts and firing/recovery notifications are suppressed. Applying an active window also clears matching existing alerts from the active list, retaining history. This is broader than pausing delivery for an existing incident. Resources outside the scope remain monitored normally.

The window expires automatically, with no re-enable request needed. Subsequent observations can raise alerts again, subject to normal alert policy and any other active maintenance window. Previously cleared alerts are not automatically restored, and expiry does not replay a backlog of maintenance notifications.

To end maintenance early, read the latest record, remove its maintenance fields and PUT the remaining state back. Do not DELETE the record unless you also intend to remove its other operator settings. An inherited window must be changed on the ancestor that owns it.

Fleet Connections

GET /api/connections Returns the canonical fleet connections ledger with per-row fleet-governance state. Requires admin access with settings:read.

The payload is the source of truth for enrollment, liveness, version drift, adapter health, config rollout, credential posture, update posture, and remote-control posture. Consumers must not rebuild those states from provider-specific config stores or display labels.

Define the private-response helper in Authentication, with settings:read on the token:

pulse_api GET /api/connections

Unified Action Planning

The existing action API separates planning, approval and execution. Run these steps separately, inspecting each response before moving on; do not paste the whole sequence as an unattended recovery script. Use the private header file and pulse_api helper from Authentication, not a token in a command or environment assignment. Inspect each reported private response file before proceeding. For remote access, change the helper as described there, using your Pulse HTTPS origin with certificate verification enabled.

Endpoint Token scope and access
GET /api/agent/resource-capabilities/{id} monitoring:read; lists the resource's advertised capabilities and parameter schemas.
POST /api/actions/plan actions:plan and permission to plan actions; creates a plan but does not approve or execute it.
POST /api/actions/{id}/decision actions:approve and permission to approve actions; records an explicit approved or rejected decision for a pending action, without executing it.
POST /api/actions/{id}/execute actions:execute and permission to execute actions; dispatches only an approved action or an approval-free executable plan. Dry-run-only plans cannot execute.
GET /api/audit/actions audit:read, audit-log read permission and licensed audit logging.
GET /api/audit/actions/{id}/events The same audit access; returns the action's lifecycle evidence.

The legacy ai:execute scope also permits planning, approval and execution, but use the narrower action scopes when possible. Approval and execution tokens must be bound to an authorised user; a scope alone does not bypass approval policy, separation of duties or step-up requirements. Audit reads through a browser session additionally require admin access. A monitoring:read token is not an action-control or audit token.

  1. Inspect capabilities. Choose the canonical resource id from GET /api/resources, not a display name or a VM number. Replace vm:42 throughout these examples and URL-encode it in request paths (vm%3A42 here). An empty capabilities list means there is nothing to plan for that resource.
pulse_api GET /api/agent/resource-capabilities/vm%3A42
  1. Plan only. Use an advertised capability and its actual parameter schema; restart and mode=graceful are examples, not universal capabilities. Choose a request ID for this specific intent. Pulse derives the actor from the authenticated credential, not a caller-supplied requestedBy value.
pulse_api POST /api/actions/plan <<'JSON'
{
  "requestId": "manual-recovery-123",
  "resourceId": "vm:42",
  "capabilityName": "restart",
  "params": { "mode": "graceful" },
  "reason": "Recover after confirmed outage"
}
JSON

Inspect the returned approval policy, blast radius, expiry and preflight checks. Keep its actionId and reviewed planHash; replace act_... and sha256:... below with those returned values. Planning is not proof that execution is safe or currently available.

  1. Decide only if approval is required. Use an authorised approver's private header file. Approve only the reviewed plan; stop on a refusal or stale-plan response rather than weakening the policy or creating another recovery action.
pulse_api POST /api/actions/act_.../decision <<'JSON'
{
  "outcome": "approved",
  "reason": "Inside maintenance window",
  "planHash": "sha256:..."
}
JSON
  1. Execute separately. Only after reviewing a successful decision, or an approval-free executable plan, use the executor's authorised header file:
pulse_api POST /api/actions/act_.../execute <<'JSON'
{
  "reason": "Execute approved recovery",
  "planHash": "sha256:..."
}
JSON

If the response is lost or times out, inspect the existing action and target before retrying: losing the connection does not prove that execution stopped. An HTTP success is not a substitute for checking the terminal result and the resource's observed state.

  1. Read the audit and events, using a private header file with audit access:
pulse_api GET '/api/audit/actions?resourceId=vm%3A42&limit=10'
pulse_api GET /api/audit/actions/act_.../events

When audit logging is unavailable, use Pulse's action detail in the signed-in UI to inspect the existing action; do not infer success or repeat execution from an unavailable audit response.

Response:

{
  "actionId": "act_...",
  "requestId": "manual-recovery-123",
  "allowed": true,
  "requiresApproval": true,
  "approvalPolicy": "admin",
  "predictedBlastRadius": ["vm:42", "node-1"],
  "rollbackAvailable": false,
  "message": "Plan created for restart on web-42. Execution requires admin approval and is not performed by this endpoint.",
  "plannedAt": "2026-05-03T10:00:00Z",
  "expiresAt": "2026-05-03T10:05:00Z",
  "resourceVersion": "resource:sha256:...",
  "policyVersion": "policy:sha256:...",
  "planHash": "sha256:...",
  "preflight": {
    "target": "vm:42",
    "currentState": "web-42 is warning",
    "intendedChange": "Restart the VM",
    "dryRunAvailable": false,
    "dryRunSummary": "No provider-supported dry run is advertised for this capability.",
    "safetyChecks": [
      "Resource was resolved from the unified resource registry.",
      "Capability is advertised by the resource contract.",
      "This endpoint plans only; it does not approve or execute the action.",
      "Execution requires admin approval."
    ],
    "verificationSteps": [
      "Refresh the resource and confirm the expected state after execution.",
      "Review /api/audit/actions/{actionId}/events for lifecycle evidence."
    ],
    "generatedAt": "2026-05-03T10:00:00Z"
  }
}

Resource Metadata

User notes, tags, and custom URLs for resources.

These routes do not require admin access. Any authenticated browser session can call them, and API tokens need the scope shown.

  • GET /api/agents/metadata (monitoring:read)

  • GET /api/agents/metadata/{agentId} (monitoring:read)

  • PUT /api/agents/metadata/{agentId} (monitoring:write)

  • DELETE /api/agents/metadata/{agentId} (monitoring:write)

  • GET /api/guests/metadata (monitoring:read)

  • GET /api/guests/metadata/{guestId} (monitoring:read)

  • PUT /api/guests/metadata/{guestId} (monitoring:write)

  • DELETE /api/guests/metadata/{guestId} (monitoring:write)

  • GET /api/docker/metadata (monitoring:read)

  • GET /api/docker/metadata/{containerId} (monitoring:read)

  • PUT /api/docker/metadata/{containerId} (monitoring:write)

  • DELETE /api/docker/metadata/{containerId} (monitoring:write)

  • GET /api/docker/runtimes/metadata (monitoring:read)

  • GET /api/docker/runtimes/metadata/{runtimeId} (monitoring:read)

  • PUT /api/docker/runtimes/metadata/{runtimeId} (monitoring:write)

  • DELETE /api/docker/runtimes/metadata/{runtimeId} (monitoring:write)

Version Info

GET /api/version Returns version, build time, and update status. Example response:

{
  "version": "6.0.0",
  "buildTime": "2026-02-21T00:00:00Z",
  "channel": "stable",
  "deploymentType": "systemd",
  "updateAvailable": false,
  "latestVersion": "6.0.0"
}

Version fields are returned as plain semantic versions (no leading v).


🖥️ Nodes & Config

Public Config

GET /api/config Returns a small public config payload (update channel, auto-update enabled).

List Nodes

GET /api/config/nodes (admin, settings:read)

This administrative inventory covers saved PVE, PBS and PMG connections, not individual VMs or agents. Keep its response private. Use the returned connection id for updates, deletion and tests; a display name, VMID or another installation's node name is not that ID. Re-read the inventory before a change rather than guessing an ID from a list position.

Add Node

POST /api/config/nodes (admin, settings:write)

Adding a connection saves credentials and enables collection; it is not a passive validation step. Use an existing dedicated, least-privilege platform token. The Pulse API token in the private header file authorises this configuration request; the Proxmox token in the JSON body authorises reads from the monitored platform. They are different credentials. Do not use a root password or widen a department-scoped token to make denied peer reads succeed; follow the scoped-access checks. For PBS and PMG, use their PBS and PMG token guidance. PBS password-based onboarding can also try to create a token and ACLs on the PBS server; it is not read-only.

Prepare the request in a private editor, not a credential-bearing shell assignment, here-document or command argument. Use the same Bash session as the private-response helper:

node_request_dir=$(umask 077; mktemp -d "${TMPDIR:-/tmp}/pulse-node.XXXXXX") &&
node_request_file="$node_request_dir/node.json" &&
(umask 077; set -C; : > "$node_request_file") &&
vi "$node_request_file"

If preparation or the editor fails, stop. In that editor, enter the following JSON, replacing the example hostname, connection name, full platform token ID (user@realm!token) and token-secret placeholder with your intended values:

{
  "type": "pve",
  "name": "PVE connection A",
  "host": "https://pve.example.net:8006",
  "tokenName": "pulse-monitor@pve!pulse-readonly",
  "tokenValue": "<platform-token-secret>",
  "verifySSL": true
}

Set verifySSL explicitly to true: omission on these new-connection and test request paths does not enable certificate verification. The example requires a certificate trusted by the Pulse server for that hostname. For a self-signed/private certificate, also supply fingerprint with the SHA-256 certificate fingerprint verified through an independent trusted channel. A fingerprint merely fetched from the endpoint is not established trust. Do not clear an existing pin, set verifySSL to false, or change to HTTP to work around a certificate failure.

After reviewing this one intended configuration change, send the file:

pulse_api POST /api/config/nodes < "$node_request_file"

The helper retains the response privately and makes one bounded request, without retries. A lost response can follow a successful save: re-read the saved inventory before deciding whether another request is needed. HTTP success does not establish fresh readings for every node, guest, backup or History view. Keep both request and response files out of reports and shared repositories; remove the temporary request under your credential policy when no longer needed.

Test Connection

POST /api/config/nodes/test-connection (admin, settings:write)

Tests supplied credentials with live platform requests without saving the connection. Use the same private JSON-file transport and explicit TLS settings, only when a live connection test is appropriate; it is not a required precursor to filing a report. Success covers the reads performed by that test, not all collection permissions, guest recovery or sustained monitoring.

Test Node Config (No Save, Live Request)

POST /api/config/nodes/test-config (admin, settings:write)

This also contacts the platform using the supplied credentials; no save does not mean offline schema validation. Do not run either test merely to collect new evidence during a freeze/thaw, backup or unresponsive-host incident. Use existing observations and the safe diagnostic guidance.

Update Node

PUT /api/config/nodes/{id} (admin, settings:write)

Review the current connection and submit only the intended supported changes from a private JSON file. Do not copy masked credentials from a GET response back as new secrets. Credential rotation and trust changes are separate decisions; preserve the existing credential and fingerprint when not changing them. A response failure does not prove the update was unapplied.

Delete Node

DELETE /api/config/nodes/{id} (admin, settings:write)

Removes the Pulse connection, not the platform workloads or the upstream token. Collection and related alert coverage can stop. Check the exact connection and arrange any needed independent coverage before an intentional removal; do not delete and re-add a connection to diagnose missing readings or identity mixing.

Test Node (Legacy)

POST /api/config/nodes/{id}/test (admin, settings:write)

Uses the saved connection for a live test. It is not a passive read of its last result or a guarantee that ordinary collection recovered.

An optional JSON body can override verifySSL and fingerprint for this test without changing the saved connection or credentials. Omitted fields keep their saved values. Send false to disable CA verification or "" to clear the saved fingerprint for the test. A nonempty fingerprint remains enforced regardless of verifySSL. Empty-body requests keep the saved TLS settings. For example, {"verifySSL":true,"fingerprint":"<SHA-256 fingerprint>"} tests an edited pin.

Refresh Cluster Nodes

POST /api/config/nodes/{id}/refresh-cluster (admin, settings:write)

Actively rediscovers a saved PVE connection's cluster membership. It does not select authorised nodes, grant missing permissions or repair an unrelated standalone installation. Keep intentional token restrictions intact.

Export Configuration

POST /api/config/export (instance admin for the default organization, tenant manager for a selected tenant, or an API token bound to the selected organization with settings:read) Request body:

{ "passphrase": "use-a-strong-passphrase" }

Returns an encrypted export bundle in data. Passphrases must be at least 12 characters.

Import Configuration

POST /api/config/import (instance admin for the default organization, tenant manager for a selected tenant, or an API token bound to the selected organization with settings:write) Request body:

{
  "data": "<exported-bundle>",
  "passphrase": "use-a-strong-passphrase"
}

🧭 Setup & Discovery

Setup Script (Public)

GET /api/setup-script Returns the Proxmox/PBS setup script as a shell-script download. Accepts an optional legacy setup_token query for compatibility. Current downloads contain no token: paste the separately revealed token only at the silent terminal prompt, or supply PULSE_SETUP_TOKEN_FILE pointing to a mode-0600 regular file in a mode-0700 directory owned by the script's user. Never put a token in a copied command or URL. Canonical callers must send a supported type of pve or pbs plus non-empty host and pulse_url; the route no longer generates placeholder-host scripts for later repair or reconstructs Pulse identity from the request origin. The route now shares the same canonical type boundary as /api/setup-script-url, rejecting unsupported node types instead of treating unknown values as PBS, and it normalizes the supplied host before script generation so downloaded artifacts and rerun URLs preserve the same canonical node identity as the bootstrap response. The optional backup_perms=true query is supported only for type=pve.

Setup Script URL

POST /api/setup-script-url (admin, settings:write) Generates a one-time setup token and URL for /api/setup-script. Canonical callers must send a supported type of pve or pbs plus a non-empty host; the backend normalizes that host before minting the setup token and now returns the canonical bootstrap identity back in the response as type, host, url, downloadURL, scriptFileName, command, commandWithEnv, commandWithoutEnv, setupToken, tokenHint, and expires. The returned commands are canonical root-or-sudo curl -fsSL bootstrap commands for the generated setup script, while url, downloadURL, and scriptFileName are the runtime-owned artifact metadata used by copy and manual download surfaces. The request body is a single canonical JSON object only; unknown fields and trailing JSON are rejected as invalid request shape, and backupPerms:true is supported only for type:"pve". This route stays on the normal authenticated bootstrap boundary: when Pulse auth is already configured it requires a real authenticated session or API token, and setup tokens do not authorize the request itself. Pulse-managed Proxmox monitor-token names on the setup/bootstrap path derive from the canonical Pulse endpoint, not request-local host fallbacks, so setup-script and turnkey node-add flows stay on one deterministic pulse-<canonical-scope-slug> identity per Pulse instance. The command, commandWithEnv, and commandWithoutEnv fields now contain identical credential-free commands. They download the complete script before running it and prompt silently in the root-or-sudo process. The token crosses the installer boundary through a private file, not process arguments or an exported secret. downloadURL equals the tokenless url, so a manual download also needs the separately revealed token at runtime. setupToken is used for /api/auto-register; tokenHint remains masked on the setup page. Settings reveals the token in a separate dialog: run the command first, then copy and paste the token only at its prompt. The artifact is reused only for the same host and options while its five-minute expiry is live, and discarded when the setup modal closes. Non-frontend consumers must validate the complete artifact, including the canonical host, type, URLs, filename, masked hint and live expiry. Current Unified Agents and the shell installer also accept the coherent older-server artifact during upgrades, but never execute its command text. For new Proxmox agent enrolment against a newer server, use its current installer; already enrolled agents keep reporting normally.

Auto-Register (Public)

POST /api/auto-register Registers a node through the canonical /api/auto-register contract using a temporary setup token carried in the JSON authToken field. Canonical callers must send a supported type of pve or pbs, an explicit source marker of agent or script, a canonical Pulse-managed tokenId in the form pulse-monitor@{pve|pbs}!pulse-<canonical-scope-slug> matching the requested type, an explicit serverName, and missing-token requests now fail with Pulse setup token required. Incomplete token completion requests now fail with tokenId and tokenValue must be provided together, and other missing canonical request fields fail with explicit Missing required canonical auto-register fields: ... guidance. Success responses now carry the canonical stored identity and caller boundary back to the installer or runtime-side Unified Agent: {"status":"success","action":"use_token","type":"pve|pbs","source":"agent|script","host":"https://...","nodeId":"<stored-name>","nodeName":"<stored-name>",...}.

Agent Install Command

POST /api/agent-install-command (admin, settings:write) Generates an API token and install command for agent-based Proxmox setup.

Discovery

GET /api/discover (admin, settings:write) Returns the last cached network discovery result from the background discovery service. It does not scan.

POST /api/discover (admin, settings:write) Runs a manual network scan. JSON body: {"subnet": "...", "use_cache": false}. subnet defaults to auto; use_cache: true returns the cached result instead of scanning.

Test Notification

POST /api/test-notification (admin, settings:write) Broadcasts a WebSocket test event.


📊 Metrics & Charts

Chart Data

GET /api/charts?range=1h Returns time-series data for CPU, Memory, and Storage. Ranges: 5m, 15m, 30m, 1h, 4h, 12h, 24h, 7d

Storage Charts

GET /api/storage-charts Returns storage chart data.

Storage Stats

GET /api/storage/{storageId} (monitoring:read) Returns one storage resource by its id, wrapped as {"data": {...}, "timestamp": 1700000000}. The data object includes id, name, node, instance, type, status, total, used, free, usage, content, shared, enabled and active. Returns HTTP 404 with code storage_not_found for an unknown ID, and HTTP 405 for methods other than GET. A request without an ID (GET /api/storage/) returns HTTP 400 with code missing_storage_id.

Recovery (formerly Backups / Snapshots)

Pulse v6 uses the recovery API to provide a platform-agnostic view of backup and snapshot artifacts. The endpoints below carry the provider-neutral contract covering subjects, points, rollups, posture, and filter semantics.

  • GET /api/recovery/points
    • Query params:
      • Core filters: provider, kind, mode, outcome, subjectResourceId, rollupId
      • Time window: from (RFC3339), to (RFC3339)
      • Paging: page, limit
      • Normalized filters: q, cluster, node, namespace, scope=workload, verification (verified | unverified | unknown)
  • GET /api/recovery/rollups
    • Query params: provider, kind, mode, outcome, subjectResourceId, rollupId, from (RFC3339), to (RFC3339), page, limit
  • GET /api/recovery/postures
    • Returns server-derived per-resource protection posture and provider evidence quality.
    • Query params: repeated resourceId values (maximum 200), state (protected | attention | unprotected | unknown), page, limit (maximum 200)
    • Batch clients must make one bounded request per 200 resource ids, never one request per table row.
    • Unknown identity, permission, history, or collection completeness remains unknown; clients must not infer a healthier state from raw backup or snapshot artifacts.
  • GET /api/recovery/series
    • Returns per-day counts for the activity chart.
    • Query params: same filters as /api/recovery/points (except paging), plus tzOffsetMinutes (integer; UTC offset minutes for day bucketing)
  • GET /api/recovery/facets
    • Returns distinct filter values (clusters/nodes/namespaces) and capability flags (size/verification/entity id present).
    • Query params: same filters as /api/recovery/points (except paging)

🔔 Notifications

Send Test Notification

POST /api/notifications/test (admin, settings:write) Sends one test notification through a single channel per request, not all configured channels. The JSON body method (or type) selects email, webhook, or apprise and uses the saved configuration. webhookId targets one specific saved webhook. config tests unsaved email or Apprise settings. If alert delivery is paused, the response includes deliveryPaused: true.

Email, Apprise, and Webhooks

  • GET /api/notifications/email (admin, settings:read; settings:write also accepted)
  • PUT /api/notifications/email (admin, settings:write)
  • GET /api/notifications/apprise (admin, settings:read; settings:write also accepted)
  • PUT /api/notifications/apprise (admin, settings:write)
  • GET /api/notifications/webhooks (admin, settings:read; settings:write also accepted)
  • POST /api/notifications/webhooks (admin, settings:write)
  • PUT /api/notifications/webhooks/<id> (admin, settings:write)
  • DELETE /api/notifications/webhooks/<id> (admin, settings:write)
  • POST /api/notifications/webhooks/test (admin, settings:write)
  • GET /api/notifications/webhook-templates (admin, settings:read; settings:write also accepted)
  • GET /api/notifications/webhook-history (admin, settings:read; settings:write also accepted)
  • GET /api/notifications/email-providers (admin, settings:read; settings:write also accepted)

Email and webhook configurations accept optional tagFilter (an array of resource-tag strings) and tagFilterMode ("all" or "any"). An omitted field is preserved on update; an empty tagFilter clears routing and restores delivery for all resources.

  • GET /api/notifications/health (admin, settings:read; settings:write also accepted)
    • Queue health is degraded whenever any retained failed or dlq delivery exists, and unavailable when queue state cannot be read. Recoverable failed attempts that returned to pending for retry do not degrade health.
    • queue.attention_required is the retained terminal-failure count. reason_codes identifies failed and/or dead-letter state without exposing notification content. Counts are retention-bounded: sent, failed, and cancelled rows are retained for 7 days; dead-letter rows for 30 days.

Audit Webhooks (Pro)

  • GET /api/admin/webhooks/audit (admin, settings:read)
  • POST /api/admin/webhooks/audit (admin, settings:write)
    • Body: { "urls": ["https://..."] }
    • Strict JSON contract: unknown fields and trailing payload are rejected.
    • Maximum 20 webhook URLs per update request.
    • URLs are normalized (trimmed) and duplicate entries are ignored.
    • Endpoint fails closed if URL validation runtime is unavailable.

Advanced Reporting (Pro)

Report generation uses settings:read. For single-resource and fleet PDF narration, an API token also needs ai:execute (or full access). Without it, the report still succeeds with a deterministic summary and no AI provider call; a single-resource PDF also explains the missing scope. Browser sessions and server-run schedules keep their existing narration behaviour. AI budgets still apply. GET and CSV never use AI narration.

  • GET /api/admin/reports/catalog (admin, settings:read)
    • Returns the canonical reporting catalog for the settings surface, including locked-state teaser copy, enabled-surface guidance copy, performance-report options, canonical single-report filename subject, canonical fallback filename date style, and the nested VM inventory export definition.
    • Metadata route: readable without the advanced_reporting feature so locked admin surfaces can render the same reporting definition before upsell.
  • POST /api/admin/reports/generate (admin, settings:read)
    • Body fields: format (pdf/csv, default pdf), resourceType, resourceId, metricType (optional), start/end (RFC3339, optional; defaults to last 24h), title (optional)
    • If title is omitted, the backend applies the canonical default title for that resource report.
    • When narration is permitted and Pulse Assistant is configured, the configured AI provider narrates a PDF's executive summary; each narration counts against the AI budget and appears in AI usage. If the call fails, times out or the budget is spent, the PDF falls back to the deterministic summary. CSV output is never narrated. The settings UI uses this method.
    • API-token callers also need the ai:execute scope for AI narration. With only settings:read the report is still produced, with the deterministic summary and a note saying why.
  • GET /api/admin/reports/generate (admin, settings:read)
    • Takes the same fields as query params and returns the same report, except that it never calls an AI provider: a PDF carries the deterministic summary and a note saying AI narration needs POST. Other methods return 405 with Allow: GET, POST.
  • POST /api/admin/reports/generate-multi (admin, settings:read)
    • Body fields: resources (1-50 entries of {resourceType,resourceId}), format, metricType (optional), start/end (RFC3339, optional; defaults to last 24h), title (optional)
    • If title is omitted, the backend applies the canonical default fleet report title.
    • AI narration of the fleet summary follows the same rule as single reports: API-token callers need ai:execute, otherwise the deterministic summary is used. CSV output is never narrated.
  • GET /api/admin/reports/inventory/vms/export (admin, settings:read)
    • Query params: format (csv only; optional and defaults to csv)
    • Exports the current fleet-wide VM inventory as spreadsheet-friendly CSV using the canonical runtime model.

Validation and limits:

  • start and end must be RFC3339 when provided.
  • Malformed start/end values, end not strictly after start, or report windows over 366 days return 400 invalid_time_range.
  • metricType must match [a-zA-Z0-9._:-]+ and be <= 64 chars, otherwise 400 invalid_metric_type.
  • title must be <= 256 chars, otherwise 400 invalid_title.
  • Report body max size is 1MB; oversized payloads return 400 body_too_large.
  • Report bodies reject trailing payload and unknown JSON fields with 400 invalid_body.
  • VM inventory export only accepts csv.

Common reporting error codes:

  • invalid_format, missing_params, invalid_resource_type, invalid_resource_id
  • invalid_metric_type, invalid_title, invalid_time_range
  • no_resources, too_many_resources, body_too_large, invalid_body

Queue and Dead-Letter Tools

  • GET /api/notifications/delivery-log?limit=200 (admin, settings:read)
    • Returns recent per-attempt delivery evidence with destination identifiers, alert identifiers, outcomes, timestamps, failure classes, and redacted error text. Completed attempts are retained for 7 days; dead-letter attempts are retained for 30 days, so the response reports both windows explicitly.
    • limit defaults to 50 and is capped at 200.
  • GET /api/notifications/queue/stats (admin, settings:read and monitoring:read)
    • Returns counts for all rows still retained by the queue. Status counts have different retention windows and are not a delivery rate or lifetime total.
  • GET /api/notifications/dlq (admin)
  • POST /api/notifications/dlq/retry (admin)
  • POST /api/notifications/dlq/delete (admin, settings:write; DELETE also accepted)
    • Body: { "id": "<notification id>" }. Marks that queue row cancelled with the note "Manually deleted from DLQ"; the row is not removed and is retained for 7 days like other cancelled rows. The endpoint does not check that the row is in the dead-letter queue.
    • Returns { "success": true, "message": "Notification deleted from DLQ", "id": "<notification id>" }. An empty or missing id, or an invalid JSON body, returns 400. An unknown id returns 500.
  • POST /api/notifications/terminal-failures/retry (admin, settings:write)
    • Returns every retained failed or dlq delivery to pending with a fresh retry budget. Existing per-attempt delivery history is preserved.
  • POST /api/notifications/terminal-failures/dismiss (admin, settings:write)
    • Marks every retained terminal delivery cancelled, clearing the active queue-health warning without deleting delivery history.

🚨 Alerts

Alert configuration and history (requires monitoring:read/monitoring:write).

For planned downtime, use Resource Maintenance and Operator State. For an existing incident whose notifications should be paused without clearing it, use the snooze and unsnooze endpoints below.

  • GET /api/alerts/config
  • PUT /api/alerts/config — replaces only the top-level keys the body carries; a key left out keeps its stored value, and the merged config is normalized as before. The body must be a JSON object.
  • GET /api/alerts/deadman/config — returns only whether an external watchdog is configured; pingUrl is ***REDACTED*** when present and never returns the credential-bearing URL
  • PUT /api/alerts/deadman/config — body { "pingUrl": "..." }; accepts a healthchecks-compatible base success URL, ***REDACTED*** to preserve the saved value, or an empty string to remove it
  • GET /api/alerts/deadman/status — live watchdog health, monitor-loop progress, sanitized delivery failure state, and the most recent restart interruption; never includes the URL or endpoint fingerprint
  • POST /api/alerts/activate enables notification delivery and can notify about existing unacknowledged critical alerts. It is not a maintenance toggle, and there is no matching /api/alerts/deactivate endpoint.
  • GET /api/alerts/active
  • GET /api/alerts/delivery-diagnosis?alertIdentifier=<alert-id> (omit alertIdentifier to get the diagnosis array for every active alert)
  • GET /api/alerts/events?alertIdentifier=<alert-id>&type=<event-type,...>&since=<RFC3339>&limit=<n> — append-only alert event log: lifecycle transitions and notification decisions, including suppressions with reasons; all parameters optional, newest first
  • GET /api/alerts/history
  • DELETE /api/alerts/history
  • GET /api/alerts/incidents
  • POST /api/alerts/incidents/note
  • POST /api/alerts/bulk/acknowledge
  • POST /api/alerts/bulk/clear
  • POST /api/alerts/acknowledge (body: { "alertIdentifier": "alert-id" })
  • POST /api/alerts/unacknowledge (body: { "alertIdentifier": "alert-id" })
  • POST /api/alerts/snooze (body: { "alertIdentifier": "alert-id", "until": "RFC3339 timestamp" }; pauses delivery and escalation for up to 30 days while monitoring continues)
  • POST /api/alerts/unsnooze (body: { "alertIdentifier": "alert-id" }; resumes normal policy without resolving the incident)
  • POST /api/alerts/clear (body: { "alertIdentifier": "alert-id" })

🛡️ Security

Security Status

GET /api/security/status Returns authentication status, proxy auth state, and security posture flags.

Change Password

POST /api/security/change-password

{ "currentPassword": "old-pass", "newPassword": "new-pass" }

In Docker installs, the response includes a restart notice.

Token management is instance administration, so a token bound to a tenant organization gets 403 on these routes. Among the tokens that remain, API-token callers manage only the tokens in their own organizations: every organization a target token is bound to must be one the caller is bound to. Tokens from other organizations are left out of the list and answer 404 on read, edit, delete and rotate. Only an instance-wide token (full access, bound only to the default organization) can create, edit, rotate or delete another instance-wide token. Browser sessions are not limited this way.

List API Tokens

GET /api/security/tokens

Create API Token

POST /api/security/tokens

{ "name": "ansible-script", "scopes": ["monitoring:read"] }

Edit API Token

PATCH /api/security/tokens/<id>

{ "scopes": ["monitoring:read", "settings:read"] }

Send scopes, name, or both; a body with neither returns 400. A rename-only body such as { "name": "new-name" } leaves the scopes unchanged, and a blank name returns 400. When scopes is sent it must contain at least one known scope, and wildcard access ("*") cannot be combined with other scopes. API-token callers that change scopes must already hold every existing scope on the target token and every scope they request. The update takes effect on the token's next request without changing its ID, secret, expiry, or organization bindings.

Rotate API Token

POST /api/security/tokens/<id>/rotate

Replaces one token with a new one and returns { "token": "raw-token", "record": { ... } } (the raw token is shown once). The new token keeps the old one's name, scopes, organization bindings, metadata and expiry, and the old token stops working. API-token callers must hold every scope on the target token.

Revoke Token

DELETE /api/security/tokens/<id>

Validate API Token (admin session or settings:write token)

POST /api/security/validate-token

{ "token": "raw-token" }

Returns { "valid": true, "message": "Token is valid" } for a known, unexpired token. An unknown or expired token returns HTTP 200 with "valid": false and "message": "Token is invalid". An empty token returns HTTP 400 with "valid": false and "message": "Token is required". When no API tokens are configured, the response is HTTP 200 with "valid": false and "message": "API token authentication is not configured". API-token callers need the settings:write scope; a token without it gets HTTP 403. Non-admin proxy-auth callers get HTTP 403. Requests over the rate limit get HTTP 429. A successful validation updates the token's last-used time. This endpoint is not gated by the Pro audit licence.

Recovery (Localhost or Recovery Token)

POST /api/security/recovery Supports actions:

  • generate_token (localhost only)
  • disable_auth
  • enable_auth

GET /api/security/recovery returns recovery mode status.

Reset Account Lockout (Admin or settings:write token)

POST /api/security/reset-lockout (admin, settings:write)

{ "identifier": "admin" }

Callers need an admin session, or an API token with the settings:write scope; a token without that scope gets 403 with error: "missing_scope". Identifier can be a username or IP address. An empty identifier returns 400 missing_identifier. Success returns { "success": true, "message": "Lockout reset for admin" }.

Regenerate API Token (Admin)

POST /api/security/regenerate-token (admin, settings:write)

Replaces every stored API token with one new full-access token. All other tokens stop working. An API token may call this only if it has full-access scope (*) and is not bound to a non-default organization; any other token gets 403, and should use POST /api/security/tokens/{id}/rotate to replace just itself.

Returns the new raw token (shown once) and updates stored hashes:

{
  "success": true,
  "token": "raw-token",
  "deploymentType": "systemd",
  "requiresRestart": false,
  "message": "New API token generated and active immediately! Save this token - it won't be shown again."
}

🧾 Audit Log (Pro)

These endpoints require admin access and the audit:read scope. On Community (no audit_logging license), these endpoints return HTTP 402 Payment Required with error: "license_required" and feature: "audit_logging" before any events are read.

List Audit Events

GET /api/audit?limit=100&event=login&user=admin&success=true&startTime=2024-01-01T00:00:00Z&endTime=2024-01-31T23:59:59Z

Response:

{
  "events": [
    {
      "id": "6b3c9c3c-9a2f-4b3c-9a3b-3d0e8c5c5d45",
      "timestamp": "2024-01-12T10:15:30Z",
      "event": "login",
      "user": "admin",
      "ip": "198.51.100.10",
      "path": "/api/login",
      "success": true,
      "details": "Successful login",
      "signature": "..."
    }
  ],
  "total": 1,
  "persistentLogging": true
}

Verify Audit Event Signature

GET /api/audit/<id>/verify

Response:

{
  "available": true,
  "verified": true,
  "message": "Event signature verified"
}

Bootstrap Token Validation (Public)

POST /api/security/validate-bootstrap-token

Provide the token via header X-Setup-Token or JSON body:

{ "token": "bootstrap-token" }

Returns 204 No Content on success.

Quick Security Setup (Public, bootstrap token required)

POST /api/security/quick-setup

Requires a valid bootstrap token (header X-Setup-Token) or an authenticated session. When authentication is already configured, a request that would change credentials (force: true) from an API token needs an instance-wide full-access token; any other token gets 403.

{
  "username": "admin",
  "password": "StrongPass!1",
  "apiToken": "token",
  "enableNotifications": false,
  "darkMode": false,
  "force": false,
  "setupToken": "optional-bootstrap-token"
}

Apply Security Restart (Systemd Only)

POST /api/security/apply-restart Applies auth changes by restarting the service (systemd deployments only).


⚙️ System Settings

Get Settings

GET /api/system/settings Retrieve current system settings.

Update Settings

POST /api/system/settings/update Update system settings. Requires admin + settings:write.

Legacy System Settings (Read Only)

GET /api/config/system Legacy system settings endpoint (read-only).

Toggle Mock Mode

GET /api/system/mock-mode POST /api/system/mock-mode PUT /api/system/mock-mode Enable or disable mock data generation (dev/demo only).

SSH Config (Temperature Monitoring)

POST /api/system/ssh-config Writes the SSH config used for temperature collection (requires setup token or auth).

Verify Temperature SSH

POST /api/system/verify-temperature-ssh Tests SSH connectivity for temperature collection (requires setup token or auth).

Scheduler Health

GET /api/monitoring/scheduler/health Returns scheduler health, DLQ, and breaker status. Requires authentication (session or API token); no specific scope is enforced.

Updates (Admin)

  • GET /api/updates/check
  • POST /api/updates/apply
  • GET /api/updates/status
  • GET /api/updates/stream
  • GET /api/updates/plan?version=X.Y.Z (optional channel, accepts v prefix)
  • GET /api/updates/history
  • GET /api/updates/history/entry?id=<event_id>
  • GET /api/updates/pro-runtime (settings:read): whether this community build can switch to the Pulse Pro runtime. The license server decides eligibility; reason explains a switch that is not offered (not_activated, not_eligible, older_release, unavailable, ...). Docker installs get the digest-pinned image commands in docker.
  • POST /api/updates/pro-runtime/apply (settings:write): installs the Pulse Pro runtime in place through the same verified, backed-up apply as an update (systemd and Proxmox LXC installs only). Returns 409 when no in-place switch is offered.

Infrastructure Updates

  • GET /api/infra-updates (requires monitoring:read)
  • GET /api/infra-updates/summary (requires monitoring:read)
  • POST /api/infra-updates/check (requires monitoring:write)
  • GET /api/infra-updates/agent/{agentId} (requires monitoring:read)
  • GET /api/infra-updates/{resourceId} (requires monitoring:read)

Diagnostics

  • GET /api/diagnostics (admin, settings:read)
  • POST /api/diagnostics/docker/prepare-token (admin, settings:write)

The Docker migration token request accepts agentId, optional tokenName, and optional enableHost. When enableHost is omitted, the generated token and install command enable both host and Docker monitoring so the machine appears in both Hosts and Docker. Set enableHost to false only for an intentional workload-only agent; that mode receives Docker-report scope only.

Logs (Admin)

  • GET /api/logs/stream (server-sent stream)
  • GET /api/logs/download (bundled logs)
  • GET /api/logs/level
  • POST /api/logs/level (set log level)

Server Info

GET /api/server/info Returns minimal server info for installer scripts.


🔑 OIDC / SSO

Provider Login

  • GET /api/oidc/{providerID}/login
  • GET /api/oidc/{providerID}/callback

OIDC and SAML configuration is managed via SSO providers.

SSO Provider Management (Community)

  • GET /api/security/sso/providers (admin, settings:read)
  • POST /api/security/sso/providers (admin, settings:write)
  • GET /api/security/sso/providers/{id} (admin, settings:read)
  • PUT /api/security/sso/providers/{id} (admin, settings:write)
  • DELETE /api/security/sso/providers/{id} (admin, settings:write)

Provider mutation request contract:

  • Max request body: 1MB.
  • Unknown JSON fields are ignored. A trailing payload after the JSON object is rejected with invalid_json.
  • Provider IDs must match server validation.
  • OIDC and SAML providers are included with the Community SSO entitlement.

SSO Test and Metadata Preview (Community)

  • POST /api/security/sso/providers/test (admin, settings:write)
  • POST /api/security/sso/providers/metadata/preview (admin, settings:read)

Test/preview request contract:

  • Max request body: 32KB.
  • Unknown JSON fields are ignored. A trailing payload after the JSON object is rejected with invalid_json.
  • Common errors include invalid_json, validation_error, rate_limited. A body over the 32KB limit is silently truncated, so it fails as invalid_json (these endpoints do not return body_too_large).

💳 License (Relay / Pro / legacy Pro+ / Cloud)

License Status (Admin)

GET /api/license/status

License Features (Authenticated)

GET /api/license/features

Activate License (Admin, settings:write)

POST /api/license/activate

{ "license_key": "PASTE_KEY_HERE" }

Clear License (Admin, settings:write)

POST /api/license/clear


👥 RBAC / Role Management (Pro)

Role-based access control endpoints for managing roles and user assignments. Requires admin access and the rbac license feature.

List Roles

GET /api/admin/roles Returns all defined roles.

Create Role

POST /api/admin/roles

{
  "id": "operator",
  "name": "Operator",
  "description": "Can view and manage alerts",
  "permissions": [
    { "action": "read", "resource": "alerts" },
    { "action": "write", "resource": "alerts" }
  ]
}

Update Role

PUT /api/admin/roles/{id} Update an existing role's name, description, or permissions.

Delete Role

DELETE /api/admin/roles/{id}

List Users

GET /api/admin/users Returns all users with their role assignments and, when available, mutable SSO presentation fields (displayName, email, providerType, providerId, and lastLoginAt). The opaque username remains the stable authorization principal.

Set User Roles

PUT /api/admin/users/{username}/roles

{ "roleIds": ["operator", "viewer"] }

Remove User Access

DELETE /api/admin/users/{username}

Deletes the Pulse RBAC identity and all assignments and revokes its active sessions. Administrators cannot remove their own current identity. This does not disable the upstream IdP account; a later authorized SSO login recreates the Pulse user record.

Note

: OIDC group-to-role mapping can automatically assign roles on login. See OIDC.md for configuration.


🏢 Organizations (Enterprise)

Multi-tenant organization management. Requires PULSE_MULTI_TENANT_ENABLED=true and an Enterprise license with the multi_tenant feature. All endpoints require authentication.

See MULTI_TENANT.md for setup and architecture details.

List Organizations

GET /api/orgs (requires settings:read) Returns organizations accessible to the authenticated user.

Create Organization

POST /api/orgs (requires settings:write, session auth only)

{ "id": "acme-corp", "displayName": "Acme Corporation" }

The creator becomes the owner and first member. Organization IDs use letters, digits, periods, underscores or hyphens, 1–64 characters, but cannot be . or ... Prefer a simple lowercase/hyphen ID such as the example.

Get Organization

GET /api/orgs/{id} (requires settings:read) Returns organization details. User must be a member.

Update Organization

PUT /api/orgs/{id} (requires settings:write, session auth only)

{ "displayName": "Updated Name" }

Admin or owner role required. The default organization cannot be updated.

Delete Organization

DELETE /api/orgs/{id} (requires settings:write, session auth only) Admin or owner role required. The default organization cannot be deleted.

List Members

GET /api/orgs/{id}/members (requires settings:read) Returns all members with their roles. User must be a member of the org.

Add or Update Member

POST /api/orgs/{id}/members (requires settings:write, session auth only)

{ "userId": "jane", "role": "editor" }

Roles: owner, admin, editor, viewer. Admin or owner role required. A new user receives a pending invitation (202) and must accept it in Pulse before gaining membership. Posting an existing member's userId updates their role; there is no member PATCH endpoint. Setting role to owner transfers ownership only to an existing member, by the current owner after fresh sign-in. Default org members cannot be managed.

Remove Member

DELETE /api/orgs/{id}/members/{userId} (requires settings:write, session auth only) Admin or owner role required. The organization owner cannot be removed.

List Outgoing Shares

GET /api/orgs/{id}/shares (requires settings:read) Returns resources shared outbound from this organization to others.

List Incoming Shares

GET /api/orgs/{id}/shares/incoming (requires settings:read) Returns resources shared inbound to this organization from other organizations.

Create Share

POST /api/orgs/{id}/shares (requires settings:write, session auth only)

{
  "targetOrgId": "partner-org",
  "resourceType": "vm",
  "resourceId": "vm-101",
  "resourceName": "Web Server",
  "accessRole": "viewer"
}

Share a resource with another organization, using the resource type and ID returned by Pulse. Supported types include vm, system-container, agent, node, docker-host, storage, pbs and pmg; generic host and container types are not supported. Access roles: viewer, editor, admin. Admin or owner role required on the source org. The share is pending until a target-org admin or owner accepts it in Pulse; changing its access role requires acceptance again.

Delete Share

DELETE /api/orgs/{id}/shares/{shareId} (requires settings:write, session auth only) Revoke a resource share. Admin or owner role required.


🤖 Pulse Intelligence

Paid gating: endpoints labeled with a paid plan require the relevant Relay, Pro, legacy Pro+, or Cloud capability and return 402 Payment Required if the feature is not licensed.

Get AI Settings

GET /api/settings/ai Returns current AI configuration (providers, models, patrol status). Requires admin + settings:read.

Update AI Settings

PUT /api/settings/ai/update (or POST /api/settings/ai/update) Configure AI providers, API keys, and preferences. Requires admin + settings:write.

List Models

GET /api/ai/models Lists models available to the configured providers (queried live from provider APIs).

Provider Tests (Admin)

  • POST /api/ai/test
  • POST /api/ai/test/{provider}

Legacy Anthropic OAuth Cleanup

Anthropic subscription OAuth is unsupported. These routes remain only for fail-closed compatibility and token cleanup:

  • POST /api/ai/oauth/start (admin): returns 501 with unsupported_anthropic_oauth
  • POST /api/ai/oauth/exchange (admin): returns 501 with unsupported_anthropic_oauth
  • GET /api/ai/oauth/callback (public): redirects to settings with ai_oauth_error=unsupported unless the provider supplied a specific error
  • POST /api/ai/oauth/disconnect (admin): clears stored legacy OAuth tokens

Execute (Chat + Tools)

POST /api/ai/execute Runs an AI request which may return tool calls, findings, or suggested actions.

Execute (Streaming)

POST /api/ai/execute/stream Streaming variant of execute (used by the UI for incremental responses).

Assistant Chat & Sessions

  • GET /api/ai/status
  • POST /api/ai/chat (streaming)
  • GET /api/ai/sessions returns session summaries; scoped Assistant handoffs may include a safe handoff_summary marker without model-only context text or command payloads.
  • POST /api/ai/sessions
  • DELETE /api/ai/sessions/{id}
  • GET /api/ai/sessions/{id}/messages
  • POST /api/ai/sessions/{id}/abort
  • POST /api/ai/sessions/{id}/summarize
  • POST /api/ai/sessions/{id}/fork
  • Legacy OpenCode-style file-change routes (GET /api/ai/sessions/{id}/diff, POST /api/ai/sessions/{id}/revert, POST /api/ai/sessions/{id}/unrevert) return 501 Not Implemented; Pulse Assistant sessions do not own file diffs or file-level revert.

Question Answers

  • POST /api/ai/question/{id}/answer

Kubernetes AI Analysis (Compatibility)

POST /api/ai/kubernetes/analyze

{ "cluster_id": "cluster-id" }

Requires Pro, legacy Pro+, or Cloud with the kubernetes_ai feature enabled. This route remains available for compatibility, but current v6 Pulse Pro marketing does not treat Kubernetes-specific analysis as a standalone plan pillar.

Alert Investigation (Pro)

POST /api/ai/investigate-alert Runs a focused investigation for an alert payload (used by the UI).

Patrol

  • GET /api/ai/patrol/digest
    • Returns the "what Patrol did for you" rollup for the last days days (query days, 1–30, default 7): the window and whether retained run history covers it, the effective Patrol mode, runs (total, scheduled, event-triggered, manual, failed, checks, resources covered, last run), findings (new, still open by severity, resolved, auto-resolved, dismissed, suppressed), investigations by outcome, Patrol-origin actions (proposed, approved, rejected, executed, verified, failed, pending), alerts Patrol reviewed, and estimated model spend with a pricing-known flag.
    • Computed from records Pulse already retains (run history, the findings store, canonical action audits, and usage cost events); nothing new is persisted. Requires the ai:execute scope. Backs the Patrol page's "This week" card; see docs/PATROL_WEEKLY_DIGEST.md.
  • GET /api/ai/patrol/attention
    • Returns the typed Patrol attention queue projected from canonical operational lifecycle records. This is the active-count and queue source used by both navigation and Patrol.
    • Query params: filter (active | open | acknowledged | suppressed | stale_unknown | resolved | all), page (minimum 1), and limit (1–200).
    • The response includes data, a lifecycle-wide summary, and bounded pagination meta. Protection context is joined in one bounded batch, not fetched per item.
  • GET /api/ai/patrol/attention/summary
    • Returns the canonical active, open, acknowledged, suppressed, stale/unknown, and recent-resolved counts plus calm, coverageState, and evaluatedAt.
    • A lifecycle-read failure returns a typed unavailable error. It never returns a synthetic zero or healthy state.
  • GET /api/ai/patrol/attention/{id}
    • Returns one attention item with its operational record, lifecycle timeline, typed evidence, recommended next step, relationships, and protection posture.
  • GET /api/ai/patrol/attention/{id}/evidence/{evidenceId}
    • Returns one exact retained evidence envelope with its current freshness.
    • Returns 410 attention_evidence_detail_expired when the operational record still links the ID but the bounded detail has expired.
  • POST /api/ai/patrol/attention/{id}/acknowledge
  • POST /api/ai/patrol/attention/{id}/unacknowledge
  • POST /api/ai/patrol/attention/{id}/suppress
    • Body: { "reason": "...", "expiresAt": "<RFC3339>" }.
    • The expiry must be in the future and no more than 30 days away.
  • POST /api/ai/patrol/attention/{id}/unsuppress
  • POST /api/ai/patrol/attention/{id}/actions/restart/plan
    • Creates or replays the one server-owned Docker restart plan attached to this operational record and its exact evidence IDs.
    • Requires the canonical action authorization, an eligible server-side offer, and the Pulse Pro ai_autofix entitlement. Clients cannot supply command authority or override the target.
  • Attention reads require monitoring:read; lifecycle mutations require monitoring:write. Action decision and execution use /api/actions and retain their existing action-specific scopes.
  • Attention IDs and evidence IDs are opaque and can contain slashes. Clients must path-escape each ID.
  • GET /api/ai/patrol/autonomy
  • PUT /api/ai/patrol/autonomy
  • GET /api/ai/patrol/status
  • GET /api/ai/patrol/findings
  • DELETE /api/ai/patrol/findings (clear all findings)
  • GET /api/ai/patrol/objectives
  • POST /api/ai/patrol/objectives
  • GET /api/ai/patrol/objectives/{id}
  • PATCH /api/ai/patrol/objectives/{id}
  • DELETE /api/ai/patrol/objectives/{id}?revision={revision}
    • Objective writes accept an outcome-oriented brief, optional optional_context, and optional canonical resource_ids. PATCH also accepts status (active, paused, or archived) and requires the current revision in its JSON body.
    • Responses include server-derived coverage (covered, degraded, or uncovered) and observer lifecycle state. Clients cannot submit either field or mark an objective covered. A saved objective remains uncovered until core records a validated, installed read-only observer with a live health lease.
    • Reads and writes require ai:execute. Stale writes return 409 patrol_objective_revision_conflict.
  • GET /api/ai/patrol/history
  • GET /api/ai/patrol/runs
  • GET /api/ai/patrol/runs/{id} (ai:execute)
    • Returns one retained Patrol run by the id from GET /api/ai/patrol/runs. Percent-encode the ID in the path. Tool-call arrays are omitted unless ?include=tool_calls. Returns 400 when the ID is empty, 404 (not_found) when no retained run has that ID, and 503 (service_unavailable) when the Patrol service is unavailable.
  • GET /api/ai/patrol/stream (ai:execute)
    • Server-sent events for live Patrol progress. Send Last-Event-ID (or the last_event_id query parameter) to resume after the last event id; replay is best-effort.
  • POST /api/ai/patrol/run (admin, ai:execute)
    • Community installs are limited to one successful full run per hour and get 429 patrol_rate_limited until it has passed. Pro and Cloud have no cap.
  • POST /api/ai/patrol/acknowledge (ai:execute)
  • POST /api/ai/patrol/dismiss
  • POST /api/ai/patrol/findings/note
  • POST /api/ai/patrol/resolve
  • POST /api/ai/patrol/snooze (ai:execute)
  • POST /api/ai/patrol/suppress (ai:execute)
  • GET /api/ai/patrol/suppressions (ai:execute)
  • POST /api/ai/patrol/suppressions (ai:execute)
  • DELETE /api/ai/patrol/suppressions/{id} (ai:execute)
  • GET /api/ai/patrol/dismissed (ai:execute)

Patrol itself and managing its findings (acknowledge, snooze, suppress, dismiss) are available on Community. Only the paid-labelled routes below return 402. Tokens issued for Pulse Mobile (relay:mobile:access) are also accepted on the acknowledge, dismiss and snooze routes.

Findings & Investigations

  • GET /api/ai/unified/findings
  • GET /api/ai/findings/{id}/investigation
  • GET /api/ai/findings/{id}/investigation/messages
  • POST /api/ai/findings/{id}/reinvestigate (Pro, ai:execute)
    • Needs the ai_autofix entitlement (402 license_required otherwise) and a Patrol mode other than Watch only (400 autonomy_disabled). Returns 409 investigation_running while one is already in progress.
  • POST /api/ai/findings/{id}/reapprove (Pro, ai:execute; retired: it always returns 410 Gone with error code command_fix_retired; approve typed actions through POST /api/actions/{id}/decision (actions:approve) instead)

Approvals & Command Execution (Pro)

  • GET /api/ai/approvals
  • GET /api/ai/approvals/{id}
  • POST /api/ai/approvals/{id}/approve
  • POST /api/ai/approvals/{id}/deny
  • POST /api/ai/run-command (retired: always returns 410 Gone with error code raw_command_retired; raw command execution is no longer supported, so use advertised typed resource actions instead)
  • GET /api/ai/agents (connected agents via /api/agent/ws)

Remediation Plans (Pro)

  • GET /api/ai/remediation/plans
  • GET /api/ai/remediation/plan?plan_id=<id>
  • POST /api/ai/remediation/approve (ai:execute; retired: on Pro builds it always returns 410 Gone with error code command_remediation_retired after the licence, method and scope checks, and community builds return a licence-required response; approve typed actions through POST /api/actions/{id}/decision (actions:approve) instead)
  • POST /api/ai/remediation/execute (admin, ai:execute; retired: on Pro builds it always returns 410 Gone with error code command_remediation_retired after the licence, method and scope checks, and community builds return a licence-required response)
  • POST /api/ai/remediation/rollback (admin, ai:execute; retired: on Pro builds it always returns 410 Gone with error code command_remediation_retired after the licence, method and scope checks, and community builds return a licence-required response; no rollback runs through this endpoint)

Intelligence & Forecasting

  • GET /api/ai/intelligence
  • GET /api/ai/intelligence/patterns
  • GET /api/ai/intelligence/predictions
  • GET /api/ai/intelligence/correlations
  • GET /api/ai/intelligence/changes (canonical unified-resource timeline first, patrol-local memory fallback)
  • GET /api/ai/intelligence/baselines
  • GET /api/ai/intelligence/remediations
  • GET /api/ai/intelligence/anomalies
  • GET /api/ai/intelligence/learning
  • GET /api/ai/forecast (params: resource_id, metric, optional resource_name, horizon_hours, threshold)
  • GET /api/ai/forecasts/overview (params: metric, horizon_hours, threshold)
  • GET /api/ai/learning/preferences (optional resource_id)
  • GET /api/ai/proxmox/events
  • GET /api/ai/proxmox/correlations
  • GET /api/ai/incidents (optional resource_id, limit)
  • GET /api/ai/incidents/{resourceId} (optional limit)
  • GET /api/ai/circuit/status

Knowledge Base

  • GET /api/ai/knowledge?guest_id=<id>
  • POST /api/ai/knowledge/save
  • POST /api/ai/knowledge/delete
  • GET /api/ai/knowledge/export?guest_id=<id>
  • POST /api/ai/knowledge/import
  • POST /api/ai/knowledge/clear

Debug

  • GET /api/ai/debug/context (admin)

Cost Tracking

  • GET /api/ai/cost/summary
  • GET /api/ai/patrol/cost-preview (optional model as provider:model, interval_minutes; projects Patrol's 30-day cost for a model and schedule from Pulse's price table and the install's run history, with the per-run token assumption, 30-day spend against budget, and a recommended schedule)
  • GET /api/ai/patrol/model-guidance (recommended / suggested / caution markers for the Patrol model pickers, plus this install's cached readiness pass)
  • POST /api/ai/cost/reset (admin)
  • GET /api/ai/cost/export (admin)

📈 Metrics Store

Auth required: monitoring:read.

Store Stats

GET /api/metrics-store/stats Returns stats for the persistent metrics store (SQLite-backed).

History

GET /api/metrics-store/history Returns historical metric series for a resource and time range.

Query params:

  • resourceType (required): node, storage, agent, disk, k8s, vm, system-container, oci-container, app-container, docker-host
  • resourceId (required)
  • metric (optional): cpu, memory, disk, etc. Omit for all metrics
  • range (optional): 1h, 6h, 12h, 24h, 1d, 7d, 30d, 90d (default 24h; duration strings also accepted)
  • maxPoints (optional): Downsample to a target number of points

License: Requests beyond Community's 7d floor require the paid long_term_metrics entitlement. Relay unlocks 14d, Pro and legacy Pro+ unlock 90d, and requests beyond the active tier's limit return 402 Payment Required. An explicit metric returns a points array; omitting it returns a metrics object keyed by metric name. Use the source ID and type from the affected chart's request, not its display name or an internal store type. Older container, dockerHost, dockerContainer, guest and docker query values are unsupported. See Metrics History for private one-shot reads, response sources and empty-history troubleshooting.


🤖 Agent Endpoints

GET /download/pulse-agent Downloads the unified agent binary. Without arch, Pulse serves the local binary on the server host.

Optional query:

  • ?arch=linux-amd64 (supported: linux-amd64, linux-arm64, linux-armv7, linux-armv6, linux-386, darwin-amd64, darwin-arm64, freebsd-amd64, freebsd-arm64, windows-amd64, windows-arm64, windows-386)

The response includes X-Checksum-Sha256 for verification.

The unified agent combines host, Docker, and Kubernetes monitoring. Use --enable-docker or --enable-kubernetes to enable additional metrics.

See UNIFIED_AGENT.md for installation instructions.

Agent Version

GET /api/agent/version Returns the current server version for agent update checks.

Agent Fleet Diagnostics

GET /api/agents/diagnostics (admin, settings:read) Returns read-only fleet triage for reported host, Docker / Podman, and Kubernetes agents, including liveness, version drift, profile deployment drift, identity-split evidence, and supported repair handoff hints. It does not enqueue remote actions.

Unified Agent Installer Script

GET /install.sh Serves the universal install.sh used to install pulse-agent on target machines.

Unified Agent Installer (Windows)

GET /install.ps1 Serves the PowerShell installer for Windows.

Submit Reports

POST /api/agents/agent/report - Agent metrics POST /api/agents/docker/report - Docker container metrics POST /api/agents/kubernetes/report - Kubernetes cluster metrics

Agent Management

GET /api/agents/agent/lookup?id=<agent_id>
GET /api/agents/agent/lookup?hostname=<hostname>
Looks up an agent by ID or hostname/display name. Requires agent:report.

POST /api/agents/agent/uninstall (agent:report token, or an admin session)
Agent self-unregister during uninstall: a token holding agent:report removes the agent that token owns. Without a token, only an admin session can remove an agent this way.

POST /api/agents/agent/unlink (admin, settings:write)
Unlinks an agent from a node.

DELETE /api/agents/agent/{agent_id} (admin, settings:write)
Removes an agent from state.

POST /api/agents/agent/{agent_id}/allow-reenroll (admin, settings:write)
Clears the removal block left by DELETE /api/agents/agent/{agent_id} so the agent can report again. Returns {"success": true, "agentId": "<agent_id>"}. Other methods return 405.

Agent Linking (Admin)

  • POST /api/agents/agent/link (admin, settings:write)
  • POST /api/agents/agent/unlink (admin, settings:write)

Agent Remote Config

GET /api/agents/agent/{agent_id}/config
Returns the server-side config payload for an agent (used by remote config and debugging). Requires agent:config:read. The config object includes the merged desired settings, command enablement decision, and desired-config metadata. When signing is configured, the signature remains backward-compatible with legacy agents: it covers agentId, commandsEnabled, settings, issuedAt, and expiresAt. desiredConfig is computed from the signed command decision plus the agent-applied settings keys and returned as tamper-evident metadata for newer clients to recompute and compare.

{
  "success": true,
  "agentId": "agent-123",
  "config": {
    "commandsEnabled": true,
    "settings": {
      "enable_docker": true
    },
    "desiredConfig": {
      "version": "host-agent-config/v1",
      "hash": "sha256:..."
    },
    "issuedAt": "2026-05-13T17:00:00Z",
    "expiresAt": "2026-05-13T17:15:00Z",
    "signature": "..."
  }
}

PATCH /api/agents/agent/{agent_id}/config (admin, agent:manage)
Updates server-side config for an agent (e.g., commandsEnabled).

Docker / Podman Module Management (Admin)

These routes manage Docker / Podman telemetry and container actions reported by the Docker / Podman module inside the installed pulse-agent binary.

  • POST /api/agents/docker/commands/{commandId}/ack (docker:report)
  • DELETE /api/agents/docker/runtimes/{agentId} (docker:manage, supports ?hide=true or ?force=true)
  • POST /api/agents/docker/runtimes/{agentId}/allow-reenroll (docker:manage)
  • PUT /api/agents/docker/runtimes/{agentId}/unhide (docker:manage)
  • PUT /api/agents/docker/runtimes/{agentId}/pending-uninstall (docker:manage)
  • PUT /api/agents/docker/runtimes/{agentId}/display-name (docker:manage)
  • POST /api/agents/docker/runtimes/{agentId}/check-updates (docker:manage)
  • POST /api/agents/docker/runtimes/{agentId}/update-all (docker:manage) — retired, returns 410 Gone; container updates run as reviewed per-container actions through /api/actions
  • POST /api/agents/docker/containers/update (docker:manage) — retired, returns 410 Gone; use the /api/actions plan/decision/execute flow instead

Kubernetes Agent Management (Admin)

  • DELETE /api/agents/kubernetes/clusters/{clusterId} (kubernetes:manage) — removes the cluster and blocks re-enrollment until allow-reenroll; takes no hide or force query parameters
  • POST /api/agents/kubernetes/clusters/{clusterId}/allow-reenroll (kubernetes:manage)
  • PUT /api/agents/kubernetes/clusters/{clusterId}/unhide (kubernetes:manage)
  • PUT /api/agents/kubernetes/clusters/{clusterId}/pending-uninstall (kubernetes:manage)
  • PUT /api/agents/kubernetes/clusters/{clusterId}/display-name (kubernetes:manage)

Agent Profiles (Pro)

GET /api/admin/profiles/ (admin, settings:write, Pro) POST /api/admin/profiles/ (admin, settings:write, Pro) GET /api/admin/profiles/{id} (admin, settings:write, Pro) PUT /api/admin/profiles/{id} (admin, settings:write, Pro) DELETE /api/admin/profiles/{id} (admin, settings:write, Pro) GET /api/admin/profiles/schema (admin, settings:write, Pro) POST /api/admin/profiles/validate (admin, settings:write, Pro) POST /api/admin/profiles/suggestions (admin, settings:write, Pro) GET /api/admin/profiles/changelog (admin, settings:write, Pro) GET /api/admin/profiles/deployments (admin, settings:write, Pro) POST /api/admin/profiles/deployments (admin, settings:write, Pro) GET /api/admin/profiles/{id}/versions (admin, settings:write, Pro) POST /api/admin/profiles/{id}/rollback/{version} (admin, settings:write, Pro) GET /api/admin/profiles/assignments (admin, settings:write, Pro) POST /api/admin/profiles/assignments (admin, settings:write, Pro) DELETE /api/admin/profiles/assignments/{agent_id} (admin, settings:write, Pro)

Every profile route requires admin access and the agent_profiles licence feature (HTTP 402 when unlicensed). Token callers also need settings:write on every profile route, including GET requests; session requests are not subject to the token scope check. Use the trailing slash on the collection path; the path without it is redirected, not served.


Availability Checks

Agentless availability checks monitor endpoint-only devices and services with ICMP ping, TCP port, UDP, HTTP, or HTTPS probes. They are managed from Settings -> Monitoring -> Availability checks and are also exposed through the API for automation.

Target Management

  • GET /api/availability-targets (admin, settings:read) - List configured targets and latest probe status.
  • POST /api/availability-targets (admin, settings:write) - Add a target.
  • PUT /api/availability-targets/{id} (admin, settings:write) - Update a target.
  • DELETE /api/availability-targets/{id} (admin, settings:write) - Remove a target.
  • POST /api/availability-targets/test (admin, settings:write) - Test an unsaved target. If the body includes the id of a saved target, that target's stored HTTP credentials are reused, but only while the HTTP scheme, host and port are unchanged.
  • POST /api/availability-targets/{id}/test (admin, settings:write) - Test a saved target.

Target payload fields:

  • name - Display name.
  • targetKind - machine, service, or device; defaults to service.
  • address - Hostname, IP address, or URL.
  • protocol - icmp, tcp, udp, http, or https. The input alias ping is accepted and is stored/returned as canonical icmp.
  • port - Required for tcp/udp, optional for http/https, and omitted for icmp.
  • path - Optional HTTP path.
  • udpMode - For UDP, response_required (default) or open_or_filtered. The latter reports silence as indeterminate and fails only on an explicit rejection.
  • udpRequest - UTF-8 request bytes, up to 512 bytes. Required by response_required; optional in open_or_filtered mode.
  • udpExpectedResponse - Optional exact UTF-8 response, up to 4096 bytes.
  • enabled - Whether the target is scheduled.
  • pollIntervalSeconds - Minimum 10 seconds; defaults to 60.
  • timeoutMillis - Minimum 250 milliseconds; defaults to 2000.
  • failureThreshold - Number of consecutive failures before alerting; defaults to 2.
  • linkedResourceId - Optional resource id hint for attaching the probe facet to an existing resource.
  • certificateMonitoringDisabled - Explicit HTTPS-only opt-out; certificate validity monitoring is enabled by default.
  • certificateExpiryWarningDays - HTTPS certificate expiry warning window; defaults to 30 days.
  • observationLocationIds - Observation locations for this one logical check: pulse:local for this Pulse server and agent:<host-agent-id> for a connected agent. Agent locations require the Pro external_probe entitlement. Include pulse:local only when a local observation is wanted; agent-only sets do not also run locally. Each location retains separate evidence rather than treating one path failure as a universal outage.
  • probeAgentId - Compatibility field for single-location clients. A non-empty registered host-agent ID selects that agent; an explicit empty string selects local execution. When updating only this field, omit observationLocationIds. For multi-location updates, send the complete desired observationLocationIds set and clear probeAgentId to "" so a legacy value cannot override a single-location edit.

External agents collect observations, but alert evaluation and notification delivery still depend on the Pulse server. Missing reports are not proof of a target outage. See probe coverage and outage limits.

An explicit linkedResourceId is authoritative and fails closed when it cannot resolve. Without it, Pulse correlates only on one exact normalized IP or hostname match. Zero matches remain standalone and multiple matches remain ambiguous; Pulse does not guess. Every configured target remains a distinct network-endpoint in /api/resources, including correlated targets. A correlated target also projects an additive availability / availabilityChecks facet onto the matched resource and exposes an outgoing checks relationship from the check resource. The check row remains the owner of probe status, incidents, evidence, and history.

Example ping-only target:

{
  "name": "Garage temperature sensor",
  "targetKind": "device",
  "address": "garage-sensor.local",
  "protocol": "ping",
  "enabled": true
}

The create response and subsequent reads return "protocol": "icmp".

History

  • POST /api/availability-history (monitoring:read) - Recent state and latency history for up to 200 availability targets in one request. Body: {"targetIds": ["<target-id>"], "range": "24h"}. targetIds is required; blank and duplicate IDs are dropped, and at most 200 unique IDs are accepted. range is optional (default 24h). Allowed ranges are 1h, 6h, 12h, 24h, 7d, 14d, 30d, 90d, and 365d. A range longer than the licence's metrics history retention returns 402 (free tier: 7 days). Non-POST methods return 405.

Response (200): start and end (UTC, end truncated to the minute) and targets. Each target has targetId and either summary (reachableSeconds, unreachableSeconds, indeterminateSeconds, unknownSeconds, coveragePercent, optional availabilityPercent, optional reachableLatencyMillis) with buckets (at most 120) and, only when present, revisionBoundaries; or an error with code not_found when the ID is not a configured target. An unknown ID does not fail the batch.

Errors: 400 with invalid_request (malformed JSON, unknown fields, or body over 32 KiB), missing_target_ids, too_many_target_ids, or invalid_range. Server-side failures return 500 or 503 with a typed error code.


🐟 TrueNAS

TrueNAS connection management endpoints for adding, testing, and removing TrueNAS SCALE/CORE instances.

Connection Management (Admin)

  • GET /api/truenas/connections (admin, settings:read) — List configured TrueNAS connections.
  • POST /api/truenas/connections (admin, settings:write) — Add a new TrueNAS connection.
  • POST /api/truenas/connections/test (admin, settings:write) — Test a TrueNAS connection before saving.
  • POST /api/truenas/connections/{id}/test (admin, settings:write) — Live-test a saved connection with its stored credentials. An optional JSON body overrides fields for this test only and is not saved. Returns 404 (truenas_not_found) for an unknown ID and 400 (truenas_invalid_config or truenas_connection_failed) on failure. The probe does not clear poll errors or advance poll freshness, so check the poll field from GET /api/truenas/connections to see whether ordinary polling has recovered.
  • DELETE /api/truenas/connections/{id} (admin, settings:write) — Remove a TrueNAS connection.
  • PUT /api/truenas/connections/{id} (admin, settings:write) — Update a saved TrueNAS connection. Send only the fields to change in a private JSON file; omitted fields keep their stored values. A masked apiKey or password returned by a GET response is kept from the stored record, so never send a masked value as a new secret. Saving does not test the connection. Setting monitorDatasets, monitorPools and monitorReplication all to false is saved as all enabled. Returns 200 with the redacted connection, 400 validation_error, 404 truenas_not_found, or 403 mock_mode_enabled in mock mode.

TrueNAS resources (pools, datasets, disks, ZFS snapshots, replication tasks, alerts) are surfaced through the unified /api/resources endpoint with source=truenas.


📱 Relay / Pulse Mobile (retiring 31 March 2027)

End-to-end encrypted relay protocol for mobile connectivity.

Relay pairing endpoints generate the QR code and deep link used by supported Pulse Mobile clients.

Relay Configuration (Relay and Above; admin session or scoped token)

  • GET /api/settings/relay (settings:read token scope, or admin session, Relay+) — Get current relay configuration.
  • PUT /api/settings/relay (settings:write token scope, or admin session, Relay+) — Update relay configuration.
  • GET /api/settings/relay/status (settings:read token scope, or admin session, Relay+) — Get relay connection status.

Mobile Onboarding

  • GET /api/onboarding/qr (settings:read) — Generate QR code for Pulse Mobile pairing.
  • POST /api/onboarding/validate (settings:read) — Validate a mobile onboarding connection.
  • GET /api/onboarding/deep-link (settings:read) — Generate deep-link URL for mobile app.

🔌 WebSocket Endpoints

  • GET /ws – Primary UI WebSocket (browser sessions).
  • GET /api/agent/ws – Agent WebSocket used for AI command execution.

Note

: This is a summary of the most common endpoints. For a complete list, inspect the network traffic of the Pulse dashboard or check the source code in internal/api/router.go.