{
  "openapi": "3.0.3",
  "info": {
    "title": "Proxy Browser Manager API",
    "version": "1.4.1",
    "description": "Programmatic access to profiles, browsers, Block Bypass, proxies, tenants, accounts, sessions, billing, and agents.\n\n`/v1` is an alias of `/api`. Authenticate with an API key (`Authorization: Bearer pbk_live_...` or `X-API-Key`) created in the dashboard API tab. The key inherits the account's role and tenant.\n\nOpening a browser always runs on the **key owner's** connected agent."
  },
  "servers": [
    { "url": "https://proxy.sporttickets.uk", "description": "Production" },
    { "url": "http://localhost:3000", "description": "Local" }
  ],
  "tags": [
    { "name": "Meta" },
    { "name": "API keys" },
    { "name": "Profiles" },
    { "name": "Browsers" },
    { "name": "Block Bypass" },
    { "name": "Sessions" },
    { "name": "Agents" },
    { "name": "Proxies" },
    { "name": "Tenants" },
    { "name": "Accounts" },
    { "name": "Billing" },
    { "name": "Observability" }
  ],
  "security": [{ "BearerAuth": [] }, { "ApiKeyAuth": [] }, { "SessionAuth": [] }],
  "components": {
    "securitySchemes": {
      "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "pbk_live_" },
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" },
      "SessionAuth": { "type": "apiKey", "in": "header", "name": "X-Auth-Token" }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "limit": { "type": "string" },
          "current": { "type": "number" },
          "max": { "nullable": true },
          "plan": { "type": "string" }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "username": { "type": "string" },
          "role": { "type": "string", "enum": ["admin", "tenant-admin", "tenant-user"] },
          "accountId": { "type": "integer" },
          "tenantId": { "type": "integer" },
          "authType": { "type": "string", "enum": ["session", "api-key"] },
          "plan": { "type": "string" },
          "limits": { "type": "object" },
          "usage": { "type": "object" },
          "credits": { "nullable": true },
          "qualifyingSeconds": { "type": "integer" }
        }
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "name": { "type": "string" },
          "prefix": { "type": "string" },
          "accountId": { "type": "integer" },
          "username": { "type": "string" },
          "role": { "type": "string" },
          "tenantId": { "type": "integer" },
          "createdAt": { "type": "string", "format": "date-time" },
          "lastUsedAt": { "nullable": true },
          "revokedAt": { "nullable": true },
          "key": { "type": "string", "description": "Full secret. Only present on create." }
        }
      },
      "BlockBypass": {
        "type": "object",
        "properties": {
          "club": { "nullable": true, "type": "string" },
          "label": { "type": "string" },
          "activated": { "type": "boolean" },
          "reason": { "type": "string" }
        }
      }
    }
  },
  "paths": {
    "/api/health": {
      "get": {
        "tags": ["Meta"],
        "security": [],
        "summary": "Liveness",
        "responses": { "200": { "description": "{ ok, api, server, agent, time }" } }
      }
    },
    "/api/agent-version": {
      "get": {
        "tags": ["Meta"],
        "security": [],
        "summary": "Current agent binary version",
        "responses": { "200": { "description": "{ version }" } }
      }
    },
    "/api/openapi.json": {
      "get": {
        "tags": ["Meta"],
        "security": [],
        "summary": "This OpenAPI document",
        "responses": { "200": { "description": "OpenAPI 3 JSON" } }
      }
    },
    "/api/login": {
      "post": {
        "tags": ["Meta"],
        "security": [],
        "summary": "Dashboard login (prefer API keys for scripts)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["username", "password"],
                "properties": {
                  "username": { "type": "string" },
                  "password": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "{ token, role, username, accountId, tenantId }" }, "401": { "description": "Invalid" } }
      }
    },
    "/api/logout": {
      "post": {
        "tags": ["Meta"],
        "summary": "Invalidate a dashboard session",
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/me": {
      "get": {
        "tags": ["Meta"],
        "summary": "Caller identity, plan, usage",
        "responses": { "200": { "description": "Me", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Me" } } } } }
      }
    },
    "/api/time": {
      "get": {
        "tags": ["Meta"],
        "summary": "Server clock",
        "responses": { "200": { "description": "{ now }" } }
      }
    },
    "/api/keys": {
      "get": {
        "tags": ["API keys"],
        "summary": "List API keys",
        "parameters": [
          { "name": "all", "in": "query", "schema": { "type": "string" }, "description": "Admin: 1 = every account" },
          { "name": "includeRevoked", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": { "200": { "description": "Array of keys (no secrets)" } }
      },
      "post": {
        "tags": ["API keys"],
        "summary": "Create a key (secret returned once)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name"],
                "properties": { "name": { "type": "string", "maxLength": 80 } }
              }
            }
          }
        },
        "responses": { "200": { "description": "ApiKey including `key`" } }
      }
    },
    "/api/keys/{id}": {
      "delete": {
        "tags": ["API keys"],
        "summary": "Revoke a key",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success, revokedAt }" } }
      }
    },
    "/api/profiles": {
      "get": {
        "tags": ["Profiles"],
        "summary": "List profiles visible to the caller",
        "parameters": [{ "name": "tenantId", "in": "query", "schema": { "type": "integer" }, "description": "Admin filter" }],
        "responses": { "200": { "description": "Array of profiles with insights, proxyFailed, proxyFailReason" } }
      },
      "post": {
        "tags": ["Profiles"],
        "summary": "Create a profile",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "browser": { "type": "string", "enum": ["chrome", "camoufox"] },
                  "proxyId": { "type": "integer" },
                  "blockBypassClub": { "type": "string", "nullable": true, "enum": ["arsenal", "chelsea", "tottenhamhotspur", "cp"] },
                  "shared": { "type": "boolean" },
                  "tenantId": { "type": "integer", "description": "Admin only" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Created profile plus `identity` (same object as GET /identity)" }, "400": { "description": "No proxies / fingerprints / bad club" } }
      }
    },
    "/api/profiles/{id}": {
      "get": {
        "tags": ["Profiles"],
        "summary": "One profile plus live browser state",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Profile with live { state, ip, blockBypass }, proxyFailed, proxyFailReason" }, "404": { "description": "Not found" } }
      },
      "patch": {
        "tags": ["Profiles"],
        "summary": "Rename / note / browser / sharing / Block Bypass club",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "note": { "type": "string" },
                  "browser": { "type": "string", "enum": ["chrome", "camoufox"] },
                  "shared": { "type": "boolean" },
                  "blockBypassClub": { "type": "string", "nullable": true }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Updated profile" } }
      },
      "delete": {
        "tags": ["Profiles"],
        "summary": "Delete profile (closes browser, purges agent dir)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/profiles/{id}/identity": {
      "get": {
        "tags": ["Profiles"],
        "summary": "Export fingerprint + full proxy credentials for a headless job",
        "description": "Does not open a browser. Same access as open (own + shared). For Members Manager Docker jobs that replay this identity without the agent. `fingerprint.format` is `pbm.fingerprint.v1`. `fingerprint.raw` is the stored pool record.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "200": { "description": "{ profileId, name, browser, fingerprint, proxy, updatedAt }" },
          "403": { "description": "No access" },
          "404": { "description": "Unknown profile" },
          "409": { "description": "No proxy or no fingerprint assigned" }
        }
      }
    },
    "/api/profiles/{id}/open": {
      "post": {
        "tags": ["Browsers"],
        "summary": "Open the profile on the caller's agent",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "screen", "in": "query", "schema": { "type": "string" }, "description": "Set 0 to skip proxy screening" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "club": { "type": "string", "nullable": true, "description": "Block Bypass club id, or omit/null for off" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{ success, message, blockBypass: { club, label, activated, reason, queueToken } }" },
          "400": { "description": "Agent offline / already open / burned proxy" },
          "402": { "description": "Out of credits" }
        }
      }
    },
    "/api/profiles/{id}/block-bypass": {
      "post": {
        "tags": ["Block Bypass"],
        "summary": "Re-inject Block Bypass into an already-open browser",
        "description": "Does not restart the browser. Empty pool returns 200 with activated=false and reason empty pool.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["club"],
                "properties": {
                  "club": { "type": "string", "enum": ["arsenal", "chelsea", "tottenhamhotspur", "cp"] },
                  "reload": { "type": "boolean", "default": true }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{ success, blockBypass: { club, activated, reason, injectedAt, queueToken } }" },
          "400": { "description": "Browser not open, or unknown/missing club" }
        }
      }
    },
    "/api/profiles/{id}/close": {
      "post": {
        "tags": ["Browsers"],
        "summary": "Close the browser",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/quick-open": {
      "post": {
        "tags": ["Browsers"],
        "summary": "Create a profile and open it in one step",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "browser": { "type": "string", "enum": ["chrome", "camoufox"] },
                  "club": { "type": "string", "nullable": true }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "{ success, profile, blockBypass }" } }
      }
    },
    "/api/profiles/{id}/rotate-proxy": {
      "post": {
        "tags": ["Profiles"],
        "summary": "Assign the next unused proxy (not probed)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success, oldProxyId, newProxyId }" } }
      }
    },
    "/api/profiles/{id}/test-proxy": {
      "post": {
        "tags": ["Profiles"],
        "summary": "Probe the currently assigned proxy",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ ok, ip, reason, proxyId, host }" } }
      }
    },
    "/api/profiles/{id}/reveal-proxy": {
      "post": {
        "tags": ["Profiles"],
        "summary": "Reveal host:port:user:pass after Test Proxy or Check pool failed. Logged without the password.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "200": { "description": "{ line, host, port, username, password, failReason }" },
          "403": { "description": "Proxy has not failed a test/check" }
        }
      }
    },
    "/api/profiles/{id}/replace-proxy": {
      "post": {
        "tags": ["Profiles"],
        "summary": "Replace with a verified-live unused proxy",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success, oldProxyId, newProxyId, ip, tried }" }, "503": { "description": "None live" } }
      }
    },
    "/api/profiles/{id}/diary": {
      "post": {
        "tags": ["Profiles"],
        "summary": "Append a diary memo",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": { "application/json": { "schema": { "type": "object", "properties": { "text": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "Entry" } }
      }
    },
    "/api/profiles/{id}/diary/{entryId}": {
      "delete": {
        "tags": ["Profiles"],
        "summary": "Delete a diary memo",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "entryId", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/profiles/{id}/logs": {
      "get": {
        "tags": ["Observability"],
        "summary": "IP + bandwidth; URL history for admins",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ ip, bandwidth, urls? }" } }
      }
    },
    "/api/block-bypass/clubs": {
      "get": {
        "tags": ["Block Bypass"],
        "summary": "Clubs that can be passed as `club` on open",
        "responses": { "200": { "description": "{ clubs: [{ id, label }] }" } }
      }
    },
    "/api/sessions": {
      "get": {
        "tags": ["Sessions"],
        "summary": "Session history, newest first",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer" } },
          { "name": "blockedOnly", "in": "query", "schema": { "type": "string" } },
          { "name": "tenantId", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "Array of session records" } }
      }
    },
    "/api/sessions/{id}": {
      "get": {
        "tags": ["Sessions"],
        "summary": "One session record",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Session" } }
      }
    },
    "/api/sessions/{id}/report": {
      "post": {
        "tags": ["Sessions"],
        "summary": "Report a session (no auto-refund)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": { "type": "string", "enum": ["blocked", "captcha", "slow", "other"] },
                  "note": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "{ success, reported }" } }
      }
    },
    "/api/agents": {
      "get": {
        "tags": ["Agents"],
        "summary": "Connected agents and their open browsers",
        "responses": { "200": { "description": "Array of agent statuses. Launching browsers may include phase preparing|starting." } }
      }
    },
    "/api/my-proxies": {
      "get": {
        "tags": ["Proxies"],
        "summary": "Caller's tenant pool without credentials",
        "responses": { "200": { "description": "[{ id, host, port, assigned }]" } }
      }
    },
    "/api/proxies": {
      "get": {
        "tags": ["Proxies"],
        "summary": "Tenant proxy list (tenant-admin)",
        "parameters": [{ "name": "tenantId", "in": "query", "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Proxies including credentials" } }
      }
    },
    "/api/tenants/{id}/proxies": {
      "get": {
        "tags": ["Proxies"],
        "summary": "Tenant proxy list with credentials",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Proxies" } }
      }
    },
    "/api/tenants/{id}/proxies/upload": {
      "post": {
        "tags": ["Proxies"],
        "summary": "Append host:port:user:pass lines",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": { "application/json": { "schema": { "type": "object", "properties": { "proxies": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "{ success, added, total }" } }
      }
    },
    "/api/tenants/{id}/proxies/replace": {
      "post": {
        "tags": ["Proxies"],
        "summary": "Replace the tenant proxy file",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": { "application/json": { "schema": { "type": "object", "properties": { "proxies": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "{ success, total }" } }
      }
    },
    "/api/tenants/{tid}/proxies/{id}": {
      "delete": {
        "tags": ["Proxies"],
        "summary": "Delete a proxy line (shifts later ids)",
        "parameters": [
          { "name": "tid", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "{ success, unlinkedProfiles }" } }
      }
    },
    "/api/tenants/{tid}/proxies/{id}/reveal": {
      "post": {
        "tags": ["Proxies"],
        "summary": "Reveal host:port:user:pass for a proxy that already failed Test or Check pool. Logged without the password.",
        "parameters": [
          { "name": "tid", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "{ line, host, port, username, password, failReason }" },
          "403": { "description": "Proxy has not failed a test/check, or no access" }
        }
      }
    },
    "/api/tenants/{tid}/proxies/{id}/test": {
      "post": {
        "tags": ["Proxies"],
        "summary": "Test one proxy line (any tenant member)",
        "parameters": [
          { "name": "tid", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "{ success, ip, latency, statusCode }" } }
      }
    },
    "/api/tenants/{id}/proxies/replace-unhealthy": {
      "post": {
        "tags": ["Proxies"],
        "summary": "Move expired/burned assigned profiles onto verified live spares",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reasons": { "type": "array", "items": { "type": "string" }, "description": "Default [expired, burned]" },
                  "proxyIds": { "type": "array", "items": { "type": "integer" } },
                  "profileIds": { "type": "array", "items": { "type": "integer" } }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{ replaced, skipped, remainingUnhealthy }" },
          "400": { "description": "No pool check yet" }
        }
      }
    },
    "/api/tenants/{id}/proxies/check": {
      "get": {
        "tags": ["Proxies"],
        "summary": "Tenant proxy pool wellness (last report + progress). Any member of the tenant.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "200": { "description": "{ tenantId, running, checked, total, report }. report.available.healthy is unused live-not-burned exits for new profiles / replace-proxy." }
        }
      },
      "post": {
        "tags": ["Proxies"],
        "summary": "Start a background CONNECT probe of every tenant proxy. Any member of the tenant.",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "202": { "description": "{ running, tenantId, total, startedAt }" },
          "409": { "description": "A check is already running" }
        }
      }
    },
    "/api/tenants": {
      "get": {
        "tags": ["Tenants"],
        "summary": "List tenants (scoped)",
        "responses": { "200": { "description": "Tenants with usage" } }
      },
      "post": {
        "tags": ["Tenants"],
        "summary": "Create tenant + tenant-admin account (admin)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "adminUsername": { "type": "string" },
                  "adminPassword": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Tenant + account" } }
      }
    },
    "/api/tenants/{id}": {
      "get": {
        "tags": ["Tenants"],
        "summary": "One tenant",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Tenant with usage" } }
      },
      "patch": {
        "tags": ["Tenants"],
        "summary": "Rename / plan / limits (admin)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string" },
                  "plan": { "type": "string", "enum": ["free", "payg", "starter", "pro", "custom"] },
                  "limits": { "type": "object" },
                  "qualifyingSeconds": { "nullable": true, "type": "integer" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Tenant" } }
      },
      "delete": {
        "tags": ["Tenants"],
        "summary": "Delete tenant and its data (admin)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/tenants/{id}/credits": {
      "get": {
        "tags": ["Billing"],
        "summary": "Balance + ledger",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ balance, granted, spent, ledger }" } }
      },
      "post": {
        "tags": ["Billing"],
        "summary": "Grant or revoke credits (admin)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": { "type": "integer" },
                  "note": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Credits" } }
      }
    },
    "/api/accounts": {
      "get": {
        "tags": ["Accounts"],
        "summary": "List accounts (tenant-admin)",
        "responses": { "200": { "description": "Accounts (no passwords)" } }
      },
      "post": {
        "tags": ["Accounts"],
        "summary": "Create account",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "username": { "type": "string" },
                  "password": { "type": "string" },
                  "role": { "type": "string" },
                  "tenantId": { "type": "integer" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Account including agentToken" } }
      }
    },
    "/api/accounts/{id}": {
      "get": {
        "tags": ["Accounts"],
        "summary": "One account",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Account" } }
      },
      "patch": {
        "tags": ["Accounts"],
        "summary": "Update role / tenant / disabled / password",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Account" } }
      },
      "delete": {
        "tags": ["Accounts"],
        "summary": "Delete account (revokes API keys)",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ success }" } }
      }
    },
    "/api/accounts/{id}/regenerate-token": {
      "post": {
        "tags": ["Accounts"],
        "summary": "Regenerate the agent WebSocket token",
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "{ id, agentToken }" } }
      }
    },
    "/api/settings/billing": {
      "get": {
        "tags": ["Billing"],
        "summary": "Global qualifying window (admin)",
        "responses": { "200": { "description": "{ qualifyingSeconds }" } }
      },
      "patch": {
        "tags": ["Billing"],
        "summary": "Set global qualifying window",
        "requestBody": {
          "content": { "application/json": { "schema": { "type": "object", "properties": { "qualifyingSeconds": { "type": "integer" } } } } }
        },
        "responses": { "200": { "description": "{ qualifyingSeconds }" } }
      }
    },
    "/api/billing/sessions": {
      "get": {
        "tags": ["Billing"],
        "summary": "Per-tenant qualification analytics (admin)",
        "parameters": [
          { "name": "since", "in": "query", "schema": { "type": "string" } },
          { "name": "tenantId", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "{ since, tenants }" } }
      }
    },
    "/api/reports": {
      "get": {
        "tags": ["Observability"],
        "summary": "Reported sessions grouped by exit IP (admin)",
        "responses": { "200": { "description": "{ totalReports, byExitIp }" } }
      }
    },
    "/api/proxy-reputation": {
      "get": {
        "tags": ["Observability"],
        "summary": "Learned exit-IP reputation (admin)",
        "responses": { "200": { "description": "{ threshold, tracked, burned, ips }" } }
      }
    },
    "/api/access-log": {
      "get": {
        "tags": ["Observability"],
        "summary": "HTTP API request log (last 2000, no bodies or secrets)",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer" } },
          { "name": "method", "in": "query", "schema": { "type": "string" } },
          { "name": "status", "in": "query", "schema": { "type": "integer" } },
          { "name": "path", "in": "query", "schema": { "type": "string" } },
          { "name": "username", "in": "query", "schema": { "type": "string" } },
          { "name": "authType", "in": "query", "schema": { "type": "string", "enum": ["api-key", "session"] } },
          { "name": "tenantId", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": { "200": { "description": "{ total, limit, entries }" } }
      }
    },
    "/api/activity": {
      "get": {
        "tags": ["Observability"],
        "summary": "Activity log (last 200)",
        "parameters": [{ "name": "tenantId", "in": "query", "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Log entries, newest first" } }
      }
    },
    "/api/metrics": {
      "get": {
        "tags": ["Observability"],
        "summary": "Usage meters",
        "parameters": [{ "name": "tenantId", "in": "query", "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Plan, limits, usage, bandwidth" } }
      }
    },
    "/api/urls": {
      "get": {
        "tags": ["Observability"],
        "summary": "Recent URL index (tenant-admin)",
        "parameters": [{ "name": "tenantId", "in": "query", "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Last 200 URLs" } }
      }
    }
  }
}
