{
  "openapi": "3.1.0",
  "info": {
    "title": "lindir API",
    "description": "Linux-centered identity and host policy (AD-class IdM). Admin routes use Bearer auth (bootstrap token or API token). Host/client routes use Host enrollment credentials.",
    "version": "0.1.0",
    "license": {
      "name": "MIT"
    }
  },
  "servers": [
    {
      "url": "http://127.0.0.1:1289",
      "description": "Local default (configure listenPort in config.json)"
    }
  ],
  "tags": [
    {
      "name": "meta",
      "description": "Health and service info (public)"
    },
    {
      "name": "users",
      "description": "User management (admin)"
    },
    {
      "name": "groups",
      "description": "Group management (admin)"
    },
    {
      "name": "hosts",
      "description": "Host enrollment (admin)"
    },
    {
      "name": "policy",
      "description": "HBAC, sudo, SSH keys (admin)"
    },
    {
      "name": "auth",
      "description": "API tokens (admin)"
    },
    {
      "name": "audit",
      "description": "Audit log (admin)"
    },
    {
      "name": "ops",
      "description": "Day-2 attention / triage (admin)"
    },
    {
      "name": "vpn",
      "description": "Optional VPN plugin (admin)"
    },
    {
      "name": "fleet",
      "description": "Native fleet: inventory, labels, queries, scripts"
    },
    {
      "name": "client",
      "description": "Enrolled host agent API"
    },
    {
      "name": "oidc",
      "description": "OpenID Connect IdP (app SSO)"
    },
    {
      "name": "webauthn",
      "description": "Passkeys / WebAuthn"
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bootstrap token from config.json, API token (ldt_\u2026) from POST /v1/auth/tokens, or one-shot enroll token (len_\u2026) limited to POST /v1/hosts/enroll"
      },
      "HostAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Format: `Host <hostname> <enrollmentSecret>`. Also accepts X-Lindir-Host + X-Lindir-Secret."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Ok": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "isAdmin": {
            "type": "boolean"
          },
          "enabled": {
            "type": "boolean"
          },
          "mustChangePassword": {
            "type": "boolean"
          },
          "lockedUntil": {
            "type": "string"
          },
          "uid": {
            "type": "integer"
          },
          "gid": {
            "type": "integer"
          },
          "home": {
            "type": "string"
          },
          "shell": {
            "type": "string"
          }
        }
      },
      "CreateUserRequest": {
        "type": "object",
        "required": [
          "username"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "password": {
            "type": "string"
          },
          "isAdmin": {
            "type": "boolean",
            "default": false
          },
          "mustChangePassword": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "SetPasswordRequest": {
        "type": "object",
        "required": [
          "userId",
          "password"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "SetUserEnabledRequest": {
        "type": "object",
        "required": [
          "userId"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "AccountPolicy": {
        "type": "object",
        "properties": {
          "minPasswordLength": {
            "type": "integer"
          },
          "passwordMaxAgeDays": {
            "type": "integer"
          },
          "lockoutThreshold": {
            "type": "integer"
          },
          "lockoutMinutes": {
            "type": "integer"
          },
          "requireChangeOnCreate": {
            "type": "boolean"
          },
          "updatedAt": {
            "type": "string"
          }
        }
      },
      "EnrollToken": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tokenPrefix": {
            "type": "string"
          },
          "hostnameHint": {
            "type": "string"
          },
          "maxUses": {
            "type": "integer"
          },
          "useCount": {
            "type": "integer"
          },
          "expiresAt": {
            "type": "string"
          },
          "createdAt": {
            "type": "string"
          },
          "revoked": {
            "type": "boolean"
          }
        }
      },
      "Group": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "gid": {
            "type": "integer"
          }
        }
      },
      "CreateGroupRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "GroupMemberRequest": {
        "type": "object",
        "required": [
          "groupId",
          "userId"
        ],
        "properties": {
          "groupId": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          }
        }
      },
      "Host": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "hostname": {
            "type": "string"
          },
          "osFamily": {
            "type": "string"
          }
        }
      },
      "EnrollHostRequest": {
        "type": "object",
        "required": [
          "hostname"
        ],
        "properties": {
          "hostname": {
            "type": "string"
          },
          "osFamily": {
            "type": "string"
          },
          "osPretty": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Optional Tailscale-style tags when VPN plugin is enabled"
          },
          "skipVpn": {
            "type": "boolean",
            "description": "Skip mesh auth-key/peer creation (host already on Tailscale/Headscale/WireGuard)"
          }
        }
      },
      "EnrollHostResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "hostname": {
            "type": "string"
          },
          "enrollmentSecret": {
            "type": "string"
          },
          "vpn": {
            "type": "string",
            "description": "Auth key / peer status, 'skipped', or 'error:\u2026' if VPN failed after IdM enroll"
          }
        }
      },
      "HbacRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "CreateHbacRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "groupIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hostIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "HbacCheckRequest": {
        "type": "object",
        "required": [
          "groupIds",
          "hostId"
        ],
        "properties": {
          "groupIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hostId": {
            "type": "string"
          }
        }
      },
      "SudoRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "command": {
            "type": "string"
          }
        }
      },
      "CreateSudoRequest": {
        "type": "object",
        "required": [
          "name",
          "command"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "runAs": {
            "type": "string",
            "default": "ALL"
          },
          "description": {
            "type": "string"
          },
          "groupIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "hostIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CreateSshKeyRequest": {
        "type": "object",
        "required": [
          "userId",
          "publicKey"
        ],
        "properties": {
          "userId": {
            "type": "string"
          },
          "publicKey": {
            "type": "string"
          },
          "comment": {
            "type": "string"
          }
        }
      },
      "SshKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "userId": {
            "type": "string"
          },
          "comment": {
            "type": "string"
          }
        }
      },
      "PublicSshKey": {
        "type": "object",
        "properties": {
          "publicKey": {
            "type": "string"
          },
          "comment": {
            "type": "string"
          }
        }
      },
      "CreateTokenRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "default": "admin"
          }
        }
      },
      "IssuedToken": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "token": {
            "type": "string",
            "description": "Plaintext shown once"
          },
          "prefix": {
            "type": "string"
          }
        }
      },
      "TokenMeta": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "revoked": {
            "type": "boolean"
          }
        }
      },
      "RevokeTokenRequest": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string"
          }
        }
      },
      "AuditEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "at": {
            "type": "string"
          },
          "actor": {
            "type": "string"
          },
          "action": {
            "type": "string"
          }
        }
      },
      "ClientAuthRequest": {
        "type": "object",
        "required": [
          "username",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "ClientAuthResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "user": {
            "$ref": "#/components/schemas/User"
          },
          "groupIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "error": {
            "type": "string"
          }
        }
      },
      "ClientBundle": {
        "type": "object",
        "description": "Policy and account bundle for an enrolled host (includes password hashes for offline PAM)",
        "properties": {
          "host": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "hostname": {
                "type": "string"
              }
            }
          },
          "users": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "groups": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "sudo": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "hbac": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "example": "ok"
                    },
                    "vpn": {
                      "type": "string",
                      "example": "null"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "This OpenAPI document",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/info": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Service metadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Build/capability info",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "platforms": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "storage": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "clientApi": {
                      "type": "boolean"
                    },
                    "oidc": {
                      "type": "boolean"
                    },
                    "webauthn": {
                      "type": "boolean"
                    },
                    "ldap": {
                      "type": "boolean"
                    },
                    "adminUi": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/users": {
      "get": {
        "tags": [
          "users"
        ],
        "summary": "List users",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "User list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/User"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "users"
        ],
        "summary": "Create user",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/v1/users/password": {
      "post": {
        "tags": [
          "users"
        ],
        "summary": "Set user password",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPasswordRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/users/enable": {
      "post": {
        "tags": [
          "users"
        ],
        "summary": "Enable or disable a user (without delete)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetUserEnabledRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/account-policy": {
      "get": {
        "tags": [
          "users"
        ],
        "summary": "Get account password/lockout policy",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Policy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountPolicy"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "users"
        ],
        "summary": "Update account password/lockout policy",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountPolicy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountPolicy"
                }
              }
            }
          }
        }
      }
    },
    "/v1/enroll-tokens": {
      "get": {
        "tags": [
          "hosts"
        ],
        "summary": "List one-shot enroll tokens",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Tokens (no plaintext)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EnrollToken"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "hosts"
        ],
        "summary": "Create one-shot enroll token (len_\u2026 shown once)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "ttlHours": {
                    "type": "integer",
                    "default": 24
                  },
                  "maxUses": {
                    "type": "integer",
                    "default": 1
                  },
                  "hostnameHint": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created (includes token + joinCommand once)"
          }
        }
      }
    },
    "/v1/enroll-tokens/revoke": {
      "post": {
        "tags": [
          "hosts"
        ],
        "summary": "Revoke an enroll token",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ops/attention": {
      "get": {
        "tags": [
          "ops"
        ],
        "summary": "Hosts needing attention (stale sync, reboot, failed jobs, denials)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Attention summary + lists"
          }
        }
      }
    },
    "/v1/groups": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "List groups",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Group list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Group"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "Create group",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGroupRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          }
        }
      }
    },
    "/v1/groups/members": {
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "Add user to group",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupMemberRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/hosts": {
      "get": {
        "tags": [
          "hosts"
        ],
        "summary": "List hosts",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Host list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Host"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/hosts/enroll": {
      "post": {
        "tags": [
          "hosts"
        ],
        "summary": "Enroll a host",
        "description": "Returns enrollmentSecret for HostAuth on /v1/client/* routes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollHostRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Enrolled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollHostResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/policy/hbac": {
      "get": {
        "tags": [
          "policy"
        ],
        "summary": "List HBAC rules",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "HBAC rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HbacRule"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "policy"
        ],
        "summary": "Create HBAC rule",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHbacRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HbacRule"
                }
              }
            }
          }
        }
      }
    },
    "/v1/policy/hbac/check": {
      "post": {
        "tags": [
          "policy"
        ],
        "summary": "Evaluate HBAC",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HbacCheckRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "allowed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/policy/sudo": {
      "get": {
        "tags": [
          "policy"
        ],
        "summary": "List sudo rules",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sudo rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SudoRule"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "policy"
        ],
        "summary": "Create sudo rule",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSudoRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SudoRule"
                }
              }
            }
          }
        }
      }
    },
    "/v1/policy/ssh-keys": {
      "post": {
        "tags": [
          "policy"
        ],
        "summary": "Add SSH public key for a user",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSshKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/policy/ssh-keys/{userId}": {
      "get": {
        "tags": [
          "policy"
        ],
        "summary": "List SSH keys for a user (by user id)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SshKey"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/tokens": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "List API tokens (no plaintext secrets)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/TokenMeta"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Create API token",
        "description": "Returns plaintext token once.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTokenRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssuedToken"
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/tokens/revoke": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Revoke API token",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RevokeTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/audit": {
      "get": {
        "tags": [
          "audit"
        ],
        "summary": "Search audit events",
        "description": "Filter by actor/action/resource/since. Matching events are sorted by `at` descending (newest first), then truncated to `limit` (default 100).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "actor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resourceType",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "resourceId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "UTC lower bound YYYY-MM-DD HH:MM:SS"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Events (newest first)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditEvent"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/vpn/sync-acls": {
      "post": {
        "tags": [
          "vpn"
        ],
        "summary": "Push group ACL intent to VPN provider",
        "description": "Push IdM groups into VPN ACL/policy (Tailscale/Headscale live API, WireGuard groups.json).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Synced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "provider": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/fleet/labels": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List fleet labels",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Label list"
          }
        }
      },
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Create fleet label",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/v1/fleet/labels/assign": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Assign label to host",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/fleet/labels/unassign": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Remove label from host",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/fleet/labels/sync-hbac": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Create labels from IdM groups and assign via HBAC",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/fleet/inventory": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List host inventory summaries",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Summaries"
          }
        }
      }
    },
    "/v1/fleet/upgrades": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List packages upgradable from distro repositories",
        "description": "Flattens inventory upgradable[] (from apt list --upgradable / pacman -Qu). Optional query hostId filters to one host.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hostId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Optional host UUID filter"
          }
        ],
        "responses": {
          "200": {
            "description": "Upgradable packages across hosts"
          }
        }
      }
    },
    "/v1/fleet/queries": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List saved queries",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Queries"
          }
        }
      },
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Create query (shell command)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/v1/fleet/queries/run": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Enqueue query on hosts",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Runs created"
          }
        }
      }
    },
    "/v1/fleet/scripts": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List scripts",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Scripts"
          }
        }
      },
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Create script",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/v1/fleet/scripts/run": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Enqueue script on hosts",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Runs created"
          }
        }
      }
    },
    "/v1/fleet/runs": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List fleet runs",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Runs"
          }
        }
      }
    },
    "/v1/fleet/packages/jobs": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List package jobs",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Jobs"
          }
        },
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "pending"
              ]
            },
            "description": "If pending, return only jobs waiting to be claimed by hosts."
          }
        ]
      },
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Enqueue package install/remove/upgrade/refresh/reboot/update-client",
        "description": "Dedicated package management for Ubuntu (apt) and CachyOS/Arch (pacman). Body: { action, hostIds, labelIds, packages }. labelIds expand to hosts server-side. action=reboot schedules a host reboot on next sync; action=update-client upgrades the lindir-client package from host repos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Jobs created"
          }
        }
      }
    },
    "/v1/fleet/energy/policies": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List energy / power policies",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Policies"
          }
        }
      },
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Create energy policy (idle sleep, screen blank/off, lid)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created"
          }
        }
      }
    },
    "/v1/fleet/energy/policies/{id}": {
      "put": {
        "tags": [
          "fleet"
        ],
        "summary": "Update energy policy",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated"
          }
        }
      },
      "delete": {
        "tags": [
          "fleet"
        ],
        "summary": "Delete energy policy and its assignments",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          }
        }
      }
    },
    "/v1/fleet/energy/policies/{id}/targets": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List host/label assignments for an energy policy",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Targets"
          }
        }
      }
    },
    "/v1/fleet/energy/assign": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Assign energy policy to hosts and/or labels",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Assigned"
          }
        }
      }
    },
    "/v1/fleet/energy/unassign": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Remove energy policy assignments",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Unassigned"
          }
        }
      }
    },
    "/v1/fleet/vulns/findings": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "List distro-aware vulnerability findings",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "hostId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Findings array"
          }
        }
      }
    },
    "/v1/fleet/vulns/job": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "Status of background vuln scan/refresh job",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Job status (idle|running|done|error)"
          }
        }
      }
    },
    "/v1/fleet/vulns/refresh": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Start background feed refresh (Arch/Ubuntu; at most once per day unless force)",
        "description": "Body optional: { \"feed\": \"all|arch|ubuntu\", \"force\": true }. Force bypasses the 1-day feed TTL. Ubuntu fetches paginate at limit=20; a total failure does not update ubuntu_feed_at.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Async job accepted"
          },
          "409": {
            "description": "Another vuln job is already running"
          }
        }
      }
    },
    "/v1/fleet/vulns/scan": {
      "post": {
        "tags": [
          "fleet"
        ],
        "summary": "Start background cache-only vuln match (no outbound security API calls)",
        "description": "Errors if Arch/CachyOS hosts exist but the AVG cache is empty \u2014 run refresh first. Returns job detail with skip counts (no versions / unknown family / empty cache).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "202": {
            "description": "Async job accepted"
          },
          "409": {
            "description": "Another vuln job is already running"
          }
        }
      }
    },
    "/v1/client/fleet/energy": {
      "get": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Effective energy policy for this host",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Policy or applied:false"
          }
        }
      }
    },
    "/v1/fleet/packages/jobs/{id}": {
      "get": {
        "tags": [
          "fleet"
        ],
        "summary": "Get package job by id (includes stdout/stderr)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "Cancel a pending package job",
        "description": "Cancels a job that has not yet been claimed by the host (status=pending). Running or finished jobs cannot be cancelled.",
        "operationId": "cancelFleetPackageJob",
        "tags": [
          "fleet"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled job",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Not pending or already finished"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/v1/client/fleet/inventory": {
      "post": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Upload host inventory snapshot",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/client/fleet/runs": {
      "get": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Pending query/script runs for this host",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Runs"
          }
        }
      }
    },
    "/v1/client/fleet/runs/{id}/result": {
      "post": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Report query/script run result",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/client/fleet/packages/jobs": {
      "get": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Claim pending package jobs (marks running)",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Jobs"
          }
        }
      }
    },
    "/v1/client/fleet/packages/jobs/{id}/result": {
      "post": {
        "tags": [
          "client",
          "fleet"
        ],
        "summary": "Report package job result",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/client/bundle": {
      "get": {
        "tags": [
          "client"
        ],
        "summary": "Fetch account and policy bundle for this host",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Bundle",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientBundle"
                }
              }
            }
          },
          "401": {
            "description": "Host unauthorized"
          }
        }
      }
    },
    "/v1/client/auth": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Authenticate a user on this host (PAM online path)",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientAuthRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated and HBAC allowed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientAuthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          },
          "403": {
            "description": "HBAC denied"
          }
        }
      }
    },
    "/v1/client/ssh-keys/{username}": {
      "get": {
        "tags": [
          "client"
        ],
        "summary": "SSH public keys for username (AuthorizedKeysCommand)",
        "security": [
          {
            "HostAuth": []
          }
        ],
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Keys (may be empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicSshKey"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/openid-configuration": {
      "get": {
        "tags": [
          "oidc"
        ],
        "summary": "OIDC discovery document",
        "operationId": "oidcDiscovery",
        "responses": {
          "200": {
            "description": "Discovery JSON"
          }
        }
      }
    },
    "/oidc/authorize": {
      "get": {
        "tags": [
          "oidc"
        ],
        "summary": "Authorization endpoint (login form)",
        "operationId": "oidcAuthorizeGet",
        "responses": {
          "200": {
            "description": "HTML login"
          }
        }
      },
      "post": {
        "tags": [
          "oidc"
        ],
        "summary": "Submit login; redirect with code",
        "operationId": "oidcAuthorizePost",
        "responses": {
          "302": {
            "description": "Redirect with code"
          }
        }
      }
    },
    "/oidc/token": {
      "post": {
        "tags": [
          "oidc"
        ],
        "summary": "Token endpoint (authorization_code, refresh_token)",
        "operationId": "oidcToken",
        "responses": {
          "200": {
            "description": "Tokens (access, id, refresh)"
          }
        }
      }
    },
    "/oidc/jwks": {
      "get": {
        "tags": [
          "oidc"
        ],
        "summary": "JWKS (RS256 public keys)",
        "operationId": "oidcJwks",
        "responses": {
          "200": {
            "description": "JWKS JSON"
          }
        }
      }
    },
    "/oidc/userinfo": {
      "get": {
        "tags": [
          "oidc"
        ],
        "summary": "UserInfo endpoint",
        "operationId": "oidcUserinfo",
        "responses": {
          "200": {
            "description": "Claims"
          }
        }
      }
    },
    "/v1/oidc/clients": {
      "get": {
        "tags": [
          "oidc"
        ],
        "summary": "List OIDC clients",
        "operationId": "listOidcClients",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Clients (includes public flag)"
          }
        }
      },
      "post": {
        "tags": [
          "oidc"
        ],
        "summary": "Register OIDC client (optional public:true for PKCE clients)",
        "operationId": "createOidcClient",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Created (includes clientSecret once; empty if public)"
          }
        }
      }
    },
    "/v1/oidc/clients/revoke": {
      "post": {
        "tags": [
          "oidc"
        ],
        "summary": "Revoke OIDC client",
        "operationId": "revokeOidcClient",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/v1/webauthn/register/begin": {
      "post": {
        "tags": [
          "webauthn"
        ],
        "summary": "Begin passkey registration",
        "operationId": "webauthnRegisterBegin",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "PublicKeyCredentialCreationOptions (base64url fields)"
          }
        }
      }
    },
    "/v1/webauthn/register/finish": {
      "post": {
        "tags": [
          "webauthn"
        ],
        "summary": "Finish passkey registration",
        "operationId": "webauthnRegisterFinish",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Credential stored"
          }
        }
      }
    },
    "/v1/webauthn/authenticate/begin": {
      "post": {
        "tags": [
          "webauthn"
        ],
        "summary": "Begin passkey authentication",
        "operationId": "webauthnAuthenticateBegin",
        "responses": {
          "200": {
            "description": "PublicKeyCredentialRequestOptions"
          }
        }
      }
    },
    "/v1/webauthn/authenticate/finish": {
      "post": {
        "tags": [
          "webauthn"
        ],
        "summary": "Finish passkey authentication (returns loginToken)",
        "operationId": "webauthnAuthenticateFinish",
        "responses": {
          "200": {
            "description": "loginToken + user"
          }
        }
      }
    },
    "/v1/webauthn/credentials": {
      "get": {
        "tags": [
          "webauthn"
        ],
        "summary": "List passkeys for a user",
        "operationId": "listWebauthnCredentials",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Credentials"
          }
        }
      }
    },
    "/v1/webauthn/credentials/revoke": {
      "post": {
        "tags": [
          "webauthn"
        ],
        "summary": "Revoke a passkey",
        "operationId": "revokeWebauthnCredential",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  }
}
