{
  "openapi": "3.1.0",
  "info": {
    "title": "Yarrix API",
    "version": "0.0.0-dev",
    "description": "REST v1. Errors are `{error: {code, message, details?}}`. Lists are bare arrays; `?limit=&cursor=` pages them, with the next cursor in the `X-Next-Cursor` response header. Network throughput is Mb/s and disk throughput MB/s.",
    "license": {
      "name": "Apache-2.0",
      "identifier": "Apache-2.0"
    }
  },
  "servers": [
    {
      "url": "/api/v1",
      "description": "Relative to the hub's base path. /healthz, /ping/* and /setup are served at the base path itself."
    }
  ],
  "paths": {
    "/healthz": {
      "get": {
        "summary": "Liveness",
        "tags": [
          "system"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/discover": {
      "get": {
        "summary": "Identity of this hub for the agent's discovery probe: service name, protocol version (v), hub id, display name, whether the PIN exchange is supported, and the public URL when one is set. Answers without sign-in and never sends CORS headers.",
        "tags": [
          "agent"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "summary": "This document",
        "tags": [
          "system"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "summary": "Roll-up of hosts, apps, backups, heartbeats and open incidents",
        "tags": [
          "status"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/problems": {
      "get": {
        "summary": "Everything not green, critical first",
        "tags": [
          "status"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": false,
            "description": "hosts, apps, backups or heartbeats.",
            "schema": {
              "enum": [
                "hosts",
                "apps",
                "backups",
                "heartbeats"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Problem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/availability": {
      "get": {
        "summary": "Availability history for one host, app, backup or heartbeat: the percentage over a period (default the last 30 days), the time counted up, degraded, down and excluded, a per-day breakdown, incident count and longest outage, and run counts for backups and heartbeats",
        "tags": [
          "status"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "hosts, apps, backups or heartbeats.",
            "schema": {
              "enum": [
                "hosts",
                "apps",
                "backups",
                "heartbeats"
              ]
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "Object id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "ISO 8601 start; default 30 days before to.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "ISO 8601 end; default now.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts": {
      "get": {
        "summary": "Incidents as routed",
        "tags": [
          "alerts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "open (default) or resolved.",
            "schema": {
              "enum": [
                "open",
                "resolved"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Incident"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "summary": "Server-sent events: state_change, alert_opened, alert_updated, alert_resolved, metrics_tick, config_changed",
        "tags": [
          "status"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "Last-Event-ID",
            "in": "query",
            "required": false,
            "description": "Header form is preferred; `?lastEventId=` also works. Replays every retained event after this id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts": {
      "get": {
        "summary": "Hosts with their now values",
        "tags": [
          "hosts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HostSummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create hosts/<id>.yaml from a host object (the same fields as a host file). The exec-field rule of PUT applies.",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Lowercase letters, digits, - and _."
                  }
                },
                "required": [
                  "id"
                ],
                "additionalProperties": {},
                "description": "A host object, as in hosts/<id>.yaml."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigWritten"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}": {
      "get": {
        "summary": "One host in full",
        "tags": [
          "hosts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostDetail"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a host and its data for good (signed-in admin only; an API token is refused). Nothing else deletes a host. A host still in config must be disabled first (enabled: false); its hosts/<id>.yaml is then removed too (one local git commit). A host that already left the config only has its data removed. Repeat the id as ?confirm=<id>. Audited.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "confirm",
            "in": "query",
            "required": true,
            "description": "The host id again, to confirm.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/capabilities": {
      "get": {
        "summary": "What the host can report, per metric",
        "tags": [
          "hosts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/metrics": {
      "get": {
        "summary": "Time series for one or more metrics",
        "tags": [
          "hosts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "metric",
            "in": "query",
            "required": false,
            "description": "Comma-separated: cpu, mem, gpu, disk, temp, load, disk_read (MB/s), disk_write (MB/s), net_rx (Mb/s), net_tx (Mb/s). A metric the host cannot report comes back empty with its capability.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "description": "1h, 24h (default), 48h, 7d; or like 30m, 3d (60 s to 90 d).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "points",
            "in": "query",
            "required": false,
            "description": "Cap on points per series (averaged buckets); avg and peak still read every point.",
            "schema": {
              "type": "integer",
              "minimum": 2,
              "maximum": 5000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HostMetrics"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/custom": {
      "get": {
        "summary": "Custom check results for a host. The command, script path and output tails are included for an admin only.",
        "tags": [
          "hosts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CustomCheck"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/custom/sources": {
      "get": {
        "summary": "What a custom check on this host can run: the script files under scripts/ and the command names its connected agent reported. Names only, never contents.",
        "tags": [
          "hosts"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomSources"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/apps": {
      "get": {
        "summary": "App checks",
        "tags": [
          "apps"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AppSummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/apps/{id}": {
      "get": {
        "summary": "One app check",
        "tags": [
          "apps"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "App id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppSummary"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups": {
      "get": {
        "summary": "Backups",
        "tags": [
          "backups"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BackupItem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups/{id}": {
      "get": {
        "summary": "One backup with its B1 to B9 checks",
        "tags": [
          "backups"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Backup id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BackupDetail"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups/{id}/runs": {
      "get": {
        "summary": "Recent runs",
        "tags": [
          "backups"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Backup id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BackupRun"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/heartbeats": {
      "get": {
        "summary": "Heartbeats",
        "tags": [
          "heartbeats"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1 to 500. Without it the whole list is returned.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "The `X-Next-Cursor` value of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HeartbeatItem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/heartbeats/{id}": {
      "get": {
        "summary": "One heartbeat, with its ping URL and ping output",
        "tags": [
          "heartbeats"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HeartbeatDetail"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/silences": {
      "get": {
        "summary": "Active silences",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Silence"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Silence a host, check or backup",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "target": {
                    "type": "string",
                    "description": "\"host:atlas\", \"check:web\" or \"backup:nightly\"."
                  },
                  "until": {
                    "type": "string"
                  },
                  "duration": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "target"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Silence"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/digest/preview": {
      "get": {
        "summary": "The daily digest as it would be sent now",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Digest"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/reports/preview": {
      "get": {
        "summary": "The weekly or monthly availability report as it would be sent now",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "every",
            "in": "query",
            "required": false,
            "description": "weekly or monthly. Required.",
            "schema": {
              "enum": [
                "weekly",
                "monthly"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deliveries": {
      "get": {
        "summary": "Outgoing deliveries and their attempts",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "queued, sending, ok, failed or held.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "destination",
            "in": "query",
            "required": false,
            "description": "Destination id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Up to 500.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Delivery"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/routing": {
      "get": {
        "summary": "alerts.yaml as the Alerts tab shows it: destinations (secrets masked; a template body for admins only), rules, behavior, shared SMTP, maintenance, and each destination's last delivery",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Routing"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/destinations/{id}/preview": {
      "get": {
        "summary": "A sample message rendered by the destination's real format (the most severe open incident, or a made-up one). Secrets stay masked; no signature is computed. The template format is admin only (403 admin_only), because its body is free text from alerts.yaml.",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Destination id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Render as another webhook format to compare: yarrix_json, slack, discord, ntfy or template.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DestinationPreview"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "The signed-in user and their preferences",
        "tags": [
          "me"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config": {
      "get": {
        "summary": "The loaded configuration (secrets as references)",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/errors": {
      "get": {
        "summary": "Config errors, warnings and invalid objects",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigErrors"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/files": {
      "get": {
        "summary": "Config files on disk",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ConfigFileEntry"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/files/{path}": {
      "get": {
        "summary": "One config file, secrets redacted. A file that does not parse or validate has text null and only issues.",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "yarrix.yaml, defaults.yaml, alerts.yaml, hosts/<id>.yaml or checks/<name>.yaml.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigFile"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Save an edit of the redacted text of a file, or create alerts.yaml or defaults.yaml on a hub that has none. Comments, order and untouched secrets are kept; one local git commit when the config dir is a repository (never pushed). Changing an exec field (commands, SSH and terminal targets) or a guarded setting (allow_metadata_ips, the database backup path, ping_url, terminal.enabled and terminal.record, and the auth providers, user rules and default_role) needs an admin cookie session: any API token gets 403 exec_field_requires_admin_session.",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "yarrix.yaml, defaults.yaml, alerts.yaml, hosts/<id>.yaml or checks/<name>.yaml.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": false,
            "description": "The etag from GET. A stale one is 409; sending neither this nor If-None-Match is 428.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "`*` creates alerts.yaml or defaults.yaml when the hub has none (201); 412 already_exists if it does. Any other path is 400 not_creatable.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "description": "The file text as GET returned it, edited. Values shown as [redacted] are kept as they are on disk."
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigWritten"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/files/hosts/{id}.yaml": {
      "delete": {
        "summary": "Remove a host from config and keep its history (signed-in admin only; If-Match required). Refused with 409 host_referenced while another host names it, and with 409 not_deletable for anything that is not a regular file in the config directory. The host then shows under Removed hosts, where DELETE /hosts/{id} removes its data. Audited as config.host_removed; one local git commit.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-Match",
            "in": "header",
            "required": true,
            "description": "The etag from GET.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets": {
      "get": {
        "summary": "The hub's encrypted secret store: state (uninitialised, ready, locked and why), hub key source, whether the recovery key is confirmed, and the stored names. Never a value.",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets/init": {
      "post": {
        "summary": "Set the secret store up (signed-in admin only). Returns the recovery key once; it is not stored and cannot be shown again.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets/recovery/confirm": {
      "post": {
        "summary": "Type the recovery key back to confirm it was saved (signed-in admin only). No secret is accepted until this succeeds. Wrong keys are rate limited.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "recovery_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "recovery_key"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets/recovery/regenerate": {
      "post": {
        "summary": "Make a new recovery key (signed-in admin only). The old one stops working at once; returned once.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets/restore": {
      "post": {
        "summary": "The hub key was lost or replaced: the recovery key re-keys the store with a new secret.key (signed-in admin only). Wrong keys are rate limited.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "recovery_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "recovery_key"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/secrets/{name}": {
      "put": {
        "summary": "Store or rotate (overwrite) a secret (signed-in admin only). The value is encrypted at rest and never returned, logged or audited; the answer is the reference to save into YAML, `${secret:NAME}`. The name is letters, digits, _, - and . (up to 64, starting with a letter or digit).",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The secret's name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "value": {
                    "type": "string",
                    "description": "The secret itself. Sent once, never returned."
                  }
                },
                "required": [
                  "value"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/effective/{kind}/{id}": {
      "get": {
        "summary": "A host after defaults and overrides are merged",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "description": "host.",
            "schema": {
              "enum": [
                "host"
              ]
            }
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/config/validate": {
      "post": {
        "summary": "Check config text against its schema. Writes nothing.",
        "tags": [
          "config"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "path": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "yarrix",
                      "defaults",
                      "alerts",
                      "host",
                      "checks"
                    ]
                  },
                  "text": {
                    "type": "string"
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResult"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent/passcode": {
      "post": {
        "summary": "Windows setup: one passcode for one enrollment token. Guesses are counted by address and hub-wide; a wrong guess can burn every unused passcode.",
        "tags": [
          "agent"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "passcode": {
                    "type": "string",
                    "description": "The six-digit passcode the installer page shows, NNN-NNN."
                  }
                },
                "required": [
                  "passcode"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/network": {
      "get": {
        "summary": "The Network page: the port, whether the LAN is on, what locks each, and the agents still on an old port",
        "tags": [
          "config"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Move the hub to another port, or turn the LAN on or off. A port change opens the new port first and tells connected agents to move; a LAN change moves the listener after the answer is sent (202). Turning the LAN off from another computer needs confirmed: true.",
        "tags": [
          "config"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "port": {
                    "description": "The port to move the hub to, 1024 to 65535. Refused when YARRIX_LISTEN sets the port.",
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991
                  },
                  "lan": {
                    "description": "true to listen on every interface, false for loopback only. Refused when YARRIX_LISTEN sets the host.",
                    "type": "boolean"
                  },
                  "confirmed": {
                    "description": "Needed to turn the LAN off from another computer: that browser and its agents lose the hub.",
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ping/{id}": {
      "post": {
        "summary": "Heartbeat success ping",
        "tags": [
          "heartbeats"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat ping id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "tail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ping/{id}/start": {
      "post": {
        "summary": "Heartbeat start ping",
        "tags": [
          "heartbeats"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat ping id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "tail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ping/{id}/fail": {
      "post": {
        "summary": "Heartbeat failure ping",
        "tags": [
          "heartbeats"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat ping id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "tail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ping/{id}/log": {
      "post": {
        "summary": "Heartbeat log ping",
        "tags": [
          "heartbeats"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat ping id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "tail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ping/{id}/{exit}": {
      "post": {
        "summary": "Heartbeat ping carrying an exit status (0 is success)",
        "tags": [
          "heartbeats"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "servers": [
          {
            "url": "/",
            "description": "Served at the hub base path, not under /api/v1."
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Heartbeat ping id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "exit",
            "in": "path",
            "required": true,
            "description": "Exit status, 0 to 255.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "tail": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups/{id}/report": {
      "post": {
        "summary": "Backup self-report (start, success, fail)",
        "tags": [
          "backups"
        ],
        "description": "An API token with the report scope (limited to listed backups when it names them).",
        "x-access": "token_report",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Backup id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "start",
                      "success",
                      "fail"
                    ]
                  },
                  "bytes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "files": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9007199254740991
                  },
                  "exit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "message": {
                    "type": "string"
                  }
                },
                "required": [
                  "event"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportResult"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups/{id}/report-token": {
      "post": {
        "summary": "Rotate the backup's report token (shown once)",
        "tags": [
          "backups"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Backup id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{id}/ack": {
      "post": {
        "summary": "Acknowledge an incident",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Incident id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/hosts/{id}/ack": {
      "post": {
        "summary": "Acknowledge every open incident on a host. Each incident is acked and audited on its own; the answer lists the ids acked and how many were already acked",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{id}/snooze": {
      "post": {
        "summary": "Snooze an incident",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Incident id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "until": {
                    "description": "ISO 8601 instant with an offset, or a duration such as \"4h\".",
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/pauses": {
      "get": {
        "summary": "Active pauses (a host, a check, a backup, a heartbeat, or global)",
        "tags": [
          "alerts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Pause"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Pause a host, check, backup or heartbeat, or all monitoring (an operator's pause needs an end, and the scope's paused time in the last 30 days plus this pause is at most 30 days; no end, or more than that, needs an admin; global needs an admin and an end of 7 days or less)",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "description": "\"global\", \"host:atlas\", \"check:web\", \"backup:nightly\" or \"heartbeat:job\"."
                  },
                  "until": {
                    "type": "string"
                  },
                  "duration": {
                    "type": "string"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "scope"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pause"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/pauses/{id}": {
      "delete": {
        "summary": "Resume a pause early (ending a global pause needs an admin)",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Pause id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/silences/{id}": {
      "delete": {
        "summary": "End a silence",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Silence id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checks/{id}/run": {
      "post": {
        "summary": "Run an already-defined check now (takes no arguments)",
        "tags": [
          "apps"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Check id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppSummary"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{id}": {
      "get": {
        "summary": "One incident with its timeline",
        "tags": [
          "alerts"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Incident id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncidentDetail"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{id}/notes": {
      "post": {
        "summary": "Add a note to an incident's timeline",
        "tags": [
          "alerts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Incident id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "What was seen or done."
                  }
                },
                "required": [
                  "text"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Incident"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/backups/{id}/verify": {
      "post": {
        "summary": "Run the backup's read-only destination and log checks again for its newest finished run (takes no arguments)",
        "tags": [
          "backups"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Backup id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/commands": {
      "get": {
        "summary": "The named commands declared in the host's agent.yaml (admin, or a read-write API key)",
        "tags": [
          "hosts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/commands/{name}/run": {
      "post": {
        "summary": "Run one of the host's declared named commands now (admin, or a read-write API key). Only the name is sent; the agent runs the argv from its own agent.yaml, with no shell. A name it did not declare is 404.",
        "tags": [
          "hosts"
        ],
        "description": "Operator role, or an API token with the operate scope (a read-write key: capped, rate-limited and announced; see docs/mcp-and-cli.md).",
        "x-access": "operator",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "A command name from agent.yaml.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/destinations/{id}/test": {
      "post": {
        "summary": "Send a test message to a destination now. The send is recorded as a finished delivery row.",
        "tags": [
          "alerts"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Destination id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestSendResult"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/me/prefs": {
      "put": {
        "summary": "Save the signed-in user's preferences",
        "tags": [
          "me"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "theme": {
                    "type": "string",
                    "enum": [
                      "light",
                      "dark"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prefs"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ssh/test": {
      "post": {
        "summary": "Run the built-in probe once against an unsaved connection. The first contact needs the confirmed host key fingerprint, as install-agent does",
        "tags": [
          "hosts"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string"
                  },
                  "ssh": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  },
                  "host_key_fingerprint": {
                    "description": "The host key fingerprint (SHA256:...) the admin confirmed from POST /ssh/hostkey. Without it only a host whose key the hub already holds (or a pinned ssh.host_key) can be tested; anything else answers host_key_unconfirmed and nothing is sent.",
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ssh/hostkey": {
      "post": {
        "summary": "The SSH host key fingerprints a target presents right now, for the admin to check before a password is used",
        "tags": [
          "hosts"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "target": {
                    "type": "string",
                    "description": "user@host or user@host:port"
                  }
                },
                "required": [
                  "target"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ssh/install-agent": {
      "post": {
        "summary": "Install the agent on a Linux (systemd) or macOS (launchd) host over SSH: detect the OS and CPU, download and check the right build, enroll it with a one-time token for this host, start it as a service. Passwords are used for this request only and are never stored, logged, audited or returned. A plaintext password is refused over plain http except on loopback; over plain http send it sealed (see POST /ssh/seal-key). Wrong passwords are rate-limited. Records an agent_install audit row (mode upgrade for a reinstall of an enrolled agent, which also records an update attempt on the ssh channel).",
        "tags": [
          "hosts"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string",
                    "description": "The host id. The host must already exist; the enrollment token is minted for it alone."
                  },
                  "target": {
                    "type": "string",
                    "description": "user@host or user@host:port. No jump hosts."
                  },
                  "hub": {
                    "type": "string",
                    "description": "The address the host uses to reach this hub (http(s):// or ws(s)://), as in the agent's settings."
                  },
                  "password": {
                    "description": "The account's SSH password. Used for this request only. Without it the hub signs in with this host's own key.",
                    "type": "string"
                  },
                  "sealed": {
                    "type": "object",
                    "properties": {
                      "v": {
                        "type": "number",
                        "const": 1
                      },
                      "kid": {
                        "type": "string"
                      },
                      "nonce": {
                        "type": "string"
                      },
                      "epk": {
                        "type": "string"
                      },
                      "iv": {
                        "type": "string"
                      },
                      "ct": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "v",
                      "kid",
                      "nonce",
                      "epk",
                      "iv",
                      "ct"
                    ],
                    "description": "The password, or both passwords for install-agent, encrypted in the browser to the key POST /ssh/seal-key offered (scheme yarrix-sealed-v1: ECDH P-256, HKDF-SHA256, AES-256-GCM). Required in place of the plaintext fields over plain http, where a plaintext password is refused."
                  },
                  "sudo_password": {
                    "description": "The sudo password, when the login is by key or differs from the login password. Not needed for root or passwordless sudo.",
                    "type": "string"
                  },
                  "host_key_fingerprint": {
                    "description": "SHA256:... the admin confirmed from /ssh/hostkey. Needed unless host_key is given.",
                    "type": "string"
                  },
                  "host_key": {
                    "description": "A pinned host key line, instead of a fingerprint.",
                    "type": "string"
                  },
                  "os": {
                    "description": "The platform chosen in the wizard. windows, unraid, synology and ugos are answered with not_automated; linux (systemd), macos (launchd) and auto are installed.",
                    "type": "string"
                  },
                  "mode": {
                    "description": "install (default) enrolls a new agent. upgrade replaces the binary and service file of a host that is already enrolled and runs an agent: no token is minted, agent.yaml and agent.env are left alone, and the answer says whether the agent reconnected on the new version.",
                    "type": "string",
                    "enum": [
                      "install",
                      "upgrade"
                    ]
                  }
                },
                "required": [
                  "host",
                  "target",
                  "hub"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ssh/install-key": {
      "post": {
        "summary": "Install a host's own public key with its SSH password, once, then prove key login works. The password is used for this request only and is never stored, logged or returned. A plaintext password is refused over plain http except on loopback; over plain http send it sealed (see POST /ssh/seal-key). Wrong passwords are rate-limited.",
        "tags": [
          "hosts"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string",
                    "description": "The host id the key is for."
                  },
                  "target": {
                    "type": "string",
                    "description": "user@host or user@host:port. No jump hosts."
                  },
                  "password": {
                    "description": "The account's SSH password. Used once, never stored. Over plain http send `sealed` instead.",
                    "type": "string"
                  },
                  "sealed": {
                    "type": "object",
                    "properties": {
                      "v": {
                        "type": "number",
                        "const": 1
                      },
                      "kid": {
                        "type": "string"
                      },
                      "nonce": {
                        "type": "string"
                      },
                      "epk": {
                        "type": "string"
                      },
                      "iv": {
                        "type": "string"
                      },
                      "ct": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "v",
                      "kid",
                      "nonce",
                      "epk",
                      "iv",
                      "ct"
                    ],
                    "description": "The password, or both passwords for install-agent, encrypted in the browser to the key POST /ssh/seal-key offered (scheme yarrix-sealed-v1: ECDH P-256, HKDF-SHA256, AES-256-GCM). Required in place of the plaintext fields over plain http, where a plaintext password is refused."
                  },
                  "host_key_fingerprint": {
                    "description": "SHA256:... the admin confirmed from /ssh/hostkey. Needed unless host_key is given.",
                    "type": "string"
                  },
                  "host_key": {
                    "description": "A pinned host key line, instead of a fingerprint.",
                    "type": "string"
                  },
                  "os": {
                    "type": "string"
                  }
                },
                "required": [
                  "host",
                  "target"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ssh/seal-key": {
      "post": {
        "summary": "A one-time offer to encrypt a password in the browser before it is sent to /ssh/install-key or /ssh/install-agent over plain http: the hub's in-memory P-256 public key and a single-use nonce good for ten minutes. Protects against someone reading the network, not against someone who can change the page.",
        "tags": [
          "hosts"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/onboarding": {
      "get": {
        "summary": "What the add-a-host wizard needs: the public URL, the agent files this hub serves, and with ?host=<id> that host's own SSH public key (made on first call)",
        "tags": [
          "hosts"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "A host id. Without it ssh_key is null.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Onboarding"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tokens": {
      "get": {
        "summary": "API tokens (hashes are never returned)",
        "tags": [
          "auth"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenList"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create an API token (shown once)",
        "tags": [
          "auth"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "scopes": {
                    "minItems": 1,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "read",
                        "operate",
                        "report",
                        "admin"
                      ]
                    },
                    "description": "read: read-only. operate: read-write for an AI agent (the operator actions, capped and announced; never configuration). report: backup reports. admin: everything."
                  },
                  "backups": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "name",
                  "scopes"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenCreated"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/tokens/{id}": {
      "delete": {
        "summary": "Revoke an API token",
        "tags": [
          "auth"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Token id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/users": {
      "get": {
        "summary": "Users and their roles",
        "tags": [
          "auth"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/users/{id}": {
      "delete": {
        "summary": "Remove a user: their sessions end on the next request. A role rule that still matches lets them sign in again. The last admin and your own account are refused (409)",
        "tags": [
          "auth"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "User id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/audit": {
      "get": {
        "summary": "Recent audit events",
        "tags": [
          "auth"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 200.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrollments": {
      "post": {
        "summary": "Mint an agent enrollment token",
        "tags": [
          "auth"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string"
                  },
                  "universal": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/enrollments/passcode": {
      "post": {
        "summary": "Make a one-time Windows setup PIN (six digits, NNN-NNN) for a host (shown once; works 15 minutes; a new one expires the old)",
        "tags": [
          "auth"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "host": {
                    "type": "string"
                  }
                },
                "required": [
                  "host"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent/pin/start": {
      "post": {
        "summary": "First step of the PIN exchange (SPAKE2): send pA, get a session and eight offers (one per live PIN, the rest decoys). The PIN is never sent. Each start is a guess: it counts against this address until a finish succeeds (five unfinished starts lock it for 15 minutes), and hub-wide (30 starts in 15 minutes burn every unused PIN).",
        "tags": [
          "agent"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "pA": {
                    "type": "string",
                    "description": "Uncompressed P-256 point, hex (65 bytes, 04-prefixed)."
                  }
                },
                "required": [
                  "pA"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent/pin/finish": {
      "post": {
        "summary": "Second step of the PIN exchange: send the session, the offer index and cA. On success, returns the enrollment sealed under the shared key (AES-256-GCM); the PIN is spent. A wrong cA, session or index is 403 and spends nothing.",
        "tags": [
          "agent"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "session": {
                    "type": "string"
                  },
                  "index": {
                    "type": "integer",
                    "minimum": -9007199254740991,
                    "maximum": 9007199254740991,
                    "description": "The offer index the start returned that matched this PIN."
                  },
                  "cA": {
                    "type": "string",
                    "description": "The agent's key confirmation, 32 bytes hex."
                  }
                },
                "required": [
                  "session",
                  "index",
                  "cA"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/providers": {
      "get": {
        "summary": "Sign-in methods",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/session": {
      "get": {
        "summary": "The current session, or 401",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "summary": "Sign out",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/link": {
      "get": {
        "summary": "Redeem a break-glass login link",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": false,
            "description": "Single-use link token.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/{provider}/{step}": {
      "get": {
        "summary": "Provider sign-in flow (redirects)",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "parameters": [
          {
            "name": "provider",
            "in": "path",
            "required": true,
            "description": "supabase, tailscale or local.",
            "schema": {
              "enum": [
                "supabase",
                "tailscale",
                "local"
              ]
            }
          },
          {
            "name": "step",
            "in": "path",
            "required": true,
            "description": "Provider-defined step, e.g. start or callback.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/setup/start": {
      "post": {
        "summary": "First-run claim, step 1 (stateless): answer the browser's blinded OPAQUE registration message. Refused with 421 for a Host that is not an IP address, localhost, a single-label or .local/.ts.net name, or the public URL (DNS rebinding guard).",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's blinded OPAQUE message (base64url). It is not the password."
                  }
                },
                "required": [
                  "username",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/setup": {
      "post": {
        "summary": "First-run claim, step 2: the first visitor creates the admin with the record the browser made (the password never reaches the hub). 410 once an admin exists; 403 once the first-run window has closed (restart the hub or run `yarrix-hub auth setup-pin`, then send the PIN too). Same Host guard as /setup/start.",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "record": {
                    "type": "string",
                    "description": "The browser's OPAQUE registration record (base64url). It is not the password."
                  },
                  "pin": {
                    "description": "Only when the hub is in pin mode: the PIN from `yarrix-hub auth setup-pin`.",
                    "type": "string"
                  }
                },
                "required": [
                  "username",
                  "record"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/recover/start": {
      "post": {
        "summary": "Recovery, step 1 (stateless): answer the browser's blinded OPAQUE registration message.",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's blinded OPAQUE message (base64url). It is not the password."
                  }
                },
                "required": [
                  "username",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/recover": {
      "post": {
        "summary": "Recovery, step 2: with the one-use PIN from `yarrix-hub auth setup-pin` (valid 15 minutes), set a new record for a username (a new admin when the name is new) and sign every session out. Five wrong tries from one address lock it for 15 minutes.",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "pin": {
                    "type": "string"
                  },
                  "username": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "record": {
                    "type": "string"
                  }
                },
                "required": [
                  "pin",
                  "username",
                  "record"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/local/start": {
      "post": {
        "summary": "Password sign-in, step 1: the browser's blinded OPAQUE message. Every start on a username counts as a failed attempt until a finish proves the password; five lock that address for 15 minutes, 100 in a row switch the password off until recovery (423).",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's blinded OPAQUE message (base64url). It is not the password."
                  }
                },
                "required": [
                  "username",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/auth/local/finish": {
      "post": {
        "summary": "Password sign-in, step 2: the browser's OPAQUE proof. Sets the session cookie when it is valid.",
        "tags": [
          "auth"
        ],
        "description": "No sign-in. (Ping URLs rely on their unguessable id.)",
        "x-access": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "login_id": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's OPAQUE proof (base64url)."
                  }
                },
                "required": [
                  "login_id",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account/password": {
      "get": {
        "summary": "Whether the signed-in user has a password and may set one",
        "tags": [
          "auth"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Change password, step 4: store the new record and sign the user's other sessions out",
        "tags": [
          "auth"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "record": {
                    "type": "string"
                  },
                  "ticket": {
                    "type": "string"
                  }
                },
                "required": [
                  "record"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account/password/reauth/start": {
      "post": {
        "summary": "Change password, step 1: start proving the current password",
        "tags": [
          "auth"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's blinded OPAQUE message (base64url). It is not the password."
                  }
                },
                "required": [
                  "username",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account/password/reauth/finish": {
      "post": {
        "summary": "Change password, step 2: finish proving the current password and receive a two-minute ticket",
        "tags": [
          "auth"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "login_id": {
                    "type": "string"
                  },
                  "request": {
                    "type": "string",
                    "description": "The browser's OPAQUE proof (base64url)."
                  }
                },
                "required": [
                  "login_id",
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account/password/start": {
      "post": {
        "summary": "Change password, step 3: blinded registration message for the new password (needs the ticket when a password exists)",
        "tags": [
          "auth"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "request": {
                    "type": "string"
                  },
                  "ticket": {
                    "type": "string"
                  }
                },
                "required": [
                  "request"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/agent/update": {
      "post": {
        "summary": "Update this host's agent to the version the hub serves (signed by a release key the agent trusts). Queued if the host is offline. Blocked, with a code, when the mode is notify, the agent cannot update itself, or it is current. Audited.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/agent/update/cancel": {
      "post": {
        "summary": "Cancel an update that has not reached the agent yet.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/hosts/{id}/agent/updates": {
      "get": {
        "summary": "The host's update attempts, newest first.",
        "tags": [
          "agent"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Host id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/update": {
      "post": {
        "summary": "Update every outdated host that can update itself, under the same rules as automatic mode but not its window: one canary host first, relaying hosts last, at most agent_updates.max_concurrent at a time, nothing while the release is paused. Hosts that have to wait come back in skipped with a code. Body {\"hosts\": [...]} narrows it.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "202": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/refused": {
      "get": {
        "summary": "Agents the hub refused at the upgrade (unknown or revoked token), by the hostname and host id they sent: hostname, host_id, ip, rdns, count, first_seen, last_seen. Dismissed ones are left out.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/status-pages": {
      "get": {
        "summary": "The status pages in yarrix.yaml and where each is served (/status/<id> and /status/<id>.json, outside /api): id, title, access (public or viewer), show_reasons, show_availability, item count. The pages themselves answer 404 unless this list has them; they are changed in config by a signed-in admin only.",
        "tags": [
          "status"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/badges": {
      "get": {
        "summary": "The badges in yarrix.yaml with their URLs (/badge/<key>.svg and .json, outside /api). The key is the credential, so only an admin sees it. A badge whose pinned key cannot be used has no URL.",
        "tags": [
          "status"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/badges/{id}/rotate": {
      "post": {
        "summary": "Mint a new key for a badge (signed-in admin only; an API token is refused). The old URL stops answering at once. 409 for a badge whose key is pinned in config: change it there.",
        "tags": [
          "status"
        ],
        "description": "A signed-in admin session. API tokens are refused.",
        "x-access": "admin_session",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Badge id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/refused/{id}/dismiss": {
      "post": {
        "summary": "Hide a refused agent from the list.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Refused agent id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/refused/{id}/reenroll": {
      "post": {
        "summary": "Make a one-time enrollment token (24 hours) for the host id in the body ({host}), and hide the row. 409 when that id is already a host, config host, token or enrollment. The token is shown once.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Refused agent id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/updates": {
      "get": {
        "summary": "The automatic rollout, hub-wide: target release, window and whether it is open, slots in use, hosts waiting for the scheduler, the canary, and the pause (with the hosts and reasons) when two hosts failed on the release.",
        "tags": [
          "agent"
        ],
        "description": "Viewer role, or an API token with the read scope.",
        "x-access": "viewer",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agents/update/resume": {
      "post": {
        "summary": "Let automatic updates (and Update all) go on after the circuit breaker paused them for the current release. Answers the rollout status; resuming what is not paused changes nothing. Audited when it resumes something.",
        "tags": [
          "agent"
        ],
        "description": "Admin role, or an API token with the admin scope.",
        "x-access": "admin",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Object"
                }
              }
            }
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/agent/ws": {
      "get": {
        "summary": "Agent websocket (per-host bearer token)",
        "tags": [
          "agent"
        ],
        "description": "A per-host agent token.",
        "x-access": "agent",
        "responses": {
          "200": {
            "description": "Success"
          },
          "401": {
            "description": "Not signed in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The role or scope is not enough.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Error envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {}
            },
            "required": [
              "code",
              "message"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "error"
        ],
        "additionalProperties": true
      },
      "Health": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "version": {
            "type": "string",
            "description": "The hub build version (the release tag, or 0.0.0-dev for an untagged build)."
          }
        },
        "required": [
          "ok",
          "version"
        ],
        "additionalProperties": true
      },
      "Ok": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "additionalProperties": true
      },
      "Object": {
        "type": "object",
        "additionalProperties": true
      },
      "Status": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string"
          },
          "overall": {
            "enum": [
              "ok",
              "warn",
              "critical"
            ]
          },
          "problems": {
            "type": "number"
          },
          "critical": {
            "type": "number"
          },
          "indefinite_pauses": {
            "type": "number"
          },
          "hosts": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "ok": {
                "type": "number"
              },
              "running": {
                "type": "number"
              },
              "unobserved": {
                "type": "number"
              },
              "warn": {
                "type": "number"
              },
              "critical": {
                "type": "number"
              }
            },
            "required": [
              "total",
              "ok",
              "running",
              "unobserved",
              "warn",
              "critical"
            ],
            "additionalProperties": true
          },
          "apps": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "ok": {
                "type": "number"
              },
              "running": {
                "type": "number"
              },
              "unobserved": {
                "type": "number"
              },
              "warn": {
                "type": "number"
              },
              "critical": {
                "type": "number"
              }
            },
            "required": [
              "total",
              "ok",
              "running",
              "unobserved",
              "warn",
              "critical"
            ],
            "additionalProperties": true
          },
          "backups": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "ok": {
                "type": "number"
              },
              "running": {
                "type": "number"
              },
              "unobserved": {
                "type": "number"
              },
              "warn": {
                "type": "number"
              },
              "critical": {
                "type": "number"
              }
            },
            "required": [
              "total",
              "ok",
              "running",
              "unobserved",
              "warn",
              "critical"
            ],
            "additionalProperties": true
          },
          "heartbeats": {
            "type": "object",
            "properties": {
              "total": {
                "type": "number"
              },
              "ok": {
                "type": "number"
              },
              "running": {
                "type": "number"
              },
              "unobserved": {
                "type": "number"
              },
              "warn": {
                "type": "number"
              },
              "critical": {
                "type": "number"
              }
            },
            "required": [
              "total",
              "ok",
              "running",
              "unobserved",
              "warn",
              "critical"
            ],
            "additionalProperties": true
          },
          "alerts": {
            "type": "object",
            "properties": {
              "open": {
                "type": "number"
              },
              "critical": {
                "type": "number"
              }
            },
            "required": [
              "open",
              "critical"
            ],
            "additionalProperties": true
          },
          "agents": {
            "type": "object",
            "properties": {
              "latest": {
                "type": "string",
                "description": "The agent version the hub expects: VERSION beside the agent files it serves, else the hub's own version."
              },
              "source": {
                "enum": [
                  "release",
                  "hub"
                ]
              },
              "total": {
                "type": "number"
              },
              "current": {
                "type": "number"
              },
              "outdated": {
                "type": "number"
              },
              "unknown": {
                "type": "number"
              },
              "behind": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Ids of the outdated hosts."
              }
            },
            "required": [
              "latest",
              "source",
              "total",
              "current",
              "outdated",
              "unknown",
              "behind"
            ],
            "additionalProperties": true
          },
          "hub_jobs": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "job": {
                  "type": "string"
                },
                "consecutive_failures": {
                  "type": "number"
                },
                "since": {
                  "type": "number"
                },
                "category": {
                  "enum": [
                    "lock",
                    "error"
                  ]
                }
              },
              "required": [
                "job",
                "consecutive_failures",
                "since",
                "category"
              ],
              "additionalProperties": true
            }
          },
          "icmp": {
            "type": "object",
            "properties": {
              "available": {
                "type": "boolean"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "available",
              "reason"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "as_of",
          "overall",
          "problems",
          "critical",
          "hosts",
          "apps",
          "backups",
          "heartbeats",
          "alerts",
          "agents"
        ],
        "additionalProperties": true
      },
      "Problem": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "severity": {
            "enum": [
              "critical",
              "warn"
            ]
          },
          "title": {
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "ui": {
            "type": "string"
          }
        },
        "required": [
          "domain",
          "id",
          "state",
          "severity",
          "title",
          "reason",
          "since",
          "ui"
        ],
        "additionalProperties": true
      },
      "Incident": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "object_id": {
            "type": "string"
          },
          "severity": {
            "enum": [
              "critical",
              "warn"
            ]
          },
          "title": {
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "consecutive_failures": {
            "type": "number"
          },
          "ui": {
            "type": "string"
          },
          "state": {
            "enum": [
              "open",
              "resolved"
            ]
          },
          "destinations": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tagged": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "domain",
          "object_id",
          "severity",
          "title",
          "reason",
          "since",
          "consecutive_failures",
          "ui",
          "state",
          "destinations",
          "tagged"
        ],
        "additionalProperties": true
      },
      "IncidentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Incident"
          },
          {
            "type": "object",
            "properties": {
              "timeline": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "at": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string"
                    },
                    "detail": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "at",
                    "kind",
                    "detail"
                  ],
                  "additionalProperties": true
                }
              }
            },
            "required": [
              "timeline"
            ],
            "additionalProperties": true
          }
        ]
      },
      "Availability": {
        "type": "object",
        "properties": {
          "domain": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "since": {
            "type": [
              "string",
              "null"
            ]
          },
          "availability": {
            "type": [
              "number",
              "null"
            ]
          },
          "up_s": {
            "type": "number"
          },
          "degraded_s": {
            "type": "number"
          },
          "down_s": {
            "type": "number"
          },
          "excluded_s": {
            "type": "number"
          },
          "observed_s": {
            "type": "number"
          },
          "enough_data": {
            "type": "boolean"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "availability": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "up_s": {
                  "type": "number"
                },
                "degraded_s": {
                  "type": "number"
                },
                "down_s": {
                  "type": "number"
                },
                "excluded_s": {
                  "type": "number"
                },
                "observed_s": {
                  "type": "number"
                }
              },
              "required": [
                "date",
                "availability",
                "up_s",
                "degraded_s",
                "down_s",
                "excluded_s",
                "observed_s"
              ],
              "additionalProperties": true
            }
          },
          "incidents": {
            "type": "object",
            "properties": {
              "count": {
                "type": "number"
              },
              "longest_s": {
                "type": "number"
              }
            },
            "required": [
              "count",
              "longest_s"
            ],
            "additionalProperties": true
          },
          "runs": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "ok": {
                "type": "number"
              },
              "total": {
                "type": "number"
              }
            }
          }
        },
        "required": [
          "domain",
          "id",
          "from",
          "to",
          "since",
          "availability",
          "up_s",
          "degraded_s",
          "down_s",
          "excluded_s",
          "observed_s",
          "enough_data",
          "days",
          "incidents",
          "runs"
        ],
        "additionalProperties": true
      },
      "Silence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "target": {
            "type": "string"
          },
          "starts_at": {
            "type": "string"
          },
          "ends_at": {
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "target",
          "starts_at",
          "ends_at",
          "reason",
          "created_by"
        ],
        "additionalProperties": true
      },
      "Pause": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "scope": {
            "type": "string"
          },
          "started_at": {
            "type": "string"
          },
          "until": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "scope",
          "started_at",
          "until",
          "reason",
          "created_by"
        ],
        "additionalProperties": true
      },
      "Delivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "number"
          },
          "incident_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "destination": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "enum": [
              "queued",
              "sending",
              "ok",
              "failed",
              "held"
            ]
          },
          "attempt": {
            "type": "number"
          },
          "next_at": {
            "type": "string"
          },
          "code": {
            "type": [
              "number",
              "null"
            ]
          },
          "latency_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "hold_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "incident_id",
          "destination",
          "event",
          "title",
          "status",
          "attempt",
          "next_at",
          "code",
          "latency_ms",
          "error",
          "hold_reason"
        ],
        "additionalProperties": true
      },
      "TestSendResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "code": {
            "type": [
              "number",
              "null"
            ]
          },
          "latency_ms": {
            "type": "number"
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivery_id": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "ok",
          "code",
          "latency_ms",
          "error",
          "delivery_id"
        ],
        "additionalProperties": true
      },
      "Onboarding": {
        "type": "object",
        "properties": {
          "public_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "agent_downloads": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Files served at /download/agent/*; null when YARRIX_AGENT_DIST is not set."
          },
          "agent_sha256": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            },
            "description": "The sha256 of each agent binary served, computed from the files; the install checks against these rather than a downloaded .sha256. Null when none are served."
          },
          "agent_manifest": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version": {
                "type": "string"
              },
              "sha256": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "description": "The version of the signed release manifest this hub serves and has checked; the install then requires that manifest. sha256 (each file's hash) is present only when the hub has verified the manifest's signature against its release keys. Null when the hub declares none."
          },
          "ssh_key": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "public_key": {
                "type": "string"
              },
              "fingerprint": {
                "type": "string"
              },
              "identity_file": {
                "type": "string"
              }
            },
            "description": "With ?host=<id>: that host's own SSH public key for its authorized_keys, and the path (on the hub) its ssh.identity_file should name. Null without a host."
          },
          "ssh_key_error": {
            "type": "string"
          }
        },
        "required": [
          "public_url",
          "agent_downloads",
          "agent_sha256",
          "agent_manifest",
          "ssh_key"
        ],
        "additionalProperties": true
      },
      "Routing": {
        "type": "object",
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "file": {
            "type": "string"
          },
          "destinations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "type": {
                  "enum": [
                    "webhook",
                    "email"
                  ]
                },
                "default": {
                  "type": "boolean"
                },
                "enabled": {
                  "type": "boolean"
                },
                "events": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "webhook": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "email": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "last_delivery": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "waiting": {
                  "type": "number"
                }
              },
              "required": [
                "id",
                "name",
                "type",
                "default",
                "enabled",
                "events",
                "webhook",
                "email",
                "last_delivery",
                "waiting"
              ],
              "additionalProperties": true
            }
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "n": {
                  "type": "number"
                },
                "when": {
                  "type": [
                    "object",
                    "null"
                  ]
                },
                "send": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "hold": {
                  "type": "boolean"
                },
                "ignore_quiet_hours": {
                  "type": "boolean"
                },
                "fallback": {
                  "type": "boolean"
                }
              },
              "required": [
                "n",
                "when",
                "send",
                "hold",
                "ignore_quiet_hours",
                "fallback"
              ],
              "additionalProperties": true
            }
          },
          "behavior": {
            "type": "object"
          },
          "delivery": {
            "type": "object"
          },
          "smtp": {
            "type": [
              "object",
              "null"
            ]
          },
          "maintenance": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "configured",
          "file",
          "destinations",
          "rules",
          "behavior",
          "delivery",
          "smtp",
          "maintenance"
        ],
        "additionalProperties": true
      },
      "DestinationPreview": {
        "type": "object",
        "properties": {
          "destination": {
            "type": "string"
          },
          "type": {
            "enum": [
              "webhook",
              "email"
            ]
          },
          "format": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "sample": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string"
              },
              "real": {
                "type": "boolean"
              }
            },
            "required": [
              "title",
              "real"
            ],
            "additionalProperties": true
          },
          "method": {
            "type": [
              "string",
              "null"
            ]
          },
          "headers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "name",
                "value"
              ],
              "additionalProperties": true
            }
          },
          "subject": {
            "type": [
              "string",
              "null"
            ]
          },
          "body": {
            "type": "string"
          }
        },
        "required": [
          "destination",
          "type",
          "format",
          "event",
          "sample",
          "method",
          "headers",
          "subject",
          "body"
        ],
        "additionalProperties": true
      },
      "Digest": {
        "type": "object",
        "additionalProperties": true,
        "description": "The digest.daily payload: event, id, title, text (the fixed plain-text layout), date, tz, as_of, url, counts, not_green, backups, delivery_failures."
      },
      "Report": {
        "type": "object",
        "additionalProperties": true,
        "description": "The report.weekly or report.monthly payload: event, id, title, text (the fixed plain-text layout), every, tz, from, to, history_since, url, domains, worst, below, not_enough, backups, incidents, paused."
      },
      "HostSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "ui": {
            "type": "string"
          },
          "availability_30d": {
            "type": "object",
            "properties": {
              "percent": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "enough_data": {
                "type": "boolean"
              },
              "excluded_s": {
                "type": "number"
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "percent",
              "enough_data",
              "excluded_s",
              "since"
            ],
            "additionalProperties": true,
            "description": "Availability over the last 30 days (D-33); present when the hub runs the alert engine."
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "os": {
            "type": "string"
          },
          "platform": {
            "type": "string"
          },
          "method": {
            "enum": [
              "agent",
              "ssh",
              "relay"
            ]
          },
          "via": {
            "type": "string"
          },
          "cpu_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "mem_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "gpu_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "disk": {
            "type": [
              "object",
              "null"
            ]
          },
          "temp_c": {
            "type": [
              "number",
              "null"
            ]
          },
          "temp_sensor": {
            "type": [
              "string",
              "null"
            ]
          },
          "load": {
            "type": [
              "number",
              "null"
            ]
          },
          "net": {
            "type": [
              "object",
              "null"
            ]
          },
          "uptime_s": {
            "type": [
              "number",
              "null"
            ]
          },
          "last_seen": {
            "type": "string"
          },
          "facts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "agent": {
            "type": "object",
            "properties": {
              "status": {
                "enum": [
                  "current",
                  "outdated",
                  "ahead",
                  "unknown",
                  "none",
                  "relayed"
                ],
                "description": "current: runs the expected version. outdated: older (compared as semver, pre-releases below their release). ahead: newer. unknown: no version reported yet. none: SSH host, no agent. relayed: reached through another host's agent (see via)."
              },
              "version": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "latest": {
                "type": "string"
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "When the host began running this version."
              },
              "via": {
                "type": "string"
              }
            },
            "required": [
              "status",
              "version",
              "latest",
              "since"
            ],
            "additionalProperties": true
          },
          "enabled": {
            "type": "boolean"
          },
          "removed_from_config": {
            "type": "boolean"
          },
          "capabilities": {
            "$ref": "#/components/schemas/Capabilities"
          },
          "thresholds": {
            "type": "object"
          },
          "breaches": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        },
        "required": [
          "id",
          "state",
          "reason",
          "since",
          "ui",
          "name",
          "cpu_pct",
          "mem_pct",
          "gpu_pct",
          "disk",
          "temp_c",
          "last_seen",
          "agent"
        ],
        "additionalProperties": true
      },
      "HostDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/HostSummary"
          },
          {
            "type": "object",
            "properties": {
              "os_version": {
                "type": "string"
              },
              "arch": {
                "type": "string"
              },
              "agent_footprint": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "rss_mb": {
                        "type": "number"
                      },
                      "cpu_pct": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "rss_mb",
                      "cpu_pct"
                    ],
                    "additionalProperties": true
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "ssh_user": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "host_key": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string"
                      },
                      "fingerprint": {
                        "type": "string"
                      },
                      "source": {
                        "enum": [
                          "config",
                          "known_hosts"
                        ]
                      },
                      "first_seen": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "type",
                      "fingerprint",
                      "first_seen"
                    ],
                    "additionalProperties": true
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "volumes": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "drives": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "arrays": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "containers": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "services": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "asserts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string",
                      "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
                    },
                    "detail": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "name",
                    "state",
                    "detail"
                  ],
                  "additionalProperties": true
                }
              }
            },
            "required": [],
            "additionalProperties": true
          }
        ]
      },
      "Capabilities": {
        "type": "object",
        "additionalProperties": {
          "type": "object",
          "properties": {
            "state": {
              "enum": [
                "ok",
                "unknown",
                "n/a"
              ]
            },
            "reason": {
              "type": "string"
            }
          },
          "required": [
            "state"
          ],
          "additionalProperties": true
        }
      },
      "HostMetrics": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string"
          },
          "range": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "metrics": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "step": {
                  "type": "number"
                },
                "points": {
                  "type": "array",
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "minItems": 2,
                    "maxItems": 2
                  }
                },
                "avg": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "peak": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "capability": {
                  "type": "object"
                }
              },
              "required": [
                "step",
                "points",
                "avg",
                "peak"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "host",
          "range",
          "from",
          "to",
          "metrics"
        ],
        "additionalProperties": true
      },
      "AppSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "ui": {
            "type": "string"
          },
          "availability_30d": {
            "type": "object",
            "properties": {
              "percent": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "enough_data": {
                "type": "boolean"
              },
              "excluded_s": {
                "type": "number"
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "percent",
              "enough_data",
              "excluded_s",
              "since"
            ],
            "additionalProperties": true,
            "description": "Availability over the last 30 days (D-33); present when the hub runs the alert engine."
          },
          "name": {
            "type": "string"
          },
          "host": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "response_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "tls_days": {
            "type": [
              "number",
              "null"
            ]
          },
          "errors_24h": {
            "type": [
              "number",
              "string",
              "null"
            ]
          },
          "source": {
            "enum": [
              "manual",
              "import"
            ]
          },
          "importer": {
            "type": [
              "string",
              "null"
            ]
          },
          "latency_1h": {
            "type": "array",
            "description": "Median response time per 5-minute bucket over the last hour, oldest first; null where nothing answered.",
            "items": {
              "type": [
                "number",
                "null"
              ]
            }
          }
        },
        "required": [
          "id",
          "state",
          "reason",
          "since",
          "ui",
          "availability_30d",
          "name",
          "host",
          "kind",
          "target",
          "response_ms",
          "tls_days",
          "errors_24h",
          "source",
          "importer",
          "latency_1h"
        ],
        "additionalProperties": true
      },
      "CustomCheck": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AppSummary"
          },
          {
            "type": "object",
            "properties": {
              "check_id": {
                "type": "string"
              },
              "domain": {
                "enum": [
                  "hosts",
                  "apps"
                ]
              },
              "mode": {
                "enum": [
                  "exit_code",
                  "number",
                  "json",
                  "regex"
                ]
              },
              "exit_code": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "output_ts": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "number": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Number mode only: the warning and critical levels, their direction and the unit, from config.",
                "properties": {
                  "warn": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "critical": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "direction": {
                    "enum": [
                      "above",
                      "below"
                    ]
                  },
                  "unit": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "recent_values": {
                "type": "array",
                "description": "The parsed value of the newest 48 runs, oldest first; null where a run produced none.",
                "items": {
                  "type": "object",
                  "properties": {
                    "at": {
                      "type": "string"
                    },
                    "value": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "at",
                    "value"
                  ],
                  "additionalProperties": true
                }
              },
              "command": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Admin only, with resolved secret values masked."
              },
              "script": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Admin only: the scripts/ path."
              },
              "stdout_tail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Admin only: the last 4 KB of the newest run's stdout, secrets masked."
              },
              "stderr_tail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Admin only: the last 4 KB of the newest run's stderr, secrets masked."
              }
            },
            "required": [
              "check_id",
              "domain",
              "mode",
              "exit_code",
              "output_ts",
              "number",
              "recent_values"
            ],
            "additionalProperties": true
          }
        ]
      },
      "CustomSources": {
        "type": "object",
        "properties": {
          "host": {
            "type": "string"
          },
          "method": {
            "enum": [
              "agent",
              "ssh",
              "relay"
            ]
          },
          "ssh": {
            "type": "boolean",
            "description": "The host has an ssh: block, so a command or script can run over SSH."
          },
          "default_shell": {
            "enum": [
              "sh",
              "powershell"
            ]
          },
          "scripts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Files under the config dir's scripts/, as scripts/... paths."
          },
          "agent_commands": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "The names the connected agent reported; null when no agent is connected."
          }
        },
        "required": [
          "host",
          "method",
          "ssh",
          "default_shell",
          "scripts",
          "agent_commands"
        ],
        "additionalProperties": true
      },
      "BackupItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "ui": {
            "type": "string"
          },
          "availability_30d": {
            "type": "object",
            "properties": {
              "percent": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "enough_data": {
                "type": "boolean"
              },
              "excluded_s": {
                "type": "number"
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "percent",
              "enough_data",
              "excluded_s",
              "since"
            ],
            "additionalProperties": true,
            "description": "Availability over the last 30 days (D-33); present when the hub runs the alert engine."
          },
          "name": {
            "type": "string"
          },
          "tool": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "source_host": {
            "type": "string"
          },
          "destination": {
            "type": "string"
          },
          "destination_host": {
            "type": [
              "string",
              "null"
            ]
          },
          "schedule": {
            "type": "string"
          },
          "last_success": {
            "type": [
              "string",
              "null"
            ]
          },
          "age_s": {
            "type": [
              "number",
              "null"
            ]
          },
          "size_bytes": {
            "type": [
              "number",
              "null"
            ]
          },
          "recent_runs": {
            "type": "array",
            "description": "The last 14 runs, newest first: when each started, its state and its bytes.",
            "items": {
              "type": "object",
              "properties": {
                "at": {
                  "type": "string"
                },
                "state": {
                  "type": "string",
                  "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
                },
                "bytes": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "required": [
                "at",
                "state",
                "bytes"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "id",
          "state",
          "reason",
          "since",
          "ui",
          "availability_30d",
          "name",
          "tool",
          "source",
          "source_host",
          "destination",
          "destination_host",
          "schedule",
          "last_success",
          "age_s",
          "size_bytes",
          "recent_runs"
        ],
        "additionalProperties": true
      },
      "BackupDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/BackupItem"
          },
          {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "host": {
                "type": "string"
              },
              "tz": {
                "type": "string"
              },
              "grace_s": {
                "type": "number"
              },
              "heartbeat": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "severity": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "next_due": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "freshness": {
                "enum": [
                  "ok",
                  "late",
                  "missed"
                ]
              },
              "checks": {
                "type": "object",
                "description": "B1 to B9 of the newest run: pass, failed, late, skip, running or unknown. B8 is judged live; B9 is always skip in this version.",
                "additionalProperties": {
                  "enum": [
                    "pass",
                    "failed",
                    "late",
                    "skip",
                    "running",
                    "unknown"
                  ]
                }
              },
              "check_details": {
                "type": "object",
                "description": "B1 to B9: why each check reads as it does (the probe's words, or why it was skipped).",
                "additionalProperties": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "size_band": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "string"
                },
                "minItems": 2,
                "maxItems": 2
              },
              "max_drop_pct": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "retention": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "min": {
                    "type": "number"
                  },
                  "max": {
                    "type": "number"
                  }
                }
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "log_tail": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Admin only (absent for other roles): the log tail of the newest run, redacted, capped at 64 KB. `source` is `agent` when it is what the agent read from the job's configured log (B2), otherwise `wrapper`: the newest output `yarrix run` sent.",
                "properties": {
                  "at": {
                    "type": "string"
                  },
                  "source": {
                    "enum": [
                      "agent",
                      "wrapper"
                    ]
                  },
                  "text": {
                    "type": "string"
                  }
                }
              }
            },
            "required": [
              "checks",
              "check_details",
              "next_due",
              "freshness"
            ],
            "additionalProperties": true
          }
        ]
      },
      "BackupRun": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "at": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "duration_s": {
            "type": [
              "number",
              "null"
            ]
          },
          "bytes": {
            "type": [
              "number",
              "null"
            ]
          },
          "files": {
            "type": [
              "number",
              "null"
            ]
          },
          "exit_code": {
            "type": [
              "number",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "opened_by": {
            "enum": [
              "report",
              "wrapper",
              "heartbeat",
              "observed"
            ]
          },
          "checks": {
            "type": "object",
            "additionalProperties": {
              "enum": [
                "pass",
                "failed",
                "late",
                "skip",
                "running",
                "unknown"
              ]
            }
          }
        },
        "required": [
          "id",
          "at",
          "state",
          "duration_s",
          "bytes",
          "files",
          "exit_code",
          "error",
          "opened_by",
          "checks"
        ],
        "additionalProperties": true
      },
      "HeartbeatItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "since": {
            "type": "string"
          },
          "ui": {
            "type": "string"
          },
          "availability_30d": {
            "type": "object",
            "properties": {
              "percent": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "enough_data": {
                "type": "boolean"
              },
              "excluded_s": {
                "type": "number"
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "percent",
              "enough_data",
              "excluded_s",
              "since"
            ],
            "additionalProperties": true,
            "description": "Availability over the last 30 days (D-33); present when the hub runs the alert engine."
          },
          "name": {
            "type": "string"
          },
          "host": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner": {
            "type": [
              "string",
              "null"
            ]
          },
          "runbook": {
            "type": [
              "string",
              "null"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "schedule": {
            "type": "string"
          },
          "grace": {
            "type": "string"
          },
          "grace_s": {
            "type": "number"
          },
          "last_ping": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_exit": {
            "type": [
              "number",
              "null"
            ]
          },
          "backup": {
            "type": [
              "string",
              "null"
            ]
          },
          "runs": {
            "type": "array",
            "description": "The pings of the last 24 runs, newest first.",
            "items": {
              "$ref": "#/components/schemas/HeartbeatPing"
            }
          }
        },
        "required": [
          "id",
          "state",
          "reason",
          "since",
          "ui",
          "availability_30d",
          "name",
          "host",
          "description",
          "owner",
          "runbook",
          "tags",
          "schedule",
          "grace",
          "grace_s",
          "last_ping",
          "last_exit",
          "backup",
          "runs"
        ],
        "additionalProperties": true
      },
      "HeartbeatPing": {
        "type": "object",
        "properties": {
          "at": {
            "type": "string"
          },
          "kind": {
            "enum": [
              "start",
              "success",
              "fail",
              "exit",
              "log"
            ]
          },
          "exit_code": {
            "type": [
              "number",
              "null"
            ]
          },
          "rid": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_s": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "at",
          "kind",
          "exit_code",
          "rid",
          "duration_s"
        ],
        "additionalProperties": true
      },
      "HeartbeatDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/HeartbeatItem"
          },
          {
            "type": "object",
            "properties": {
              "tz": {
                "type": "string"
              },
              "max_duration_s": {
                "type": "number"
              },
              "next_due": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ping_path": {
                "type": "string"
              },
              "ping_url": {
                "type": "string",
                "description": "The full ping URL on the hub's public URL. A credential."
              },
              "runs": {
                "type": "array",
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/HeartbeatPing"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "body": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "body"
                      ],
                      "additionalProperties": true
                    }
                  ]
                }
              }
            },
            "required": [
              "tz",
              "max_duration_s",
              "next_due",
              "ping_path",
              "ping_url",
              "runs"
            ],
            "additionalProperties": true
          }
        ]
      },
      "ReportResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "run_id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "description": "ok, up, success, warn, late, critical, failed, missed, running, unobserved, asleep, unknown."
          }
        },
        "required": [
          "ok",
          "run_id",
          "state"
        ],
        "additionalProperties": true
      },
      "Me": {
        "type": "object",
        "properties": {
          "user": {
            "type": "object",
            "properties": {
              "username": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "role": {
                "enum": [
                  "viewer",
                  "operator",
                  "admin"
                ]
              },
              "via": {
                "enum": [
                  "session",
                  "token"
                ],
                "description": "How the caller is signed in. Exec fields can only be changed from a session."
              }
            },
            "required": [
              "username",
              "name",
              "role",
              "via"
            ],
            "additionalProperties": true
          },
          "prefs": {
            "type": "object",
            "properties": {
              "theme": {
                "enum": [
                  "light",
                  "dark"
                ]
              }
            },
            "required": [],
            "additionalProperties": true
          }
        },
        "required": [
          "user",
          "prefs"
        ],
        "additionalProperties": true
      },
      "Prefs": {
        "type": "object",
        "properties": {
          "theme": {
            "enum": [
              "light",
              "dark"
            ]
          }
        },
        "required": [],
        "additionalProperties": true
      },
      "ConfigErrors": {
        "type": "object",
        "properties": {
          "errors": {
            "type": "array"
          },
          "warnings": {
            "type": "array"
          },
          "invalid": {
            "type": "array"
          }
        },
        "required": [
          "errors",
          "warnings",
          "invalid"
        ],
        "additionalProperties": true
      },
      "TokenList": {
        "type": "object",
        "properties": {
          "tokens": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "prefix": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "scopes": {
                  "type": "array",
                  "items": {
                    "enum": [
                      "read",
                      "operate",
                      "report",
                      "admin"
                    ]
                  }
                },
                "backups": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "string"
                  }
                },
                "createdBy": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "createdAt": {
                  "type": "number"
                },
                "lastUsedAt": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "revokedAt": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "prefix",
                "name",
                "scopes",
                "backups",
                "createdBy",
                "createdAt",
                "lastUsedAt",
                "revokedAt"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "tokens"
        ],
        "additionalProperties": true
      },
      "TokenCreated": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "token": {
            "type": "string",
            "description": "The secret. Returned once; only its hash is stored."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "note": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "prefix",
          "token",
          "scopes",
          "note"
        ],
        "additionalProperties": true
      },
      "ConfigFileEntry": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "size": {
            "type": "number"
          }
        },
        "required": [
          "path",
          "kind",
          "size"
        ],
        "additionalProperties": true
      },
      "ConfigWritten": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "etag": {
            "type": "string"
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "issues": {
            "type": "array"
          },
          "exec_changes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "commit": {
            "type": "object",
            "properties": {
              "status": {
                "enum": [
                  "committed",
                  "skipped",
                  "failed"
                ]
              },
              "sha": {
                "type": "string"
              },
              "reason": {
                "type": "string"
              },
              "error": {
                "type": "string"
              }
            },
            "required": [
              "status"
            ],
            "additionalProperties": true
          }
        },
        "required": [
          "path",
          "kind",
          "etag",
          "text",
          "issues",
          "exec_changes",
          "commit"
        ],
        "additionalProperties": true
      },
      "ConfigFile": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "etag": {
            "type": "string"
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "issues": {
            "type": "array"
          },
          "secrets": {
            "type": "array",
            "description": "Every secret in the file by path: the reference as masked text, kind reference | plaintext | mixed, and missing (the reference does not resolve here). Never a value.",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string"
                },
                "ref": {
                  "type": "string"
                },
                "kind": {
                  "enum": [
                    "reference",
                    "plaintext",
                    "mixed"
                  ]
                },
                "missing": {
                  "type": "boolean"
                }
              },
              "required": [
                "path",
                "ref",
                "kind",
                "missing"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "path",
          "kind",
          "etag",
          "text",
          "issues",
          "secrets"
        ],
        "additionalProperties": true
      },
      "ValidateResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "file": {
                  "type": "string"
                },
                "line": {
                  "type": "number"
                },
                "col": {
                  "type": "number"
                },
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "severity": {
                  "enum": [
                    "error",
                    "warning"
                  ]
                }
              },
              "required": [
                "file",
                "line",
                "col",
                "path",
                "message",
                "severity"
              ],
              "additionalProperties": true
            }
          }
        },
        "required": [
          "ok",
          "issues"
        ],
        "additionalProperties": true
      }
    }
  }
}
