{
  "components": {
    "responses": {},
    "schemas": {
      "SshPublicKeySingleResponse": {
        "description": "Single SSH public key response",
        "properties": {
          "data": { "$ref": "#/components/schemas/SshPublicKey" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "SshPublicKeySingleResponse",
        "type": "object"
      },
      "SshUsernameSingleResponse": {
        "description": "Single SSH username response",
        "properties": {
          "data": { "$ref": "#/components/schemas/SshUsername" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "SshUsernameSingleResponse",
        "type": "object"
      },
      "PaginatedMeta": {
        "description": "Meta object for paginated list responses (includes pagination block)",
        "properties": {
          "pagination": { "$ref": "#/components/schemas/Pagination" },
          "request_id": {
            "description": "Unique request identifier",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "type": "string"
          },
          "timestamp": {
            "description": "ISO 8601 UTC response timestamp",
            "example": "2026-04-15T10:00:00.000Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": ["request_id", "timestamp", "pagination"],
        "title": "PaginatedMeta",
        "type": "object"
      },
      "OrphanedClusters": {
        "additionalProperties": {
          "description": "List of node names in this orphaned cluster",
          "items": { "type": "string" },
          "type": "array"
        },
        "description": "Clusters that could not be assigned to any admin due to capacity constraints. Empty map when system is not degraded.",
        "example": {
          "cluster-orphaned-1": ["node-uuid-5", "node-uuid-6"],
          "cluster-orphaned-2": ["node-uuid-7"]
        },
        "title": "OrphanedClusters",
        "type": "object"
      },
      "CommandCreateRequest": {
        "description": "Create a new command with flexible targeting options",
        "example": {
          "command_text": "ABC=value\necho $ABC\nsudo docker ps",
          "targeting": {
            "node_filters": { "id_type": "persistent", "status": "healthy" },
            "node_ids": ["01234567-89ab-cdef-0123-456789abcdef"],
            "type": "nodes"
          }
        },
        "properties": {
          "command_text": {
            "description": "Multi-line shell script/commands to execute",
            "example": "ABC=value\necho $ABC\nsystemctl restart nginx",
            "minLength": 1,
            "type": "string"
          },
          "expired_at": {
            "description": "Deadline after which pending/sent executions are automatically expired. Must be in the future. Immutable after creation.",
            "example": "2025-12-31T23:59:59Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "targeting": {
            "example": {
              "cluster_filters": { "name": "*prod*" },
              "cluster_names": ["prod", "staging"],
              "node_filters": { "id_type": "persistent", "status": "healthy" },
              "type": "clusters"
            },
            "properties": {
              "cluster_filters": {
                "additionalProperties": false,
                "description": "Optional filters to apply to target clusters (AND logic with node_filters). Supports all cluster list filters.",
                "properties": {
                  "has_node_limit": {
                    "description": "Filter clusters that have (true) or do not have (false) a node limit set",
                    "type": "boolean"
                  },
                  "inserted_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters inserted after or on this date"
                  },
                  "inserted_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters inserted before or on this date"
                  },
                  "ipv4_range": {
                    "description": "Filter by IPv4 range (CIDR notation)",
                    "type": "string"
                  },
                  "name": {
                    "description": "Filter by cluster name (exact match or wildcard: prod*, *staging, etc.)",
                    "type": "string"
                  },
                  "node_count": {
                    "description": "Filter by exact node count",
                    "type": "integer"
                  },
                  "node_count__gte": {
                    "description": "Filter by node count greater than or equal to",
                    "type": "integer"
                  },
                  "node_count__lte": {
                    "description": "Filter by node count less than or equal to",
                    "type": "integer"
                  },
                  "updated_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters updated after or on this datetime"
                  },
                  "updated_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters updated before or on this datetime"
                  }
                },
                "type": "object"
              },
              "cluster_names": {
                "description": "Array of cluster names (required when type is 'clusters') (will always be deduplicated)",
                "example": ["prod", "staging"],
                "items": { "type": "string" },
                "type": "array"
              },
              "node_filters": {
                "additionalProperties": false,
                "description": "Optional filters to apply to target nodes (AND logic with cluster_filters). Supports all node list filters except cluster_name.",
                "properties": {
                  "cluster_name": {
                    "description": "Filter by cluster name (exact match or wildcard: prod*, *staging, etc.)",
                    "type": "string"
                  },
                  "id_type": {
                    "description": "Filter by node ID type",
                    "enum": ["persistent", "random"],
                    "type": "string"
                  },
                  "inserted_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes inserted after or on this date"
                  },
                  "inserted_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes inserted before or on this date"
                  },
                  "last_seen_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes last seen after or on this datetime"
                  },
                  "last_seen_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes last seen before or on this datetime"
                  },
                  "self_update_enabled": {
                    "description": "Filter by self-update enabled status",
                    "type": "boolean"
                  },
                  "status": {
                    "description": "Filter by node status",
                    "enum": ["healthy", "unhealthy", "unreachable"],
                    "type": "string"
                  },
                  "updated_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes updated after or on this datetime"
                  },
                  "updated_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes updated before or on this datetime"
                  },
                  "version": {
                    "description": "Filter by node version (exact match or wildcard: 1.0.0, 1.*, etc.)",
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "node_ids": {
                "description": "Array of node IDs (required when type is 'nodes') (will always be deduplicated)",
                "example": [
                  "01234567-89ab-cdef-0123-456789abcdef",
                  "fedcba98-7654-3210-fedc-ba9876543210"
                ],
                "items": { "format": "uuid", "type": "string" },
                "type": "array"
              },
              "type": {
                "description": "Targeting strategy: 'all' for all nodes, 'nodes' for specific nodes, 'clusters' for specific clusters",
                "enum": ["all", "nodes", "clusters"],
                "type": "string"
              }
            },
            "required": ["type"],
            "type": "object"
          },
          "timeout": {
            "description": "Command timeout in milliseconds (optional, null or omitted means no timeout, must be > 0)",
            "example": 30000,
            "minimum": 1,
            "nullable": true,
            "type": "integer"
          }
        },
        "required": ["command_text", "targeting"],
        "title": "CommandCreateRequest",
        "type": "object"
      },
      "ClusterSingleResponse": {
        "description": "Single cluster response",
        "properties": {
          "data": { "$ref": "#/components/schemas/ClusterResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "ClusterSingleResponse",
        "type": "object"
      },
      "Webhook": {
        "description": "A webhook subscription. Receives a POST per matching event with the full\nCloudEvents envelope as the body and an HMAC-SHA256 signature in the\n`X-Edge-Signature` header.\n\nWebhooks are immutable after create — to change any field, delete and\nrecreate. `secret` and `headers` are write-only — encrypted at rest and\nnever appear in any response.\n",
        "example": {
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2026-05-05T10:00:00Z",
          "subscribed_events": [
            "edge.node.registered",
            "edge.command_execution.completed"
          ],
          "updated_at": "2026-05-05T10:00:00Z",
          "url": "https://example.com/edge-events"
        },
        "properties": {
          "id": {
            "description": "Unique webhook identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the webhook was created",
            "format": "date-time",
            "type": "string"
          },
          "subscribed_events": {
            "description": "Explicit list of event types this webhook fires on. Each entry is a literal event type from the catalog (no wildcards). See [AsyncAPI spec](/asyncdoc) for the full catalog.",
            "example": [
              "edge.node.registered",
              "edge.command_execution.completed"
            ],
            "items": { "type": "string" },
            "type": "array"
          },
          "updated_at": {
            "description": "When the webhook was last updated",
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "description": "Destination URL — receives a POST per matching event",
            "example": "https://example.com/edge-events",
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "subscribed_events",
          "inserted_at",
          "updated_at"
        ],
        "title": "Webhook",
        "type": "object"
      },
      "AgentCommandExecutionPaginatedResponse": {
        "description": "Paginated list of command executions for the agent",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Internal.AgentCommandExecutionResponse"
            },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "AgentCommandExecutionPaginatedResponse",
        "type": "object"
      },
      "EdgeClustersResponse": {
        "description": "All edge cluster assignments across all admins",
        "properties": {
          "data": { "$ref": "#/components/schemas/EdgeClusters" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "EdgeClustersResponse",
        "type": "object"
      },
      "SshPublicKeyCreateRequest": {
        "description": "Create a new SSH public key for a username. The key must be in valid OpenSSH format.",
        "example": {
          "key_name": "laptop-key",
          "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop"
        },
        "properties": {
          "key_name": {
            "description": "Human-readable name for the SSH key",
            "example": "laptop-key",
            "maxLength": 255,
            "minLength": 1,
            "type": "string"
          },
          "public_key": {
            "description": "SSH public key in OpenSSH format (algorithm base64data [comment]). Supported algorithms: ssh-ed25519 (recommended), ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa",
            "example": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop",
            "pattern": "^(ssh-ed25519|ecdsa-sha2-nistp(?:256|384|521)|ssh-rsa)\\s+([A-Za-z0-9+/]+=*)\\s*(.*)$",
            "type": "string"
          }
        },
        "required": ["public_key", "key_name"],
        "title": "SshPublicKeyCreateRequest",
        "type": "object"
      },
      "ClusterPaginatedResponse": {
        "description": "Paginated list of clusters with metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/ClusterResponse" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "ClusterPaginatedResponse",
        "type": "object"
      },
      "SshUsername": {
        "description": "SSH username information for node access",
        "example": {
          "has_password": true,
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2025-06-23T10:30:00Z",
          "node_id": "fedcba98-7654-3210-fedc-ba9876543210",
          "public_keys": [
            {
              "id": "fedcba98-7654-3210-fedc-ba9876543210",
              "inserted_at": "2025-06-23T10:30:00Z",
              "key_name": "laptop",
              "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop",
              "ssh_username_id": "01234567-89ab-cdef-0123-456789abcdef",
              "updated_at": "2025-06-23T10:30:00Z"
            }
          ],
          "updated_at": "2025-06-23T10:30:00Z",
          "username": "admin"
        },
        "properties": {
          "has_password": {
            "description": "Whether this username has a password configured for authentication",
            "example": true,
            "type": "boolean"
          },
          "id": {
            "description": "Unique SSH username identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the SSH username was created",
            "format": "date-time",
            "type": "string"
          },
          "node_id": {
            "description": "Node this username belongs to",
            "format": "uuid",
            "type": "string"
          },
          "public_keys": {
            "description": "SSH public keys associated with this username (only included when preloaded)",
            "items": { "$ref": "#/components/schemas/SshPublicKey" },
            "nullable": true,
            "type": "array"
          },
          "updated_at": {
            "description": "When the SSH username was last updated",
            "format": "date-time",
            "type": "string"
          },
          "username": {
            "description": "SSH username for node access (3-32 characters)",
            "example": "admin",
            "type": "string"
          }
        },
        "required": [
          "id",
          "username",
          "has_password",
          "node_id",
          "inserted_at",
          "updated_at"
        ],
        "title": "SshUsername",
        "type": "object"
      },
      "AdminClusters": {
        "description": "All admin clusters Netmaker knows about, sorted by name.",
        "items": { "$ref": "#/components/schemas/AdminCluster" },
        "title": "AdminClusters",
        "type": "array"
      },
      "SelfUpdateRequestResponse": {
        "description": "Self-update request information",
        "example": {
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2025-06-17T10:30:00Z",
          "status": "completed",
          "summary": { "failed": 2, "total": 10, "triggered": 8 },
          "targeting": {
            "node_filters": {
              "self_update_enabled": true,
              "status": "healthy"
            },
            "type": "all"
          },
          "updated_at": "2025-06-17T10:35:00Z"
        },
        "properties": {
          "id": {
            "description": "Unique request identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the request was created",
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "description": "Request processing status",
            "enum": ["pending", "processing", "completed"],
            "type": "string"
          },
          "summary": {
            "description": "Summary of results (available after completion)",
            "example": { "failed": 2, "total": 10, "triggered": 8 },
            "nullable": true,
            "properties": {
              "failed": {
                "description": "Nodes where update failed",
                "type": "integer"
              },
              "total": {
                "description": "Total nodes targeted",
                "type": "integer"
              },
              "triggered": {
                "description": "Nodes where update was triggered",
                "type": "integer"
              }
            },
            "type": "object"
          },
          "targeting": {
            "description": "Targeting configuration (same format as commands)",
            "example": {
              "node_filters": {
                "self_update_enabled": true,
                "status": "healthy"
              },
              "type": "all"
            },
            "type": "object"
          },
          "updated_at": {
            "description": "When the request was last updated",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": ["id", "targeting", "status", "inserted_at", "updated_at"],
        "title": "SelfUpdateRequestResponse",
        "type": "object"
      },
      "CommandExecutionSingleResponse": {
        "description": "Single command execution response",
        "properties": {
          "data": { "$ref": "#/components/schemas/CommandExecutionResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "CommandExecutionSingleResponse",
        "type": "object"
      },
      "SelfUpdateRequestCreateRequest": {
        "description": "Create a new self-update request.\n\nUses the same targeting system as commands. Only healthy nodes with self_update_enabled=true will be updated.\n",
        "example": {
          "targeting": { "node_filters": { "version": "0.1.*" }, "type": "all" }
        },
        "properties": {
          "targeting": {
            "description": "Targeting specification",
            "properties": {
              "cluster_filters": {
                "additionalProperties": false,
                "description": "Optional filters to apply to target clusters (AND logic with node_filters). Supports all cluster list filters.",
                "properties": {
                  "has_node_limit": {
                    "description": "Filter clusters that have (true) or do not have (false) a node limit set",
                    "type": "boolean"
                  },
                  "inserted_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters inserted after or on this date"
                  },
                  "inserted_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters inserted before or on this date"
                  },
                  "ipv4_range": {
                    "description": "Filter by IPv4 range (CIDR notation)",
                    "type": "string"
                  },
                  "name": {
                    "description": "Filter by cluster name (exact match or wildcard: prod*, *staging, etc.)",
                    "type": "string"
                  },
                  "node_count": {
                    "description": "Filter by exact node count",
                    "type": "integer"
                  },
                  "node_count__gte": {
                    "description": "Filter by node count greater than or equal to",
                    "type": "integer"
                  },
                  "node_count__lte": {
                    "description": "Filter by node count less than or equal to",
                    "type": "integer"
                  },
                  "updated_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters updated after or on this datetime"
                  },
                  "updated_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter clusters updated before or on this datetime"
                  }
                },
                "type": "object"
              },
              "cluster_names": {
                "description": "Cluster names (required if type is 'clusters')",
                "items": { "type": "string" },
                "type": "array"
              },
              "node_filters": {
                "additionalProperties": false,
                "description": "Optional filters to apply to target nodes (AND logic with cluster_filters). Supports all node list filters except cluster_name.",
                "properties": {
                  "cluster_name": {
                    "description": "Filter by cluster name (exact match or wildcard: prod*, *staging, etc.)",
                    "type": "string"
                  },
                  "id_type": {
                    "description": "Filter by node ID type",
                    "enum": ["persistent", "random"],
                    "type": "string"
                  },
                  "inserted_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes inserted after or on this date"
                  },
                  "inserted_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes inserted before or on this date"
                  },
                  "last_seen_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes last seen after or on this datetime"
                  },
                  "last_seen_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes last seen before or on this datetime"
                  },
                  "self_update_enabled": {
                    "description": "Filter by self-update enabled status",
                    "type": "boolean"
                  },
                  "status": {
                    "description": "Filter by node status",
                    "enum": ["healthy", "unhealthy", "unreachable"],
                    "type": "string"
                  },
                  "updated_at__gte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes updated after or on this datetime"
                  },
                  "updated_at__lte": {
                    "anyOf": [
                      { "format": "date-time", "type": "string" },
                      { "format": "date", "type": "string" }
                    ],
                    "description": "Filter nodes updated before or on this datetime"
                  },
                  "version": {
                    "description": "Filter by node version (exact match or wildcard: 1.0.0, 1.*, etc.)",
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "node_ids": {
                "description": "Node IDs (required if type is 'nodes')",
                "items": { "format": "uuid", "type": "string" },
                "type": "array"
              },
              "type": {
                "description": "Targeting type",
                "enum": ["all", "nodes", "clusters"],
                "type": "string"
              }
            },
            "required": ["type"],
            "type": "object"
          }
        },
        "required": ["targeting"],
        "title": "SelfUpdateRequestCreateRequest",
        "type": "object"
      },
      "EnrollmentKeyCreateRequest": {
        "description": "Parameters for creating a new enrollment key for a cluster. All fields are optional.",
        "example": {
          "expired_at": "2026-12-31T23:59:59Z",
          "name": "prod rollout",
          "uses_remaining": 5
        },
        "properties": {
          "expired_at": {
            "description": "Expiry datetime (ISO 8601). Omit or pass null for no expiry.",
            "example": "2026-12-31T23:59:59Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "description": "Optional human-readable label for this key. Display only — not used for lookup.",
            "example": "prod rollout",
            "nullable": true,
            "type": "string"
          },
          "uses_remaining": {
            "description": "Number of uses (must be >= 1). Pass null for unlimited. Omit to use the default of 1.",
            "example": 5,
            "minimum": 1,
            "nullable": true,
            "type": "integer"
          }
        },
        "title": "EnrollmentKeyCreateRequest",
        "type": "object"
      },
      "SshPublicKey": {
        "description": "SSH public key information for username access",
        "example": {
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2025-06-23T10:30:00Z",
          "key_name": "laptop-key",
          "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop",
          "ssh_username_id": "fedcba98-7654-3210-fedc-ba9876543210",
          "updated_at": "2025-06-23T10:30:00Z"
        },
        "properties": {
          "id": {
            "description": "Unique SSH public key identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the SSH public key was created",
            "format": "date-time",
            "type": "string"
          },
          "key_name": {
            "description": "Human-readable name for the SSH key",
            "example": "laptop-key",
            "type": "string"
          },
          "public_key": {
            "description": "SSH public key in OpenSSH format (algorithm base64data [comment])",
            "example": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop",
            "type": "string"
          },
          "ssh_username_id": {
            "description": "SSH username this key belongs to",
            "format": "uuid",
            "type": "string"
          },
          "updated_at": {
            "description": "When the SSH public key was last updated",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "public_key",
          "key_name",
          "ssh_username_id",
          "inserted_at",
          "updated_at"
        ],
        "title": "SshPublicKey",
        "type": "object"
      },
      "UnifiedMetricsResponse": {
        "description": "Complete metrics from all sources: host (Node Exporter) and agent (PromEx).\nProvides a unified view of node health and performance.\nUses best-effort fetching - if one source fails, it's marked as unavailable.\n",
        "properties": {
          "data": {
            "properties": {
              "agent": {
                "description": "Agent application metrics from PromEx",
                "properties": {
                  "application": {
                    "description": "BEAM VM stats",
                    "nullable": true,
                    "type": "object"
                  },
                  "available": {
                    "description": "Whether agent metrics were successfully fetched",
                    "type": "boolean"
                  },
                  "commands": {
                    "description": "Command execution metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "discovery": {
                    "description": "Admin discovery metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "health_check": {
                    "description": "Health check report metrics (HTTP fallback mode)",
                    "nullable": true,
                    "type": "object"
                  },
                  "oban_queues": {
                    "description": "Oban job queue states",
                    "items": { "type": "object" },
                    "nullable": true,
                    "type": "array"
                  },
                  "proxy": {
                    "description": "Proxy server metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "ssh": {
                    "description": "SSH server metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "vpn": {
                    "description": "VPN config pull metrics",
                    "nullable": true,
                    "type": "object"
                  }
                },
                "type": "object"
              },
              "cluster_name": {
                "description": "Name of the cluster this node belongs to",
                "type": "string"
              },
              "host": {
                "description": "Host-level metrics from Node Exporter",
                "properties": {
                  "available": {
                    "description": "Whether host metrics were successfully fetched",
                    "type": "boolean"
                  },
                  "cpu": {
                    "description": "CPU metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "disk": {
                    "description": "Disk metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "memory": {
                    "description": "Memory metrics",
                    "nullable": true,
                    "type": "object"
                  },
                  "uptime": {
                    "description": "Uptime information",
                    "nullable": true,
                    "type": "object"
                  }
                },
                "type": "object"
              },
              "node_id": {
                "description": "Node unique identifier",
                "format": "uuid",
                "type": "string"
              },
              "timestamp": {
                "description": "When the metrics were collected (ISO 8601 format)",
                "format": "date-time",
                "type": "string"
              }
            },
            "required": ["node_id", "cluster_name", "timestamp"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "UnifiedMetricsResponse",
        "type": "object"
      },
      "WebhookSingleResponse": {
        "description": "Single webhook response",
        "properties": {
          "data": { "$ref": "#/components/schemas/Webhook" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "WebhookSingleResponse",
        "type": "object"
      },
      "AliasSingleResponse": {
        "description": "Single alias response",
        "properties": {
          "data": { "$ref": "#/components/schemas/AliasResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "AliasSingleResponse",
        "type": "object"
      },
      "AdminTopologyEntry": {
        "description": "A peer admin in the cluster topology",
        "example": {
          "admin_peer_count": 1,
          "edge_node_capacity": 249,
          "erlang_node_name": "admin@admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal",
          "max_wireguard_peers": 250,
          "name": "admin-k7m3n2p9x4j6",
          "netmaker_host_id": "95e2707e-d11f-4551-bdd4-4ab2ab917505",
          "vpn_hostname": "admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal"
        },
        "properties": {
          "admin_peer_count": {
            "description": "Admin-mesh peers for this admin (= total_admins - 1)",
            "type": "integer"
          },
          "edge_node_capacity": {
            "description": "Edge nodes this admin can own (= max_wireguard_peers - admin_peer_count)",
            "type": "integer"
          },
          "erlang_node_name": {
            "description": "Erlang distribution node name",
            "type": "string"
          },
          "max_wireguard_peers": {
            "description": "Operator-configured WireGuard peer budget for this admin",
            "type": "integer"
          },
          "name": {
            "description": "Admin name (e.g., admin-k7m3n2p9x4j6)",
            "type": "string"
          },
          "netmaker_host_id": {
            "description": "Netmaker host ID for this admin (UUID format)",
            "type": "string"
          },
          "vpn_hostname": {
            "description": "Netmaker dns hostname",
            "type": "string"
          }
        },
        "required": [
          "name",
          "max_wireguard_peers",
          "admin_peer_count",
          "edge_node_capacity",
          "erlang_node_name",
          "netmaker_host_id"
        ],
        "title": "AdminTopologyEntry",
        "type": "object"
      },
      "ChangesetErrorResponse": {
        "description": "422 Validation Failed",
        "example": {
          "error": {
            "code": "validation_failed",
            "details": {
              "name": ["can't be blank"],
              "timeout": ["must be greater than 0"]
            },
            "message": "Validation failed"
          },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "validation_failed", "type": "string" },
              "details": { "nullable": true },
              "message": { "example": "Validation failed", "type": "string" }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "ChangesetErrorResponse",
        "type": "object"
      },
      "HostMetricsResponse": {
        "description": "Host-level system metrics from Node Exporter (CPU, memory, disk, uptime).\n",
        "example": {
          "cluster_name": "production",
          "cpu": {
            "cores": 4,
            "load_15m": 0.9,
            "load_1m": 1.2,
            "load_5m": 1.1,
            "usage_percent": 25.5
          },
          "disk": {
            "available_bytes": 58249036800,
            "available_gb": 54.2,
            "total_bytes": 107374182400,
            "total_gb": 100.0,
            "usage_percent": 45.8,
            "used_bytes": 49125145600,
            "used_gb": 45.8
          },
          "memory": {
            "available_bytes": 2814377984,
            "available_gb": 2.6,
            "total_bytes": 8589934592,
            "total_gb": 8.0,
            "usage_percent": 67.3,
            "used_bytes": 5775556608,
            "used_gb": 5.4
          },
          "node_id": "550e8400-e29b-41d4-a716-446655440000",
          "timestamp": "2025-01-15T15:30:00Z",
          "uptime": { "human": "1d 1h 1m", "seconds": 90061 }
        },
        "properties": {
          "cluster_name": {
            "description": "Name of the cluster this node belongs to",
            "type": "string"
          },
          "cpu": {
            "description": "CPU metrics",
            "properties": {
              "cores": {
                "description": "Number of CPU cores detected on the system",
                "nullable": true,
                "type": "integer"
              },
              "load_15m": {
                "description": "System load average over the last 15 minutes",
                "nullable": true,
                "type": "number"
              },
              "load_1m": {
                "description": "System load average over the last 1 minute",
                "nullable": true,
                "type": "number"
              },
              "load_5m": {
                "description": "System load average over the last 5 minutes",
                "nullable": true,
                "type": "number"
              }
            },
            "type": "object"
          },
          "disk": {
            "description": "Disk metrics for root filesystem (/)",
            "properties": {
              "available_bytes": {
                "description": "Available disk space in bytes",
                "nullable": true,
                "type": "integer"
              },
              "available_gb": {
                "description": "Available disk space in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              },
              "total_bytes": {
                "description": "Total disk space in bytes",
                "nullable": true,
                "type": "integer"
              },
              "total_gb": {
                "description": "Total disk space in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              },
              "usage_percent": {
                "description": "Disk usage percentage calculated as (total - available) / total * 100",
                "nullable": true,
                "type": "number"
              },
              "used_bytes": {
                "description": "Used disk space in bytes (calculated as total - available)",
                "nullable": true,
                "type": "integer"
              },
              "used_gb": {
                "description": "Used disk space in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              }
            },
            "type": "object"
          },
          "memory": {
            "description": "Memory metrics",
            "properties": {
              "available_bytes": {
                "description": "Available RAM in bytes (includes buffers/cache)",
                "nullable": true,
                "type": "integer"
              },
              "available_gb": {
                "description": "Available RAM in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              },
              "total_bytes": {
                "description": "Total RAM in bytes",
                "nullable": true,
                "type": "integer"
              },
              "total_gb": {
                "description": "Total RAM in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              },
              "usage_percent": {
                "description": "Memory usage percentage calculated as (total - available) / total * 100",
                "nullable": true,
                "type": "number"
              },
              "used_bytes": {
                "description": "Used RAM in bytes (calculated as total - available)",
                "nullable": true,
                "type": "integer"
              },
              "used_gb": {
                "description": "Used RAM in gigabytes (GB)",
                "nullable": true,
                "type": "number"
              }
            },
            "type": "object"
          },
          "node_id": {
            "description": "Node unique identifier",
            "format": "uuid",
            "type": "string"
          },
          "timestamp": {
            "description": "When the metrics were collected (ISO 8601 format)",
            "format": "date-time",
            "type": "string"
          },
          "uptime": {
            "description": "System uptime information",
            "properties": {
              "human": {
                "description": "Human-readable uptime format (e.g., '1d 2h 30m', '5h 15m', '30m')",
                "nullable": true,
                "type": "string"
              },
              "seconds": {
                "description": "System uptime in seconds since last boot",
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "required": ["node_id", "cluster_name", "timestamp"],
        "title": "HostMetricsResponse",
        "type": "object"
      },
      "NotFoundResponse": {
        "description": "404 Not Found",
        "example": {
          "error": { "code": "not_found", "message": "Resource not found" },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "not_found", "type": "string" },
              "details": { "nullable": true },
              "message": { "example": "Resource not found", "type": "string" }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "NotFoundResponse",
        "type": "object"
      },
      "ChangeClusterRequest": {
        "description": "Request to move a node to a different cluster. Performs cluster migration via Netmaker (best-effort, reconciliation worker handles failures).",
        "example": { "cluster_name": "prod-west" },
        "properties": {
          "cluster_name": {
            "description": "Name of the target cluster to move this node to.",
            "example": "prod-west",
            "maxLength": 24,
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          }
        },
        "required": ["cluster_name"],
        "title": "ChangeClusterRequest",
        "type": "object"
      },
      "MyAdminClusterResponse": {
        "description": "Topology and state of the admin cluster this admin belongs to",
        "properties": {
          "data": { "$ref": "#/components/schemas/MyAdminCluster" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "MyAdminClusterResponse",
        "type": "object"
      },
      "AgentMetricsResponse": {
        "description": "Application-level metrics from edge_agent PromEx (BEAM stats, Oban, business metrics).\n",
        "example": {
          "data": {
            "application": {
              "memory_binary_bytes": 2097152,
              "memory_binary_mb": 2.0,
              "memory_ets_bytes": 4194304,
              "memory_ets_mb": 4.0,
              "memory_processes_bytes": 83886080,
              "memory_processes_mb": 80.0,
              "memory_total_bytes": 125829120,
              "memory_total_mb": 120.0,
              "process_count": 342,
              "uptime_human": "2d 0h 0m",
              "uptime_seconds": 172800
            },
            "cluster_name": "production",
            "commands": {
              "completed_total": 150,
              "enqueued_total": 152,
              "reported_total": 150,
              "synced_total": 156
            },
            "discovery": { "admins_found_last": 5, "scans_total": 48 },
            "health_check": { "reports_total": 0 },
            "node_id": "550e8400-e29b-41d4-a716-446655440000",
            "oban_queues": [
              {
                "available": 0,
                "completed": 245,
                "discarded": 1,
                "executing": 1,
                "queue": "default",
                "retryable": 0
              },
              {
                "available": 0,
                "completed": 150,
                "discarded": 0,
                "executing": 0,
                "queue": "commands",
                "retryable": 0
              }
            ],
            "proxy": {
              "bytes_down_mb": 900.0,
              "bytes_down_total": 943718400,
              "bytes_up_mb": 150.0,
              "bytes_up_total": 157286400,
              "http_blocked_by_reason": {
                "agent_port_blocked": 1,
                "docker_network_blocked": 4,
                "localhost_blocked": 8,
                "metadata_service_blocked": 2
              },
              "http_blocked_total": 15,
              "http_connections_total": 523,
              "socks5_blocked_by_reason": {
                "docker_port_blocked": 2,
                "localhost_blocked": 3
              },
              "socks5_blocked_total": 5,
              "socks5_connections_total": 87,
              "tunnels_closed_deadline_total": 12,
              "tunnels_closed_drain_timeout_total": 3,
              "tunnels_closed_normal_total": 590,
              "tunnels_closed_total": 605
            },
            "ssh": { "authentications_total": 45, "connections_total": 44 },
            "timestamp": "2025-01-15T15:30:00Z",
            "vpn": { "pulls_total": 7 }
          }
        },
        "properties": {
          "data": {
            "properties": {
              "application": {
                "description": "Application health and BEAM VM stats",
                "properties": {
                  "memory_binary_bytes": {
                    "description": "Memory used by binaries in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_binary_mb": {
                    "description": "Memory used by binaries in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_ets_bytes": {
                    "description": "Memory used by ETS tables in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_ets_mb": {
                    "description": "Memory used by ETS tables in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_processes_bytes": {
                    "description": "Memory used by BEAM processes in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_processes_mb": {
                    "description": "Memory used by BEAM processes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_total_bytes": {
                    "description": "Total BEAM memory allocated in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_total_mb": {
                    "description": "Total BEAM memory in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "process_count": {
                    "description": "Number of BEAM processes running",
                    "nullable": true,
                    "type": "integer"
                  },
                  "uptime_human": {
                    "description": "Human-readable uptime (e.g., '2d 5h 30m')",
                    "nullable": true,
                    "type": "string"
                  },
                  "uptime_seconds": {
                    "description": "Application uptime in seconds",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "cluster_name": {
                "description": "Name of the cluster this node belongs to",
                "type": "string"
              },
              "commands": {
                "description": "Command execution metrics",
                "properties": {
                  "completed_total": {
                    "description": "Total command executions completed",
                    "nullable": true,
                    "type": "integer"
                  },
                  "enqueued_total": {
                    "description": "Total command executions enqueued for local execution",
                    "nullable": true,
                    "type": "integer"
                  },
                  "reported_total": {
                    "description": "Total results reported back to admin",
                    "nullable": true,
                    "type": "integer"
                  },
                  "synced_total": {
                    "description": "Total command sync calls made to admin",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "discovery": {
                "description": "Admin discovery metrics",
                "properties": {
                  "admins_found_last": {
                    "description": "Number of admins discovered during the last scan",
                    "nullable": true,
                    "type": "integer"
                  },
                  "scans_total": {
                    "description": "Total discovery scans performed",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "health_check": {
                "description": "Health check report metrics (only active when VPN is down and agent is in HTTP fallback mode)",
                "properties": {
                  "reports_total": {
                    "description": "Total health check reports sent to admin via HTTP fallback",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "node_id": {
                "description": "Node unique identifier",
                "format": "uuid",
                "type": "string"
              },
              "oban_queues": {
                "description": "Oban job queue states",
                "items": {
                  "properties": {
                    "available": {
                      "description": "Jobs available to run",
                      "type": "integer"
                    },
                    "completed": {
                      "description": "Completed jobs",
                      "type": "integer"
                    },
                    "discarded": {
                      "description": "Discarded jobs (max retries exceeded)",
                      "type": "integer"
                    },
                    "executing": {
                      "description": "Jobs currently executing",
                      "type": "integer"
                    },
                    "queue": {
                      "description": "Queue name (e.g., 'default', 'commands')",
                      "type": "string"
                    },
                    "retryable": {
                      "description": "Jobs awaiting retry",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "proxy": {
                "description": "Proxy server connection and security metrics",
                "properties": {
                  "bytes_down_mb": {
                    "description": "Cumulative target→client bytes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "bytes_down_total": {
                    "description": "Cumulative bytes forwarded target→client across all tunnels",
                    "nullable": true,
                    "type": "integer"
                  },
                  "bytes_up_mb": {
                    "description": "Cumulative client→target bytes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "bytes_up_total": {
                    "description": "Cumulative bytes forwarded client→target across all tunnels",
                    "nullable": true,
                    "type": "integer"
                  },
                  "http_blocked_by_reason": {
                    "additionalProperties": { "type": "integer" },
                    "description": "HTTP blocked requests grouped by reason",
                    "example": {
                      "docker_network_blocked": 3,
                      "docker_port_blocked": 1,
                      "localhost_blocked": 5,
                      "metadata_service_blocked": 2
                    },
                    "nullable": true,
                    "type": "object"
                  },
                  "http_blocked_total": {
                    "description": "Total HTTP requests blocked by security rules",
                    "nullable": true,
                    "type": "integer"
                  },
                  "http_connections_total": {
                    "description": "Total HTTP proxy connections",
                    "nullable": true,
                    "type": "integer"
                  },
                  "socks5_blocked_by_reason": {
                    "additionalProperties": { "type": "integer" },
                    "description": "SOCKS5 blocked requests grouped by reason",
                    "example": {
                      "kubernetes_port_blocked": 2,
                      "localhost_blocked": 3
                    },
                    "nullable": true,
                    "type": "object"
                  },
                  "socks5_blocked_total": {
                    "description": "Total SOCKS5 requests blocked by security rules",
                    "nullable": true,
                    "type": "integer"
                  },
                  "socks5_connections_total": {
                    "description": "Total SOCKS5 proxy connections",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_deadline_total": {
                    "description": "Tunnels force-closed by the total-duration deadline (slowloris defence)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_drain_timeout_total": {
                    "description": "Tunnels force-closed after exceeding the graceful drain grace window",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_normal_total": {
                    "description": "Tunnels closed normally (both sides EOF'd cleanly)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_total": {
                    "description": "Total tunnels that reached end-of-life (all close reasons combined)",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "ssh": {
                "description": "SSH server metrics",
                "properties": {
                  "authentications_total": {
                    "description": "Total SSH authentication attempts (all methods)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_total": {
                    "description": "Total SSH connections established",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "timestamp": {
                "description": "When the metrics were collected (ISO 8601 format)",
                "format": "date-time",
                "type": "string"
              },
              "vpn": {
                "description": "VPN connectivity metrics",
                "properties": {
                  "pulls_total": {
                    "description": "Total VPN config pulls performed (daily backstop for DNS recovery after netclient restart)",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        },
        "title": "AgentMetricsResponse",
        "type": "object"
      },
      "OrphanedClustersResponse": {
        "description": "Clusters with no assigned admin instance",
        "properties": {
          "data": { "$ref": "#/components/schemas/OrphanedClusters" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "OrphanedClustersResponse",
        "type": "object"
      },
      "ServiceUnavailableResponse": {
        "description": "503 Service Unavailable",
        "example": {
          "error": {
            "code": "service_unavailable",
            "message": "Downstream dependency unreachable"
          },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "service_unavailable", "type": "string" },
              "details": { "nullable": true },
              "message": {
                "example": "Downstream dependency unreachable",
                "type": "string"
              }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "ServiceUnavailableResponse",
        "type": "object"
      },
      "AdminCluster": {
        "description": "An admin cluster as known to Netmaker, plus its admin members.",
        "example": {
          "admin_count": 1,
          "admins": [
            {
              "ipv4_address": "100.64.0.1",
              "last_checked_in": "2026-04-28T12:34:56Z",
              "name": "admin-7k3m9p2n",
              "netmaker_host_id": "f272e703-b48f-4b61-b4c1-bfe4fffde62b",
              "status": "online",
              "use_static_port": true,
              "vpn_hostname": "admin-7k3m9p2n.admin-cluster-main.nm.internal",
              "wireguard_ip_address": "10.0.0.7",
              "wireguard_port": 51820
            }
          ],
          "ipv4_range": "100.64.0.0/24",
          "name": "admin-cluster-main"
        },
        "properties": {
          "admin_count": {
            "description": "Number of admins in this cluster",
            "type": "integer"
          },
          "admins": {
            "description": "Admins present in this cluster, sorted by name",
            "items": { "$ref": "#/components/schemas/AdminClusterMember" },
            "type": "array"
          },
          "ipv4_range": {
            "description": "IPv4 CIDR for the admin cluster network",
            "type": "string"
          },
          "name": {
            "description": "Admin cluster network name (e.g., admin-cluster-main)",
            "type": "string"
          }
        },
        "required": ["name", "ipv4_range", "admin_count", "admins"],
        "title": "AdminCluster",
        "type": "object"
      },
      "MyAdminCluster": {
        "description": "Topology and state of the admin cluster this admin belongs to.",
        "example": {
          "degraded": false,
          "name": "admin-cluster-1",
          "topology": [
            {
              "admin_peer_count": 1,
              "edge_node_capacity": 249,
              "erlang_node_name": "admin@admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal",
              "max_wireguard_peers": 250,
              "name": "admin-k7m3n2p9x4j6",
              "netmaker_host_id": "95e2707e-d11f-4551-bdd4-4ab2ab917505",
              "vpn_hostname": "admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal"
            },
            {
              "admin_peer_count": 1,
              "edge_node_capacity": 249,
              "erlang_node_name": "admin@admin-x9j4p2k7m8n3.admin-cluster-1.nm.internal",
              "max_wireguard_peers": 250,
              "name": "admin-x9j4p2k7m8n3",
              "netmaker_host_id": "7f3c8d4e-9a1b-4c2d-8e3f-5a6b7c8d9e0f",
              "vpn_hostname": "admin-x9j4p2k7m8n3.admin-cluster-1.nm.internal"
            }
          ],
          "total_admins": 2,
          "total_edge_capacity": 498,
          "total_nodes": 42,
          "weak_leader": "admin-k7m3n2p9x4j6"
        },
        "properties": {
          "degraded": {
            "description": "True when total_nodes exceeds total_edge_capacity",
            "type": "boolean"
          },
          "name": { "description": "Admin cluster name", "type": "string" },
          "topology": {
            "description": "List of all admins in the cluster",
            "items": { "$ref": "#/components/schemas/AdminTopologyEntry" },
            "type": "array"
          },
          "total_admins": {
            "description": "Total number of admins in the cluster",
            "type": "integer"
          },
          "total_edge_capacity": {
            "description": "Sum of edge_node_capacity across all admins in this admin cluster. This is the system's total edge-node sharding budget.",
            "type": "integer"
          },
          "total_nodes": {
            "description": "Total nodes registered in the system across all clusters",
            "type": "integer"
          },
          "weak_leader": {
            "description": "Name of the current weak leader admin (alphabetically first admin ID in the cluster). Best-effort duplicate work reduction — not a strong guarantee. Always populated — defaults to self on bootstrap, updated on first metadata recomputation.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "total_admins",
          "total_nodes",
          "total_edge_capacity",
          "degraded",
          "weak_leader",
          "topology"
        ],
        "title": "MyAdminCluster",
        "type": "object"
      },
      "NodePaginatedResponse": {
        "description": "Paginated list of nodes with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/NodeResponse" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "NodePaginatedResponse",
        "type": "object"
      },
      "EventTypeListResponse": {
        "description": "Full catalog of event types Edge Core can publish.",
        "properties": {
          "data": {
            "description": "Full catalog of event types Edge Core can publish, in catalog order.\n\nUse this list to build a webhook's `subscribed_events` array — every\nentry here is a valid value, anything else is rejected at create time.\nThe list is static (code-owned). See [AsyncAPI spec](/asyncdoc) for\neach event's payload shape.\n",
            "example": [
              "edge.enrollment_key.verified",
              "edge.node.registered",
              "edge.node.reregistered",
              "edge.node.version_changed",
              "edge.node.status_changed",
              "edge.node.cluster_changed",
              "edge.node.update_triggered",
              "edge.command_execution.created",
              "edge.command_execution.sent",
              "edge.command_execution.completed",
              "edge.command_execution.cancelled",
              "edge.command_execution.expired",
              "edge.command_execution.pruned",
              "edge.ssh_username.verified",
              "edge.self_update_request.completed"
            ],
            "items": { "type": "string" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "EventTypeListResponse",
        "type": "object"
      },
      "ClusterUpdateRequest": {
        "description": "Parameters for updating a cluster. Only provided fields are updated. Pass null to unset a nullable field.",
        "example": { "node_limit": 50 },
        "properties": {
          "node_limit": {
            "description": "Maximum nodes allowed in this cluster (null means no limit enforced)",
            "example": 50,
            "minimum": 1,
            "nullable": true,
            "type": "integer"
          }
        },
        "title": "ClusterUpdateRequest",
        "type": "object"
      },
      "Meta": {
        "description": "Request metadata present on every response",
        "properties": {
          "request_id": {
            "description": "Unique request identifier (mirrors x-request-id response header)",
            "example": "550e8400-e29b-41d4-a716-446655440000",
            "type": "string"
          },
          "timestamp": {
            "description": "ISO 8601 UTC timestamp of when the response was generated",
            "example": "2026-04-15T10:00:00.000Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": ["request_id", "timestamp"],
        "title": "Meta",
        "type": "object"
      },
      "EnrollmentKeyPaginatedResponse": {
        "description": "Paginated list of enrollment keys with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/EnrollmentKeyResponse" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "EnrollmentKeyPaginatedResponse",
        "type": "object"
      },
      "AdminClusterMember": {
        "description": "An admin instance present in a Netmaker admin-cluster network. May include stale/disconnected entries.",
        "example": {
          "ipv4_address": "100.64.0.1",
          "last_checked_in": "2026-04-28T12:34:56Z",
          "name": "admin-7k3m9p2n",
          "netmaker_host_id": "f272e703-b48f-4b61-b4c1-bfe4fffde62b",
          "status": "online",
          "use_static_port": true,
          "vpn_hostname": "admin-7k3m9p2n.admin-cluster-main.nm.internal",
          "wireguard_ip_address": "10.0.0.7",
          "wireguard_port": 51820
        },
        "properties": {
          "ipv4_address": {
            "description": "IPv4 address assigned within the admin cluster CIDR (without prefix length)",
            "nullable": true,
            "type": "string"
          },
          "last_checked_in": {
            "description": "Last time this admin's netclient reported in to Netmaker (ISO 8601)",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "description": "Admin name (e.g., admin-k7m3n2p9x4j6)",
            "type": "string"
          },
          "netmaker_host_id": {
            "description": "Netmaker host ID (UUID)",
            "type": "string"
          },
          "status": {
            "description": "Netmaker-derived status: online (recent checkin), offline (stale checkin), disconnected (admin disabled)",
            "enum": ["online", "offline", "disconnected"],
            "nullable": true,
            "type": "string"
          },
          "use_static_port": {
            "description": "True when the admin pins WireGuard to a fixed port across restarts",
            "type": "boolean"
          },
          "vpn_hostname": {
            "description": "DNS hostname inside the admin cluster network",
            "type": "string"
          },
          "wireguard_ip_address": {
            "description": "IP address WireGuard peers send tunnel packets to (public or LAN-reachable)",
            "nullable": true,
            "type": "string"
          },
          "wireguard_port": {
            "description": "WireGuard listen port",
            "nullable": true,
            "type": "integer"
          }
        },
        "required": ["name", "vpn_hostname", "netmaker_host_id"],
        "title": "AdminClusterMember",
        "type": "object"
      },
      "SshUsernameCreateRequest": {
        "description": "Create a new SSH username for a node, optionally with password and/or public keys",
        "example": {
          "password": "MySecurePassword123!",
          "public_keys": [
            {
              "key_name": "laptop",
              "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop"
            },
            {
              "key_name": "ci",
              "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... ci@deploy"
            }
          ],
          "username": "admin"
        },
        "properties": {
          "password": {
            "description": "Optional password for username/password SSH authentication (12-128 characters if provided, will be hashed with Argon2)",
            "example": "MySecurePassword123!",
            "maxLength": 128,
            "minLength": 12,
            "nullable": true,
            "type": "string"
          },
          "public_keys": {
            "description": "Optional array of SSH public keys to create with this username",
            "items": {
              "properties": {
                "key_name": {
                  "description": "Human-readable name for the SSH key",
                  "example": "laptop",
                  "type": "string"
                },
                "public_key": {
                  "description": "SSH public key in OpenSSH format",
                  "example": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQw7Di3fBr2oc2vbZN5YLz8YpJ8PQb5bXwQwe+QgYX8 user@laptop",
                  "type": "string"
                }
              },
              "required": ["key_name", "public_key"],
              "type": "object"
            },
            "nullable": true,
            "type": "array"
          },
          "username": {
            "description": "SSH username for node access (3-32 characters, must start with letter or underscore, lowercase letters/digits/hyphens/underscores only)",
            "example": "admin",
            "maxLength": 32,
            "minLength": 3,
            "pattern": "^[a-z_][a-z0-9_-]*$",
            "type": "string"
          }
        },
        "required": ["username"],
        "title": "SshUsernameCreateRequest",
        "type": "object"
      },
      "AdminClustersResponse": {
        "description": "All admin clusters Netmaker knows about",
        "properties": {
          "data": { "$ref": "#/components/schemas/AdminClusters" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "AdminClustersResponse",
        "type": "object"
      },
      "CommandExecutionPaginatedResponse": {
        "description": "Paginated list of command executions with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/CommandExecutionResponse"
            },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "CommandExecutionPaginatedResponse",
        "type": "object"
      },
      "AliasPaginatedResponse": {
        "description": "Paginated list of aliases with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/AliasResponse" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "AliasPaginatedResponse",
        "type": "object"
      },
      "AdminMetricsResponse": {
        "description": "Application-level metrics from edge_admin PromEx (BEAM stats, metadata, Oban, etc.).\n",
        "example": {
          "data": {
            "application": {
              "atom_count": 38522,
              "ets_count": 144,
              "memory_atom_bytes": 1223807,
              "memory_atom_mb": 1.17,
              "memory_binary_bytes": 8817904,
              "memory_binary_mb": 8.41,
              "memory_code_bytes": 30935279,
              "memory_code_mb": 29.5,
              "memory_ets_bytes": 2884160,
              "memory_ets_mb": 2.75,
              "memory_processes_bytes": 25999000,
              "memory_processes_mb": 24.79,
              "memory_total_bytes": 101185152,
              "memory_total_mb": 96.5,
              "port_count": 31,
              "process_count": 811,
              "uptime_human": "23m",
              "uptime_seconds": 1404
            },
            "commands": {
              "delivery_delivered_count": 0,
              "delivery_total": 48,
              "execution_completed_total": 21,
              "execution_delivered_total": 23,
              "expiration_total": 2
            },
            "discovery": {
              "dns_resolutions_total": 12,
              "peer_connections_total": 3,
              "scans_total": 144
            },
            "event_broker": {
              "enabled": true,
              "enqueues_total": 1235,
              "publishes_error_total": 4,
              "publishes_ok_total": 1230,
              "publishes_total": 1234
            },
            "gateways": {
              "active_count": 0,
              "connections_total": 2,
              "scrapes_total": 0
            },
            "membership": { "complete_total": 1, "steps_completed_total": 4 },
            "metadata": {
              "assigned_clusters": 0,
              "degraded": false,
              "orphaned_clusters": 0,
              "recomputations_total": 26
            },
            "nodes": { "health_checks_total": 480 },
            "oban_queues": [
              {
                "available": 0,
                "cancelled": 0,
                "completed": 81,
                "discarded": 0,
                "executing": 0,
                "queue": "zombie_admin_cleanup",
                "retryable": 0,
                "scheduled": 0
              },
              {
                "available": 0,
                "cancelled": 0,
                "completed": 0,
                "discarded": 0,
                "executing": 0,
                "queue": "execution_creation",
                "retryable": 0,
                "scheduled": 0
              }
            ],
            "proxy": {
              "auth_failures_total": 34,
              "bytes_down_mb": 9419.4,
              "bytes_down_total": 9876543210,
              "bytes_up_mb": 1452.9,
              "bytes_up_total": 1523456789,
              "connections_auth_failed_total": 34,
              "connections_failure_total": 22,
              "connections_success_total": 1189,
              "connections_total": 1245,
              "tunnels_closed_deadline_total": 38,
              "tunnels_closed_drain_timeout_total": 11,
              "tunnels_closed_normal_total": 1140,
              "tunnels_closed_total": 1189
            },
            "quantum": {
              "jobs_exceptions_total": 0,
              "jobs_executed_total": 156
            },
            "reconciliation": { "errors": 0, "total": 12 },
            "self_updates": { "completed_total": 3 },
            "ssh": { "verifications_failed": 1, "verifications_total": 34 },
            "timestamp": "2025-12-26T15:30:00Z",
            "vpn": {
              "zombie_cleanup_deleted_count": 0,
              "zombie_cleanup_total": 81
            },
            "webhook": {
              "deliveries_ok_total": 2400,
              "deliveries_recoverable_total": 60,
              "deliveries_terminal_total": 10,
              "deliveries_total": 2470,
              "fan_outs_total": 1235
            }
          },
          "meta": "Elixir.EdgeAdminWeb.Schemas.CommonSchemas.MetaSchema"
        },
        "properties": {
          "data": {
            "properties": {
              "application": {
                "description": "Application health and BEAM VM stats",
                "properties": {
                  "atom_count": {
                    "description": "Number of allocated atoms",
                    "nullable": true,
                    "type": "integer"
                  },
                  "ets_count": {
                    "description": "Number of ETS tables",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_atom_bytes": {
                    "description": "Memory used by atoms in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_atom_mb": {
                    "description": "Memory used by atoms in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_binary_bytes": {
                    "description": "Memory used by binaries in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_binary_mb": {
                    "description": "Memory used by binaries in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_code_bytes": {
                    "description": "Memory used by code in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_code_mb": {
                    "description": "Memory used by code in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_ets_bytes": {
                    "description": "Memory used by ETS tables in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_ets_mb": {
                    "description": "Memory used by ETS tables in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_processes_bytes": {
                    "description": "Memory used by processes in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_processes_mb": {
                    "description": "Memory used by processes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "memory_total_bytes": {
                    "description": "Total BEAM memory allocated in bytes",
                    "nullable": true,
                    "type": "integer"
                  },
                  "memory_total_mb": {
                    "description": "Total BEAM memory in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "port_count": {
                    "description": "Number of active ports",
                    "nullable": true,
                    "type": "integer"
                  },
                  "process_count": {
                    "description": "Number of BEAM processes running",
                    "nullable": true,
                    "type": "integer"
                  },
                  "uptime_human": {
                    "description": "Human-readable uptime (e.g., '2d 5h 30m')",
                    "nullable": true,
                    "type": "string"
                  },
                  "uptime_seconds": {
                    "description": "Application uptime in seconds",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "commands": {
                "description": "Command execution and delivery metrics",
                "properties": {
                  "delivery_delivered_count": {
                    "description": "Number of executions queued for delivery in last batch run",
                    "nullable": true,
                    "type": "integer"
                  },
                  "delivery_total": {
                    "description": "Total delivery batch runs (Quantum scheduler cycles)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "execution_completed_total": {
                    "description": "Total executions completed (result reported back by agent)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "execution_delivered_total": {
                    "description": "Total individual execution delivery attempts to agents (success + failure)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "expiration_total": {
                    "description": "Total stale execution expiration sweeps",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "discovery": {
                "description": "Peer admin discovery metrics",
                "properties": {
                  "dns_resolutions_total": {
                    "description": "Total DNS resolution attempts during peer discovery",
                    "nullable": true,
                    "type": "integer"
                  },
                  "peer_connections_total": {
                    "description": "Total Erlang peer connection attempts (success + failure + already_connected)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "scans_total": {
                    "description": "Total peer discovery scan cycles completed",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "event_broker": {
                "description": "Event broker publish metrics. When the broker is disabled (default), `enabled` is `false`\nand all counters are 0. A sustained gap between `enqueues_total` and `publishes_ok_total`\nindicates broker failures with events accumulating in Oban for retry.\n",
                "properties": {
                  "enabled": {
                    "description": "Whether the event broker is enabled (mirrors EVENT_BROKER_ENABLED config)",
                    "nullable": true,
                    "type": "boolean"
                  },
                  "enqueues_total": {
                    "description": "Total events enqueued for async broker delivery (before any publish attempt)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "publishes_error_total": {
                    "description": "Total broker publishes that failed (will be retried by Oban)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "publishes_ok_total": {
                    "description": "Total broker publishes that succeeded",
                    "nullable": true,
                    "type": "integer"
                  },
                  "publishes_total": {
                    "description": "Total broker publish attempts (ok + error)",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "gateways": {
                "description": "Gateway connection and scrape metrics",
                "properties": {
                  "active_count": {
                    "description": "Current number of active gateway connections",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_total": {
                    "description": "Total gateway connection events (connects + disconnects)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "scrapes_total": {
                    "description": "Total metrics scrape operations performed by gateways",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "membership": {
                "description": "Admin-cluster membership initialization metrics",
                "properties": {
                  "complete_total": {
                    "description": "Total full membership sequences completed (success + failure)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "steps_completed_total": {
                    "description": "Total individual membership steps completed across all restarts",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "metadata": {
                "description": "Admin metadata and cluster assignment status",
                "properties": {
                  "assigned_clusters": {
                    "description": "Number of clusters assigned to this admin",
                    "nullable": true,
                    "type": "integer"
                  },
                  "degraded": {
                    "description": "Whether admin is in degraded state",
                    "nullable": true,
                    "type": "boolean"
                  },
                  "orphaned_clusters": {
                    "description": "Number of orphaned clusters detected",
                    "nullable": true,
                    "type": "integer"
                  },
                  "recomputations_total": {
                    "description": "Total metadata recomputations performed",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "nodes": {
                "description": "Node health check metrics",
                "properties": {
                  "health_checks_total": {
                    "description": "Total node health checks performed",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "oban_queues": {
                "description": "Oban job queue states",
                "items": {
                  "properties": {
                    "available": {
                      "description": "Jobs available to run",
                      "type": "integer"
                    },
                    "cancelled": {
                      "description": "Cancelled jobs",
                      "type": "integer"
                    },
                    "completed": {
                      "description": "Completed jobs",
                      "type": "integer"
                    },
                    "discarded": {
                      "description": "Discarded jobs (max retries exceeded)",
                      "type": "integer"
                    },
                    "executing": {
                      "description": "Jobs currently executing",
                      "type": "integer"
                    },
                    "queue": {
                      "description": "Queue name (e.g., 'zombie_admin_cleanup', 'execution_creation')",
                      "type": "string"
                    },
                    "retryable": {
                      "description": "Jobs awaiting retry",
                      "type": "integer"
                    },
                    "scheduled": {
                      "description": "Jobs scheduled for future execution",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "proxy": {
                "description": "HTTP and SOCKS5 forward proxy metrics (connections, tunnels, bytes transferred)",
                "properties": {
                  "auth_failures_total": {
                    "description": "Total proxy authentication failures (mirrors connections_auth_failed_total)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "bytes_down_mb": {
                    "description": "Cumulative target→client bytes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "bytes_down_total": {
                    "description": "Cumulative bytes forwarded target→client across all tunnels",
                    "nullable": true,
                    "type": "integer"
                  },
                  "bytes_up_mb": {
                    "description": "Cumulative client→target bytes in MB",
                    "nullable": true,
                    "type": "number"
                  },
                  "bytes_up_total": {
                    "description": "Cumulative bytes forwarded client→target across all tunnels",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_auth_failed_total": {
                    "description": "Total proxy connections rejected at authentication",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_failure_total": {
                    "description": "Total proxy connections that failed for non-auth reasons (protocol, network, gateway, etc.)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_success_total": {
                    "description": "Total proxy connections that authenticated and established a tunnel",
                    "nullable": true,
                    "type": "integer"
                  },
                  "connections_total": {
                    "description": "Total proxy connections seen (success + auth_failed + failure)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_deadline_total": {
                    "description": "Tunnels force-closed by the total-duration deadline (slowloris defence)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_drain_timeout_total": {
                    "description": "Tunnels force-closed after exceeding the graceful drain grace window",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_normal_total": {
                    "description": "Tunnels closed normally (both sides EOF'd cleanly)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "tunnels_closed_total": {
                    "description": "Total tunnels that reached end-of-life (all close reasons combined)",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "quantum": {
                "description": "Quantum scheduler job execution metrics",
                "properties": {
                  "jobs_exceptions_total": {
                    "description": "Total Quantum job exceptions/failures",
                    "nullable": true,
                    "type": "integer"
                  },
                  "jobs_executed_total": {
                    "description": "Total Quantum jobs executed across all job types",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "reconciliation": {
                "description": "Cluster reconciliation metrics (Netmaker ↔ DB sync)",
                "properties": {
                  "errors": {
                    "description": "Number of errors in last reconciliation run",
                    "nullable": true,
                    "type": "integer"
                  },
                  "total": {
                    "description": "Total cluster reconciliation runs",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "self_updates": {
                "description": "Self-update request processing metrics",
                "properties": {
                  "completed_total": {
                    "description": "Total self-update requests processed to completion",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "ssh": {
                "description": "SSH credential verification metrics",
                "properties": {
                  "verifications_failed": {
                    "description": "Total SSH credential verification failures",
                    "nullable": true,
                    "type": "integer"
                  },
                  "verifications_total": {
                    "description": "Total SSH credential verification attempts (all auth methods)",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "timestamp": {
                "description": "When the metrics were collected (ISO 8601 format)",
                "format": "date-time",
                "type": "string"
              },
              "vpn": {
                "description": "VPN management metrics",
                "properties": {
                  "zombie_cleanup_deleted_count": {
                    "description": "Number of zombie admins deleted in last cleanup",
                    "nullable": true,
                    "type": "integer"
                  },
                  "zombie_cleanup_total": {
                    "description": "Total zombie admin cleanup runs",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "webhook": {
                "description": "Webhook delivery metrics. `fan_outs_total` counts publish-time fan-out invocations\n(one per published event regardless of how many webhooks match). `deliveries_*` count\nindividual HTTP delivery attempts and their outcomes — `ok`, `recoverable` (retried by\nOban: 408/429/503/network until `WEBHOOK_MAX_ATTEMPTS` is exhausted), `terminal`\n(cancelled by the worker, no further retries).\n",
                "properties": {
                  "deliveries_ok_total": {
                    "description": "Total deliveries that returned 2xx",
                    "nullable": true,
                    "type": "integer"
                  },
                  "deliveries_recoverable_total": {
                    "description": "Total deliveries that hit a recoverable error (will be retried)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "deliveries_terminal_total": {
                    "description": "Total deliveries that hit a terminal error (cancelled, contributes to auto-disable)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "deliveries_total": {
                    "description": "Total webhook delivery attempts (ok + recoverable + terminal)",
                    "nullable": true,
                    "type": "integer"
                  },
                  "fan_outs_total": {
                    "description": "Total fan-out invocations from the publish path",
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          }
        },
        "required": ["data", "meta"],
        "title": "AdminMetricsResponse",
        "type": "object"
      },
      "EnrollmentKeySingleResponse": {
        "description": "Single enrollment key response",
        "properties": {
          "data": { "$ref": "#/components/schemas/EnrollmentKeyResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "EnrollmentKeySingleResponse",
        "type": "object"
      },
      "CommandPaginatedResponse": {
        "description": "Paginated list of commands with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/CommandResponse" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "CommandPaginatedResponse",
        "type": "object"
      },
      "WebhookCreateRequest": {
        "description": "Create a new webhook subscription. Webhooks are immutable after create —\nto change any field, delete and recreate.\n\n- `url` is SSRF-checked downstream. Loopback, RFC1918, link-local, and\n  cloud-metadata IPs/hostnames are denied unless\n  `WEBHOOK_ALLOW_PRIVATE_IPS=true` is set on the admin process.\n- `secret` is the HMAC-SHA256 signing key. Stays on the server; receivers\n  verify the `X-Edge-Signature` header against their stored copy.\n- `headers` is an arbitrary string→string map stamped on every request\n  (e.g. `Authorization: Bearer ...`). Up to 20 entries.\n- `subscribed_events` is an explicit list of event-type strings — no\n  wildcards. Each string must be a known event type from the catalog;\n  unknown values are rejected at create time.\n\nDelivery retry budget is `WEBHOOK_MAX_ATTEMPTS` (default 3).\n\nThe full event catalog with payload schemas is documented in the\n[AsyncAPI spec](/asyncdoc).\n",
        "example": {
          "headers": { "Authorization": "Bearer xoxb-token" },
          "secret": "a-cryptographically-random-32-byte-secret",
          "subscribed_events": [
            "edge.node.registered",
            "edge.command_execution.completed"
          ],
          "url": "https://example.com/edge-events"
        },
        "properties": {
          "headers": {
            "additionalProperties": { "maxLength": 4096, "type": "string" },
            "description": "Extra HTTP headers. Up to 20 entries; each value up to 4096 characters.",
            "example": {
              "Authorization": "Bearer xoxb-token",
              "X-Custom": "value"
            },
            "maxProperties": 20,
            "type": "object"
          },
          "secret": {
            "description": "HMAC-SHA256 signing key (≥ 32 bytes). Never returned in responses.",
            "example": "a-cryptographically-random-32-byte-secret",
            "maxLength": 256,
            "minLength": 32,
            "type": "string"
          },
          "subscribed_events": {
            "description": "Explicit list of event types this webhook subscribes to. Each entry must be a known event type from the catalog. See [AsyncAPI spec](/asyncdoc).",
            "example": [
              "edge.node.registered",
              "edge.command_execution.completed"
            ],
            "items": { "maxLength": 256, "type": "string" },
            "maxItems": 20,
            "minItems": 1,
            "type": "array"
          },
          "url": {
            "description": "Absolute http(s) URL. SSRF-checked downstream.",
            "example": "https://example.com/edge-events",
            "maxLength": 2048,
            "minLength": 1,
            "pattern": "^https?://.+",
            "type": "string"
          }
        },
        "required": ["url", "secret", "subscribed_events"],
        "title": "WebhookCreateRequest",
        "type": "object"
      },
      "CancelExecutionData": {
        "description": "Cancellation request result",
        "example": { "result": "cancellation request sent" },
        "properties": {
          "result": {
            "description": "Cancellation request status message",
            "example": "cancellation request sent",
            "type": "string"
          }
        },
        "required": ["result"],
        "title": "CancelExecutionData",
        "type": "object"
      },
      "SelfUpdateRequestSingleResponse": {
        "description": "Single self-update request response",
        "properties": {
          "data": { "$ref": "#/components/schemas/SelfUpdateRequestResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "SelfUpdateRequestSingleResponse",
        "type": "object"
      },
      "AdminResponse": {
        "description": "Single admin identity response",
        "properties": {
          "data": { "$ref": "#/components/schemas/Admin" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "AdminResponse",
        "type": "object"
      },
      "CancelExecutionResponse": {
        "description": "Response from command execution cancellation request",
        "properties": {
          "data": { "$ref": "#/components/schemas/CancelExecutionData" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "CancelExecutionResponse",
        "type": "object"
      },
      "CommandExecutionResponse": {
        "description": "Command execution information",
        "example": {
          "cluster_name": "prod-east",
          "command_id": "fedcba98-7654-3210-fedc-ba9876543210",
          "command_text": "echo hello\nls -la",
          "completed_at": "2025-06-17T10:31:00Z",
          "exit_code": 5,
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2025-06-17T10:30:00Z",
          "node_id": "abcdef01-2345-6789-abcd-ef0123456789",
          "output": "$ ABC=value\n$ echo $ABC\nvalue\n$ systemctl restart nginx\nFailed to restart nginx.service: Unit not found\n",
          "sent_at": "2025-06-17T10:30:00Z",
          "status": "completed",
          "target_all": false,
          "updated_at": "2025-06-17T10:31:00Z"
        },
        "properties": {
          "cancelled_at": {
            "description": "When the command execution was cancelled",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "cluster_name": {
            "description": "The cluster explicitly targeted when this execution was created. Only set when the command used `clusters` targeting against a single cluster — null for `all`, `nodes`, or multi-cluster targeting.",
            "nullable": true,
            "type": "string"
          },
          "command_id": {
            "description": "ID of the command being executed",
            "format": "uuid",
            "type": "string"
          },
          "command_text": {
            "description": "The command text being executed (denormalized for convenience)",
            "example": "echo hello\nls -la",
            "nullable": true,
            "type": "string"
          },
          "completed_at": {
            "description": "When the command execution was completed",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "exit_code": {
            "description": "Final exit code (0 = success, non-zero = failure)",
            "example": 5,
            "nullable": true,
            "type": "integer"
          },
          "expired_at": {
            "description": "The expiration deadline from the parent command (null if no expiration was set)",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "Unique command execution identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the execution was created",
            "format": "date-time",
            "type": "string"
          },
          "node_id": {
            "description": "ID of the target node",
            "format": "uuid",
            "type": "string"
          },
          "output": {
            "description": "Combined output from command execution",
            "example": "$ ABC=value\n$ echo $ABC\nvalue\n$ systemctl restart nginx\nFailed to restart nginx.service: Unit not found\n",
            "nullable": true,
            "type": "string"
          },
          "sent_at": {
            "description": "When the command was sent to the agent",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "description": "Current execution status",
            "enum": ["pending", "sent", "completed", "cancelled", "expired"],
            "type": "string"
          },
          "target_all": {
            "description": "Whether this execution was created from a system-wide command",
            "type": "boolean"
          },
          "timeout": {
            "description": "Command timeout in milliseconds (null means no timeout)",
            "nullable": true,
            "type": "integer"
          },
          "updated_at": {
            "description": "When the execution was last updated",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "command_id",
          "node_id",
          "target_all",
          "status",
          "inserted_at",
          "updated_at"
        ],
        "title": "CommandExecutionResponse",
        "type": "object"
      },
      "ClusterCreateRequest": {
        "description": "Parameters for creating a new cluster",
        "example": {
          "ipv4_range": "100.64.1.0/24",
          "name": "prod-east",
          "node_limit": 50
        },
        "properties": {
          "ipv4_range": {
            "description": "IPv4 CIDR range (auto-generated if not provided)",
            "example": "100.64.0.0/24",
            "nullable": true,
            "pattern": "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\/\\d{1,2}$",
            "type": "string"
          },
          "name": {
            "description": "Cluster name — primary identifier (max 24 chars, lowercase alphanumeric + hyphens)",
            "example": "prod-east",
            "maxLength": 24,
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          },
          "node_limit": {
            "description": "Maximum nodes allowed in this cluster (null means no limit enforced)",
            "example": 50,
            "minimum": 1,
            "nullable": true,
            "type": "integer"
          }
        },
        "required": ["name"],
        "title": "ClusterCreateRequest",
        "type": "object"
      },
      "CreateAliasRequest": {
        "description": "Parameters for creating a new DNS alias for a node",
        "example": { "name": "web-server" },
        "properties": {
          "name": {
            "description": "Alias name (lowercase alphanumeric with hyphens)",
            "example": "web-server",
            "maxLength": 63,
            "minLength": 1,
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          }
        },
        "required": ["name"],
        "title": "CreateAliasRequest",
        "type": "object"
      },
      "WebhookPaginatedResponse": {
        "description": "Paginated list of webhooks with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/Webhook" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "WebhookPaginatedResponse",
        "type": "object"
      },
      "CommandSingleResponse": {
        "description": "Single command response",
        "properties": {
          "data": { "$ref": "#/components/schemas/CommandResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "CommandSingleResponse",
        "type": "object"
      },
      "ConflictResponse": {
        "description": "409 Conflict",
        "example": {
          "error": { "code": "conflict", "message": "Resource already exists" },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "conflict", "type": "string" },
              "details": { "nullable": true },
              "message": {
                "example": "Resource already exists",
                "type": "string"
              }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "ConflictResponse",
        "type": "object"
      },
      "BadRequestResponse": {
        "description": "400 Bad Request",
        "example": {
          "error": {
            "code": "bad_request",
            "details": {
              "ipv4_range": ["Invalid format."],
              "name": ["Invalid format. Expected ~r/^[a-z0-9]/"]
            },
            "message": "Invalid request parameters"
          },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "bad_request", "type": "string" },
              "details": { "nullable": true },
              "message": {
                "example": "Invalid request parameters",
                "type": "string"
              }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "BadRequestResponse",
        "type": "object"
      },
      "CommandResponse": {
        "description": "Command information",
        "example": {
          "command_text": "ABC=value\necho $ABC\nsystemctl restart nginx",
          "expired_at": "2025-12-31T23:59:59Z",
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "inserted_at": "2025-06-17T10:30:00Z",
          "targeting": {
            "node_filters": { "status": "healthy" },
            "node_ids": ["01234567-89ab-cdef-0123-456789abcdef"],
            "type": "nodes"
          },
          "timeout": 30000,
          "updated_at": "2025-06-17T10:30:00Z"
        },
        "properties": {
          "command_text": {
            "description": "Multi-line shell script/commands",
            "example": "ABC=value\necho $ABC\nsystemctl restart nginx",
            "type": "string"
          },
          "expired_at": {
            "description": "Deadline after which pending/sent executions are automatically expired (immutable after creation)",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "Unique command identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the command was created",
            "format": "date-time",
            "type": "string"
          },
          "targeting": {
            "description": "Targeting configuration used when creating this command (informational only, not filterable)",
            "example": {
              "node_filters": { "status": "healthy" },
              "node_ids": ["01234567-89ab-cdef-0123-456789abcdef"],
              "type": "nodes"
            },
            "type": "object"
          },
          "timeout": {
            "description": "Command timeout in milliseconds (optional, null means no timeout)",
            "example": 30000,
            "nullable": true,
            "type": "integer"
          },
          "updated_at": {
            "description": "When the command was last updated",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "command_text",
          "targeting",
          "inserted_at",
          "updated_at"
        ],
        "title": "CommandResponse",
        "type": "object"
      },
      "EdgeClusters": {
        "additionalProperties": {
          "additionalProperties": {
            "description": "List of node names in this cluster",
            "items": { "type": "string" },
            "type": "array"
          },
          "description": "Clusters managed by this admin",
          "type": "object"
        },
        "description": "All edge cluster assignments across all admins",
        "example": {
          "admin-k7m3n2p9x4j6": {
            "cluster-p4k7n2m9x3j6": ["node-uuid-x"],
            "cluster-x7j2p9k4m8n3": ["node-uuid-1", "node-uuid-2"]
          },
          "admin-x9j4p2k7m8n3": {
            "cluster-j6m8n3p7k2x4": [],
            "cluster-m3n9p2k8x7j4": ["node-uuid-3"]
          }
        },
        "title": "EdgeClusters",
        "type": "object"
      },
      "EnrollmentKeyUpdateRequest": {
        "description": "Parameters for updating an enrollment key. Only fields that are present in the request body are updated — omitting a field leaves it unchanged.\n\n- `name`: pass a string to set a label, or `null` to clear it.\n- `uses_remaining`: pass a positive integer to set a limit, or `null` to make the key unlimited.\n- `expired_at`: pass a datetime to set expiry, or `null` to remove expiry.\n",
        "example": {
          "expired_at": "2026-12-31T23:59:59Z",
          "name": "prod rollout",
          "uses_remaining": null
        },
        "properties": {
          "expired_at": {
            "description": "Expiry datetime (ISO 8601), or null to remove expiry. Omit to leave unchanged.",
            "example": "2026-12-31T23:59:59Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "description": "Human-readable label for this key, or null to clear. Omit to leave unchanged.",
            "example": "prod rollout",
            "nullable": true,
            "type": "string"
          },
          "uses_remaining": {
            "description": "Positive integer to set a use limit, or null to make the key unlimited. Omit to leave unchanged.",
            "example": 10,
            "minimum": 1,
            "nullable": true,
            "type": "integer"
          }
        },
        "title": "EnrollmentKeyUpdateRequest",
        "type": "object"
      },
      "NodeSummary": {
        "description": "Brief node information within cluster response",
        "example": {
          "id": "abc12345-1234-1234-1234-123456789abc",
          "id_type": "persistent",
          "status": "healthy",
          "vpn_hostname": "node-abc12345-1234-1234-1234-123456789abc.cluster-prod-east.nm.internal"
        },
        "properties": {
          "id": {
            "description": "Node ID",
            "example": "abc12345-1234-1234-1234-123456789abc",
            "type": "string"
          },
          "id_type": {
            "description": "Node ID type",
            "enum": ["persistent", "random"],
            "example": "persistent",
            "type": "string"
          },
          "status": {
            "description": "Node status",
            "enum": ["healthy", "unhealthy", "unreachable"],
            "example": "healthy",
            "type": "string"
          },
          "vpn_hostname": {
            "description": "DNS hostname for this node",
            "example": "node-abc12345-1234-1234-1234-123456789abc.cluster-prod-east.nm.internal",
            "type": "string"
          }
        },
        "required": ["id", "status", "id_type", "vpn_hostname"],
        "title": "NodeSummary",
        "type": "object"
      },
      "NodeSingleResponse": {
        "description": "Single node response",
        "properties": {
          "data": { "$ref": "#/components/schemas/NodeResponse" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["data", "meta"],
        "title": "NodeSingleResponse",
        "type": "object"
      },
      "SshPublicKeyPaginatedResponse": {
        "description": "Paginated list of SSH public keys with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/SshPublicKey" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "SshPublicKeyPaginatedResponse",
        "type": "object"
      },
      "Pagination": {
        "description": "Pagination metadata for list responses",
        "properties": {
          "has_next": {
            "description": "Whether a next page exists",
            "example": true,
            "type": "boolean"
          },
          "has_prev": {
            "description": "Whether a previous page exists",
            "example": false,
            "type": "boolean"
          },
          "next_page": {
            "description": "Next page number, null when on the last page",
            "example": 2,
            "nullable": true,
            "type": "integer"
          },
          "page": {
            "description": "Current page number",
            "example": 1,
            "type": "integer"
          },
          "page_size": {
            "description": "Items per page",
            "example": 20,
            "type": "integer"
          },
          "prev_page": {
            "description": "Previous page number, null when on the first page",
            "nullable": true,
            "type": "integer"
          },
          "total_count": {
            "description": "Total number of items",
            "example": 150,
            "type": "integer"
          },
          "total_pages": {
            "description": "Total number of pages",
            "example": 8,
            "type": "integer"
          }
        },
        "required": [
          "page",
          "page_size",
          "total_count",
          "total_pages",
          "has_next",
          "has_prev",
          "next_page",
          "prev_page"
        ],
        "title": "Pagination",
        "type": "object"
      },
      "Admin": {
        "description": "This admin's identity, configuration, and derived capacity numbers",
        "example": {
          "admin_cluster_name": "admin-cluster-1",
          "admin_peer_count": 1,
          "edge_node_capacity": 249,
          "erlang_node_name": "admin@admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal",
          "id": "k7m3n2p9x4j6",
          "last_computed_at": "2025-01-15T12:00:00Z",
          "max_wireguard_peers": 250,
          "name": "admin-k7m3n2p9x4j6",
          "netmaker_host_id": "95e2707e-d11f-4551-bdd4-4ab2ab917505",
          "vpn_hostname": "admin-k7m3n2p9x4j6.admin-cluster-1.nm.internal"
        },
        "properties": {
          "admin_cluster_name": {
            "description": "Name of the admin cluster this admin belongs to",
            "type": "string"
          },
          "admin_peer_count": {
            "description": "Number of other admins this admin meshes with (= total_admins - 1). Derived from current topology.",
            "type": "integer"
          },
          "edge_node_capacity": {
            "description": "Maximum edge nodes this admin can own. Derived: max_wireguard_peers - admin_peer_count. The cluster-sharding algorithm consumes this value.",
            "type": "integer"
          },
          "erlang_node_name": {
            "description": "Erlang distribution node name",
            "type": "string"
          },
          "id": {
            "description": "Admin ID (e.g., k7m3n2p9x4j6)",
            "type": "string"
          },
          "last_computed_at": {
            "description": "Last time metadata was computed",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "max_wireguard_peers": {
            "description": "Operator-configured WireGuard peer budget for this admin. Counts both admin-mesh peers and edge-node peers.",
            "type": "integer"
          },
          "name": {
            "description": "Admin name (e.g., admin-k7m3n2p9x4j6)",
            "type": "string"
          },
          "netmaker_host_id": {
            "description": "Netmaker host ID for this admin (UUID format)",
            "type": "string"
          },
          "vpn_hostname": {
            "description": "DNS hostname for this admin",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "max_wireguard_peers",
          "admin_peer_count",
          "edge_node_capacity",
          "erlang_node_name",
          "vpn_hostname",
          "admin_cluster_name",
          "netmaker_host_id"
        ],
        "title": "Admin",
        "type": "object"
      },
      "SelfUpdateRequestPaginatedResponse": {
        "description": "Paginated list of self-update requests",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/SelfUpdateRequestResponse"
            },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "SelfUpdateRequestPaginatedResponse",
        "type": "object"
      },
      "ClusterResponse": {
        "description": "Edge cluster information",
        "example": {
          "id": "abc12345-1234-1234-1234-123456789abc",
          "inserted_at": "2025-06-09T08:00:00Z",
          "ipv4_range": "100.64.0.0/24",
          "name": "prod-east",
          "network_name": "cluster-prod-east",
          "node_count": 2,
          "node_limit": 50,
          "nodes": [
            {
              "id": "abc12345-1234-1234-1234-123456789abc",
              "id_type": "persistent",
              "status": "healthy",
              "vpn_hostname": "node-abc12345-1234-1234-1234-123456789abc.cluster-prod-east.nm.internal"
            },
            {
              "id": "def67890-5678-5678-5678-567890abcdef",
              "id_type": "persistent",
              "status": "healthy",
              "vpn_hostname": "node-def67890-5678-5678-5678-567890abcdef.cluster-prod-east.nm.internal"
            }
          ],
          "updated_at": "2025-06-09T08:00:00Z",
          "vpn_domain": "cluster-prod-east.nm.internal"
        },
        "properties": {
          "id": {
            "description": "Unique cluster identifier (UUID for database compatibility, use name for API operations)",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the cluster was created",
            "format": "date-time",
            "type": "string"
          },
          "ipv4_range": {
            "description": "IPv4 CIDR range for this cluster",
            "example": "100.64.0.0/24",
            "type": "string"
          },
          "name": {
            "description": "Cluster name - primary identifier used in API operations (max 24 chars, alphanumeric with hyphens)",
            "example": "prod-east",
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          },
          "network_name": {
            "description": "Netmaker network name",
            "example": "cluster-prod-east",
            "type": "string"
          },
          "node_count": {
            "description": "Number of nodes in this cluster",
            "type": "integer"
          },
          "node_limit": {
            "description": "Maximum number of nodes allowed in this cluster (null means no limit enforced)",
            "nullable": true,
            "type": "integer"
          },
          "nodes": {
            "description": "Summary of nodes in this cluster",
            "items": { "$ref": "#/components/schemas/NodeSummary" },
            "type": "array"
          },
          "updated_at": {
            "description": "When the cluster was last updated",
            "format": "date-time",
            "type": "string"
          },
          "vpn_domain": {
            "description": "DNS domain suffix for nodes in this cluster",
            "example": "cluster-prod-east.nm.internal",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "ipv4_range",
          "node_limit",
          "node_count",
          "nodes",
          "network_name",
          "vpn_domain",
          "inserted_at",
          "updated_at"
        ],
        "title": "ClusterResponse",
        "type": "object"
      },
      "AliasResponse": {
        "description": "Node alias with custom DNS entry",
        "example": {
          "cluster_name": "prod",
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "inserted_at": "2024-01-15T10:30:00Z",
          "name": "web-server",
          "node_id": "01234567-89ab-cdef-0123-456789abcdef",
          "updated_at": "2024-01-15T10:30:00Z",
          "vpn_hostname": "node-web-server.cluster-prod.nm.internal"
        },
        "properties": {
          "cluster_name": {
            "description": "Name of the cluster",
            "example": "prod-east",
            "type": "string"
          },
          "id": {
            "description": "Unique alias identifier",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "Timestamp when the alias was created",
            "format": "date-time",
            "type": "string"
          },
          "name": {
            "description": "Alias name (used in DNS)",
            "example": "web-server",
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          },
          "node_id": {
            "description": "ID of the node this alias belongs to",
            "format": "uuid",
            "type": "string"
          },
          "updated_at": {
            "description": "Timestamp when the alias was last updated",
            "format": "date-time",
            "type": "string"
          },
          "vpn_hostname": {
            "description": "Full DNS hostname (FQDN)",
            "example": "node-web-server.cluster-prod.nm.internal",
            "type": "string"
          }
        },
        "required": ["id", "name", "vpn_hostname", "node_id", "cluster_name"],
        "title": "AliasResponse",
        "type": "object"
      },
      "EnrollmentKeyResponse": {
        "description": "Enrollment key information",
        "example": {
          "cluster_name": "prod-east",
          "expired_at": "2026-12-31T23:59:59Z",
          "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "inserted_at": "2025-06-09T08:00:00Z",
          "key": "eyJzZXJ2ZXIiOiJodHRwczovL25ldG1ha2VyLmV4YW1wbGUuY29tIiwia2V5IjoiYWJjMTIzIn0=",
          "last_used_at": null,
          "name": "prod rollout",
          "updated_at": "2025-06-09T08:00:00Z",
          "uses_remaining": 5
        },
        "properties": {
          "cluster_name": {
            "description": "Cluster this key belongs to",
            "type": "string"
          },
          "expired_at": {
            "description": "Expiry datetime (ISO 8601). null means never expires.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "Enrollment key ID",
            "format": "uuid",
            "type": "string"
          },
          "inserted_at": {
            "description": "When the enrollment key was created",
            "format": "date-time",
            "type": "string"
          },
          "key": {
            "description": "Enrollment key blob (base64 JSON). Set as ENROLLMENT_KEY on the agent.",
            "type": "string"
          },
          "last_used_at": {
            "description": "When the key was last used. null if unused.",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "description": "Optional human-readable label for this key (display only). null if unset.",
            "nullable": true,
            "type": "string"
          },
          "updated_at": {
            "description": "When the enrollment key was last updated",
            "format": "date-time",
            "type": "string"
          },
          "uses_remaining": {
            "description": "Remaining uses. null means unlimited.",
            "nullable": true,
            "type": "integer"
          }
        },
        "required": [
          "id",
          "cluster_name",
          "key",
          "uses_remaining",
          "inserted_at",
          "updated_at"
        ],
        "title": "EnrollmentKeyResponse",
        "type": "object"
      },
      "ForbiddenResponse": {
        "description": "403 Forbidden",
        "example": {
          "error": {
            "code": "forbidden",
            "message": "Insufficient permissions"
          },
          "meta": {
            "request_id": "550e8400-e29b-41d4-a716-446655440000",
            "timestamp": "2026-04-15T10:00:00.000Z"
          }
        },
        "properties": {
          "error": {
            "properties": {
              "code": { "example": "forbidden", "type": "string" },
              "details": { "nullable": true },
              "message": {
                "example": "Insufficient permissions",
                "type": "string"
              }
            },
            "required": ["code", "message"],
            "type": "object"
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        },
        "required": ["error", "meta"],
        "title": "ForbiddenResponse",
        "type": "object"
      },
      "NodeResponse": {
        "description": "Edge node information",
        "example": {
          "api_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
          "cluster_name": "prod-east",
          "host_metrics_port": 49100,
          "http_port": 44000,
          "http_proxy_port": 44880,
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "id_type": "persistent",
          "inserted_at": "2025-06-09T08:00:00Z",
          "last_seen_at": "2025-06-09T08:20:00Z",
          "mdns_hostname": "node-01234567-89ab-cdef-0123-456789abcdef.local",
          "netmaker_host_id": "def67890-5678-5678-5678-567890abcdef",
          "node_name": "node-01234567-89ab-cdef-0123-456789abcdef",
          "proxy_password": "securepassword123",
          "self_update_enabled": false,
          "socks5_proxy_port": 44180,
          "ssh_port": 42222,
          "status": "healthy",
          "updated_at": "2025-06-09T08:20:00Z",
          "version": "0.1.0",
          "vpn_hostname": "node-01234567-89ab-cdef-0123-456789abcdef.cluster-prod-east.nm.internal",
          "wireguard_metrics_port": 49586
        },
        "properties": {
          "api_token": {
            "description": "API token for agent authentication",
            "type": "string"
          },
          "cluster_name": {
            "description": "Name of the cluster this node belongs to",
            "example": "prod-east",
            "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          },
          "host_metrics_port": {
            "description": "Host metrics port (Node Exporter)",
            "type": "integer"
          },
          "http_port": { "description": "HTTP API port", "type": "integer" },
          "http_proxy_port": {
            "description": "HTTP proxy port",
            "type": "integer"
          },
          "id": {
            "description": "Unique node identifier",
            "format": "uuid",
            "type": "string"
          },
          "id_type": {
            "description": "Type of node identifier (persistent or random)",
            "enum": ["persistent", "random"],
            "type": "string"
          },
          "inserted_at": {
            "description": "When the node was created",
            "format": "date-time",
            "type": "string"
          },
          "last_seen_at": {
            "description": "Last heartbeat timestamp from the node",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "mdns_hostname": {
            "description": "mDNS hostname — resolvable on the local LAN via multicast DNS",
            "example": "node-01234567-89ab-cdef-0123-456789abcdef.local",
            "type": "string"
          },
          "netmaker_host_id": {
            "description": "Netmaker Host UUID for API operations",
            "format": "uuid",
            "nullable": true,
            "type": "string"
          },
          "node_name": {
            "description": "Human-readable node name (derived from ID)",
            "example": "node-01234567-89ab-cdef-0123-456789abcdef",
            "type": "string"
          },
          "proxy_password": {
            "description": "Password for proxy authentication (username is always '_')",
            "type": "string"
          },
          "self_update_enabled": {
            "description": "Whether self-updates are enabled",
            "type": "boolean"
          },
          "socks5_proxy_port": {
            "description": "SOCKS5 proxy port",
            "type": "integer"
          },
          "ssh_port": { "description": "SSH port", "type": "integer" },
          "status": {
            "description": "Current node status",
            "enum": ["healthy", "unhealthy", "unreachable"],
            "type": "string"
          },
          "updated_at": {
            "description": "When the node was last updated",
            "format": "date-time",
            "type": "string"
          },
          "version": {
            "description": "Agent version",
            "nullable": true,
            "type": "string"
          },
          "vpn_hostname": {
            "description": "DNS hostname for this node",
            "example": "node-01234567-89ab-cdef-0123-456789abcdef.cluster-abc.nm.internal",
            "type": "string"
          },
          "wireguard_metrics_port": {
            "description": "WireGuard metrics port (WireGuard Exporter)",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "cluster_name",
          "id_type",
          "http_port",
          "ssh_port",
          "host_metrics_port",
          "wireguard_metrics_port",
          "http_proxy_port",
          "socks5_proxy_port",
          "api_token",
          "proxy_password",
          "inserted_at",
          "updated_at"
        ],
        "title": "NodeResponse",
        "type": "object"
      },
      "SshUsernamePaginatedResponse": {
        "description": "Paginated list of SSH usernames with filtering and sorting metadata",
        "properties": {
          "data": {
            "items": { "$ref": "#/components/schemas/SshUsername" },
            "type": "array"
          },
          "meta": { "$ref": "#/components/schemas/PaginatedMeta" }
        },
        "required": ["data", "meta"],
        "title": "SshUsernamePaginatedResponse",
        "type": "object"
      }
    },
    "securitySchemes": {
      "apiKey": {
        "bearerFormat": "opaque",
        "description": "API key for REST API access (API_KEY or MASTER_KEY)",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "REST API for Edge Admin — the orchestration server for Edge Core.\n\nMost endpoints require an API key (`Authorization: Bearer <API_KEY>`\nor `<MASTER_KEY>`). A small set of bootstrap endpoints is intentionally\npublic — see the per-operation `security` field in the spec; an empty\narray there means the endpoint is unauthenticated by design (e.g.\npublic enrollment-key creation).\n\n**Explore:**\n- [Swagger UI](/swaggerui) — interactive API explorer\n- [ReDoc](/redoc) — reference documentation\n- [Raw spec](/api/openapi) — OpenAPI JSON\n\n**Event streaming:** Edge Admin also publishes lifecycle events to a message broker.\nSee the [AsyncAPI spec](/asyncdoc) or [download it](/api/asyncapi).\n",
    "title": "Edge Admin OpenAPI",
    "version": "0.2.0"
  },
  "openapi": "3.0.0",
  "paths": {
    "/api/v1/admins/me": {
      "get": {
        "callbacks": {},
        "description": "Returns the current admin's identity and configuration from metadata.",
        "operationId": "EdgeAdminWeb.Controllers.Admins.AdminController.show",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AdminResponse" }
              }
            },
            "description": "Admin identity"
          }
        },
        "summary": "Get the current admin",
        "tags": ["Admins.Metadata"]
      }
    },
    "/api/v1/admins/my_admin_cluster": {
      "get": {
        "callbacks": {},
        "description": "Returns metadata and peer topology for the admin cluster this admin belongs to.",
        "operationId": "EdgeAdminWeb.Controllers.Admins.AdminClusterController.show",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyAdminClusterResponse"
                }
              }
            },
            "description": "Admin cluster topology"
          }
        },
        "summary": "Get this admin's admin cluster",
        "tags": ["Admins.Metadata"]
      }
    },
    "/api/v1/admins/admin_clusters": {
      "get": {
        "callbacks": {},
        "description": "Lists every admin cluster Netmaker knows about, with each cluster's admins.\nIncludes admins this instance is not a member of (cross-cluster visibility)\nand may include stale entries.\n",
        "operationId": "EdgeAdminWeb.Controllers.Admins.AdminClustersController.index",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminClustersResponse"
                }
              }
            },
            "description": "Admin clusters"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Netmaker unavailable"
          }
        },
        "summary": "List all admin clusters from Netmaker",
        "tags": ["Admins.Metadata"]
      }
    },
    "/api/v1/admins/edge_clusters": {
      "get": {
        "callbacks": {},
        "description": "Returns all edge cluster assignments across all admins from metadata",
        "operationId": "EdgeAdminWeb.Controllers.Admins.EdgeClustersController.index",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EdgeClustersResponse"
                }
              }
            },
            "description": "Edge clusters"
          }
        },
        "summary": "Get all edge cluster assignments",
        "tags": ["Admins.Metadata"]
      }
    },
    "/api/v1/admins/orphaned_clusters": {
      "get": {
        "callbacks": {},
        "description": "Returns all clusters that could not be assigned to any admin due to capacity constraints. Empty map when system is not degraded.",
        "operationId": "EdgeAdminWeb.Controllers.Admins.OrphanedClustersController.index",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrphanedClustersResponse"
                }
              }
            },
            "description": "Orphaned clusters"
          }
        },
        "summary": "Get all orphaned clusters",
        "tags": ["Admins.Metadata"]
      }
    },
    "/api/v1/admins/me/metrics": {
      "get": {
        "callbacks": {},
        "description": "Returns application-level metrics from the edge_admin PromEx:\n- Application: uptime, BEAM stats (processes, memory, atoms, ETS tables)\n- Metadata: degraded status, orphaned/assigned clusters\n- Membership: step counts and full sequence completions\n- Discovery: peer discovery scan, DNS resolution, and connection counts\n- Nodes: health check statistics\n- Commands: delivery runs, per-execution delivery, completions, expirations\n- SSH: credential verification attempts and failures\n- Reconciliation: cluster Netmaker↔DB sync runs and errors\n- Self-updates: request processing completions\n- Gateways: connection events, active count, scrape totals\n- Event broker: publish/enqueue counters (zeroed when broker is disabled)\n- Oban: job queue states (available, scheduled, executing, etc.)\n",
        "operationId": "EdgeAdminWeb.Controllers.Metrics.AdminMetricsController.show",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminMetricsResponse"
                }
              }
            },
            "description": "Admin metrics retrieved successfully"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Metrics unavailable"
          }
        },
        "summary": "Get metrics for this admin",
        "tags": ["Admins.Metrics"]
      }
    },
    "/api/v1/clusters": {
      "post": {
        "callbacks": {},
        "description": "Create a new edge cluster with optional IP range. The name `default` is reserved (used as a URL keyword on convenience routes) and will be rejected with HTTP 422.\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.ClusterController.create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ClusterCreateRequest" }
            }
          },
          "description": "Cluster creation parameters",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterSingleResponse"
                }
              }
            },
            "description": "Cluster created successfully"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Cluster name already exists, or IP range conflicts with an existing cluster"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Netmaker unavailable or in degraded mode"
          }
        },
        "summary": "Create a new cluster",
        "tags": ["Nodes.Cluster"]
      },
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of all edge clusters with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.ClusterController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,name",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by cluster name (exact match or wildcard: prod*, *tion, *rod*)",
            "in": "query",
            "name": "name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by IPv4 range (exact match or wildcard)",
            "in": "query",
            "name": "ipv4_range",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by exact node limit",
            "in": "query",
            "name": "node_limit",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter by whether a node limit is set: true returns clusters with a limit, false returns unlimited",
            "in": "query",
            "name": "has_node_limit",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by minimum node_count",
            "in": "query",
            "name": "node_count__gte",
            "required": false,
            "schema": { "minimum": 0, "type": "integer" }
          },
          {
            "description": "Filter by maximum node_count",
            "in": "query",
            "name": "node_count__lte",
            "required": false,
            "schema": { "minimum": 0, "type": "integer" }
          },
          {
            "description": "Filter by minimum node_limit",
            "in": "query",
            "name": "node_limit__gte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter by maximum node_limit",
            "in": "query",
            "name": "node_limit__lte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterPaginatedResponse"
                }
              }
            },
            "description": "Paginated cluster list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List all clusters",
        "tags": ["Nodes.Cluster"]
      }
    },
    "/api/v1/clusters/{name}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific cluster by name",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.ClusterController.show",
        "parameters": [
          {
            "description": "Cluster name",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "maxLength": 24,
              "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterSingleResponse"
                }
              }
            },
            "description": "Cluster details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Cluster not found"
          }
        },
        "summary": "Get a specific cluster",
        "tags": ["Nodes.Cluster"]
      },
      "patch": {
        "callbacks": {},
        "description": "Update a cluster's settings. Only provided fields are changed. Pass null to unset a nullable field.\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.ClusterController.update",
        "parameters": [
          {
            "description": "Cluster name",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "maxLength": 24,
              "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ClusterUpdateRequest" }
            }
          },
          "description": "Cluster update parameters",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClusterSingleResponse"
                }
              }
            },
            "description": "Cluster updated successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Cluster not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error or node_limit below current node count"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Netmaker unavailable or in degraded mode"
          }
        },
        "summary": "Update a cluster",
        "tags": ["Nodes.Cluster"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete an empty cluster (must have no nodes).\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.ClusterController.delete",
        "parameters": [
          {
            "description": "Cluster name",
            "in": "path",
            "name": "name",
            "required": true,
            "schema": {
              "maxLength": 24,
              "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Cluster deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Cluster not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Cannot delete cluster with nodes"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Netmaker unavailable or in degraded mode"
          }
        },
        "summary": "Delete a cluster",
        "tags": ["Nodes.Cluster"]
      }
    },
    "/api/v1/clusters/default/enrollment_keys/public": {
      "post": {
        "callbacks": {},
        "description": "Public endpoint (no authentication required). Only enabled when both\nPUBLIC_ENROLLMENT_KEY_ENABLED=true and DEFAULT_CLUSTER_NAME are configured.\n\n**Note:** This endpoint is unavailable during degraded mode (503).\n",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.create_for_public",
        "parameters": [],
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeySingleResponse"
                }
              }
            },
            "description": "Enrollment key created"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenResponse" }
              }
            },
            "description": "Public enrollment disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Default cluster not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Get a public enrollment key for the default cluster",
        "tags": ["Nodes.EnrollmentKey"]
      }
    },
    "/api/v1/clusters/default/enrollment_keys": {
      "post": {
        "callbacks": {},
        "description": "Convenience endpoint for the default cluster (configured via DEFAULT_CLUSTER_NAME env).\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.create_for_default",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollmentKeyCreateRequest"
              }
            }
          },
          "description": "Enrollment key parameters",
          "required": false
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeySingleResponse"
                }
              }
            },
            "description": "Enrollment key created"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ForbiddenResponse" }
              }
            },
            "description": "Default cluster not configured"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Default cluster not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Create an enrollment key for the default cluster",
        "tags": ["Nodes.EnrollmentKey"]
      }
    },
    "/api/v1/clusters/{cluster_name}/enrollment_keys": {
      "post": {
        "callbacks": {},
        "description": "Create a new enrollment key for an edge cluster. The returned `key` blob must be set as the `ENROLLMENT_KEY`\nenvironment variable on the agent to allow it to join the cluster's VPN network.\n\nKeys can be limited by use count (`uses_remaining`) or by expiry time (`expired_at`). Omit both for a single-use key with no expiry.\n\n**Note:** This endpoint is unavailable during degraded mode (503).\n",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.create",
        "parameters": [
          {
            "description": "Cluster name",
            "in": "path",
            "name": "cluster_name",
            "required": true,
            "schema": {
              "maxLength": 24,
              "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollmentKeyCreateRequest"
              }
            }
          },
          "description": "Enrollment key parameters",
          "required": false
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeySingleResponse"
                }
              }
            },
            "description": "Enrollment key created"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Cluster not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Create an enrollment key for a cluster",
        "tags": ["Nodes.EnrollmentKey"]
      }
    },
    "/api/v1/enrollment_keys": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of enrollment keys with filtering and sorting.",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by cluster name (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by enrollment key name (case-insensitive substring or wildcard: prod*, *rollout, etc.)",
            "in": "query",
            "name": "name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by whether the key has a name set: true returns keys with a human-readable label, false returns unlabeled keys (e.g. those issued by the public/default-cluster endpoint)",
            "in": "query",
            "name": "has_name",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by exact key value",
            "in": "query",
            "name": "key",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by exact uses_remaining (positive integer; use is_unlimited=true to find unlimited keys)",
            "in": "query",
            "name": "uses_remaining",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter by whether the key has unlimited uses: true returns unlimited keys (uses_remaining is null), false returns keys with a finite use count",
            "in": "query",
            "name": "is_unlimited",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by whether the key is exhausted: true returns keys with uses_remaining == 0, false returns keys with uses remaining",
            "in": "query",
            "name": "is_spent",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by whether the key is expired: true returns keys where expired_at is in the past, false returns active keys (including those with no expiry)",
            "in": "query",
            "name": "is_expired",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by whether the key has never been used: true returns keys where last_used_at is null, false returns keys that have been used at least once",
            "in": "query",
            "name": "is_never_used",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by whether the key has an expiry set: true returns keys with expired_at present, false returns keys with no expiry (unlimited lifetime)",
            "in": "query",
            "name": "has_expiry",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by minimum uses_remaining",
            "in": "query",
            "name": "uses_remaining__gte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter by maximum uses_remaining",
            "in": "query",
            "name": "uses_remaining__lte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter records where expired_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "expired_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where expired_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "expired_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where last_used_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "last_used_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where last_used_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "last_used_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeyPaginatedResponse"
                }
              }
            },
            "description": "Paginated enrollment key list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List enrollment keys",
        "tags": ["Nodes.EnrollmentKey"]
      }
    },
    "/api/v1/enrollment_keys/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific enrollment key by ID",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.show",
        "parameters": [
          {
            "description": "Enrollment key ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeySingleResponse"
                }
              }
            },
            "description": "Enrollment key"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Not found"
          }
        },
        "summary": "Get an enrollment key",
        "tags": ["Nodes.EnrollmentKey"]
      },
      "patch": {
        "callbacks": {},
        "description": "Update expired_at or uses_remaining. Pass null to unset a nullable field.\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.update",
        "parameters": [
          {
            "description": "Enrollment key ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnrollmentKeyUpdateRequest"
              }
            }
          },
          "description": "Update parameters",
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnrollmentKeySingleResponse"
                }
              }
            },
            "description": "Updated enrollment key"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Update an enrollment key",
        "tags": ["Nodes.EnrollmentKey"]
      },
      "delete": {
        "callbacks": {},
        "description": "Permanently deletes an enrollment key. **Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.EnrollmentKeyController.delete",
        "parameters": [
          {
            "description": "Enrollment key ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Enrollment key deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Delete an enrollment key",
        "tags": ["Nodes.EnrollmentKey"]
      }
    },
    "/api/v1/nodes": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of all registered edge nodes with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.NodeController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,status",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by node ID type",
            "in": "query",
            "name": "id_type",
            "required": false,
            "schema": { "enum": ["persistent", "random"], "type": "string" }
          },
          {
            "description": "Filter by node status",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": ["healthy", "unhealthy", "unreachable"],
              "type": "string"
            }
          },
          {
            "description": "Filter by agent version (exact match or wildcard: 1.0.0, 1.*, etc.)",
            "in": "query",
            "name": "version",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by self-update enabled status",
            "in": "query",
            "name": "self_update_enabled",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by cluster name (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter records where last_seen_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "last_seen_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where last_seen_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "last_seen_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodePaginatedResponse"
                }
              }
            },
            "description": "Paginated list of nodes"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List all nodes",
        "tags": ["Nodes.Node"]
      }
    },
    "/api/v1/nodes/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific node by ID",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.NodeController.show",
        "parameters": [
          {
            "description": "Node ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NodeSingleResponse" }
              }
            },
            "description": "Node details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          }
        },
        "summary": "Get a specific node",
        "tags": ["Nodes.Node"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete a node. Removes the Netmaker host first, then the DB row. Cascade: `ssh_usernames` (and their `ssh_public_keys`) and `aliases` are deleted; `command_executions` are kept with `node_id` set to NULL for history.\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.NodeController.delete",
        "parameters": [
          {
            "description": "Node ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Node deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Delete a node",
        "tags": ["Nodes.Node"]
      }
    },
    "/api/v1/nodes/{id}/change_cluster": {
      "patch": {
        "callbacks": {},
        "description": "Move a node to a different cluster. Performs cluster migration via Netmaker (best-effort, reconciliation worker handles failures).\n\n**Note:** This endpoint is unavailable during degraded mode (503).",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.NodeController.change_cluster",
        "parameters": [
          {
            "description": "Node ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChangeClusterRequest" }
            }
          },
          "description": "Cluster change parameters",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NodeSingleResponse" }
              }
            },
            "description": "Node cluster changed successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Node already in the target cluster, or target cluster has reached its node limit"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Change a node's cluster",
        "tags": ["Nodes.Node"]
      }
    },
    "/api/v1/nodes/{node_id}/aliases": {
      "post": {
        "callbacks": {},
        "description": "Creates a new alias and corresponding DNS entry for the specified node",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.AliasController.create",
        "parameters": [
          {
            "description": "Node ID",
            "in": "path",
            "name": "node_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateAliasRequest" }
            }
          },
          "description": "Alias creation parameters",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AliasSingleResponse" }
              }
            },
            "description": "Alias created successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Alias name already exists in this cluster, or node is not yet enrolled/has no IP in the VPN network"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Create a new alias for a node",
        "tags": ["Nodes.Alias"]
      }
    },
    "/api/v1/aliases": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of node aliases with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.AliasController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,name",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by alias name (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by node ID (exact match UUID)",
            "in": "query",
            "name": "node_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Filter by cluster name (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AliasPaginatedResponse"
                }
              }
            },
            "description": "Paginated list of aliases"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List all aliases",
        "tags": ["Nodes.Alias"]
      }
    },
    "/api/v1/aliases/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific alias by ID",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.AliasController.show",
        "parameters": [
          {
            "description": "Alias ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AliasSingleResponse" }
              }
            },
            "description": "Alias details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Alias not found"
          }
        },
        "summary": "Get a specific alias",
        "tags": ["Nodes.Alias"]
      },
      "delete": {
        "callbacks": {},
        "description": "Deletes an alias and its corresponding DNS entry",
        "operationId": "EdgeAdminWeb.Controllers.Nodes.AliasController.delete",
        "parameters": [
          {
            "description": "Alias ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Alias deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Alias not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Service Unavailable"
          }
        },
        "summary": "Delete an alias",
        "tags": ["Nodes.Alias"]
      }
    },
    "/api/v1/nodes/{node_id}/metrics": {
      "get": {
        "callbacks": {},
        "description": "Returns aggregated metrics from all available sources for a node:\n- Host metrics (Node Exporter): CPU, memory, disk, uptime\n- Agent metrics (agent PromEx): BEAM stats, commands, proxy, SSH, VPN, health check, Oban\n\nProvides a complete view of node health and performance in a single request.\nUses best-effort fetching - if one source fails, others are still returned.\n",
        "operationId": "EdgeAdminWeb.Controllers.Metrics.NodeMetricsController.show_unified",
        "parameters": [
          {
            "description": "Node UUID",
            "in": "path",
            "name": "node_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnifiedMetricsResponse"
                }
              }
            },
            "description": "Unified metrics retrieved successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Metrics unavailable"
          }
        },
        "summary": "Get unified metrics for a node",
        "tags": ["Nodes.Metrics"]
      }
    },
    "/api/v1/nodes/{node_id}/metrics/host": {
      "get": {
        "callbacks": {},
        "description": "Returns host-level system metrics from Node Exporter:\n- CPU: cores, load averages\n- Memory: usage, total/available/used in bytes and GB\n- Disk: usage, total/available/used for root filesystem\n- Uptime: seconds and human-readable format\n",
        "operationId": "EdgeAdminWeb.Controllers.Metrics.NodeMetricsController.show_host",
        "parameters": [
          {
            "description": "Node UUID",
            "in": "path",
            "name": "node_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HostMetricsResponse" }
              }
            },
            "description": "Host metrics retrieved successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Metrics unavailable"
          }
        },
        "summary": "Get host metrics for a node",
        "tags": ["Nodes.Metrics"]
      }
    },
    "/api/v1/nodes/{node_id}/metrics/agent": {
      "get": {
        "callbacks": {},
        "description": "Returns application-level metrics from the edge_agent PromEx:\n- Application: uptime, BEAM stats (processes, memory breakdown)\n- Commands: sync/enqueue/complete/report statistics\n- Discovery: admin discovery scan metrics\n- Proxy: HTTP and SOCKS5 connection and blocked-request statistics\n- SSH: authentication attempts and connection count\n- VPN: config pull count (daily backstop for DNS recovery)\n- Health Check: fallback health report count (only non-zero when VPN is down)\n- Oban: job queue states (available, executing, completed, etc.)\n",
        "operationId": "EdgeAdminWeb.Controllers.Metrics.NodeMetricsController.show_agent",
        "parameters": [
          {
            "description": "Node UUID",
            "in": "path",
            "name": "node_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentMetricsResponse"
                }
              }
            },
            "description": "Agent metrics retrieved successfully"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Metrics unavailable"
          }
        },
        "summary": "Get agent metrics for a node",
        "tags": ["Nodes.Metrics"]
      }
    },
    "/api/v1/commands": {
      "post": {
        "callbacks": {},
        "description": "Create a new command for execution on nodes using flexible targeting options.\n\nTargeting types:\n- 'all': Target all nodes (with optional filters)\n- 'nodes': Target specific nodes by IDs (with optional filters)\n- 'clusters': Target specific clusters by names (with optional filters)\n\nNode and cluster filters can be applied to further refine targeting.\n",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandController.create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CommandCreateRequest" }
            }
          },
          "description": "Command creation parameters",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommandSingleResponse"
                }
              }
            },
            "description": "Command created successfully"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          }
        },
        "summary": "Create a new command",
        "tags": ["Commands.Command"]
      },
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of all commands with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by command text (exact match or wildcard: ls*, *docker*, etc.)",
            "in": "query",
            "name": "command_text",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by whether a timeout is set: true returns commands with a timeout, false returns commands without",
            "in": "query",
            "name": "has_timeout",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by whether an expiration is set: true returns commands with expired_at, false returns commands without",
            "in": "query",
            "name": "has_expired_at",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter commands with timeout greater than or equal to this value (milliseconds)",
            "in": "query",
            "name": "timeout__gte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter commands with timeout less than or equal to this value (milliseconds)",
            "in": "query",
            "name": "timeout__lte",
            "required": false,
            "schema": { "minimum": 1, "type": "integer" }
          },
          {
            "description": "Filter records where expired_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "expired_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where expired_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "expired_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommandPaginatedResponse"
                }
              }
            },
            "description": "Paginated list of commands"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List all commands",
        "tags": ["Commands.Command"]
      }
    },
    "/api/v1/commands/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific command by ID",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandController.show",
        "parameters": [
          {
            "description": "Command ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommandSingleResponse"
                }
              }
            },
            "description": "Command details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Command not found"
          }
        },
        "summary": "Get a specific command",
        "tags": ["Commands.Command"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete a command and all its related command executions (cascaded deletion).\n\nOnly commands where ALL executions are completed can be deleted.\nAttempting to delete a command with pending or sent executions will return 409.\n",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandController.delete",
        "parameters": [
          {
            "description": "Command ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Command deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Command not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Cannot delete command with non-completed executions"
          }
        },
        "summary": "Delete a command",
        "tags": ["Commands.Command"]
      }
    },
    "/api/v1/command_executions": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of command executions with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandExecutionController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,status",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by execution status",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": ["pending", "sent", "completed", "cancelled", "expired"],
              "type": "string"
            }
          },
          {
            "description": "Filter by target_all flag",
            "in": "query",
            "name": "target_all",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by exact exit code",
            "in": "query",
            "name": "exit_code",
            "required": false,
            "schema": { "minimum": 0, "type": "integer" }
          },
          {
            "description": "Filter by whether output is present: true returns executions with output, false returns executions with no output",
            "in": "query",
            "name": "has_output",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by command ID",
            "in": "query",
            "name": "command_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Filter by node ID",
            "in": "query",
            "name": "node_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Text search in output (exact match or wildcard: *error*, *failed, etc.)",
            "in": "query",
            "name": "output",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by cluster name via node's cluster (exact match or wildcard: prod*, *staging, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by cluster_id presence (true = cluster-wide executions, false = non-cluster-wide)",
            "in": "query",
            "name": "has_cluster",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter executions with exit code greater than or equal to this value (e.g. 1 for all failures)",
            "in": "query",
            "name": "exit_code__gte",
            "required": false,
            "schema": { "minimum": 0, "type": "integer" }
          },
          {
            "description": "Filter executions with exit code less than or equal to this value",
            "in": "query",
            "name": "exit_code__lte",
            "required": false,
            "schema": { "minimum": 0, "type": "integer" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where sent_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "sent_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where sent_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "sent_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where completed_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "completed_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where completed_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "completed_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where cancelled_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "cancelled_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where cancelled_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "cancelled_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommandExecutionPaginatedResponse"
                }
              }
            },
            "description": "Paginated list of command executions"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List command executions",
        "tags": ["Commands.CommandExecution"]
      }
    },
    "/api/v1/command_executions/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific command execution by ID",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandExecutionController.show",
        "parameters": [
          {
            "description": "Command Execution ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommandExecutionSingleResponse"
                }
              }
            },
            "description": "Command execution details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Command execution not found"
          }
        },
        "summary": "Get a specific command execution",
        "tags": ["Commands.CommandExecution"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete a specific command execution.\n\nOnly completed, cancelled, or expired executions can be deleted. Attempting to delete pending or sent executions will return 409.\n",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandExecutionController.delete",
        "parameters": [
          {
            "description": "Command Execution ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Command execution deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Command execution not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Cannot delete non-completed execution"
          }
        },
        "summary": "Delete a command execution",
        "tags": ["Commands.CommandExecution"]
      }
    },
    "/api/v1/command_executions/{id}/cancel": {
      "patch": {
        "callbacks": {},
        "description": "Attempts to cancel a command execution. Behavior depends on current status:\n\n- `pending`: Immediately marked `cancelled` in the database (command never ran).\n- `sent`: Sends cancellation request to agent (best-effort, async). The agent is the source of truth — if it already ran the command, it reports back the real result and the execution is marked `completed`. If the agent honoured the cancellation (exit code 143), it is marked `cancelled`.\n- `completed` / `cancelled`: Returns 409 conflict (already terminal).\n\nReturns 200 if cancellation was initiated. For `sent` executions, poll status to confirm the outcome.\n",
        "operationId": "EdgeAdminWeb.Controllers.Commands.CommandExecutionController.cancel",
        "parameters": [
          {
            "description": "Command Execution ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelExecutionResponse"
                }
              }
            },
            "description": "Cancellation request sent"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Command execution not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Execution not in a cancellable state (already terminal)"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableResponse"
                }
              }
            },
            "description": "Agent unreachable for cancellation request (sent status only)"
          }
        },
        "summary": "Cancel a command execution",
        "tags": ["Commands.CommandExecution"]
      }
    },
    "/api/v1/nodes/{node_id}/ssh_usernames": {
      "post": {
        "callbacks": {},
        "description": "Create a new SSH username for a specific node, optionally with public keys and/or password",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshUsernameController.create",
        "parameters": [
          {
            "description": "Node ID to create SSH username for",
            "in": "path",
            "name": "node_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SshUsernameCreateRequest"
              }
            }
          },
          "description": "SSH username creation data",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshUsernameSingleResponse"
                }
              }
            },
            "description": "SSH username created"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Node not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Username already exists for this node"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          }
        },
        "summary": "Create SSH username",
        "tags": ["Ssh.SshUsername"]
      }
    },
    "/api/v1/ssh_usernames": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of SSH usernames with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshUsernameController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,username",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by username (exact match or wildcard: root*, *admin, etc.)",
            "in": "query",
            "name": "username",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by node ID",
            "in": "query",
            "name": "node_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Filter by whether username has password configured",
            "in": "query",
            "name": "has_password",
            "required": false,
            "schema": { "type": "boolean" }
          },
          {
            "description": "Filter by cluster name via node's cluster (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshUsernamePaginatedResponse"
                }
              }
            },
            "description": "Paginated list of SSH usernames"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List SSH usernames",
        "tags": ["Ssh.SshUsername"]
      }
    },
    "/api/v1/ssh_usernames/{id}": {
      "get": {
        "callbacks": {},
        "description": "Get a specific SSH username by ID",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshUsernameController.show",
        "parameters": [
          {
            "description": "SSH username ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshUsernameSingleResponse"
                }
              }
            },
            "description": "SSH username details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "SSH username not found"
          }
        },
        "summary": "Get SSH username",
        "tags": ["Ssh.SshUsername"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete an SSH username and all associated public keys",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshUsernameController.delete",
        "parameters": [
          {
            "description": "SSH username ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "SSH username deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "SSH username not found"
          }
        },
        "summary": "Delete SSH username",
        "tags": ["Ssh.SshUsername"]
      }
    },
    "/api/v1/ssh_usernames/{ssh_username_id}/ssh_public_keys": {
      "post": {
        "callbacks": {},
        "description": "Create a new SSH public key for a specific SSH username. The key must be in valid OpenSSH format.",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshPublicKeyController.create",
        "parameters": [
          {
            "description": "SSH username ID to create public key for",
            "in": "path",
            "name": "ssh_username_id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SshPublicKeyCreateRequest"
              }
            }
          },
          "description": "SSH public key creation data",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshPublicKeySingleResponse"
                }
              }
            },
            "description": "SSH public key created"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "SSH username not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Key name already exists for this username"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error - invalid key format or unsupported algorithm"
          }
        },
        "summary": "Create SSH public key",
        "tags": ["Ssh.SshPublicKey"]
      }
    },
    "/api/v1/ssh_public_keys": {
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of SSH public keys with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshPublicKeyController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,key_name",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by key name (exact match or wildcard: my-key*, *prod, etc.)",
            "in": "query",
            "name": "key_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by public key content (useful for searching email comments: *@example.com)",
            "in": "query",
            "name": "public_key",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by SSH username ID",
            "in": "query",
            "name": "ssh_username_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Filter by node ID",
            "in": "query",
            "name": "node_id",
            "required": false,
            "schema": { "format": "uuid", "type": "string" }
          },
          {
            "description": "Filter by SSH username (exact match or wildcard: deploy*, *admin, etc.)",
            "in": "query",
            "name": "username",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by cluster name via node's cluster (exact match or wildcard: prod*, *east, etc.)",
            "in": "query",
            "name": "cluster_name",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshPublicKeyPaginatedResponse"
                }
              }
            },
            "description": "Paginated list of SSH public keys"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List SSH public keys",
        "tags": ["Ssh.SshPublicKey"]
      }
    },
    "/api/v1/ssh_public_keys/{id}": {
      "get": {
        "callbacks": {},
        "description": "Get a specific SSH public key by ID",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshPublicKeyController.show",
        "parameters": [
          {
            "description": "SSH public key ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SshPublicKeySingleResponse"
                }
              }
            },
            "description": "SSH public key details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "SSH public key not found"
          }
        },
        "summary": "Get SSH public key",
        "tags": ["Ssh.SshPublicKey"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete an SSH public key",
        "operationId": "EdgeAdminWeb.Controllers.Ssh.SshPublicKeyController.delete",
        "parameters": [
          {
            "description": "SSH public key ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "SSH public key deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "SSH public key not found"
          }
        },
        "summary": "Delete SSH public key",
        "tags": ["Ssh.SshPublicKey"]
      }
    },
    "/api/v1/self_update_requests": {
      "post": {
        "callbacks": {},
        "description": "Create a new self-update request to trigger agent updates.\n\nThe request will be processed asynchronously. Only healthy nodes with self_update_enabled=true will be updated.\n\nTargeting types:\n- 'all': Target all nodes (with optional filters)\n- 'nodes': Target specific nodes by IDs (with optional filters)\n- 'clusters': Target specific clusters by names (with optional filters)\n\nNode and cluster filters can be applied to further refine targeting.\n",
        "operationId": "EdgeAdminWeb.Controllers.SelfUpdates.SelfUpdateRequestController.create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelfUpdateRequestCreateRequest"
              }
            }
          },
          "description": "Self-update request creation parameters",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SelfUpdateRequestSingleResponse"
                }
              }
            },
            "description": "Self-update request created successfully"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          }
        },
        "summary": "Create a new self-update request",
        "tags": ["SelfUpdates.Request"]
      },
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of all self-update requests with filtering and sorting",
        "operationId": "EdgeAdminWeb.Controllers.SelfUpdates.SelfUpdateRequestController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by status",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": ["pending", "processing", "completed"],
              "type": "string"
            }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SelfUpdateRequestPaginatedResponse"
                }
              }
            },
            "description": "Paginated list of self-update requests"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List all self-update requests",
        "tags": ["SelfUpdates.Request"]
      }
    },
    "/api/v1/self_update_requests/{id}": {
      "get": {
        "callbacks": {},
        "description": "Returns details for a specific self-update request by ID",
        "operationId": "EdgeAdminWeb.Controllers.SelfUpdates.SelfUpdateRequestController.show",
        "parameters": [
          {
            "description": "Self-update request ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SelfUpdateRequestSingleResponse"
                }
              }
            },
            "description": "Self-update request details"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Self-update request not found"
          }
        },
        "summary": "Get a specific self-update request",
        "tags": ["SelfUpdates.Request"]
      },
      "delete": {
        "callbacks": {},
        "description": "Delete a self-update request.\n\nOnly completed requests can be deleted.\nAttempting to delete a pending or processing request will return 409.\n",
        "operationId": "EdgeAdminWeb.Controllers.SelfUpdates.SelfUpdateRequestController.delete",
        "parameters": [
          {
            "description": "Self-update request ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": {
            "content": { "": {} },
            "description": "Self-update request deleted successfully"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Self-update request not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ConflictResponse" }
              }
            },
            "description": "Cannot delete non-completed request"
          }
        },
        "summary": "Delete a self-update request",
        "tags": ["SelfUpdates.Request"]
      }
    },
    "/api/v1/event_types": {
      "get": {
        "callbacks": {},
        "description": "Returns the full catalog of event types Edge Core can publish. Use this list to build a webhook's `subscribed_events` array — every value here is valid, anything else is rejected at create time. Static, code-owned list. See [AsyncAPI spec](/asyncdoc) for each event's payload shape.",
        "operationId": "EdgeAdminWeb.Controllers.Events.EventTypeController.index",
        "parameters": [],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventTypeListResponse"
                }
              }
            },
            "description": "Event types"
          }
        },
        "summary": "List event types",
        "tags": ["Events.Type"]
      }
    },
    "/api/v1/webhooks": {
      "post": {
        "callbacks": {},
        "description": "Create a webhook subscription. The destination URL is validated against the SSRF deny list at create time. `secret` (HMAC signing key, >= 32 bytes) and `headers` are write-only — encrypted at rest and never returned in any GET response. `subscribed_events` is an explicit list of event types this webhook fires on; the full catalog is documented in the [AsyncAPI spec](/asyncdoc).",
        "operationId": "EdgeAdminWeb.Controllers.Events.WebhookController.create",
        "parameters": [],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookCreateRequest" }
            }
          },
          "description": "Webhook creation data",
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSingleResponse"
                }
              }
            },
            "description": "Webhook created"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangesetErrorResponse"
                }
              }
            },
            "description": "Validation error"
          }
        },
        "summary": "Create webhook",
        "tags": ["Events.Webhook"]
      },
      "get": {
        "callbacks": {},
        "description": "Returns a paginated list of webhook subscriptions. The full catalog of event types you can subscribe to is documented in the [AsyncAPI spec](/asyncdoc).",
        "operationId": "EdgeAdminWeb.Controllers.Events.WebhookController.index",
        "parameters": [
          {
            "description": "Page number (1-indexed)",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": { "default": 1, "minimum": 1, "type": "integer" }
          },
          {
            "description": "Items per page",
            "example": 20,
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Comma-separated list of fields to sort by",
            "example": "inserted_at,url",
            "in": "query",
            "name": "order_by",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Comma-separated list of sort directions (asc/desc) corresponding to order_by fields",
            "example": "desc,asc",
            "in": "query",
            "name": "order_directions",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter by URL (exact match or wildcard: prefix*, *suffix, *substring*)",
            "in": "query",
            "name": "url",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Returns webhooks whose `subscribed_events` list contains this event type. Pass a literal event type, e.g. `edge.node.registered`. See [AsyncAPI spec](/asyncdoc) for the catalog.",
            "in": "query",
            "name": "event_type",
            "required": false,
            "schema": { "type": "string" }
          },
          {
            "description": "Filter records where inserted_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "inserted_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where inserted_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "inserted_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or after this datetime (ISO 8601 datetime; date-only is treated as start of day UTC)",
            "in": "query",
            "name": "updated_at__gte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          },
          {
            "description": "Filter records where updated_at is on or before this datetime (ISO 8601 datetime; date-only is treated as end of day UTC)",
            "in": "query",
            "name": "updated_at__lte",
            "required": false,
            "schema": {
              "anyOf": [
                { "format": "date-time", "type": "string" },
                { "format": "date", "type": "string" }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookPaginatedResponse"
                }
              }
            },
            "description": "Paginated webhooks"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid query parameters"
          }
        },
        "summary": "List webhooks",
        "tags": ["Events.Webhook"]
      }
    },
    "/api/v1/webhooks/{id}": {
      "get": {
        "callbacks": {},
        "description": "Get a single webhook by ID.",
        "operationId": "EdgeAdminWeb.Controllers.Events.WebhookController.show",
        "parameters": [
          {
            "description": "Webhook ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSingleResponse"
                }
              }
            },
            "description": "Webhook"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BadRequestResponse" }
              }
            },
            "description": "Invalid path parameters"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Webhook not found"
          }
        },
        "summary": "Get webhook",
        "tags": ["Events.Webhook"]
      },
      "delete": {
        "callbacks": {},
        "description": "Permanently delete a webhook. Webhooks are immutable after create — to change any field (URL, secret, headers, subscribed_events) delete and recreate. The retry budget is process-wide (`WEBHOOK_MAX_ATTEMPTS` env var on the admin), not per-webhook.",
        "operationId": "EdgeAdminWeb.Controllers.Events.WebhookController.delete",
        "parameters": [
          {
            "description": "Webhook ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": { "format": "uuid", "type": "string" }
          }
        ],
        "responses": {
          "204": { "content": { "": {} }, "description": "Webhook deleted" },
          "404": {
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NotFoundResponse" }
              }
            },
            "description": "Webhook not found"
          }
        },
        "summary": "Delete webhook",
        "tags": ["Events.Webhook"]
      }
    }
  },
  "security": [{ "apiKey": [] }],
  "servers": [],
  "tags": [
    { "name": "Admins.Metadata" },
    { "name": "Admins.Metrics" },
    { "name": "Nodes.Cluster" },
    { "name": "Nodes.EnrollmentKey" },
    { "name": "Nodes.Node" },
    { "name": "Nodes.Alias" },
    { "name": "Nodes.Metrics" },
    { "name": "Commands.Command" },
    { "name": "Commands.CommandExecution" },
    { "name": "Ssh.SshUsername" },
    { "name": "Ssh.SshPublicKey" },
    { "name": "SelfUpdates.Request" },
    { "name": "Events.Type" },
    { "name": "Events.Webhook" }
  ]
}
