{
  "openapi": "3.1.0",
  "info": {
    "title": "Ovira panel API",
    "version": "0.9.9",
    "summary": "Manage clients, inbounds, nodes and panel settings over HTTP.",
    "description": "Use the same REST routes as the panel web app. Public subscription routes use the client token in their URL.\n\n**Authentication.** Send `Authorization: Bearer ovk_...` for an API token, or use the panel session cookie. A bearer header takes precedence over the cookie. Token requests need no CSRF header; session writes require `X-CSRF-Token`.\n\n**Permissions.** A full token follows its account’s current rights. A custom token carries only its chosen permissions, capped by the account’s current rights, and never counts as an owner. Owner-only routes require a full owner token. For client action routes the chosen action determines the permission: edit, reset, kick or delete. Full inbound lists, details, deployments and logs require `inbounds.view: all`; own scope can use the brief list.\n\n**Session-only actions.** Operations marked `x-ovira-token: false` reject tokens with 403 `token_forbidden`. These include account and token management, logout, admin password/2FA changes, panel address/TLS changes, backup restore and protocol package removal. Billing, license changes and update installation are also session-only and are outside this document. Other owner administration is available to full owner tokens.\n\n**Errors.** Failures have `error`, plus `code` when applicable, `field` for a bad request field and optional `params`. Token authentication can return `token_invalid`, `token_expired` or `token_ip`. Blocked account writes return `account_blocked`."
  },
  "servers": [
    {
      "url": "/",
      "description": "Replace this URL with the panel origin and configured base path, without /api. Subscription routes use the origin without the panel base path."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "cookieAuth": []
    }
  ],
  "tags": [
    {
      "name": "Auth"
    },
    {
      "name": "Account"
    },
    {
      "name": "API tokens"
    },
    {
      "name": "Overview"
    },
    {
      "name": "Clients"
    },
    {
      "name": "WireGuard"
    },
    {
      "name": "Connections"
    },
    {
      "name": "Inbounds"
    },
    {
      "name": "Groups"
    },
    {
      "name": "Nodes"
    },
    {
      "name": "Admins"
    },
    {
      "name": "Roles"
    },
    {
      "name": "Settings"
    },
    {
      "name": "Backup"
    },
    {
      "name": "Updates"
    },
    {
      "name": "Audit"
    },
    {
      "name": "Subscription"
    }
  ],
  "paths": {
    "/api/meta": {
      "get": {
        "operationId": "getMeta",
        "tags": [
          "Auth"
        ],
        "summary": "Panel metadata for the sign-in page.",
        "security": [],
        "x-ovira-permission": "public",
        "responses": {
          "200": {
            "description": "Panel name, version, language and sign-in options.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Meta"
                }
              }
            }
          }
        }
      }
    },
    "/api/openapi.json": {
      "get": {
        "operationId": "getOpenAPI",
        "tags": [
          "Auth"
        ],
        "summary": "This OpenAPI document.",
        "security": [],
        "x-ovira-permission": "public",
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 document, with the running version in info.version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenAPIDocument"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "operationId": "login",
        "tags": [
          "Auth"
        ],
        "summary": "Sign in with a username and password.",
        "security": [],
        "x-ovira-permission": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in (Me, with the session cookie set), or a second-factor challenge.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Me"
                    },
                    {
                      "$ref": "#/components/schemas/MFAPending"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Wrong username or password.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The account is blocked or the licence refuses it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/mfa": {
      "post": {
        "operationId": "loginMFA",
        "tags": [
          "Auth"
        ],
        "summary": "Finish signing in with a second factor.",
        "security": [],
        "x-ovira-permission": "public",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MFAInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signed in, with the session cookie set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "The code did not work or the ticket expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "logout",
        "tags": [
          "Auth"
        ],
        "summary": "Sign out of the panel.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "responses": {
          "200": {
            "description": "Signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/me": {
      "get": {
        "operationId": "getMe",
        "tags": [
          "Auth"
        ],
        "summary": "Who this request is acting as.",
        "x-ovira-permission": "any",
        "responses": {
          "200": {
            "description": "The signed-in admin, their rights and, for a token request, the token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "description": "No valid session or token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/usage": {
      "get": {
        "operationId": "getAuthUsage",
        "tags": [
          "Auth"
        ],
        "summary": "The account's own current traffic pool.",
        "x-ovira-permission": "any",
        "responses": {
          "200": {
            "description": "Current pool usage; an owner always reads 0 for the limit and the end date.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountUsage"
                }
              }
            }
          }
        }
      }
    },
    "/api/overview": {
      "get": {
        "operationId": "getOverview",
        "tags": [
          "Overview"
        ],
        "summary": "Dashboard counts, recent activity and the license.",
        "x-ovira-permission": "any",
        "parameters": [
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Viewer's offset from UTC in minutes, -840 to 840, so day buckets start at local midnight; 0 when absent."
          }
        ],
        "responses": {
          "200": {
            "description": "Counts, traffic series, clients needing attention, the licence and, by permission, the account plan, inbounds and host statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overview"
                }
              }
            }
          }
        }
      }
    },
    "/api/system": {
      "get": {
        "operationId": "getSystem",
        "tags": [
          "Overview"
        ],
        "summary": "CPU, memory, disk and load of the panel host.",
        "x-ovira-permission": "nodes.view",
        "responses": {
          "200": {
            "description": "System statistics, sampled at most every two seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SystemInfo"
                }
              }
            }
          }
        }
      }
    },
    "/api/updates": {
      "get": {
        "operationId": "getUpdates",
        "tags": [
          "Updates"
        ],
        "summary": "The panel update state.",
        "x-ovira-permission": "staff",
        "responses": {
          "200": {
            "description": "Current update state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateInfo"
                }
              }
            }
          }
        }
      }
    },
    "/api/updates/check": {
      "post": {
        "operationId": "checkUpdates",
        "tags": [
          "Updates"
        ],
        "summary": "Ask the update server for a newer release.",
        "x-ovira-permission": "staff",
        "responses": {
          "200": {
            "description": "The update state after checking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateInfo"
                }
              }
            }
          }
        }
      }
    },
    "/api/audit": {
      "get": {
        "operationId": "getAudit",
        "tags": [
          "Audit"
        ],
        "summary": "Recent audit entries.",
        "x-ovira-permission": "staff",
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            },
            "description": "Only entries with a lower id; 0 or absent starts from the newest."
          }
        ],
        "responses": {
          "200": {
            "description": "Up to 100 audit entries, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditEntry"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/backup": {
      "get": {
        "operationId": "downloadBackup",
        "tags": [
          "Backup"
        ],
        "summary": "Download a database backup.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "A consistent SQLite snapshot of the panel database.",
            "content": {
              "application/vnd.sqlite3": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/api/backup/restore": {
      "post": {
        "operationId": "restoreBackup",
        "tags": [
          "Backup"
        ],
        "summary": "Upload a backup to restore on the next restart.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/BackupUpload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The backup is staged for the next restart.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestoreResult"
                }
              }
            }
          },
          "400": {
            "description": "The upload is not an Ovira backup.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/backup/telegram": {
      "get": {
        "operationId": "getTelegramBackup",
        "tags": [
          "Backup"
        ],
        "summary": "The saved Telegram backup settings.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "Bot settings without the token, the schedule and the backups in the chat.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramBackup"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putTelegramBackup",
        "tags": [
          "Backup"
        ],
        "summary": "Save Telegram backup settings.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TelegramConfig"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramBackup"
                }
              }
            }
          },
          "400": {
            "description": "The bot token or chat is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/backup/telegram/chats": {
      "post": {
        "operationId": "telegramChats",
        "tags": [
          "Backup"
        ],
        "summary": "List chats the bot has seen.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TelegramChatsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The chats the bot can post in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramChats"
                }
              }
            }
          },
          "400": {
            "description": "No bot token is saved or given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Telegram could not be reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/backup/telegram/send": {
      "post": {
        "operationId": "telegramSend",
        "tags": [
          "Backup"
        ],
        "summary": "Send a backup to Telegram now.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "The backup was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TelegramSend"
                }
              }
            }
          },
          "400": {
            "description": "Backups are not available or the send failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A send is already running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/backup/telegram/restore": {
      "post": {
        "operationId": "telegramRestore",
        "tags": [
          "Backup"
        ],
        "summary": "Restore a backup downloaded from Telegram on the next restart.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TelegramRestoreInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The backup is staged for the next restart.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestoreResult"
                }
              }
            }
          },
          "400": {
            "description": "The file id is not a backup of this panel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients": {
      "get": {
        "operationId": "listClients",
        "tags": [
          "Clients"
        ],
        "summary": "List and search clients.",
        "x-ovira-permission": "clients.view",
        "parameters": [
          {
            "name": "owner",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only this owner's clients; ignored for an admin whose view is limited to their own."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Case-insensitive search in name, note and contact."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "active",
                "online",
                "expiring",
                "limited",
                "expired",
                "disabled"
              ],
              "default": "all"
            },
            "description": "Bucket to keep; all or empty keeps every client."
          },
          {
            "name": "group",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only clients in this group; 0 keeps every group."
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "used",
                "expire",
                "online",
                "created"
              ],
              "default": "created"
            },
            "description": "Field to sort by; an unknown value sorts by id."
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "description": "Sort direction."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "Page number, from 1."
          },
          {
            "name": "size",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50
            },
            "description": "Page size, 1-500; anything else means 50."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clients with the bucket counts of the whole filtered list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientPage"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createClient",
        "tags": [
          "Clients"
        ],
        "summary": "Create a client.",
        "x-ovira-permission": "clients.create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The licence does not allow another client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/bulk": {
      "post": {
        "operationId": "bulkCreateClients",
        "tags": [
          "Clients"
        ],
        "summary": "Create several clients from one plan.",
        "x-ovira-permission": "clients.create",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new clients; a partial failure also carries an error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkCreateResult"
                }
              }
            }
          },
          "400": {
            "description": "The plan is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/bulk-action": {
      "post": {
        "operationId": "bulkClientAction",
        "tags": [
          "Clients"
        ],
        "summary": "Apply one action to many clients.",
        "x-ovira-permission": "clients.edit",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkAction"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many clients changed and why the others did not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkActionResult"
                }
              }
            }
          },
          "400": {
            "description": "No ids, too many ids or an unknown action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API token does not hold the permission this action needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}": {
      "get": {
        "operationId": "getClient",
        "tags": [
          "Clients"
        ],
        "summary": "One client with its live sessions, history and profiles.",
        "x-ovira-permission": "clients.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          }
        ],
        "responses": {
          "200": {
            "description": "The client and its details.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientDetail"
                }
              }
            }
          },
          "404": {
            "description": "No such client, or it is outside the admin's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "updateClient",
        "tags": [
          "Clients"
        ],
        "summary": "Change a client.",
        "x-ovira-permission": "clients.edit",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such client, or it is outside the admin's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteClient",
        "tags": [
          "Clients"
        ],
        "summary": "Delete a client.",
        "x-ovira-permission": "clients.delete",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          }
        ],
        "responses": {
          "200": {
            "description": "The client was deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such client, or it is outside the admin's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/{action}": {
      "post": {
        "operationId": "clientAction",
        "tags": [
          "Clients"
        ],
        "summary": "Enable, disable, reset, kick, extend, reissue or rotate a client.",
        "x-ovira-permission": "clients.edit",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          },
          {
            "name": "action",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "enable",
                "disable",
                "reset",
                "extend",
                "reissue",
                "rotate-sub",
                "kick"
              ]
            },
            "description": "What to do: switch on or off, reset the traffic, extend the plan, reissue the certificate, replace the subscription token or close the sessions."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientAction"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The client after the action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Client"
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The API token does not hold the permission this action needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such client or action, or the client is outside the admin's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/usage": {
      "get": {
        "operationId": "getClientUsage",
        "tags": [
          "Clients"
        ],
        "summary": "Traffic used by a client over time.",
        "x-ovira-permission": "clients.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "Window to read; 24h is bucketed by hour, the others by day."
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Viewer's offset from UTC in minutes, -840 to 840, so day buckets start at local midnight; 0 when absent."
          }
        ],
        "responses": {
          "200": {
            "description": "Usage points for the window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSeries"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/profile": {
      "get": {
        "operationId": "getClientProfile",
        "tags": [
          "Clients"
        ],
        "summary": "Download a ready-made profile.",
        "x-ovira-permission": "clients.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          },
          {
            "name": "key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Profile key from ClientDetail.profiles; empty or all combines every compatible server."
          }
        ],
        "responses": {
          "200": {
            "description": "The OpenVPN profile file.",
            "content": {
              "application/x-openvpn-profile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such client or profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/qr": {
      "get": {
        "operationId": "getClientQR",
        "tags": [
          "Clients"
        ],
        "summary": "A QR code image for the client's subscription link.",
        "x-ovira-permission": "clients.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          }
        ],
        "responses": {
          "200": {
            "description": "A 320-pixel PNG QR code of the subscription link.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/wireguard": {
      "get": {
        "operationId": "listWireGuardDevices",
        "tags": [
          "WireGuard"
        ],
        "summary": "List a client's WireGuard devices.",
        "x-ovira-permission": "staff",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          }
        ],
        "responses": {
          "200": {
            "description": "One row per device slot and the WireGuard inbounds the client may use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WGDevices"
                }
              }
            }
          },
          "404": {
            "description": "No such client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/wireguard/{slot}/reset": {
      "post": {
        "operationId": "resetWireGuardKey",
        "tags": [
          "WireGuard"
        ],
        "summary": "Replace a WireGuard key.",
        "x-ovira-permission": "staff",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          },
          {
            "name": "slot",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Device slot, from 1 up to the client's device cap."
          }
        ],
        "responses": {
          "200": {
            "description": "The device with its new key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WGDevice"
                }
              }
            }
          },
          "404": {
            "description": "No such client or slot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/clients/{id}/wireguard/{slot}/{key}/{format}": {
      "get": {
        "operationId": "getWireGuardConfig",
        "tags": [
          "WireGuard"
        ],
        "summary": "Download a WireGuard configuration.",
        "x-ovira-permission": "staff",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Client id."
          },
          {
            "name": "slot",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Device slot, from 1 up to the client's device cap."
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Profile key of the WireGuard inbound."
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "config.conf",
                "qr"
              ]
            },
            "description": "The configuration file, or a PNG QR code of it."
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration in the requested format.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such client, slot, profile or format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/connections": {
      "get": {
        "operationId": "listConnections",
        "tags": [
          "Connections"
        ],
        "summary": "Currently open connections.",
        "x-ovira-permission": "connections.view",
        "responses": {
          "200": {
            "description": "Open sessions the admin can see, with their device caps.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LiveSession"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/connections/history": {
      "get": {
        "operationId": "getConnectionHistory",
        "tags": [
          "Connections"
        ],
        "summary": "Recently closed connections.",
        "x-ovira-permission": "connections.view",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 200
            },
            "description": "How many sessions to return, 1-1000; anything else means 200."
          },
          {
            "name": "client",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only this client's sessions; 0 keeps every client."
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Search in client name and address."
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "openvpn",
                "ocserv",
                "wireguard"
              ]
            },
            "description": "Only sessions of this protocol; an unknown value keeps every protocol."
          },
          {
            "name": "node",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Only sessions on this node; 0 is this server, absent means every node."
          }
        ],
        "responses": {
          "200": {
            "description": "Closed sessions, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/VPNSession"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/connections/kick": {
      "post": {
        "operationId": "kickConnection",
        "tags": [
          "Connections"
        ],
        "summary": "Close an open connection.",
        "x-ovira-permission": "connections.kick",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectionKickInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection was closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "That connection is already closed, or is outside the admin's scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/groups": {
      "get": {
        "operationId": "listGroups",
        "tags": [
          "Groups"
        ],
        "summary": "List groups.",
        "x-ovira-permission": "groups.view",
        "responses": {
          "200": {
            "description": "The groups the admin may see.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createGroup",
        "tags": [
          "Groups"
        ],
        "summary": "Create a group.",
        "x-ovira-permission": "groups.manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/groups/bulk": {
      "post": {
        "operationId": "bulkGroupAction",
        "tags": [
          "Groups"
        ],
        "summary": "Enable, disable or delete several groups.",
        "x-ovira-permission": "groups.manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupBulk"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "The action ran."
          },
          "400": {
            "description": "The action is not one of enable, disable or delete.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/groups/assign": {
      "post": {
        "operationId": "assignGroups",
        "tags": [
          "Groups"
        ],
        "summary": "Add or remove groups for many clients.",
        "x-ovira-permission": "groups.manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupAssign"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "The groups were changed."
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/groups/{id}": {
      "put": {
        "operationId": "updateGroup",
        "tags": [
          "Groups"
        ],
        "summary": "Rename or change a group.",
        "x-ovira-permission": "groups.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Group id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroupInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Group"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such group.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes": {
      "get": {
        "operationId": "listNodes",
        "tags": [
          "Nodes"
        ],
        "summary": "List nodes.",
        "x-ovira-permission": "nodes.view",
        "responses": {
          "200": {
            "description": "The nodes the admin may see, the panel's own server first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createNode",
        "tags": [
          "Nodes"
        ],
        "summary": "Add a node.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The licence does not allow another node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/install": {
      "get": {
        "operationId": "getNodeInstall",
        "tags": [
          "Nodes"
        ],
        "summary": "The install command for a new node.",
        "x-ovira-permission": "nodes.view",
        "responses": {
          "200": {
            "description": "The install command, the installer address and the panel address the node should accept.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeInstall"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/0/local-install": {
      "post": {
        "operationId": "localNodeInstall",
        "tags": [
          "Nodes"
        ],
        "summary": "Install a node on the panel host.",
        "x-ovira-permission": "owner",
        "responses": {
          "202": {
            "description": "The install started; poll nodes for its outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LocalNodeInstall"
                }
              }
            }
          },
          "400": {
            "description": "The install cannot run on this host.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/{id}": {
      "put": {
        "operationId": "updateNode",
        "tags": [
          "Nodes"
        ],
        "summary": "Change a node.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 is the panel's own server."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteNode",
        "tags": [
          "Nodes"
        ],
        "summary": "Remove a node.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 cannot be removed."
          }
        ],
        "responses": {
          "204": {
            "description": "The node was removed; its inbounds are unassigned."
          },
          "400": {
            "description": "The main server cannot be removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/{id}/{action}": {
      "post": {
        "operationId": "nodeAction",
        "tags": [
          "Nodes"
        ],
        "summary": "Sync a node or reset its traffic counters.",
        "x-ovira-permission": "nodes.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 is the panel's own server."
          },
          {
            "name": "action",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "sync",
                "reset-traffic"
              ]
            },
            "description": "Push the state to the node, or zero its traffic counters."
          }
        ],
        "responses": {
          "200": {
            "description": "The node after the action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeResult"
                }
              }
            }
          },
          "404": {
            "description": "No such node or action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/{id}/protocols": {
      "get": {
        "operationId": "listNodeProtocols",
        "tags": [
          "Nodes"
        ],
        "summary": "Protocols set up on a node.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 is the panel's own server."
          }
        ],
        "responses": {
          "200": {
            "description": "Package state of each protocol and how many inbounds use it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeProtocols"
                }
              }
            }
          }
        }
      }
    },
    "/api/nodes/{id}/protocols/{kind}": {
      "delete": {
        "operationId": "deleteNodeProtocol",
        "tags": [
          "Nodes"
        ],
        "summary": "Remove a protocol from a node.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 is the panel's own server."
          },
          {
            "name": "kind",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "openvpn",
                "ocserv",
                "wireguard"
              ]
            },
            "description": "Protocol to uninstall."
          }
        ],
        "responses": {
          "200": {
            "description": "The remaining protocol state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NodeProtocols"
                }
              }
            }
          },
          "400": {
            "description": "The protocol is in use or not installed by the panel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/brief": {
      "get": {
        "operationId": "listInstancesBrief",
        "tags": [
          "Inbounds"
        ],
        "summary": "Inbounds with just their names and states.",
        "x-ovira-permission": "inbounds.view",
        "parameters": [
          {
            "name": "deployments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 also lists the per-node deployments of a multi-node inbound."
          }
        ],
        "responses": {
          "200": {
            "description": "The brief list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/InstanceBrief"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/instances": {
      "get": {
        "operationId": "listInstances",
        "tags": [
          "Inbounds"
        ],
        "summary": "List inbounds.",
        "x-ovira-permission": "inbounds.view",
        "parameters": [
          {
            "name": "deployments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 also lists the per-node deployments of a multi-node inbound."
          }
        ],
        "responses": {
          "200": {
            "description": "The inbounds and the host's versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Create an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InstanceCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new inbound; a warning when part of it did not apply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The licence does not allow another inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/preflight": {
      "post": {
        "operationId": "preflightInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Check an inbound plan before creating it.",
        "x-ovira-permission": "inbounds.manage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreflightInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One finding per node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreflightResult"
                }
              }
            }
          },
          "400": {
            "description": "The draft is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/defaults": {
      "get": {
        "operationId": "getInstanceDefaults",
        "tags": [
          "Inbounds"
        ],
        "summary": "Default settings for a new inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "openvpn",
                "ocserv",
                "wireguard"
              ],
              "default": "openvpn"
            },
            "description": "Protocol the defaults are for."
          },
          {
            "name": "nodeId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Node the defaults are for; -1 (the default) is an unassigned inbound."
          }
        ],
        "responses": {
          "200": {
            "description": "Instance settings, or the OpenConnect defaults with their install state.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/InstanceSettings"
                    },
                    {
                      "$ref": "#/components/schemas/OCDefaults"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/access": {
      "get": {
        "operationId": "getInstanceAccess",
        "tags": [
          "Inbounds"
        ],
        "summary": "Groups that may use each inbound.",
        "x-ovira-permission": "inbounds.manage",
        "responses": {
          "200": {
            "description": "The groups and how many clients have their own inbound list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceAccessView"
                }
              }
            }
          }
        }
      }
    },
    "/api/ocserv/install": {
      "post": {
        "operationId": "installOCServ",
        "tags": [
          "Inbounds"
        ],
        "summary": "Install the OpenConnect server on the panel host.",
        "x-ovira-permission": "inbounds.manage",
        "responses": {
          "202": {
            "description": "The install started; poll instances/defaults for the outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OCDefaults"
                }
              }
            }
          },
          "400": {
            "description": "The install cannot run on this host.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}": {
      "get": {
        "operationId": "getInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "One inbound with its configuration and deployments.",
        "x-ovira-permission": "inbounds.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "responses": {
          "200": {
            "description": "The inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceDetail"
                }
              }
            }
          },
          "404": {
            "description": "No such inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "updateInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Change an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InstanceInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed inbound; a warning when part of it did not apply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceResult"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Remove an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "responses": {
          "200": {
            "description": "The inbound was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/deployments": {
      "get": {
        "operationId": "listDeployments",
        "tags": [
          "Inbounds"
        ],
        "summary": "Where an inbound is deployed.",
        "x-ovira-permission": "inbounds.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "responses": {
          "200": {
            "description": "One entry per node, with its job.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeploymentsResult"
                }
              }
            }
          },
          "404": {
            "description": "No such inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "deployInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Deploy an inbound to nodes.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          },
          {
            "name": "check",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "1 checks the nodes and answers 200 without deploying."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeployInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The findings of a check.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeploymentsResult"
                }
              }
            }
          },
          "202": {
            "description": "The deployments were queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "No node, too many nodes or an invalid body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/deployments/{node}": {
      "delete": {
        "operationId": "removeDeployment",
        "tags": [
          "Inbounds"
        ],
        "summary": "Remove an inbound from a node.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          },
          {
            "name": "node",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id; 0 is the panel's own server."
          }
        ],
        "responses": {
          "200": {
            "description": "The deployment was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The id or node is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/deployments/{node}/overrides": {
      "put": {
        "operationId": "putDeploymentOverrides",
        "tags": [
          "Inbounds"
        ],
        "summary": "Change what one node overrides for an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          },
          {
            "name": "node",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeploymentOverride"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The override was saved and the node redeploys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The inbound is not deployed on that node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteDeploymentOverrides",
        "tags": [
          "Inbounds"
        ],
        "summary": "Clear one node's overrides for an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          },
          {
            "name": "node",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Node id."
          }
        ],
        "responses": {
          "202": {
            "description": "The overrides were cleared and the node redeploys.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The inbound is not deployed on that node.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/node": {
      "post": {
        "operationId": "assignInstanceNode",
        "tags": [
          "Inbounds"
        ],
        "summary": "Assign an inbound to a node.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NodeIDInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The inbound on its new node; a warning when part of it did not apply.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceResult"
                }
              }
            }
          },
          "400": {
            "description": "No node was chosen or it is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "unassignInstanceNode",
        "tags": [
          "Inbounds"
        ],
        "summary": "Remove an inbound from its node.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "responses": {
          "200": {
            "description": "The inbound, now unassigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceResult"
                }
              }
            }
          },
          "400": {
            "description": "The inbound cannot be unassigned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/control": {
      "post": {
        "operationId": "controlInstance",
        "tags": [
          "Inbounds"
        ],
        "summary": "Start, stop or restart an inbound.",
        "x-ovira-permission": "inbounds.manage",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ControlInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The inbound after the action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceView"
                }
              }
            }
          },
          "400": {
            "description": "The action is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/instances/{id}/logs": {
      "get": {
        "operationId": "getInstanceLogs",
        "tags": [
          "Inbounds"
        ],
        "summary": "Recent logs of an inbound.",
        "x-ovira-permission": "inbounds.view",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Inbound id."
          },
          {
            "name": "lines",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 200
            },
            "description": "How many log lines to read, 10-2000; anything else means 200."
          }
        ],
        "responses": {
          "200": {
            "description": "The log text.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceLogs"
                }
              }
            }
          },
          "404": {
            "description": "No such inbound.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/password": {
      "post": {
        "operationId": "changePassword",
        "tags": [
          "Account"
        ],
        "summary": "Change your own password.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordChangeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The password was changed; other sessions were signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The current password or the new one is not acceptable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/2fa/setup": {
      "post": {
        "operationId": "setup2FA",
        "tags": [
          "Account"
        ],
        "summary": "Start setting up a second factor.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "responses": {
          "200": {
            "description": "The pending secret, its URI and a QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MFASetup"
                }
              }
            }
          },
          "400": {
            "description": "Two-factor sign-in is already on.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/2fa/enable": {
      "post": {
        "operationId": "enable2FA",
        "tags": [
          "Account"
        ],
        "summary": "Turn on the second factor.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CodeInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The second factor is on; these recovery codes are shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecoveryCodes"
                }
              }
            }
          },
          "400": {
            "description": "The setup was not started or the code did not match.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/2fa/disable": {
      "post": {
        "operationId": "disable2FA",
        "tags": [
          "Account"
        ],
        "summary": "Turn off the second factor.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The second factor is off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The password is wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/2fa/recovery": {
      "post": {
        "operationId": "regenerateRecoveryCodes",
        "tags": [
          "Account"
        ],
        "summary": "Replace the recovery codes.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new codes, shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecoveryCodes"
                }
              }
            }
          },
          "400": {
            "description": "Two-factor sign-in is off or the password is wrong.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/sessions": {
      "get": {
        "operationId": "listSessions",
        "tags": [
          "Account"
        ],
        "summary": "Your signed-in browsers.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "responses": {
          "200": {
            "description": "The sessions, newest activity first and the current one marked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AdminSessionView"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/account/sessions/{id}": {
      "delete": {
        "operationId": "revokeSession",
        "tags": [
          "Account"
        ],
        "summary": "Sign out one browser.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The session's ref, or its id with at least 16 characters."
          }
        ],
        "responses": {
          "200": {
            "description": "The session was signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/sessions/revoke-others": {
      "post": {
        "operationId": "revokeOtherSessions",
        "tags": [
          "Account"
        ],
        "summary": "Sign out every other browser.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "responses": {
          "200": {
            "description": "The other sessions were signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/tokens": {
      "get": {
        "operationId": "listTokens",
        "tags": [
          "API tokens"
        ],
        "summary": "Your API tokens.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "responses": {
          "200": {
            "description": "The tokens; secrets are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Token"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createToken",
        "tags": [
          "API tokens"
        ],
        "summary": "Create an API token.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new token; the secret is shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenCreated"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The name is already used.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed password or code attempts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/account/tokens/{id}": {
      "delete": {
        "operationId": "revokeToken",
        "tags": [
          "API tokens"
        ],
        "summary": "Revoke one of your tokens.",
        "x-ovira-permission": "any",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Token id."
          }
        ],
        "responses": {
          "200": {
            "description": "The token was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such token for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/tokens": {
      "get": {
        "operationId": "listAdminTokens",
        "tags": [
          "API tokens"
        ],
        "summary": "The API tokens of one admin (owner only).",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          }
        ],
        "responses": {
          "200": {
            "description": "That admin's tokens; secrets are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Token"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/tokens/{tid}": {
      "delete": {
        "operationId": "revokeAdminToken",
        "tags": [
          "API tokens"
        ],
        "summary": "Revoke one token of an admin (owner only).",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          },
          {
            "name": "tid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Token id."
          }
        ],
        "responses": {
          "200": {
            "description": "The token was revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such admin or token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins": {
      "get": {
        "operationId": "listAdmins",
        "tags": [
          "Admins"
        ],
        "summary": "List admins.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "Every account, the owner included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Admin"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAdmin",
        "tags": [
          "Admins"
        ],
        "summary": "Create an admin.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Admin"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid or the username is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The licence does not allow another account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}": {
      "get": {
        "operationId": "getAdmin",
        "tags": [
          "Admins"
        ],
        "summary": "One admin.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          }
        ],
        "responses": {
          "200": {
            "description": "The admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Admin"
                }
              }
            }
          },
          "404": {
            "description": "No such admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "updateAdmin",
        "tags": [
          "Admins"
        ],
        "summary": "Change an admin.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Admin"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid, the username is taken or the last owner would be lost.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAdmin",
        "tags": [
          "Admins"
        ],
        "summary": "Remove an admin.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          },
          {
            "name": "clients",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "delete"
              ]
            },
            "description": "delete deletes the admin's clients instead of moving them."
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Admin who receives the clients; default is the caller."
          }
        ],
        "responses": {
          "200": {
            "description": "The admin was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminDeleteResult"
                }
              }
            }
          },
          "400": {
            "description": "The account cannot be deleted or the target admin is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/password": {
      "post": {
        "operationId": "setAdminPassword",
        "tags": [
          "Admins"
        ],
        "summary": "Set another admin's password.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The password was set; that admin's sessions were signed out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The password is too weak.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/reset-2fa": {
      "post": {
        "operationId": "resetAdmin2FA",
        "tags": [
          "Admins"
        ],
        "summary": "Turn off another admin's second factor.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          }
        ],
        "responses": {
          "200": {
            "description": "The second factor was turned off.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "404": {
            "description": "No such admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/usage": {
      "get": {
        "operationId": "getAdminUsage",
        "tags": [
          "Admins"
        ],
        "summary": "One admin's traffic over time.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "Window to read; 24h is bucketed by hour, the others by day."
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Viewer's offset from UTC in minutes, -840 to 840, so day buckets start at local midnight; 0 when absent."
          }
        ],
        "responses": {
          "200": {
            "description": "Usage points of the admin's clients.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSeries"
                }
              }
            }
          },
          "404": {
            "description": "No such admin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admins/{id}/{action}": {
      "post": {
        "operationId": "adminAction",
        "tags": [
          "Admins"
        ],
        "summary": "Add traffic, extend, reset usage or enable and disable an admin.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Admin id."
          },
          {
            "name": "action",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "add-traffic",
                "extend",
                "reset-usage",
                "disable",
                "enable",
                "disable-clients",
                "enable-clients"
              ]
            },
            "description": "The plan change; it wins over body.action."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminAction"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The admin after the action.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Admin"
                }
              }
            }
          },
          "400": {
            "description": "The action is not valid or the last owner cannot be disabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin-roles": {
      "get": {
        "operationId": "listRoles",
        "tags": [
          "Roles"
        ],
        "summary": "List admin roles.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "The roles and the permission keys they may grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RoleList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createRole",
        "tags": [
          "Roles"
        ],
        "summary": "Create an admin role.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoleInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid or the name is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/admin-roles/{id}": {
      "put": {
        "operationId": "updateRole",
        "tags": [
          "Roles"
        ],
        "summary": "Change an admin role.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Role id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Role"
                }
              }
            }
          },
          "400": {
            "description": "The request is not valid, the role is built-in or the name is taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteRole",
        "tags": [
          "Roles"
        ],
        "summary": "Remove an admin role.",
        "x-ovira-permission": "owner",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Role id."
          }
        ],
        "responses": {
          "200": {
            "description": "The role was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OK"
                }
              }
            }
          },
          "400": {
            "description": "The role is built-in.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Accounts still use the role.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/settings": {
      "get": {
        "operationId": "getSettings",
        "tags": [
          "Settings"
        ],
        "summary": "Panel and account settings.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "The saved settings, the panel configuration on disk and the values running now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettingsView"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putSettings",
        "tags": [
          "Settings"
        ],
        "summary": "Change panel settings.",
        "x-ovira-permission": "owner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Settings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Settings"
                }
              }
            }
          },
          "400": {
            "description": "A setting is not valid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/settings/panel": {
      "put": {
        "operationId": "putPanelConfig",
        "tags": [
          "Settings"
        ],
        "summary": "Change the listen address, base path or TLS files.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PanelConfig"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved panel configuration; it applies on restart.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PanelConfigResult"
                }
              }
            }
          },
          "400": {
            "description": "The configuration is not valid or a file is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/settings/restart": {
      "post": {
        "operationId": "restartPanel",
        "tags": [
          "Settings"
        ],
        "summary": "Restart the panel.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "The restart was scheduled; the panel stops answering shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RestartResult"
                }
              }
            }
          },
          "400": {
            "description": "Restart is not available in this mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/settings/tls": {
      "get": {
        "operationId": "getTLS",
        "tags": [
          "Settings"
        ],
        "summary": "The panel's certificate settings.",
        "x-ovira-permission": "owner",
        "responses": {
          "200": {
            "description": "The certificate the panel serves and whether HTTPS is on.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TLSView"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putTLS",
        "tags": [
          "Settings"
        ],
        "summary": "Change the panel's certificate.",
        "x-ovira-permission": "owner",
        "x-ovira-token": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TLSInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new certificate; it applies on restart.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TLSPutResult"
                }
              }
            }
          },
          "400": {
            "description": "The mode is not valid or the certificate could not be issued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}": {
      "get": {
        "operationId": "getSubscriptionPage",
        "tags": [
          "Subscription"
        ],
        "summary": "The subscription page for a client.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ovpn",
                "raw"
              ]
            },
            "description": "Return the profile file instead of the page; OpenVPN user agents get it anyway."
          }
        ],
        "responses": {
          "200": {
            "description": "The page, or a ready-made profile for a VPN client.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "application/x-openvpn-profile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many failed token guesses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/info": {
      "get": {
        "operationId": "getSubscriptionInfo",
        "tags": [
          "Subscription"
        ],
        "summary": "Profiles and endpoints for a subscription link.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          }
        ],
        "responses": {
          "200": {
            "description": "The client's usage, profiles, servers and page settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubInfo"
                }
              }
            }
          },
          "404": {
            "description": "No such token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/usage": {
      "get": {
        "operationId": "getSubscriptionUsage",
        "tags": [
          "Subscription"
        ],
        "summary": "Traffic used by a subscription link's client.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          },
          {
            "name": "range",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d"
              ],
              "default": "30d"
            },
            "description": "Window to read; 24h is bucketed by hour, the others by day."
          },
          {
            "name": "tz",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Viewer's offset from UTC in minutes, -840 to 840, so day buckets start at local midnight; 0 when absent."
          }
        ],
        "responses": {
          "200": {
            "description": "Usage points for the window.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageSeries"
                }
              }
            }
          },
          "404": {
            "description": "No such token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/qr": {
      "get": {
        "operationId": "getSubscriptionQR",
        "tags": [
          "Subscription"
        ],
        "summary": "A QR code for a subscription link.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          }
        ],
        "responses": {
          "200": {
            "description": "A 360-pixel PNG QR code of the subscription link.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/qr/{key}": {
      "get": {
        "operationId": "getSubscriptionProfileQR",
        "tags": [
          "Subscription"
        ],
        "summary": "A QR code for one OpenConnect server.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Profile key of an OpenConnect server."
          }
        ],
        "responses": {
          "200": {
            "description": "A 360-pixel PNG QR code of the server's connect URL.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such token or OpenConnect server.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/wireguard/{slot}/{key}/{format}": {
      "get": {
        "operationId": "getSubscriptionWireGuard",
        "tags": [
          "Subscription"
        ],
        "summary": "A WireGuard configuration for a subscription link.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          },
          {
            "name": "slot",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Device slot, from 1 up to the client's device cap."
          },
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Profile key of the WireGuard inbound."
          },
          {
            "name": "format",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "config.conf",
                "qr"
              ]
            },
            "description": "The configuration file, or a PNG QR code of it."
          }
        ],
        "responses": {
          "200": {
            "description": "The configuration in the requested format.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such token, slot, profile or format.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/{subPath}/{token}/profile/{file}": {
      "get": {
        "operationId": "getSubscriptionProfileFile",
        "tags": [
          "Subscription"
        ],
        "summary": "Download one profile file.",
        "security": [],
        "x-ovira-permission": "public",
        "parameters": [
          {
            "name": "subPath",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The panel's subscription path."
          },
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The client's subscription token."
          },
          {
            "name": "file",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Profile key, at most 24 characters, with or without .ovpn."
          }
        ],
        "responses": {
          "200": {
            "description": "The OpenVPN profile file.",
            "content": {
              "application/x-openvpn-profile": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No such token or profile.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ovk_...",
        "description": "An API token secret. Send it as `Authorization: Bearer ovk_...`; no CSRF header is needed."
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "ovira_sid",
        "description": "The panel session cookie. Writes also need the matching `X-CSRF-Token` header."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable reason the client can act on: token_invalid, token_expired, token_ip, token_forbidden, account_blocked, token_limit, wg_downgrade, oc_downgrade, roles_downgrade or unassigned_downgrade."
          },
          "field": {
            "type": "string",
            "description": "The request field that was wrong."
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Details for the reason, such as the minimum version or the refused address."
          }
        }
      },
      "OK": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true."
          }
        }
      },
      "OpenAPIDocument": {
        "type": "object",
        "description": "This OpenAPI 3.1 document."
      },
      "Scope": {
        "type": "string",
        "enum": [
          "none",
          "own",
          "all"
        ],
        "description": "How far a permission reaches: none, the admin's own records, or everything (for groups.view and inbounds.view, own means the groups and inbounds the admin was granted)."
      },
      "Perms": {
        "type": "object",
        "additionalProperties": {
          "$ref": "#/components/schemas/Scope"
        },
        "description": "Permission key to scope."
      },
      "Meta": {
        "type": "object",
        "properties": {
          "version": {
            "type": "string",
            "description": "Panel version."
          },
          "updatePending": {
            "type": "boolean",
            "description": "True when an update is installed and waits for a restart."
          },
          "locale": {
            "type": "string",
            "description": "Default interface language: en or fa."
          },
          "brand": {
            "type": "string",
            "description": "Panel name shown on the sign-in page."
          },
          "panelAccent": {
            "type": "string",
            "description": "The panel's accent preset."
          }
        }
      },
      "LoginInput": {
        "type": "object",
        "required": [
          "username",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "Admin username."
          },
          "password": {
            "type": "string",
            "description": "Admin password."
          },
          "remember": {
            "type": "boolean",
            "description": "Keep the browser signed in for 30 days instead of the idle timeout."
          }
        }
      },
      "MFAPending": {
        "type": "object",
        "required": [
          "mfa",
          "ticket",
          "remember"
        ],
        "properties": {
          "mfa": {
            "type": "boolean",
            "description": "Always true."
          },
          "ticket": {
            "type": "string",
            "description": "Ticket to send to auth/mfa; valid for five minutes."
          },
          "remember": {
            "type": "boolean",
            "description": "What login asked for."
          }
        }
      },
      "MFAInput": {
        "type": "object",
        "required": [
          "ticket",
          "code"
        ],
        "properties": {
          "ticket": {
            "type": "string",
            "description": "The ticket from auth/login."
          },
          "code": {
            "type": "string",
            "description": "A TOTP code or one of the recovery codes."
          },
          "remember": {
            "type": "boolean",
            "description": "Keep the browser signed in for 30 days."
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "admin": {
            "$ref": "#/components/schemas/AdminAccount"
          },
          "csrf": {
            "type": "string",
            "description": "CSRF token for writes; empty for a token request."
          },
          "license": {
            "type": "object",
            "description": "The license status the panel shows on its License page. Its fields are internal and may change between versions; do not depend on them."
          },
          "system": {
            "type": "string",
            "description": "Name of the host system, e.g. Debian or Ubuntu."
          },
          "roleName": {
            "type": "string",
            "description": "Name of the role that applies to this request."
          },
          "perms": {
            "$ref": "#/components/schemas/Perms"
          },
          "limits": {
            "$ref": "#/components/schemas/RoleLimits"
          },
          "account": {
            "$ref": "#/components/schemas/AccountPlan"
          },
          "token": {
            "$ref": "#/components/schemas/TokenInfo"
          }
        }
      },
      "TokenInfo": {
        "type": "object",
        "required": [
          "id",
          "name",
          "full",
          "expiresAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Token id."
          },
          "name": {
            "type": "string",
            "description": "Token name."
          },
          "full": {
            "type": "boolean",
            "description": "True when the token acts as the account exactly."
          },
          "expiresAt": {
            "type": "integer",
            "description": "Expiry in Unix seconds; 0 means never."
          }
        }
      },
      "AccountUsage": {
        "type": "object",
        "required": [
          "usedBytes",
          "trafficLimit",
          "expireAt"
        ],
        "properties": {
          "usedBytes": {
            "type": "integer",
            "description": "Counted traffic of the account's clients, in bytes."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "The account's traffic pool in bytes; 0 means unlimited (always 0 for the owner)."
          },
          "expireAt": {
            "type": "integer",
            "description": "Account end in Unix seconds; 0 means never (always 0 for the owner)."
          }
        }
      },
      "AccountPlan": {
        "type": "object",
        "required": [
          "state",
          "expireAt",
          "trafficLimit",
          "usedBytes",
          "clients",
          "maxClients",
          "roleName",
          "canAssign"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "expired",
              "traffic"
            ],
            "description": "Why the account may or may not work."
          },
          "expireAt": {
            "type": "integer",
            "description": "Account end in Unix seconds; 0 means never."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Account traffic pool in bytes; 0 means unlimited."
          },
          "usedBytes": {
            "type": "integer",
            "description": "Traffic used by the account's clients, in bytes."
          },
          "clients": {
            "type": "integer",
            "description": "Clients the account owns."
          },
          "maxClients": {
            "type": "integer",
            "description": "Client cap of the account; 0 means none."
          },
          "roleName": {
            "type": "string",
            "description": "Name of the account's role."
          },
          "canAssign": {
            "type": "boolean",
            "description": "False when the account may not create or assign clients yet."
          }
        }
      },
      "MFASetup": {
        "type": "object",
        "required": [
          "secret",
          "uri",
          "qr"
        ],
        "properties": {
          "secret": {
            "type": "string",
            "description": "The pending TOTP secret, not yet active."
          },
          "uri": {
            "type": "string",
            "description": "otpauth:// URI to enter in the authenticator app."
          },
          "qr": {
            "type": "string",
            "description": "The same URI as a PNG data URL."
          }
        }
      },
      "RecoveryCodes": {
        "type": "object",
        "required": [
          "recoveryCodes"
        ],
        "properties": {
          "recoveryCodes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ten new one-time codes; the old ones no longer work."
          }
        }
      },
      "AdminSessionView": {
        "type": "object",
        "required": [
          "id",
          "createdAt",
          "expiresAt",
          "lastSeenAt",
          "ip",
          "userAgent",
          "current",
          "ref"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Session id (a hash of the cookie)."
          },
          "createdAt": {
            "type": "integer",
            "description": "When the browser signed in, in Unix seconds."
          },
          "expiresAt": {
            "type": "integer",
            "description": "When the session ends, in Unix seconds."
          },
          "lastSeenAt": {
            "type": "integer",
            "description": "Last request from this browser, in Unix seconds."
          },
          "ip": {
            "type": "string",
            "description": "Address the browser signed in from."
          },
          "userAgent": {
            "type": "string",
            "description": "Browser user agent, at most 200 characters."
          },
          "current": {
            "type": "boolean",
            "description": "True for the session making the request."
          },
          "ref": {
            "type": "string",
            "description": "The first 16 characters of id, used to revoke the session."
          }
        }
      },
      "Token": {
        "type": "object",
        "required": [
          "id",
          "name",
          "prefix",
          "full",
          "perms",
          "allowIps",
          "createdAt",
          "expiresAt",
          "lastUsedAt",
          "lastUsedIp"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Token id."
          },
          "name": {
            "type": "string",
            "description": "Name the admin chose; unique per admin."
          },
          "prefix": {
            "type": "string",
            "description": "The first characters of the secret, so tokens can be told apart."
          },
          "full": {
            "type": "boolean",
            "description": "True when the token acts as the account exactly, ignoring perms."
          },
          "perms": {
            "$ref": "#/components/schemas/Perms"
          },
          "allowIps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Addresses or networks the token may be used from; empty means any."
          },
          "createdAt": {
            "type": "integer",
            "description": "When the token was created, in Unix seconds."
          },
          "expiresAt": {
            "type": "integer",
            "description": "Expiry in Unix seconds; 0 means never."
          },
          "lastUsedAt": {
            "type": "integer",
            "description": "Last use in Unix seconds; 0 means never used."
          },
          "lastUsedIp": {
            "type": "string",
            "description": "Address of the last use."
          }
        }
      },
      "TokenCreate": {
        "type": "object",
        "required": [
          "name",
          "password"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "1-64 characters, unique for the admin."
          },
          "full": {
            "type": "boolean",
            "description": "Give the token every right the account has; perms is then ignored."
          },
          "perms": {
            "$ref": "#/components/schemas/Perms"
          },
          "allowIps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "At most 32 addresses or networks; empty means any."
          },
          "expiresInDays": {
            "type": "integer",
            "description": "0 for never, otherwise 1-3650."
          },
          "password": {
            "type": "string",
            "description": "The account's current password."
          },
          "code": {
            "type": "string",
            "description": "The current TOTP code when the account has 2FA on."
          }
        }
      },
      "TokenCreated": {
        "type": "object",
        "required": [
          "token",
          "item"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The secret, shown once and never stored."
          },
          "item": {
            "$ref": "#/components/schemas/Token"
          }
        }
      },
      "AdminAccount": {
        "type": "object",
        "required": [
          "id",
          "username",
          "role",
          "roleId",
          "overrides",
          "totpEnabled",
          "disabled",
          "expireAt",
          "trafficLimit",
          "usedBytes",
          "note",
          "createdAt",
          "lastLoginAt",
          "passChangedAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Admin id."
          },
          "username": {
            "type": "string",
            "description": "Admin username."
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "reseller",
              "custom"
            ],
            "description": "Kind of account."
          },
          "roleId": {
            "type": "integer",
            "description": "Role row id; 0 for an owner."
          },
          "overrides": {
            "$ref": "#/components/schemas/LimitOverrides"
          },
          "totpEnabled": {
            "type": "boolean",
            "description": "True when two-factor sign-in is on."
          },
          "disabled": {
            "type": "boolean",
            "description": "True when the account may not sign in."
          },
          "expireAt": {
            "type": "integer",
            "description": "Account end in Unix seconds; 0 means never."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Traffic pool in bytes; 0 means unlimited."
          },
          "usedBytes": {
            "type": "integer",
            "description": "Counted traffic of the account's clients, in bytes."
          },
          "note": {
            "type": "string",
            "description": "Free text for the owner."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "lastLoginAt": {
            "type": "integer",
            "description": "Last sign-in in Unix seconds; 0 means never."
          },
          "passChangedAt": {
            "type": "integer",
            "description": "Last password change in Unix seconds."
          }
        }
      },
      "Admin": {
        "type": "object",
        "required": [
          "id",
          "username",
          "role",
          "roleId",
          "overrides",
          "totpEnabled",
          "disabled",
          "expireAt",
          "trafficLimit",
          "usedBytes",
          "note",
          "createdAt",
          "lastLoginAt",
          "passChangedAt",
          "roleName",
          "state",
          "clients",
          "online",
          "maxClients",
          "limits"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Admin id."
          },
          "username": {
            "type": "string",
            "description": "Admin username."
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "reseller",
              "custom"
            ],
            "description": "Kind of account."
          },
          "roleId": {
            "type": "integer",
            "description": "Role row id; 0 for an owner."
          },
          "overrides": {
            "$ref": "#/components/schemas/LimitOverrides"
          },
          "totpEnabled": {
            "type": "boolean",
            "description": "True when two-factor sign-in is on."
          },
          "disabled": {
            "type": "boolean",
            "description": "True when the account may not sign in."
          },
          "expireAt": {
            "type": "integer",
            "description": "Account end in Unix seconds; 0 means never."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Traffic pool in bytes; 0 means unlimited."
          },
          "usedBytes": {
            "type": "integer",
            "description": "Counted traffic of the account's clients, in bytes."
          },
          "note": {
            "type": "string",
            "description": "Free text for the owner."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "lastLoginAt": {
            "type": "integer",
            "description": "Last sign-in in Unix seconds; 0 means never."
          },
          "passChangedAt": {
            "type": "integer",
            "description": "Last password change in Unix seconds."
          },
          "roleName": {
            "type": "string",
            "description": "Name of the effective role."
          },
          "state": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "expired",
              "traffic"
            ],
            "description": "Why the account may or may not work."
          },
          "clients": {
            "type": "integer",
            "description": "Clients the account owns."
          },
          "online": {
            "type": "integer",
            "description": "Open sessions of the account's clients."
          },
          "maxClients": {
            "type": "integer",
            "description": "Effective client cap; 0 means none."
          },
          "limits": {
            "$ref": "#/components/schemas/RoleLimits"
          }
        }
      },
      "AdminInput": {
        "type": "object",
        "required": [
          "username"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "3-32 characters without spaces or quotes; required."
          },
          "password": {
            "type": "string",
            "description": "Initial password; required on create, ignored on update."
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "reseller"
            ],
            "description": "Built-in account kind, used when roleId is 0."
          },
          "roleId": {
            "type": "integer",
            "description": "Any role, built-in or custom; 0 with role owner makes the account an owner."
          },
          "overrides": {
            "$ref": "#/components/schemas/LimitOverrides"
          },
          "disabled": {
            "type": "boolean",
            "description": "True to block sign-in."
          },
          "expireAt": {
            "type": "integer",
            "description": "Account end in Unix seconds; 0 means never."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Traffic pool in bytes; 0 means unlimited."
          },
          "note": {
            "type": "string",
            "description": "Free text, at most 500 characters."
          }
        }
      },
      "AdminAction": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "add-traffic",
              "extend",
              "reset-usage",
              "disable",
              "enable",
              "disable-clients",
              "enable-clients"
            ],
            "description": "The plan change to apply; the path action wins."
          },
          "bytes": {
            "type": "integer",
            "description": "add-traffic: bytes to add; extend: a new pool when above 0."
          },
          "days": {
            "type": "integer",
            "description": "extend: days added from the later of now and the current end; 0-3650."
          },
          "resetUsage": {
            "type": "boolean",
            "description": "extend: also zero the used traffic."
          }
        }
      },
      "AdminDeleteResult": {
        "type": "object",
        "required": [
          "ok",
          "clients"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true."
          },
          "clients": {
            "type": "integer",
            "description": "How many of the admin's clients were moved or deleted."
          }
        }
      },
      "Role": {
        "type": "object",
        "required": [
          "id",
          "name",
          "builtin",
          "perms",
          "limits",
          "createdAt",
          "admins"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Role id."
          },
          "name": {
            "type": "string",
            "description": "Role name."
          },
          "builtin": {
            "type": "boolean",
            "description": "True for the built-in admin and reseller roles, which cannot be edited."
          },
          "perms": {
            "$ref": "#/components/schemas/Perms"
          },
          "limits": {
            "$ref": "#/components/schemas/RoleLimits"
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds; 0 for built-in roles."
          },
          "admins": {
            "type": "integer",
            "description": "How many accounts use the role."
          }
        }
      },
      "RoleInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "1-40 characters."
          },
          "perms": {
            "$ref": "#/components/schemas/Perms"
          },
          "limits": {
            "$ref": "#/components/schemas/RoleLimits"
          }
        }
      },
      "PermSpec": {
        "type": "object",
        "required": [
          "key",
          "scoped"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Permission key, e.g. clients.view."
          },
          "scoped": {
            "type": "boolean",
            "description": "True when none/own/all applies; false when only none/all does."
          }
        }
      },
      "LimitOverrides": {
        "type": "object",
        "properties": {
          "maxClients": {
            "type": "integer",
            "description": "Client cap; 0 means no cap."
          },
          "allowedGroups": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids the account may use."
          },
          "allowedInbounds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbound ids the account may use."
          },
          "requireGroup": {
            "type": "boolean",
            "description": "True when clients must join an allowed group instead of hand-picked inbounds."
          },
          "maxClientTraffic": {
            "type": "integer",
            "description": "Per-client traffic cap in bytes; 0 means no cap."
          },
          "maxClientDays": {
            "type": "integer",
            "description": "Per-client lifetime in days; 0 means no cap."
          },
          "maxDevices": {
            "type": "integer",
            "description": "Per-client device cap; 0 means no cap."
          },
          "allowUnlimited": {
            "type": "boolean",
            "description": "True when the account may create clients without a traffic cap."
          }
        }
      },
      "RoleLimits": {
        "type": "object",
        "required": [
          "maxClients",
          "allowedGroups",
          "allowedInbounds",
          "requireGroup",
          "maxClientTraffic",
          "maxClientDays",
          "maxDevices",
          "allowUnlimited",
          "trafficLimit"
        ],
        "properties": {
          "maxClients": {
            "type": "integer",
            "description": "Client cap; 0 means no cap."
          },
          "allowedGroups": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids the account may use."
          },
          "allowedInbounds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbound ids the account may use."
          },
          "requireGroup": {
            "type": "boolean",
            "description": "True when clients must join an allowed group instead of hand-picked inbounds."
          },
          "maxClientTraffic": {
            "type": "integer",
            "description": "Per-client traffic cap in bytes; 0 means no cap."
          },
          "maxClientDays": {
            "type": "integer",
            "description": "Per-client lifetime in days; 0 means no cap."
          },
          "maxDevices": {
            "type": "integer",
            "description": "Per-client device cap; 0 means no cap."
          },
          "allowUnlimited": {
            "type": "boolean",
            "description": "True when the account may create clients without a traffic cap."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Default traffic pool, in bytes, for new accounts on this role; 0 means unlimited."
          }
        }
      },
      "RoleList": {
        "type": "object",
        "required": [
          "items",
          "perms"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Role"
            },
            "description": "Every role, built-in first."
          },
          "perms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PermSpec"
            },
            "description": "The permission keys a role may grant."
          }
        }
      },
      "Overview": {
        "type": "object",
        "required": [
          "clients",
          "sessions",
          "throughput",
          "hourly",
          "daily",
          "top",
          "attention",
          "attentionTotal",
          "license",
          "publicHost"
        ],
        "properties": {
          "clients": {
            "$ref": "#/components/schemas/ClientCounts"
          },
          "sessions": {
            "type": "integer",
            "description": "Open sessions the admin can see."
          },
          "throughput": {
            "$ref": "#/components/schemas/Throughput"
          },
          "hourly": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsagePoint"
            },
            "description": "Traffic of the last 24 hours, one point per hour."
          },
          "daily": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsagePoint"
            },
            "description": "Traffic of the last 30 days, one point per day in the viewer's time zone."
          },
          "top": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TopUser"
            },
            "description": "The busiest clients of the last 30 days."
          },
          "attention": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attention"
            },
            "description": "At most six clients that need attention."
          },
          "attentionTotal": {
            "type": "integer",
            "description": "How many clients need attention in all."
          },
          "license": {
            "type": "object",
            "description": "The license status the panel shows on its License page. Its fields are internal and may change between versions; do not depend on them."
          },
          "publicHost": {
            "type": "string",
            "description": "The address written into client profiles."
          },
          "account": {
            "$ref": "#/components/schemas/AccountPlan"
          },
          "instances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OverviewInstance"
            },
            "description": "Inbounds, present when the admin may view them."
          },
          "system": {
            "$ref": "#/components/schemas/SystemInfo"
          },
          "systemName": {
            "type": "string",
            "description": "Host system name, present when the admin may view nodes."
          }
        }
      },
      "Attention": {
        "type": "object",
        "required": [
          "id",
          "name",
          "reason",
          "expireAt",
          "used",
          "limit"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Client id."
          },
          "name": {
            "type": "string",
            "description": "Client name."
          },
          "reason": {
            "type": "string",
            "enum": [
              "expires",
              "quota",
              "expired",
              "limited"
            ],
            "description": "Why the client needs attention."
          },
          "expireAt": {
            "type": "integer",
            "description": "Client end in Unix seconds; 0 means never."
          },
          "used": {
            "type": "integer",
            "description": "Traffic used, in bytes."
          },
          "limit": {
            "type": "integer",
            "description": "Traffic limit in bytes; 0 means unlimited."
          }
        }
      },
      "Throughput": {
        "type": "object",
        "required": [
          "up",
          "down"
        ],
        "properties": {
          "up": {
            "type": "number",
            "description": "Upload rate of the visible sessions, in bytes per second."
          },
          "down": {
            "type": "number",
            "description": "Download rate of the visible sessions, in bytes per second."
          }
        }
      },
      "TopUser": {
        "type": "object",
        "required": [
          "clientId",
          "name",
          "up",
          "down"
        ],
        "properties": {
          "clientId": {
            "type": "integer",
            "description": "Client id."
          },
          "name": {
            "type": "string",
            "description": "Client name."
          },
          "up": {
            "type": "integer",
            "description": "Bytes uploaded in the period."
          },
          "down": {
            "type": "integer",
            "description": "Bytes downloaded in the period."
          }
        }
      },
      "OverviewInstance": {
        "type": "object",
        "required": [
          "id",
          "name",
          "proto",
          "port",
          "enabled",
          "active",
          "link"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Inbound id."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "proto": {
            "type": "string",
            "description": "Transport, e.g. udp or tcp."
          },
          "port": {
            "type": "integer",
            "description": "Port the inbound listens on."
          },
          "enabled": {
            "type": "boolean",
            "description": "True when the inbound is switched on."
          },
          "active": {
            "type": "boolean",
            "description": "True when a deployment is running."
          },
          "link": {
            "$ref": "#/components/schemas/LinkState"
          }
        }
      },
      "SystemInfo": {
        "type": "object",
        "required": [
          "hostname",
          "os",
          "cpus",
          "cpu",
          "load1",
          "memTotal",
          "memUsed",
          "diskTotal",
          "diskUsed",
          "netRx",
          "netTx",
          "uptime",
          "goroutines"
        ],
        "properties": {
          "hostname": {
            "type": "string",
            "description": "Host name."
          },
          "os": {
            "type": "string",
            "description": "Operating system."
          },
          "cpus": {
            "type": "integer",
            "description": "Number of CPUs."
          },
          "cpu": {
            "type": "number",
            "description": "CPU use in percent, 0-100."
          },
          "load1": {
            "type": "number",
            "description": "One-minute load average."
          },
          "memTotal": {
            "type": "integer",
            "description": "Total memory in bytes."
          },
          "memUsed": {
            "type": "integer",
            "description": "Used memory in bytes."
          },
          "diskTotal": {
            "type": "integer",
            "description": "Total disk space of the data directory, in bytes."
          },
          "diskUsed": {
            "type": "integer",
            "description": "Used disk space of the data directory, in bytes."
          },
          "netRx": {
            "type": "number",
            "description": "Receive rate in bytes per second."
          },
          "netTx": {
            "type": "number",
            "description": "Send rate in bytes per second."
          },
          "uptime": {
            "type": "integer",
            "description": "Host uptime in seconds."
          },
          "goroutines": {
            "type": "integer",
            "description": "Goroutines running in the panel."
          }
        }
      },
      "Release": {
        "type": "object",
        "required": [
          "tag",
          "name",
          "notes",
          "prerelease",
          "published"
        ],
        "properties": {
          "tag": {
            "type": "string",
            "description": "Release tag, e.g. v1.2.3."
          },
          "name": {
            "type": "string",
            "description": "Release title."
          },
          "notes": {
            "type": "string",
            "description": "Release notes as plain text."
          },
          "prerelease": {
            "type": "boolean",
            "description": "True for a beta release."
          },
          "published": {
            "type": "integer",
            "description": "Publication time in Unix seconds."
          }
        }
      },
      "UpdateInfo": {
        "type": "object",
        "required": [
          "current",
          "channel",
          "auto",
          "autoHour",
          "newer",
          "recent",
          "checkedAt",
          "timezone"
        ],
        "properties": {
          "current": {
            "type": "string",
            "description": "Version the panel runs."
          },
          "channel": {
            "type": "string",
            "enum": [
              "stable",
              "beta"
            ],
            "description": "Release channel."
          },
          "auto": {
            "type": "string",
            "enum": [
              "off",
              "notify",
              "auto"
            ],
            "description": "What to do with a new release."
          },
          "autoHour": {
            "type": "integer",
            "description": "Local hour for automatic installs."
          },
          "latest": {
            "$ref": "#/components/schemas/Release"
          },
          "newer": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Release"
            },
            "description": "Releases newer than current, newest first."
          },
          "recent": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Release"
            },
            "description": "Recently published releases."
          },
          "checkedAt": {
            "type": "integer",
            "description": "Last check in Unix seconds; 0 means never."
          },
          "checkError": {
            "type": "string",
            "description": "Why the last check failed."
          },
          "installing": {
            "type": "string",
            "description": "Tag being installed, while an install runs."
          },
          "step": {
            "type": "string",
            "description": "Where the running install is."
          },
          "lastError": {
            "type": "string",
            "description": "Why the last install failed."
          },
          "timezone": {
            "type": "string",
            "description": "Time zone used for autoHour."
          },
          "source": {
            "type": "string",
            "enum": [
              "mirror",
              "github"
            ],
            "description": "Where the last check read releases from."
          }
        }
      },
      "NodeUpdateState": {
        "type": "object",
        "required": [
          "target",
          "step",
          "updatedAt"
        ],
        "properties": {
          "target": {
            "type": "string",
            "description": "Tag being installed on the node."
          },
          "step": {
            "type": "string",
            "description": "Where the node's install is: running, complete or failed."
          },
          "error": {
            "type": "string",
            "description": "Why the install failed."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last progress report in Unix seconds."
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "required": [
          "id",
          "adminId",
          "adminName",
          "action",
          "target",
          "detail",
          "ip",
          "at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Entry id; use it as the next before cursor."
          },
          "adminId": {
            "type": "integer",
            "description": "Admin who acted; 0 for the panel itself."
          },
          "adminName": {
            "type": "string",
            "description": "Name of that admin."
          },
          "action": {
            "type": "string",
            "description": "What was done, e.g. client.create."
          },
          "target": {
            "type": "string",
            "description": "What it was done to."
          },
          "detail": {
            "type": "string",
            "description": "Extra detail for the entry."
          },
          "ip": {
            "type": "string",
            "description": "Address the request came from."
          },
          "at": {
            "type": "integer",
            "description": "When it happened, in Unix seconds."
          }
        }
      },
      "Client": {
        "type": "object",
        "required": [
          "id",
          "ownerId",
          "name",
          "note",
          "contact",
          "enabled",
          "trafficLimit",
          "usedUp",
          "usedDown",
          "lifetimeUp",
          "lifetimeDown",
          "expireAt",
          "expireAfterDays",
          "firstUsedAt",
          "maxDevices",
          "resetStrategy",
          "lastResetAt",
          "staticIp",
          "subToken",
          "certSerial",
          "instances",
          "createdAt",
          "updatedAt",
          "lastOnlineAt",
          "status",
          "online",
          "subUrl",
          "pending",
          "groups"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Client id."
          },
          "ownerId": {
            "type": "integer",
            "description": "Admin that owns the client."
          },
          "name": {
            "type": "string",
            "description": "Client name; also the OpenVPN user name."
          },
          "note": {
            "type": "string",
            "description": "Free text, at most 500 UTF-8 bytes."
          },
          "contact": {
            "type": "string",
            "description": "Free text, at most 120 UTF-8 bytes."
          },
          "enabled": {
            "type": "boolean",
            "description": "False when the client is switched off."
          },
          "password": {
            "type": "string",
            "description": "The client's VPN password."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Traffic limit in bytes; 0 means unlimited."
          },
          "usedUp": {
            "type": "integer",
            "description": "Bytes uploaded in the current traffic period."
          },
          "usedDown": {
            "type": "integer",
            "description": "Bytes downloaded in the current traffic period."
          },
          "lifetimeUp": {
            "type": "integer",
            "description": "Bytes uploaded since creation, never reset."
          },
          "lifetimeDown": {
            "type": "integer",
            "description": "Bytes downloaded since creation, never reset."
          },
          "expireAt": {
            "type": "integer",
            "description": "End date in Unix seconds; 0 means never."
          },
          "expireAfterDays": {
            "type": "integer",
            "description": "Lifetime in days counted from the first connection; 0 means none."
          },
          "firstUsedAt": {
            "type": "integer",
            "description": "When the client first connected, in Unix seconds; 0 means never."
          },
          "maxDevices": {
            "type": "integer",
            "description": "Device cap; 0 means no cap."
          },
          "resetStrategy": {
            "type": "string",
            "enum": [
              "none",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "When used traffic resets."
          },
          "lastResetAt": {
            "type": "integer",
            "description": "Last traffic reset in Unix seconds."
          },
          "staticIp": {
            "type": "string",
            "description": "Fixed tunnel address; empty means the next free one."
          },
          "subToken": {
            "type": "string",
            "description": "Secret in the client's subscription link."
          },
          "certSerial": {
            "type": "string",
            "description": "Serial of the client certificate."
          },
          "instances": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbounds the client may use; empty means all it is entitled to."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last change in Unix seconds."
          },
          "lastOnlineAt": {
            "type": "integer",
            "description": "Last connection in Unix seconds; 0 means never."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "expired",
              "limited"
            ],
            "description": "Why the client may or may not connect."
          },
          "online": {
            "type": "integer",
            "description": "Open sessions of the client."
          },
          "subUrl": {
            "type": "string",
            "description": "The client's subscription link."
          },
          "owner": {
            "type": "string",
            "description": "Username of the owner."
          },
          "pending": {
            "type": "boolean",
            "description": "True while the expiry clock has not started (no connection yet)."
          },
          "groups": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids the client belongs to."
          },
          "ownerBlock": {
            "type": "string",
            "description": "Why the owner's own state blocks the client: disabled, expired or traffic."
          }
        }
      },
      "ClientInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "2-48 ASCII letters, digits, dot, dash, underscore or @; must start with a letter or digit."
          },
          "note": {
            "type": "string",
            "description": "Free text, at most 500 UTF-8 bytes."
          },
          "contact": {
            "type": "string",
            "description": "Free text, at most 120 UTF-8 bytes."
          },
          "enabled": {
            "type": "boolean",
            "description": "True to enable the client. Omitted or false creates a disabled client.",
            "default": false
          },
          "password": {
            "type": "string",
            "description": "4-64 characters without spaces or quotes; empty on create generates one."
          },
          "trafficLimit": {
            "type": "integer",
            "description": "Traffic limit in bytes; 0 means unlimited."
          },
          "expireAt": {
            "type": "integer",
            "description": "End date in Unix seconds; 0 means never."
          },
          "expireAfterDays": {
            "type": "integer",
            "description": "Lifetime in days counted from the first connection; 0 means none."
          },
          "maxDevices": {
            "type": "integer",
            "description": "Device cap; 0 means no cap."
          },
          "resetStrategy": {
            "type": "string",
            "enum": [
              "none",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "When used traffic resets; empty means none."
          },
          "staticIp": {
            "type": "string",
            "description": "Fixed tunnel address; empty means the next free one."
          },
          "instances": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbounds the client may use; empty means all it is entitled to."
          },
          "groups": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids; omitted on update leaves them as they are."
          },
          "ownerId": {
            "type": "integer",
            "description": "Create the client for this admin; needs clients.create for every client."
          }
        }
      },
      "ClientPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "size",
          "counts"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Client"
            },
            "description": "The requested page of clients."
          },
          "total": {
            "type": "integer",
            "description": "Clients that matched the filters."
          },
          "page": {
            "type": "integer",
            "description": "Page number, from 1."
          },
          "size": {
            "type": "integer",
            "description": "Page size, 1-500 (50 when out of range)."
          },
          "counts": {
            "$ref": "#/components/schemas/ClientCounts"
          }
        }
      },
      "ClientCounts": {
        "type": "object",
        "additionalProperties": {
          "type": "integer"
        },
        "description": "How many clients are in each bucket: all (total on the dashboard), active, online, expiring, limited, expired and disabled, plus pending on the dashboard."
      },
      "ClientDetail": {
        "type": "object",
        "required": [
          "client",
          "live",
          "history",
          "profiles"
        ],
        "properties": {
          "client": {
            "$ref": "#/components/schemas/Client"
          },
          "live": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LiveSession"
            },
            "description": "Open sessions of the client."
          },
          "history": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VPNSession"
            },
            "description": "The client's last 30 closed sessions."
          },
          "cert": {
            "$ref": "#/components/schemas/ClientCert",
            "description": "The client certificate, or null when it cannot be read."
          },
          "profiles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Endpoint"
            },
            "description": "The profiles the client can download."
          }
        }
      },
      "ClientCert": {
        "type": "object",
        "required": [
          "subject",
          "serial",
          "notBefore",
          "notAfter"
        ],
        "properties": {
          "subject": {
            "type": "string",
            "description": "Common name of the certificate."
          },
          "serial": {
            "type": "string",
            "description": "Serial number in hex."
          },
          "notBefore": {
            "type": "integer",
            "description": "Start of validity in Unix seconds."
          },
          "notAfter": {
            "type": "integer",
            "description": "End of validity in Unix seconds."
          }
        }
      },
      "ClientAction": {
        "type": "object",
        "properties": {
          "days": {
            "type": "integer",
            "description": "extend: days to add."
          },
          "bytes": {
            "type": "integer",
            "description": "extend: bytes of traffic to add."
          },
          "resetUsage": {
            "type": "boolean",
            "description": "extend: also zero the used traffic."
          }
        }
      },
      "BulkCreate": {
        "type": "object",
        "required": [
          "prefix",
          "count",
          "plan"
        ],
        "properties": {
          "prefix": {
            "type": "string",
            "description": "Names are prefix followed by the number."
          },
          "start": {
            "type": "integer",
            "description": "First number in the names; 0 starts at 1."
          },
          "count": {
            "type": "integer",
            "description": "How many clients to create, 1-500."
          },
          "plan": {
            "$ref": "#/components/schemas/ClientInput"
          }
        }
      },
      "BulkCreateResult": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Client"
            },
            "description": "The clients that were created."
          },
          "error": {
            "type": "string",
            "description": "Why the rest could not be created, when only some were."
          }
        }
      },
      "BulkAction": {
        "type": "object",
        "required": [
          "ids",
          "action"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "1-1000 client ids."
          },
          "action": {
            "type": "string",
            "enum": [
              "enable",
              "disable",
              "reset",
              "extend",
              "delete",
              "kick"
            ],
            "description": "What to do with each client."
          },
          "days": {
            "type": "integer",
            "description": "extend: days to add."
          },
          "bytes": {
            "type": "integer",
            "description": "extend: bytes of traffic to add."
          }
        }
      },
      "BulkActionResult": {
        "type": "object",
        "required": [
          "done",
          "failed"
        ],
        "properties": {
          "done": {
            "type": "integer",
            "description": "How many clients changed."
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Why each client that failed did so."
          }
        }
      },
      "Group": {
        "type": "object",
        "required": [
          "id",
          "name",
          "note",
          "disabled",
          "allServers",
          "instances",
          "clients",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Group id."
          },
          "name": {
            "type": "string",
            "description": "Group name."
          },
          "note": {
            "type": "string",
            "description": "Free text, at most 200 characters."
          },
          "disabled": {
            "type": "boolean",
            "description": "True when the group gives no access."
          },
          "allServers": {
            "type": "boolean",
            "description": "True when the group includes every inbound, even ones added later."
          },
          "instances": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbound ids the group grants."
          },
          "clients": {
            "type": "integer",
            "description": "How many clients are in the group."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          }
        }
      },
      "GroupInput": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "1-40 characters."
          },
          "note": {
            "type": "string",
            "description": "Free text, at most 200 characters."
          },
          "disabled": {
            "type": "boolean",
            "description": "True to give the group no access."
          },
          "allServers": {
            "type": "boolean",
            "description": "True to include every inbound, even ones added later."
          },
          "instances": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Inbound ids the group grants."
          }
        }
      },
      "GroupList": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Group"
            },
            "description": "The groups the admin may see."
          }
        }
      },
      "GroupBulk": {
        "type": "object",
        "required": [
          "ids",
          "action"
        ],
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids."
          },
          "action": {
            "type": "string",
            "enum": [
              "enable",
              "disable",
              "delete"
            ],
            "description": "What to do with each group."
          }
        }
      },
      "GroupAssign": {
        "type": "object",
        "required": [
          "clients"
        ],
        "properties": {
          "clients": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Client ids to change."
          },
          "add": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids to add."
          },
          "remove": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Group ids to remove."
          }
        }
      },
      "LiveSession": {
        "type": "object",
        "required": [
          "sessionId",
          "instanceId",
          "instance",
          "kind",
          "nodeId",
          "node",
          "cid",
          "clientId",
          "name",
          "realAddr",
          "vpnIp",
          "platform",
          "version",
          "connectedAt",
          "up",
          "down",
          "rateUp",
          "rateDown",
          "maxDevices"
        ],
        "properties": {
          "lastHandshake": {
            "type": "integer",
            "description": "WireGuard only: last handshake in Unix seconds."
          },
          "sessionId": {
            "type": "integer",
            "description": "Session row id."
          },
          "instanceId": {
            "type": "integer",
            "description": "Inbound the session runs on."
          },
          "instance": {
            "type": "string",
            "description": "Inbound name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol of the inbound."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node the inbound runs on; 0 is this server."
          },
          "node": {
            "type": "string",
            "description": "Node name."
          },
          "cid": {
            "type": "integer",
            "description": "Protocol-level connection id, used to kick the session."
          },
          "clientId": {
            "type": "integer",
            "description": "Client id; 0 for an unknown common name."
          },
          "name": {
            "type": "string",
            "description": "Client name."
          },
          "realAddr": {
            "type": "string",
            "description": "Address the client connects from."
          },
          "vpnIp": {
            "type": "string",
            "description": "Tunnel address of the session."
          },
          "platform": {
            "type": "string",
            "description": "Client platform as reported."
          },
          "version": {
            "type": "string",
            "description": "Client software version."
          },
          "connectedAt": {
            "type": "integer",
            "description": "When the session opened, in Unix seconds."
          },
          "up": {
            "type": "integer",
            "description": "Bytes sent to the client in this session."
          },
          "down": {
            "type": "integer",
            "description": "Bytes received from the client in this session."
          },
          "rateUp": {
            "type": "number",
            "description": "Current upload rate in bytes per second."
          },
          "rateDown": {
            "type": "number",
            "description": "Current download rate in bytes per second."
          },
          "maxDevices": {
            "type": "integer",
            "description": "The client's device cap; 0 means no cap."
          }
        }
      },
      "VPNSession": {
        "type": "object",
        "required": [
          "id",
          "clientId",
          "clientName",
          "instanceId",
          "cid",
          "commonName",
          "realAddr",
          "vpnIp",
          "platform",
          "clientVersion",
          "connectedAt",
          "endedAt",
          "bytesUp",
          "bytesDown",
          "endReason",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Session row id."
          },
          "clientId": {
            "type": "integer",
            "description": "Client id; 0 for an unknown common name."
          },
          "clientName": {
            "type": "string",
            "description": "Client name."
          },
          "instanceId": {
            "type": "integer",
            "description": "Inbound the session ran on."
          },
          "cid": {
            "type": "integer",
            "description": "Protocol-level connection id."
          },
          "commonName": {
            "type": "string",
            "description": "Common name the client authenticated with."
          },
          "realAddr": {
            "type": "string",
            "description": "Address the client connected from."
          },
          "vpnIp": {
            "type": "string",
            "description": "Tunnel address of the session."
          },
          "platform": {
            "type": "string",
            "description": "Client platform as reported."
          },
          "clientVersion": {
            "type": "string",
            "description": "Client software version."
          },
          "connectedAt": {
            "type": "integer",
            "description": "When the session opened, in Unix seconds."
          },
          "endedAt": {
            "type": "integer",
            "description": "When the session closed, in Unix seconds."
          },
          "bytesUp": {
            "type": "integer",
            "description": "Bytes sent to the client."
          },
          "bytesDown": {
            "type": "integer",
            "description": "Bytes received from the client."
          },
          "endReason": {
            "type": "string",
            "description": "Why the session closed."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard",
              ""
            ],
            "description": "Protocol of the inbound; empty once the inbound is deleted."
          }
        }
      },
      "UsagePoint": {
        "type": "object",
        "required": [
          "t",
          "up",
          "down"
        ],
        "properties": {
          "t": {
            "type": "integer",
            "description": "Start of the bucket in Unix seconds."
          },
          "up": {
            "type": "integer",
            "description": "Bytes uploaded in the bucket."
          },
          "down": {
            "type": "integer",
            "description": "Bytes downloaded in the bucket."
          }
        }
      },
      "UsageSeries": {
        "type": "object",
        "required": [
          "points",
          "from",
          "to",
          "bucket",
          "offset"
        ],
        "properties": {
          "points": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsagePoint"
            },
            "description": "Traffic points, oldest first."
          },
          "from": {
            "type": "integer",
            "description": "Start of the window in Unix seconds."
          },
          "to": {
            "type": "integer",
            "description": "End of the window in Unix seconds."
          },
          "bucket": {
            "type": "integer",
            "description": "Bucket size in seconds: 3600 or 86400."
          },
          "offset": {
            "type": "integer",
            "description": "Shift applied to day buckets, in seconds, so they start at local midnight."
          }
        }
      },
      "WGDevice": {
        "type": "object",
        "required": [
          "lastHandshake",
          "endpoint",
          "id",
          "clientId",
          "slot",
          "index",
          "publicKey",
          "createdAt"
        ],
        "properties": {
          "lastHandshake": {
            "type": "integer",
            "description": "Last handshake in Unix seconds; 0 means never."
          },
          "endpoint": {
            "type": "string",
            "description": "Address the device last connected from."
          },
          "id": {
            "type": "integer",
            "description": "Device row id."
          },
          "clientId": {
            "type": "integer",
            "description": "Owner client id."
          },
          "slot": {
            "type": "integer",
            "description": "One-based device slot of the client."
          },
          "index": {
            "type": "integer",
            "description": "Panel-wide device index."
          },
          "publicKey": {
            "type": "string",
            "description": "The device's WireGuard public key."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          }
        }
      },
      "WGDeviceStatus": {
        "type": "object",
        "required": [
          "slot",
          "publicKey",
          "lastHandshake",
          "endpoint",
          "instance",
          "up",
          "down",
          "live"
        ],
        "properties": {
          "slot": {
            "type": "integer",
            "description": "One-based device slot of the client."
          },
          "publicKey": {
            "type": "string",
            "description": "The device's WireGuard public key."
          },
          "lastHandshake": {
            "type": "integer",
            "description": "Last handshake in Unix seconds; 0 means never."
          },
          "endpoint": {
            "type": "string",
            "description": "Address the device last connected from."
          },
          "instance": {
            "type": "string",
            "description": "Inbound the device is connected to, when it is live."
          },
          "instanceId": {
            "type": "integer",
            "description": "Inbound id, set on the live row only."
          },
          "up": {
            "type": "integer",
            "description": "Bytes uploaded by the device."
          },
          "down": {
            "type": "integer",
            "description": "Bytes downloaded by the device."
          },
          "live": {
            "type": "boolean",
            "description": "True when the device shook hands in the last three minutes."
          }
        }
      },
      "WGDevices": {
        "type": "object",
        "required": [
          "devices",
          "servers"
        ],
        "properties": {
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WGDeviceStatus"
            },
            "description": "One row per device slot of the client."
          },
          "servers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Endpoint"
            },
            "description": "The WireGuard inbounds the client may use."
          }
        }
      },
      "Endpoint": {
        "type": "object",
        "required": [
          "key",
          "instanceId",
          "instance",
          "kind",
          "nodeId",
          "node",
          "label",
          "host",
          "port",
          "proto",
          "ratio",
          "description",
          "authMode"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Profile key, e.g. 12-3 (inbound 12, node address 3)."
          },
          "instanceId": {
            "type": "integer",
            "description": "Inbound id."
          },
          "instance": {
            "type": "string",
            "description": "Inbound name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol of the inbound."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node id; 0 is this server."
          },
          "node": {
            "type": "string",
            "description": "Node name."
          },
          "label": {
            "type": "string",
            "description": "Address label shown to clients; may be empty."
          },
          "host": {
            "type": "string",
            "description": "Host name or address clients connect to."
          },
          "port": {
            "type": "integer",
            "description": "Port clients connect to."
          },
          "proto": {
            "type": "string",
            "description": "Transport, e.g. udp or tcp."
          },
          "ratio": {
            "type": "number",
            "description": "Traffic multiplier of the node for quota accounting."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the profile."
          },
          "authMode": {
            "type": "string",
            "enum": [
              "cert",
              "cert+pass",
              "pass"
            ],
            "description": "How clients prove themselves."
          },
          "connectUrl": {
            "type": "string",
            "description": "OpenConnect only: the address to type into the app."
          },
          "camouflage": {
            "type": "boolean",
            "description": "OpenConnect only: true when connectUrl carries a camouflage secret."
          },
          "pin": {
            "type": "string",
            "description": "OpenConnect only: pin-sha256 of the server certificate."
          },
          "trusted": {
            "type": "boolean",
            "description": "OpenConnect only: true when the client's system trusts the certificate, so no pin is needed."
          }
        }
      },
      "NodeEndpoint": {
        "type": "object",
        "required": [
          "label",
          "host",
          "port",
          "instance"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Address label shown to clients; may be empty."
          },
          "host": {
            "type": "string",
            "description": "Host name or address clients connect to."
          },
          "port": {
            "type": "integer",
            "description": "Port clients connect to; 0 means the inbound's own port."
          },
          "instance": {
            "type": "integer",
            "description": "Inbound id this address belongs to; 0 means every inbound on the node."
          }
        }
      },
      "InstanceSettings": {
        "type": "object",
        "required": [
          "authMode",
          "tlsMode",
          "dataCiphers",
          "fallbackCipher",
          "authDigest",
          "tlsMinVersion",
          "dns",
          "searchDomain",
          "redirectGateway",
          "blockOutsideDns",
          "pushRoutes",
          "ipv6",
          "network6",
          "clientToClient",
          "maxClients",
          "keepaliveInterval",
          "keepaliveTimeout",
          "tunMtu",
          "mssFix",
          "fragment",
          "sndRcvBuf",
          "compression",
          "renegSec",
          "portShare",
          "exitNotify",
          "multihome",
          "remotes",
          "remote",
          "verb",
          "nat",
          "serverCustom",
          "clientCustom",
          "description"
        ],
        "properties": {
          "authMode": {
            "type": "string",
            "enum": [
              "cert",
              "cert+pass",
              "pass"
            ],
            "description": "How clients prove themselves."
          },
          "tlsMode": {
            "type": "string",
            "enum": [
              "tls-crypt",
              "tls-auth",
              "none"
            ],
            "description": "How the control channel is protected."
          },
          "dataCiphers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Negotiated AEAD ciphers."
          },
          "fallbackCipher": {
            "type": "string",
            "description": "Cipher for pre-2.4 clients; empty means none."
          },
          "authDigest": {
            "type": "string",
            "description": "HMAC digest."
          },
          "tlsMinVersion": {
            "type": "string",
            "enum": [
              "1.2",
              "1.3"
            ],
            "description": "Oldest TLS version accepted."
          },
          "dns": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Resolvers pushed to clients."
          },
          "searchDomain": {
            "type": "string",
            "description": "DOMAIN pushed to clients."
          },
          "redirectGateway": {
            "type": "boolean",
            "description": "True to send every packet through the tunnel."
          },
          "blockOutsideDns": {
            "type": "boolean",
            "description": "True to block DNS outside the tunnel (Windows leak fix)."
          },
          "pushRoutes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "CIDRs routed through the tunnel."
          },
          "ipv6": {
            "type": "boolean",
            "description": "True to offer tunnel addresses from network6."
          },
          "network6": {
            "type": "string",
            "description": "IPv6 tunnel network in CIDR form."
          },
          "clientToClient": {
            "type": "boolean",
            "description": "True to let clients reach each other."
          },
          "maxClients": {
            "type": "integer",
            "description": "Concurrent client cap; 0 means none."
          },
          "keepaliveInterval": {
            "type": "integer",
            "description": "Keepalive interval in seconds."
          },
          "keepaliveTimeout": {
            "type": "integer",
            "description": "Keepalive timeout in seconds."
          },
          "tunMtu": {
            "type": "integer",
            "description": "MTU of the tunnel; 0 means the default."
          },
          "mssFix": {
            "type": "integer",
            "description": "MSS clamping value; 0 means off."
          },
          "fragment": {
            "type": "integer",
            "description": "UDP fragment size; 0 means off."
          },
          "sndRcvBuf": {
            "type": "integer",
            "description": "Socket buffer size; 0 means the system default."
          },
          "compression": {
            "type": "string",
            "enum": [
              "off",
              "lz4-v2",
              "lzo"
            ],
            "description": "Compression mode."
          },
          "renegSec": {
            "type": "integer",
            "description": "Key renegotiation interval in seconds."
          },
          "portShare": {
            "type": "string",
            "description": "host port to share with another service; TCP only."
          },
          "exitNotify": {
            "type": "boolean",
            "description": "True to tell clients when the server stops."
          },
          "multihome": {
            "type": "boolean",
            "description": "True to answer UDP from the address the packet arrived on."
          },
          "remotes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Remote"
            },
            "description": "Addresses clients use for this inbound; empty means the node's addresses."
          },
          "remote": {
            "type": "string",
            "description": "The first of remotes, kept for older versions."
          },
          "verb": {
            "type": "integer",
            "description": "Log verbosity, 0-11."
          },
          "nat": {
            "type": "boolean",
            "description": "True to keep a NAT rule for the tunnel network."
          },
          "serverCustom": {
            "type": "string",
            "description": "Extra server configuration lines, checked against a denylist."
          },
          "clientCustom": {
            "type": "string",
            "description": "Extra client configuration lines, checked against a denylist."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the inbound's profiles."
          },
          "oc": {
            "$ref": "#/components/schemas/OCSettings"
          },
          "wg": {
            "$ref": "#/components/schemas/WGSettings"
          }
        }
      },
      "OCSettings": {
        "type": "object",
        "required": [
          "dtls",
          "camouflage",
          "camouflageSecret",
          "camouflageRealm",
          "certSource",
          "acmeHost",
          "idleTimeout",
          "mobileIdleTimeout",
          "cookieTimeout",
          "dpd",
          "mobileDpd",
          "ciscoCompat",
          "tlsPriorities",
          "proxyProtocol",
          "serverCustom"
        ],
        "properties": {
          "dtls": {
            "type": "boolean",
            "description": "True to listen on UDP on the same port."
          },
          "camouflage": {
            "type": "boolean",
            "description": "True to answer browser probes with a decoy page; needs ocserv 1.2.0 or newer."
          },
          "camouflageSecret": {
            "type": "string",
            "description": "Secret in the connect URL: https://host:port/?SECRET."
          },
          "camouflageRealm": {
            "type": "string",
            "description": "Realm shown to browsers that probe the port."
          },
          "certSource": {
            "type": "string",
            "enum": [
              "pki",
              "panel",
              "acme"
            ],
            "description": "Where the server certificate comes from."
          },
          "acmeHost": {
            "type": "string",
            "description": "acme: the DNS name pointing at the node; empty uses its public address."
          },
          "idleTimeout": {
            "type": "integer",
            "description": "Idle timeout for desktop clients, in seconds."
          },
          "mobileIdleTimeout": {
            "type": "integer",
            "description": "Idle timeout for mobile clients, in seconds."
          },
          "cookieTimeout": {
            "type": "integer",
            "description": "How long a session cookie stays resumable, in seconds."
          },
          "dpd": {
            "type": "integer",
            "description": "Dead peer detection interval in seconds."
          },
          "mobileDpd": {
            "type": "integer",
            "description": "Dead peer detection interval for mobile clients, in seconds."
          },
          "ciscoCompat": {
            "type": "boolean",
            "description": "True to accept old Cisco clients."
          },
          "tlsPriorities": {
            "type": "string",
            "description": "GnuTLS priority string."
          },
          "proxyProtocol": {
            "type": "boolean",
            "description": "True to accept PROXY protocol headers from a relay."
          },
          "serverCustom": {
            "type": "string",
            "description": "Extra ocserv.conf lines, checked against a denylist."
          }
        }
      },
      "WGSettings": {
        "type": "object",
        "required": [
          "publicKey",
          "obfuscation",
          "amnezia",
          "mtu",
          "keepalive",
          "allowedIPs"
        ],
        "properties": {
          "privateKey": {
            "type": "string",
            "description": "Server private key; never returned by the API."
          },
          "publicKey": {
            "type": "string",
            "description": "Server public key."
          },
          "obfuscation": {
            "type": "string",
            "description": "Obfuscation mode; empty means plain WireGuard."
          },
          "amnezia": {
            "$ref": "#/components/schemas/Amnezia"
          },
          "mtu": {
            "type": "integer",
            "description": "Tunnel MTU."
          },
          "keepalive": {
            "type": "integer",
            "description": "Persistent keepalive in seconds; 0 means off."
          },
          "allowedIPs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "CIDRs routed through the tunnel."
          },
          "filenameTemplate": {
            "type": "string",
            "description": "Optional client download filename for this inbound. A fixed name or a template using {username}, {device} and {inbound}; empty keeps automatic naming. Applies to WireGuard and AmneziaWG downloads."
          }
        }
      },
      "Amnezia": {
        "type": "object",
        "required": [
          "jc",
          "jmin",
          "jmax",
          "s1",
          "s2",
          "h1",
          "h2",
          "h3",
          "h4"
        ],
        "properties": {
          "jc": {
            "type": "integer",
            "description": "Junk packet count."
          },
          "jmin": {
            "type": "integer",
            "description": "Minimum junk packet size."
          },
          "jmax": {
            "type": "integer",
            "description": "Maximum junk packet size."
          },
          "s1": {
            "type": "integer",
            "description": "Init packet junk size."
          },
          "s2": {
            "type": "integer",
            "description": "Response packet junk size."
          },
          "h1": {
            "type": "integer",
            "description": "Init packet header type."
          },
          "h2": {
            "type": "integer",
            "description": "Response packet header type."
          },
          "h3": {
            "type": "integer",
            "description": "Cookie reply header type."
          },
          "h4": {
            "type": "integer",
            "description": "Transport header type."
          },
          "i1": {
            "type": "string",
            "description": "Special init packet, first variant."
          },
          "i2": {
            "type": "string",
            "description": "Special init packet, second variant."
          },
          "i3": {
            "type": "string",
            "description": "Special init packet, third variant."
          },
          "i4": {
            "type": "string",
            "description": "Special init packet, fourth variant."
          },
          "i5": {
            "type": "string",
            "description": "Special init packet, fifth variant."
          },
          "j1": {
            "type": "string",
            "description": "Special junk packet, first variant."
          },
          "j2": {
            "type": "string",
            "description": "Special junk packet, second variant."
          },
          "j3": {
            "type": "string",
            "description": "Special junk packet, third variant."
          },
          "itime": {
            "type": "integer",
            "description": "Init packet time field."
          }
        }
      },
      "Remote": {
        "type": "object",
        "required": [
          "label",
          "host",
          "port"
        ],
        "properties": {
          "label": {
            "type": "string",
            "description": "Label shown to clients; may be empty."
          },
          "host": {
            "type": "string",
            "description": "Host name or address."
          },
          "port": {
            "type": "integer",
            "description": "Port; 0 means the inbound's own port."
          }
        }
      },
      "Instance": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "nodeId",
          "name",
          "enabled",
          "proto",
          "port",
          "network",
          "settings",
          "mgmtPort",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "displayName": {
            "type": "string",
            "description": "Name shown instead of name, when set."
          },
          "parentId": {
            "type": "integer",
            "description": "For a deployment: the id of the inbound it belongs to."
          },
          "id": {
            "type": "integer",
            "description": "Inbound id."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol; fixed at creation."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node it runs on; 0 is this server and -1 means not assigned yet."
          },
          "name": {
            "type": "string",
            "description": "Inbound name; also the service name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True when the inbound is switched on."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port the inbound listens on."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          },
          "mgmtPort": {
            "type": "integer",
            "description": "Management port of the tunnel process."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last change in Unix seconds."
          }
        }
      },
      "InstanceInput": {
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string",
            "description": "Name shown instead of name, when set."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol; fixed after creation, empty means openvpn."
          },
          "nodeId": {
            "type": "integer",
            "description": "Where the inbound starts; assigning a node is a separate call."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True to switch the inbound on."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port to listen on."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          }
        }
      },
      "InstanceView": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "nodeId",
          "name",
          "enabled",
          "proto",
          "port",
          "network",
          "settings",
          "mgmtPort",
          "createdAt",
          "updatedAt",
          "active",
          "state",
          "link",
          "pool",
          "clients"
        ],
        "properties": {
          "displayName": {
            "type": "string",
            "description": "Name shown instead of name, when set."
          },
          "parentId": {
            "type": "integer",
            "description": "For a deployment: the id of the inbound it belongs to."
          },
          "id": {
            "type": "integer",
            "description": "Inbound id."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol; fixed at creation."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node it runs on; 0 is this server and -1 means not assigned yet."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True when the inbound is switched on."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port the inbound listens on."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          },
          "mgmtPort": {
            "type": "integer",
            "description": "Management port of the tunnel process."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last change in Unix seconds."
          },
          "active": {
            "type": "boolean",
            "description": "True when the tunnel process runs."
          },
          "state": {
            "type": "string",
            "description": "State the service manager reports, e.g. active or failed."
          },
          "link": {
            "$ref": "#/components/schemas/LinkState"
          },
          "pool": {
            "$ref": "#/components/schemas/PoolView"
          },
          "clients": {
            "type": "integer",
            "description": "Clients that may use the inbound."
          },
          "cert": {
            "$ref": "#/components/schemas/CertState"
          },
          "deployments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            },
            "description": "Where the inbound is deployed, for an inbound with several nodes."
          }
        }
      },
      "PoolView": {
        "type": "object",
        "required": [
          "server",
          "start",
          "end",
          "staticStart",
          "staticEnd"
        ],
        "properties": {
          "server": {
            "type": "string",
            "description": "Tunnel address of the server."
          },
          "start": {
            "type": "string",
            "description": "First address handed to clients."
          },
          "end": {
            "type": "string",
            "description": "Last address handed to clients."
          },
          "staticStart": {
            "type": "string",
            "description": "First address for clients with a fixed address."
          },
          "staticEnd": {
            "type": "string",
            "description": "Last address for clients with a fixed address."
          }
        }
      },
      "LinkState": {
        "type": "object",
        "required": [
          "connected",
          "online"
        ],
        "properties": {
          "connected": {
            "type": "boolean",
            "description": "True when the panel's management link is open."
          },
          "error": {
            "type": "string",
            "description": "Why the link is down."
          },
          "version": {
            "type": "string",
            "description": "Version of the server the link reports."
          },
          "online": {
            "type": "integer",
            "description": "Sessions the link reports."
          }
        }
      },
      "CertState": {
        "type": "object",
        "required": [
          "source"
        ],
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "pki",
              "acme"
            ],
            "description": "Where the certificate comes from."
          },
          "host": {
            "type": "string",
            "description": "acme: the certified name or address."
          },
          "notAfter": {
            "type": "integer",
            "description": "Expiry in Unix seconds; 0 means unknown."
          },
          "lastError": {
            "type": "string",
            "description": "Why the last issue or renewal failed."
          },
          "servingFallback": {
            "type": "boolean",
            "description": "True while an acme inbound serves the Ovira certificate because no trusted one could be obtained."
          },
          "pin": {
            "type": "string",
            "description": "pin-sha256 of the certificate served right now."
          }
        }
      },
      "OCDefaults": {
        "type": "object",
        "required": [
          "settings",
          "supported",
          "installed",
          "version",
          "installing",
          "installError",
          "camouflageMin",
          "camouflageOk",
          "certSource",
          "panelCert",
          "acme"
        ],
        "properties": {
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          },
          "supported": {
            "type": "boolean",
            "description": "False when the node must be updated before it can run OpenConnect."
          },
          "reason": {
            "type": "string",
            "description": "Why OpenConnect is not supported there."
          },
          "installed": {
            "type": "boolean",
            "description": "True when ocserv is installed."
          },
          "version": {
            "type": "string",
            "description": "Installed ocserv version; empty when unknown or missing."
          },
          "installing": {
            "type": "boolean",
            "description": "True while a local install runs."
          },
          "installError": {
            "type": "string",
            "description": "Why the last local install failed."
          },
          "camouflageMin": {
            "type": "string",
            "description": "Oldest ocserv version that supports camouflage."
          },
          "camouflageOk": {
            "type": "boolean",
            "description": "False when the host is known to be too old for camouflage."
          },
          "certSource": {
            "type": "string",
            "enum": [
              "pki",
              "panel",
              "acme"
            ],
            "description": "Default certificate source for a new inbound there."
          },
          "panelCert": {
            "type": "boolean",
            "description": "True when the panel has a certificate it can share."
          },
          "acme": {
            "type": "boolean",
            "description": "True when the node can get Let's Encrypt certificates itself."
          }
        }
      },
      "InstanceList": {
        "type": "object",
        "required": [
          "items",
          "system",
          "openvpn",
          "publicHost"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InstanceView"
            },
            "description": "The inbounds the admin may see."
          },
          "system": {
            "type": "string",
            "description": "Name of the host system."
          },
          "openvpn": {
            "type": "string",
            "description": "OpenVPN version installed on the panel host."
          },
          "publicHost": {
            "type": "string",
            "description": "The address written into client profiles."
          }
        }
      },
      "InstanceBrief": {
        "type": "object",
        "required": [
          "id",
          "technicalName",
          "nodeId",
          "node",
          "name",
          "proto",
          "port",
          "enabled",
          "authMode",
          "description",
          "network",
          "staticStart",
          "staticEnd",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Inbound (or deployment) id."
          },
          "parentId": {
            "type": "integer",
            "description": "For a deployment: the id of the inbound it belongs to."
          },
          "technicalName": {
            "type": "string",
            "description": "Name of the tunnel service."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node it runs on; 0 is this server."
          },
          "node": {
            "type": "string",
            "description": "Node name."
          },
          "name": {
            "type": "string",
            "description": "Inbound name, or the parent's name for a deployment."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port the inbound listens on."
          },
          "enabled": {
            "type": "boolean",
            "description": "True when the inbound is switched on."
          },
          "authMode": {
            "type": "string",
            "enum": [
              "cert",
              "cert+pass",
              "pass"
            ],
            "description": "How clients prove themselves."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the profiles."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "staticStart": {
            "type": "string",
            "description": "First address for clients with a fixed address."
          },
          "staticEnd": {
            "type": "string",
            "description": "Last address for clients with a fixed address."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol."
          }
        }
      },
      "InstanceDetail": {
        "type": "object",
        "required": [
          "instance",
          "config",
          "path"
        ],
        "properties": {
          "instance": {
            "$ref": "#/components/schemas/InstanceView"
          },
          "config": {
            "type": "string",
            "description": "The configuration file rendered for this instance."
          },
          "path": {
            "type": "string",
            "description": "Where the configuration lives on the host."
          },
          "shared": {
            "$ref": "#/components/schemas/InstanceInput"
          },
          "overrides": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/DeploymentOverride"
            },
            "description": "Per-node overrides keyed by node id, for an inbound with several nodes."
          },
          "deployments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            },
            "description": "Where the inbound is deployed."
          },
          "configError": {
            "type": "string",
            "description": "Why the configuration could not be rendered."
          }
        }
      },
      "InstanceResult": {
        "type": "object",
        "required": [
          "instance"
        ],
        "properties": {
          "instance": {
            "$ref": "#/components/schemas/InstanceView"
          },
          "warning": {
            "type": "string",
            "description": "What did not finish, when the change partly applied."
          }
        }
      },
      "InstanceAccessView": {
        "type": "object",
        "required": [
          "groups",
          "ownListClients"
        ],
        "properties": {
          "groups": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Group"
            },
            "description": "Groups and the inbounds they grant."
          },
          "ownListClients": {
            "type": "integer",
            "description": "Clients with their own inbound list instead of a group."
          }
        }
      },
      "NodeView": {
        "type": "object",
        "required": [
          "id",
          "name",
          "kind",
          "enabled",
          "address",
          "apiPort",
          "certFp",
          "ratio",
          "endpoints",
          "note",
          "trafficUp",
          "trafficDown",
          "createdAt",
          "updatedAt",
          "online",
          "lastSeen",
          "instances",
          "sessions",
          "updateAvailable"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Node id; 0 is this server."
          },
          "name": {
            "type": "string",
            "description": "Node name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "local",
              "remote"
            ],
            "description": "local for the panel's own server, remote for another one."
          },
          "enabled": {
            "type": "boolean",
            "description": "True when the panel may use the node."
          },
          "address": {
            "type": "string",
            "description": "Where the panel reaches the node's API."
          },
          "apiPort": {
            "type": "integer",
            "description": "Port of the node's API."
          },
          "certFp": {
            "type": "string",
            "description": "SHA-256 of the node's TLS certificate."
          },
          "ratio": {
            "type": "number",
            "description": "Traffic multiplier for quota accounting."
          },
          "endpoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NodeEndpoint"
            },
            "description": "Addresses clients use to reach the node."
          },
          "note": {
            "type": "string",
            "description": "Free text for the owner."
          },
          "trafficUp": {
            "type": "integer",
            "description": "Real bytes uploaded, before the multiplier."
          },
          "trafficDown": {
            "type": "integer",
            "description": "Real bytes downloaded, before the multiplier."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last change in Unix seconds."
          },
          "protocols": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ProtocolState"
            },
            "description": "Protocol package state, keyed by openvpn, ocserv or wireguard."
          },
          "online": {
            "type": "boolean",
            "description": "True when the node answered recently."
          },
          "error": {
            "type": "string",
            "description": "Why the node is unreachable."
          },
          "lastSeen": {
            "type": "integer",
            "description": "Last answer in Unix seconds; 0 means never."
          },
          "status": {
            "$ref": "#/components/schemas/NodeStatus"
          },
          "applyErrors": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-inbound apply errors reported by the node."
          },
          "instances": {
            "type": "integer",
            "description": "Inbounds on the node."
          },
          "sessions": {
            "type": "integer",
            "description": "Open sessions on the node."
          },
          "updateAvailable": {
            "type": "boolean",
            "description": "True when the node runs another panel version."
          },
          "update": {
            "$ref": "#/components/schemas/NodeUpdateState"
          },
          "ocLocal": {
            "$ref": "#/components/schemas/OCLocal"
          },
          "localProtocols": {
            "$ref": "#/components/schemas/LocalProtocols"
          },
          "localInstall": {
            "$ref": "#/components/schemas/LocalNodeInstall"
          }
        }
      },
      "NodeInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Node name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True to let the panel use the node; defaults to true on create."
          },
          "address": {
            "type": "string",
            "description": "Where the panel reaches the node's API."
          },
          "apiPort": {
            "type": "integer",
            "description": "Port of the node's API."
          },
          "key": {
            "type": "string",
            "description": "Node key from the node's installer; empty on update keeps the saved one."
          },
          "ratio": {
            "type": "number",
            "description": "Traffic multiplier for quota accounting; defaults to 1."
          },
          "endpoints": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NodeEndpoint"
            },
            "description": "Addresses clients use to reach the node."
          },
          "note": {
            "type": "string",
            "description": "Free text for the owner."
          }
        }
      },
      "NodeList": {
        "type": "object",
        "required": [
          "items",
          "allowed",
          "maxNodes",
          "defaultPort"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NodeView"
            },
            "description": "Every node, the panel's own server first."
          },
          "allowed": {
            "type": "boolean",
            "description": "True when the license includes nodes."
          },
          "maxNodes": {
            "type": "integer",
            "description": "Remote-node cap; 0 means unlimited when allowed is true. If allowed is false, remote nodes are unavailable."
          },
          "defaultPort": {
            "type": "integer",
            "description": "Port a new node's API listens on."
          }
        }
      },
      "NodeResult": {
        "type": "object",
        "required": [
          "node"
        ],
        "properties": {
          "node": {
            "$ref": "#/components/schemas/NodeView"
          }
        }
      },
      "NodeInstall": {
        "type": "object",
        "required": [
          "command",
          "panelIp",
          "script"
        ],
        "properties": {
          "command": {
            "type": "string",
            "description": "The one-line command to run on the new server."
          },
          "panelIp": {
            "type": "string",
            "description": "This panel's address, which the node is told to accept; empty when unknown."
          },
          "script": {
            "type": "string",
            "description": "Address of the node installer."
          }
        }
      },
      "LocalNodeInstall": {
        "type": "object",
        "required": [
          "installing",
          "installError"
        ],
        "properties": {
          "installing": {
            "type": "boolean",
            "description": "True while this server is being prepared."
          },
          "installError": {
            "type": "string",
            "description": "Why the last preparation failed."
          }
        }
      },
      "LocalProtocols": {
        "type": "object",
        "required": [
          "openvpn",
          "wireguard"
        ],
        "properties": {
          "openvpn": {
            "type": "string",
            "description": "OpenVPN version installed here; empty when missing."
          },
          "wireguard": {
            "type": "boolean",
            "description": "True when WireGuard can run here."
          }
        }
      },
      "OCLocal": {
        "type": "object",
        "required": [
          "version",
          "installing"
        ],
        "properties": {
          "version": {
            "type": "string",
            "description": "ocserv version installed here; empty when missing."
          },
          "installing": {
            "type": "boolean",
            "description": "True while ocserv is being installed."
          }
        }
      },
      "NodeProtocols": {
        "type": "object",
        "required": [
          "protocols",
          "references"
        ],
        "properties": {
          "protocols": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ProtocolState"
            },
            "description": "Package state, keyed by openvpn, ocserv or wireguard."
          },
          "references": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Inbounds on the node, keyed by protocol."
          }
        }
      },
      "ProtocolState": {
        "type": "object",
        "required": [
          "installed",
          "owned",
          "busy",
          "stage",
          "logs"
        ],
        "properties": {
          "installed": {
            "type": "boolean",
            "description": "True when the package is installed."
          },
          "owned": {
            "type": "boolean",
            "description": "True when the panel installed it and may remove it."
          },
          "busy": {
            "type": "boolean",
            "description": "True while an install or removal runs."
          },
          "stage": {
            "type": "string",
            "description": "Where the install is."
          },
          "error": {
            "type": "string",
            "description": "Why the last install failed."
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the install printed."
          }
        }
      },
      "NodeStatus": {
        "type": "object",
        "required": [
          "protocol",
          "version",
          "arch",
          "openvpn",
          "ocserv",
          "features",
          "dataDir",
          "group",
          "stateHash",
          "hostname",
          "os",
          "cpus",
          "cpu",
          "load1",
          "memTotal",
          "memUsed",
          "netRx",
          "netTx",
          "uptime",
          "units",
          "routes"
        ],
        "properties": {
          "protocols": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/ProtocolState"
            },
            "description": "Package state, keyed by protocol."
          },
          "updatePending": {
            "type": "boolean",
            "description": "True when an update waits for a restart."
          },
          "updateError": {
            "type": "string",
            "description": "Why the last node update failed."
          },
          "protocol": {
            "type": "integer",
            "description": "Version of the agent protocol."
          },
          "version": {
            "type": "string",
            "description": "Agent version."
          },
          "arch": {
            "type": "string",
            "description": "CPU architecture."
          },
          "openvpn": {
            "type": "string",
            "description": "OpenVPN version; empty when missing."
          },
          "ocserv": {
            "type": "string",
            "description": "ocserv version; empty when missing."
          },
          "features": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Features the node supports."
          },
          "dataDir": {
            "type": "string",
            "description": "The node's data directory."
          },
          "group": {
            "type": "string",
            "description": "Group the tunnel processes run under."
          },
          "stateHash": {
            "type": "string",
            "description": "Hash of the applied state."
          },
          "hostname": {
            "type": "string",
            "description": "Host name."
          },
          "os": {
            "type": "string",
            "description": "Operating system."
          },
          "cpus": {
            "type": "integer",
            "description": "Number of CPUs."
          },
          "cpu": {
            "type": "number",
            "description": "CPU use in percent, 0-100."
          },
          "load1": {
            "type": "number",
            "description": "One-minute load average."
          },
          "memTotal": {
            "type": "integer",
            "description": "Total memory in bytes."
          },
          "memUsed": {
            "type": "integer",
            "description": "Used memory in bytes."
          },
          "netRx": {
            "type": "number",
            "description": "Receive rate in bytes per second."
          },
          "netTx": {
            "type": "number",
            "description": "Send rate in bytes per second."
          },
          "uptime": {
            "type": "integer",
            "description": "Host uptime in seconds."
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/UnitState"
            },
            "description": "State of the tunnel services, keyed by unit name."
          },
          "routes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Route"
            },
            "description": "Routes the agent keeps for the tunnels."
          },
          "certs": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/CertState"
            },
            "description": "Certificate of each OpenConnect inbound, keyed by name."
          }
        }
      },
      "UnitState": {
        "type": "object",
        "required": [
          "active",
          "state"
        ],
        "properties": {
          "active": {
            "type": "boolean",
            "description": "True when the unit runs."
          },
          "state": {
            "type": "string",
            "description": "State the service manager reports."
          }
        }
      },
      "Route": {
        "type": "object",
        "required": [
          "prefix",
          "dev"
        ],
        "properties": {
          "prefix": {
            "type": "string",
            "description": "Network in CIDR form."
          },
          "dev": {
            "type": "string",
            "description": "Interface the route goes through."
          }
        }
      },
      "Deployment": {
        "type": "object",
        "required": [
          "nodeId",
          "node"
        ],
        "properties": {
          "nodeId": {
            "type": "integer",
            "description": "Node id."
          },
          "node": {
            "type": "string",
            "description": "Node name."
          },
          "instance": {
            "$ref": "#/components/schemas/InstanceView"
          },
          "job": {
            "$ref": "#/components/schemas/DeploymentJob"
          }
        }
      },
      "DeploymentJob": {
        "type": "object",
        "required": [
          "serviceId",
          "nodeId",
          "instanceId",
          "status",
          "stage",
          "logs",
          "updatedAt"
        ],
        "properties": {
          "serviceId": {
            "type": "integer",
            "description": "Inbound the job deploys."
          },
          "nodeId": {
            "type": "integer",
            "description": "Node the job runs on."
          },
          "instanceId": {
            "type": "integer",
            "description": "The inbound's row on that node."
          },
          "status": {
            "type": "string",
            "description": "queued, running, done or failed."
          },
          "stage": {
            "type": "string",
            "description": "Where the job is."
          },
          "error": {
            "type": "string",
            "description": "Why the job failed."
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the job printed."
          },
          "updatedAt": {
            "type": "integer",
            "description": "Last change in Unix seconds."
          }
        }
      },
      "DeploymentCheck": {
        "type": "object",
        "required": [
          "nodeId"
        ],
        "properties": {
          "nodeId": {
            "type": "integer",
            "description": "Node that was checked."
          },
          "error": {
            "type": "string",
            "description": "What would stop the deployment to that node."
          }
        }
      },
      "DeploymentOverride": {
        "type": "object",
        "properties": {
          "port": {
            "type": "integer",
            "description": "Port on this node instead of the shared one."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network on this node instead of the shared one."
          },
          "remotes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Remote"
            },
            "description": "Client addresses for this node instead of the shared ones."
          }
        }
      },
      "DeployInput": {
        "type": "object",
        "required": [
          "nodeIds"
        ],
        "properties": {
          "nodeIds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "1-100 nodes to deploy to."
          },
          "ports": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Port to use on each node, keyed by node id."
          }
        }
      },
      "DeploymentsResult": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            },
            "description": "Checked or current deployments."
          }
        }
      },
      "PreflightInput": {
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string",
            "description": "Name shown instead of name, when set."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol; empty means openvpn."
          },
          "nodeId": {
            "type": "integer",
            "description": "Ignored: nodeIds names the nodes to check."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True to switch the inbound on."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port to listen on."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          },
          "nodeIds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Nodes to check the draft against; at most 100."
          },
          "ports": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Port to check on each node, keyed by node id."
          }
        }
      },
      "PreflightResult": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeploymentCheck"
            },
            "description": "One finding per node."
          }
        }
      },
      "Settings": {
        "type": "object",
        "properties": {
          "publicHost": {
            "type": "string",
            "description": "Address written into client profiles; empty uses the request's host."
          },
          "subBaseUrl": {
            "type": "string",
            "description": "Absolute origin for subscription links; empty uses the request's origin."
          },
          "brandName": {
            "type": "string",
            "description": "Name shown on subscription pages."
          },
          "supportUrl": {
            "type": "string",
            "description": "Support link shown to clients."
          },
          "supportLabel": {
            "type": "string",
            "description": "Text of the support link."
          },
          "channelUrl": {
            "type": "string",
            "description": "Channel link shown beside support."
          },
          "panelAccent": {
            "type": "string",
            "description": "Accent preset of the admin panel."
          },
          "subTheme": {
            "type": "string",
            "enum": [
              "night",
              "aurora",
              "pop"
            ],
            "description": "Theme of the subscription page."
          },
          "subAccent": {
            "type": "string",
            "description": "Accent colour as #rrggbb; empty uses the theme's own colour."
          },
          "subProtocols": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubProtocol"
            },
            "description": "Tab order, names, notes and apps of the subscription page."
          },
          "defaultLocale": {
            "type": "string",
            "enum": [
              "en",
              "fa"
            ],
            "description": "Default interface language."
          },
          "expiryWarnDays": {
            "type": "integer",
            "description": "How many days before expiry a client needs attention."
          },
          "trafficWarnPct": {
            "type": "integer",
            "description": "Used-traffic percentage that needs attention."
          },
          "usageRetainDays": {
            "type": "integer",
            "description": "How long hourly traffic is kept, in days."
          },
          "sessionRetainDays": {
            "type": "integer",
            "description": "How long closed sessions are kept, in days."
          },
          "auditRetainDays": {
            "type": "integer",
            "description": "How long audit entries are kept, in days."
          },
          "adminSessionHours": {
            "type": "integer",
            "description": "Idle timeout of panel sign-ins, in hours."
          },
          "clientNamePrefix": {
            "type": "string",
            "description": "Suggested prefix for bulk client creation."
          },
          "updateChannel": {
            "type": "string",
            "enum": [
              "stable",
              "beta"
            ],
            "description": "Release channel."
          },
          "autoUpdate": {
            "type": "string",
            "enum": [
              "off",
              "notify",
              "auto"
            ],
            "description": "What to do with a new release."
          },
          "autoUpdateHour": {
            "type": "integer",
            "description": "Local hour for automatic installs."
          },
          "updateNodes": {
            "type": "boolean",
            "description": "True to update nodes together with the panel."
          }
        }
      },
      "SettingsView": {
        "type": "object",
        "required": [
          "settings",
          "panel",
          "running",
          "machineId",
          "system",
          "openvpn",
          "version",
          "dataDir"
        ],
        "properties": {
          "settings": {
            "$ref": "#/components/schemas/Settings"
          },
          "panel": {
            "$ref": "#/components/schemas/PanelConfig"
          },
          "running": {
            "$ref": "#/components/schemas/PanelConfig"
          },
          "machineId": {
            "type": "string",
            "description": "The machine id the license is bound to."
          },
          "system": {
            "type": "string",
            "description": "Name of the host system."
          },
          "openvpn": {
            "type": "string",
            "description": "OpenVPN version installed here."
          },
          "version": {
            "type": "string",
            "description": "Panel version."
          },
          "dataDir": {
            "type": "string",
            "description": "The panel's data directory."
          }
        }
      },
      "PanelConfig": {
        "type": "object",
        "required": [
          "listen"
        ],
        "properties": {
          "listen": {
            "type": "string",
            "description": "Address the panel listens on, e.g. :2053."
          },
          "basePath": {
            "type": "string",
            "description": "Secret path in front of the panel API."
          },
          "subPath": {
            "type": "string",
            "description": "Path of the public subscription links."
          },
          "tlsCert": {
            "type": "string",
            "description": "Certificate file; empty means plain HTTP."
          },
          "tlsKey": {
            "type": "string",
            "description": "Key file of tlsCert."
          },
          "trustProxy": {
            "type": "boolean",
            "description": "True to take the client address and scheme from X-Forwarded-* headers."
          }
        }
      },
      "PanelConfigResult": {
        "type": "object",
        "required": [
          "panel",
          "restartRequired"
        ],
        "properties": {
          "panel": {
            "$ref": "#/components/schemas/PanelConfig"
          },
          "restartRequired": {
            "type": "boolean",
            "description": "Always true; changes apply on restart."
          }
        }
      },
      "RestartResult": {
        "type": "object",
        "required": [
          "ok",
          "panel"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true."
          },
          "panel": {
            "$ref": "#/components/schemas/PanelConfig"
          }
        }
      },
      "TLSView": {
        "type": "object",
        "required": [
          "cert",
          "serving",
          "publicHost"
        ],
        "properties": {
          "cert": {
            "$ref": "#/components/schemas/CertInfo"
          },
          "serving": {
            "type": "boolean",
            "description": "True when the panel serves HTTPS."
          },
          "publicHost": {
            "type": "string",
            "description": "The address written into client profiles."
          }
        }
      },
      "TLSInput": {
        "type": "object",
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "domain",
              "ip",
              "none"
            ],
            "description": "Get a Let's Encrypt certificate for a domain or an IP, or turn HTTPS off."
          },
          "domain": {
            "type": "string",
            "description": "The name to certify; for ip it defaults to the public address."
          },
          "email": {
            "type": "string",
            "description": "Contact address of the ACME account."
          }
        }
      },
      "TLSPutResult": {
        "type": "object",
        "required": [
          "cert",
          "restartRequired"
        ],
        "properties": {
          "cert": {
            "$ref": "#/components/schemas/CertInfo"
          },
          "restartRequired": {
            "type": "boolean",
            "description": "Always true; the certificate applies on restart."
          }
        }
      },
      "CertInfo": {
        "type": "object",
        "required": [
          "mode",
          "domain",
          "email",
          "subject",
          "issuer",
          "notAfter"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "none",
              "custom",
              "domain",
              "ip"
            ],
            "description": "How the certificate is obtained."
          },
          "domain": {
            "type": "string",
            "description": "The name the certificate is for."
          },
          "email": {
            "type": "string",
            "description": "Contact address of the ACME account."
          },
          "subject": {
            "type": "string",
            "description": "Common name of the served certificate."
          },
          "issuer": {
            "type": "string",
            "description": "Issuer of the served certificate."
          },
          "notAfter": {
            "type": "integer",
            "description": "Expiry in Unix seconds; 0 means no certificate."
          },
          "lastError": {
            "type": "string",
            "description": "Why the last issue or renewal failed."
          }
        }
      },
      "TelegramBackup": {
        "type": "object",
        "required": [
          "enabled",
          "hasToken",
          "chatId",
          "intervalHours",
          "proxy",
          "lastAt",
          "lastError",
          "lastErrorAt",
          "nextAt",
          "sending",
          "entries"
        ],
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "True when scheduled backups are on."
          },
          "hasToken": {
            "type": "boolean",
            "description": "True when a bot token is saved; the token itself is never returned."
          },
          "chatId": {
            "type": "string",
            "description": "Chat that receives the backups."
          },
          "intervalHours": {
            "type": "integer",
            "description": "Hours between backups, 1-168; 24 when nothing is saved."
          },
          "proxy": {
            "type": "string",
            "description": "Proxy used to reach the Bot API; empty means none."
          },
          "lastAt": {
            "type": "integer",
            "description": "When a backup was last sent, in Unix seconds; 0 means never."
          },
          "lastError": {
            "type": "string",
            "description": "Why the last send failed."
          },
          "lastErrorAt": {
            "type": "integer",
            "description": "When the last send failed, in Unix seconds."
          },
          "nextAt": {
            "type": "integer",
            "description": "When the next backup is due, in Unix seconds; 0 means none is scheduled."
          },
          "sending": {
            "type": "boolean",
            "description": "True while a send is running."
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TelegramEntry"
            },
            "description": "Backups remembered in the chat, newest first."
          }
        }
      },
      "TelegramConfig": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "True to send scheduled backups."
          },
          "token": {
            "type": "string",
            "description": "Bot token from @BotFather; empty keeps the saved one."
          },
          "chatId": {
            "type": "string",
            "description": "Numeric chat id that receives the backups."
          },
          "intervalHours": {
            "type": "integer",
            "description": "Hours between backups, 1-168; 0 means the default of 24."
          },
          "proxy": {
            "type": "string",
            "description": "http, https or socks5 proxy URL to reach the Bot API."
          }
        }
      },
      "TelegramEntry": {
        "type": "object",
        "required": [
          "messageId",
          "fileId",
          "fileName",
          "size",
          "at"
        ],
        "properties": {
          "messageId": {
            "type": "integer",
            "description": "Message that holds the backup."
          },
          "fileId": {
            "type": "string",
            "description": "File id, used to restore the backup."
          },
          "fileName": {
            "type": "string",
            "description": "File name of the backup."
          },
          "size": {
            "type": "integer",
            "description": "Backup size in bytes."
          },
          "at": {
            "type": "integer",
            "description": "When the backup was sent, in Unix seconds."
          }
        }
      },
      "TelegramChat": {
        "type": "object",
        "required": [
          "id",
          "title",
          "type"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Chat id to save as chatId."
          },
          "title": {
            "type": "string",
            "description": "Chat title."
          },
          "type": {
            "type": "string",
            "description": "private, group, supergroup or channel."
          }
        }
      },
      "TelegramChatsInput": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "Bot token to look up; empty uses the saved one."
          }
        }
      },
      "TelegramChats": {
        "type": "object",
        "required": [
          "chats"
        ],
        "properties": {
          "chats": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TelegramChat"
            },
            "description": "Chats the bot can post in."
          }
        }
      },
      "TelegramSend": {
        "type": "object",
        "required": [
          "ok",
          "entry",
          "state"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true."
          },
          "entry": {
            "$ref": "#/components/schemas/TelegramEntry"
          },
          "state": {
            "$ref": "#/components/schemas/TelegramBackup"
          }
        }
      },
      "RestoreResult": {
        "type": "object",
        "required": [
          "ok",
          "restartRequired"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Always true."
          },
          "restartRequired": {
            "type": "boolean",
            "description": "Always true; the backup replaces the database on restart."
          }
        }
      },
      "SubInfo": {
        "type": "object",
        "required": [
          "name",
          "status",
          "up",
          "down",
          "used",
          "limit",
          "expireAt",
          "expireAfterDays",
          "pending",
          "firstUsedAt",
          "online",
          "maxDevices",
          "resetStrategy",
          "lastResetAt",
          "lastOnlineAt",
          "createdAt",
          "profiles",
          "openconnect",
          "wireguard",
          "combined",
          "brand",
          "supportUrl",
          "supportLabel",
          "channelUrl",
          "theme",
          "accent",
          "protocols",
          "locale",
          "subUrl",
          "serverTime"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Client name; also the VPN user name."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled",
              "expired",
              "limited"
            ],
            "description": "Why the client may or may not connect."
          },
          "up": {
            "type": "integer",
            "description": "Bytes uploaded in the current traffic period."
          },
          "down": {
            "type": "integer",
            "description": "Bytes downloaded in the current traffic period."
          },
          "used": {
            "type": "integer",
            "description": "up plus down, in bytes."
          },
          "limit": {
            "type": "integer",
            "description": "Traffic limit in bytes; 0 means unlimited."
          },
          "expireAt": {
            "type": "integer",
            "description": "End date in Unix seconds; 0 means never."
          },
          "expireAfterDays": {
            "type": "integer",
            "description": "Lifetime in days counted from the first connection; 0 means none."
          },
          "pending": {
            "type": "boolean",
            "description": "True while the expiry clock has not started."
          },
          "firstUsedAt": {
            "type": "integer",
            "description": "When the client first connected, in Unix seconds; 0 means never."
          },
          "online": {
            "type": "integer",
            "description": "Open sessions of the client."
          },
          "maxDevices": {
            "type": "integer",
            "description": "Device cap; 0 means no cap."
          },
          "resetStrategy": {
            "type": "string",
            "enum": [
              "none",
              "daily",
              "weekly",
              "monthly"
            ],
            "description": "When used traffic resets."
          },
          "lastResetAt": {
            "type": "integer",
            "description": "Last traffic reset in Unix seconds."
          },
          "lastOnlineAt": {
            "type": "integer",
            "description": "Last connection in Unix seconds; 0 means never."
          },
          "createdAt": {
            "type": "integer",
            "description": "Creation time in Unix seconds."
          },
          "profiles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubProfile"
            },
            "description": "OpenVPN profiles the client can download."
          },
          "openconnect": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubOpenConnect"
            },
            "description": "OpenConnect servers the client can sign in to."
          },
          "wireguard": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubWireGuard"
            },
            "description": "WireGuard configurations, one per device slot."
          },
          "combined": {
            "type": "boolean",
            "description": "True when several profiles can be merged into one file."
          },
          "brand": {
            "type": "string",
            "description": "Panel name shown on the page."
          },
          "supportUrl": {
            "type": "string",
            "description": "Support link."
          },
          "supportLabel": {
            "type": "string",
            "description": "Text of the support link."
          },
          "channelUrl": {
            "type": "string",
            "description": "Channel link."
          },
          "theme": {
            "type": "string",
            "enum": [
              "night",
              "aurora",
              "pop"
            ],
            "description": "Theme of the page."
          },
          "accent": {
            "type": "string",
            "description": "Accent colour as #rrggbb; empty uses the theme's own colour."
          },
          "protocols": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubProtocol"
            },
            "description": "Protocol tabs in display order."
          },
          "locale": {
            "type": "string",
            "enum": [
              "en",
              "fa"
            ],
            "description": "Language of the page."
          },
          "subUrl": {
            "type": "string",
            "description": "This subscription link."
          },
          "serverTime": {
            "type": "integer",
            "description": "The server's clock, in Unix seconds."
          },
          "username": {
            "type": "string",
            "description": "Sign-in name, when a server needs a password."
          },
          "password": {
            "type": "string",
            "description": "Client password, when a server needs one."
          }
        }
      },
      "SubProfile": {
        "type": "object",
        "required": [
          "key",
          "name",
          "node",
          "label",
          "proto",
          "port",
          "ratio",
          "description",
          "authMode",
          "file"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Profile key, used in the download path."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "node": {
            "type": "string",
            "description": "Node name; empty when every server is on one node."
          },
          "label": {
            "type": "string",
            "description": "Address label; may be empty."
          },
          "proto": {
            "type": "string",
            "description": "Transport, e.g. udp or tcp."
          },
          "port": {
            "type": "integer",
            "description": "Port clients connect to."
          },
          "ratio": {
            "type": "number",
            "description": "Traffic multiplier of the node."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the profile."
          },
          "authMode": {
            "type": "string",
            "enum": [
              "cert",
              "cert+pass",
              "pass"
            ],
            "description": "How the client proves itself."
          },
          "file": {
            "type": "string",
            "description": "Download file name, e.g. 12-3.ovpn."
          }
        }
      },
      "SubOpenConnect": {
        "type": "object",
        "required": [
          "kind",
          "key",
          "name",
          "node",
          "label",
          "host",
          "port",
          "proto",
          "ratio",
          "description",
          "connectUrl",
          "camouflage",
          "username",
          "password",
          "trusted"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "description": "Always ocserv."
          },
          "key": {
            "type": "string",
            "description": "Server key, used in the QR path."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "node": {
            "type": "string",
            "description": "Node name; empty when every server is on one node."
          },
          "label": {
            "type": "string",
            "description": "Address label; may be empty."
          },
          "host": {
            "type": "string",
            "description": "Host clients connect to."
          },
          "port": {
            "type": "integer",
            "description": "Port clients connect to."
          },
          "proto": {
            "type": "string",
            "description": "Transport, e.g. tcp or tcp+udp."
          },
          "ratio": {
            "type": "number",
            "description": "Traffic multiplier of the node."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the server."
          },
          "connectUrl": {
            "type": "string",
            "description": "The address to type into the app."
          },
          "camouflage": {
            "type": "boolean",
            "description": "True when connectUrl carries a camouflage secret."
          },
          "username": {
            "type": "string",
            "description": "Sign-in name."
          },
          "password": {
            "type": "string",
            "description": "Sign-in password."
          },
          "pin": {
            "type": "string",
            "description": "pin-sha256 of the server certificate, when it is not trusted."
          },
          "trusted": {
            "type": "boolean",
            "description": "True when the client's system trusts the certificate, so no pin is needed."
          }
        }
      },
      "SubWireGuard": {
        "type": "object",
        "required": [
          "key",
          "name",
          "node",
          "label",
          "slot",
          "mode",
          "description"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Server key, used in the config path."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "node": {
            "type": "string",
            "description": "Node name."
          },
          "label": {
            "type": "string",
            "description": "Address label; may be empty."
          },
          "slot": {
            "type": "integer",
            "description": "Device slot the configuration is for, from 1."
          },
          "mode": {
            "type": "string",
            "description": "Obfuscation mode; empty means plain WireGuard."
          },
          "description": {
            "type": "string",
            "description": "Text shown with the configuration."
          }
        }
      },
      "SubProtocol": {
        "type": "object",
        "required": [
          "key",
          "hidden",
          "label",
          "note",
          "customApps",
          "apps"
        ],
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "openvpn",
              "wireguard",
              "amnezia",
              "openconnect"
            ],
            "description": "Protocol the tab shows."
          },
          "hidden": {
            "type": "boolean",
            "description": "True to hide the tab."
          },
          "label": {
            "type": "string",
            "description": "Tab name; empty uses the default."
          },
          "note": {
            "type": "string",
            "description": "Plain text shown at the top of the tab, at most 500 characters."
          },
          "customApps": {
            "type": "boolean",
            "description": "True to use the apps below instead of the built-in recommendations."
          },
          "apps": {
            "$ref": "#/components/schemas/SubApps"
          }
        }
      },
      "SubApps": {
        "type": "object",
        "required": [
          "android",
          "ios",
          "desktop"
        ],
        "properties": {
          "android": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubApp"
            },
            "description": "Recommended Android apps."
          },
          "ios": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubApp"
            },
            "description": "Recommended iOS apps."
          },
          "desktop": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubApp"
            },
            "description": "Recommended desktop apps."
          }
        }
      },
      "SubApp": {
        "type": "object",
        "required": [
          "name",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "App name, at most 40 characters."
          },
          "url": {
            "type": "string",
            "description": "Download link, at most 300 characters."
          }
        }
      },
      "BackupUpload": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary",
            "description": "A .db or .db.gz backup of an Ovira panel."
          }
        }
      },
      "TelegramRestoreInput": {
        "type": "object",
        "required": [
          "fileId"
        ],
        "properties": {
          "fileId": {
            "type": "string",
            "description": "File id of a backup the bot sent to the chat."
          }
        }
      },
      "ConnectionKickInput": {
        "type": "object",
        "required": [
          "instanceId",
          "cid"
        ],
        "properties": {
          "instanceId": {
            "type": "integer",
            "description": "Inbound the connection runs on."
          },
          "cid": {
            "type": "integer",
            "description": "Protocol-level connection id from LiveSession.cid."
          }
        }
      },
      "NodeIDInput": {
        "type": "object",
        "required": [
          "nodeId"
        ],
        "properties": {
          "nodeId": {
            "type": "integer",
            "description": "Node to assign; required, 0 is the panel's own server."
          }
        }
      },
      "ControlInput": {
        "type": "object",
        "required": [
          "action"
        ],
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "start",
              "stop",
              "restart"
            ],
            "description": "What to do with the tunnel process."
          }
        }
      },
      "PasswordChangeInput": {
        "type": "object",
        "required": [
          "current",
          "new"
        ],
        "properties": {
          "current": {
            "type": "string",
            "description": "The password in use now."
          },
          "new": {
            "type": "string",
            "description": "The new password, checked for strength."
          }
        }
      },
      "CodeInput": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A code from the authenticator app."
          }
        }
      },
      "PasswordInput": {
        "type": "object",
        "required": [
          "password"
        ],
        "properties": {
          "password": {
            "type": "string",
            "description": "The account's current password, or a new one that is checked for strength."
          }
        }
      },
      "InstanceCreateInput": {
        "type": "object",
        "properties": {
          "displayName": {
            "type": "string",
            "description": "Name shown instead of name, when set."
          },
          "kind": {
            "type": "string",
            "enum": [
              "openvpn",
              "ocserv",
              "wireguard"
            ],
            "description": "Protocol; empty means openvpn."
          },
          "nodeId": {
            "type": "integer",
            "description": "Ignored on create; the inbound starts unassigned (-1). Use nodeIds to deploy after creation."
          },
          "name": {
            "type": "string",
            "description": "Inbound name."
          },
          "enabled": {
            "type": "boolean",
            "description": "True to switch the inbound on."
          },
          "proto": {
            "type": "string",
            "enum": [
              "udp",
              "tcp",
              "udp6",
              "tcp6"
            ],
            "description": "Transport."
          },
          "port": {
            "type": "integer",
            "description": "Port to listen on."
          },
          "network": {
            "type": "string",
            "description": "Tunnel network in CIDR form."
          },
          "settings": {
            "$ref": "#/components/schemas/InstanceSettings"
          },
          "accessGroups": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Groups that may use the inbound; absent grants every group."
          },
          "ownListClients": {
            "type": "boolean",
            "description": "True to let clients pick this inbound directly; absent means true."
          },
          "nodeIds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Nodes to deploy to right after creation; empty means none."
          },
          "ports": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Port to use on each deployment node, keyed by node id."
          }
        }
      },
      "InstanceLogs": {
        "type": "object",
        "required": [
          "logs"
        ],
        "properties": {
          "logs": {
            "type": "string",
            "description": "The last lines of the inbound's log."
          }
        }
      }
    }
  }
}
