{
  "openapi": "3.1.0",
  "info": {
    "title": "Kepeink Management API",
    "version": "2026-07-02",
    "description": "Stable API used by the Kepeink management console and automation clients."
  },
  "servers": [{ "url": "https://manage.kepeink.hu" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [
    { "name": "platform-admin", "description": "Operator-only; requires platform-admin session. 403 otherwise." }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer" },
      "cookieAuth": { "type": "apiKey", "in": "cookie", "name": "__Host-kepeink_session", "description": "Browser session cookie. Session-only operations reject machine kpm_ bearer tokens with 403 Forbidden." }
    },
    "schemas": {
      "APIKeyCreateRequest": {
        "type": "object",
        "required": ["label"],
        "properties": { "label": { "type": "string", "maxLength": 80 } }
      },
      "APIKeyCreateResponse": {
        "type": "object",
        "required": ["token_id", "workspace_id", "kind", "label", "role", "created_at", "is_current", "api_key"],
        "properties": {
          "token_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "kind": { "const": "machine" },
          "label": { "type": "string" },
          "role": { "enum": ["owner", "admin", "member", "viewer"] },
          "created_at": { "type": "string", "format": "date-time" },
          "is_current": { "type": "boolean" },
          "api_key": { "type": "string", "description": "Plaintext kpm_ token returned once." }
        }
      },
      "AccountDeleteRequestResponse": {
        "type": "object",
        "required": ["challenge_id"],
        "properties": { "challenge_id": { "type": "string" } }
      },
      "AccountDeleteConfirmRequest": {
        "type": "object",
        "required": ["challenge_id", "code"],
        "properties": {
          "challenge_id": { "type": "string" },
          "code": { "type": "string", "pattern": "^[0-9]{8}$" }
        }
      },
      "AccountExport": {
        "type": "object",
        "required": ["generated_at", "user", "workspaces", "tunnels", "custom_domains", "tls_advanced_interest", "credit_grants", "usage_summary", "audit_events", "audit_truncated"],
        "properties": {
          "generated_at": { "type": "string", "format": "date-time" },
          "user": { "$ref": "#/components/schemas/AccountExportUser" },
          "workspaces": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportWorkspace" } },
          "tunnels": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportTunnel" } },
          "custom_domains": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportCustomDomain" } },
          "tls_advanced_interest": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportTLSInterest" } },
          "credit_grants": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportCreditGrant" } },
          "usage_summary": { "type": "array", "items": { "$ref": "#/components/schemas/AccountExportUsageSummary" } },
          "audit_events": { "type": "array", "maxItems": 10000, "items": { "$ref": "#/components/schemas/AccountExportAuditEvent" } },
          "audit_truncated": { "type": "boolean" }
        }
      },
      "AccountExportUser": {
        "type": "object",
        "required": ["user_id", "email", "status", "created_at"],
        "properties": {
          "user_id": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "status": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "registered_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportWorkspace": {
        "type": "object",
        "required": ["workspace_id", "name", "status", "role", "created_at"],
        "properties": {
          "workspace_id": { "type": "string" },
          "name": { "type": "string" },
          "status": { "type": "string" },
          "role": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportTunnel": {
        "type": "object",
        "required": ["tunnel_id", "workspace_id", "sni", "backend_mode", "target", "enabled", "created_at", "archived"],
        "properties": {
          "tunnel_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "sni": { "type": "string" },
          "backend_mode": { "type": "string" },
          "target": { "type": "string" },
          "enabled": { "type": "boolean" },
          "created_at": { "type": "string", "format": "date-time" },
          "archived": { "type": "boolean" },
          "archived_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportCustomDomain": {
        "type": "object",
        "required": ["domain_id", "workspace_id", "hostname", "alias_hostname", "enabled", "dns_status", "cert_status", "route_status", "notification_status", "created_by_user_id", "created_at", "updated_at"],
        "properties": {
          "domain_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "hostname": { "type": "string" },
          "alias_hostname": { "type": "string" },
          "desired_tunnel_id": { "type": "string" },
          "enabled": { "type": "boolean" },
          "dns_status": { "type": "string" },
          "cert_status": { "type": "string" },
          "route_status": { "type": "string" },
          "notification_status": { "type": "string" },
          "created_by_user_id": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportTLSInterest": {
        "type": "object",
        "required": ["interest_id", "email", "user_id", "workspace_id", "created_at", "updated_at"],
        "properties": {
          "interest_id": { "type": "string" },
          "email": { "type": "string", "format": "email" },
          "user_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "tunnel_id": { "type": "string" },
          "source_ip": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportCreditGrant": {
        "type": "object",
        "required": ["workspace_id", "kind", "delta_bytes", "created_at"],
        "properties": {
          "workspace_id": { "type": "string" },
          "kind": { "type": "string" },
          "delta_bytes": { "type": "integer", "format": "int64" },
          "new_entitled_until": { "type": "string", "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "AccountExportUsageSummary": {
        "type": "object",
        "required": ["tunnel_id", "bytes_in", "bytes_out"],
        "properties": {
          "tunnel_id": { "type": "string" },
          "bytes_in": { "type": "integer", "format": "int64" },
          "bytes_out": { "type": "integer", "format": "int64" }
        }
      },
      "AccountExportAuditEvent": {
        "type": "object",
        "properties": {
          "log_id": { "type": "string" },
          "at": { "type": "string", "format": "date-time" },
          "actor_user_id": { "type": "string" },
          "actor_token_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "action": { "type": "string" },
          "resource_type": { "type": "string" },
          "resource_id": { "type": "string" },
          "detail": { "type": "object", "additionalProperties": true },
          "source_ip": { "type": "string" }
        }
      },
      "CustomDomain": {
        "type": "object",
        "required": ["domain_id", "workspace_id", "hostname", "alias_id", "alias_hostname", "enabled", "cert_source", "dns_status", "cert_status", "route_status", "notification_status", "created_by_user_id", "created_at", "updated_at", "verification_token", "subdomain_cname_value", "apex_txt_name", "apex_txt_value", "apex_a_values", "management_url"],
        "properties": {
          "domain_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "hostname": { "type": "string" },
          "alias_id": { "type": "string" },
          "alias_hostname": { "type": "string" },
          "desired_tunnel_id": { "type": "string" },
          "desired_tunnel_sni": { "type": "string" },
          "enabled": { "type": "boolean" },
          "cert_source": { "type": "string", "enum": ["cert_worker", "agent_managed_acme"], "description": "cert_worker: edge-terminated, Kepeink issues the cert. agent_managed_acme: end-to-end, the customer's agent holds the cert for the customer-owned hostname (free)." },
          "dns_status": { "type": "string", "enum": ["pending", "verified", "failed"] },
          "cert_status": { "type": "string", "enum": ["pending", "active", "failed", "renewing"] },
          "route_status": { "type": "string", "enum": ["inactive", "status_page", "active"] },
          "notification_status": { "type": "string", "enum": ["pending", "sent", "failed"] },
          "dns_verified_at": { "type": "string", "format": "date-time" },
          "cert_ready_at": { "type": "string", "format": "date-time" },
          "route_ready_at": { "type": "string", "format": "date-time" },
          "next_dns_check_at": { "type": "string", "format": "date-time" },
          "next_cert_check_at": { "type": "string", "format": "date-time" },
          "last_error": { "type": "string" },
          "created_by_user_id": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "verification_token": { "type": "string" },
          "subdomain_cname_value": { "type": "string" },
          "apex_txt_name": { "type": "string" },
          "apex_txt_value": { "type": "string" },
          "apex_a_values": { "type": "array", "items": { "type": "string" } },
          "management_url": { "type": "string", "format": "uri" }
        }
      },
      "CustomDomainListResponse": {
        "type": "object",
        "required": ["domains"],
        "properties": {
          "domains": { "type": "array", "items": { "$ref": "#/components/schemas/CustomDomain" } }
        }
      },
      "CustomDomainCreateRequest": {
        "type": "object",
        "required": ["hostname"],
        "properties": {
          "hostname": { "type": "string" },
          "desired_tunnel_id": { "type": "string" }
        }
      },
      "CustomDomainPatchRequest": {
        "type": "object",
        "properties": {
          "desired_tunnel_id": { "type": "string" },
          "enabled": { "type": "boolean" }
        }
      },
      "Tunnel": {
        "type": "object",
        "required": ["tunnel_id", "workspace_id", "sni", "public_url", "tls_mode", "cert_source", "backend_mode", "target", "enabled", "created_by_user_id", "created_at", "updated_at"],
        "properties": {
          "tunnel_id": { "type": "string" },
          "workspace_id": { "type": "string" },
          "sni": { "type": "string" },
          "public_url": { "type": "string", "format": "uri" },
          "tls_mode": { "enum": ["terminated", "passthrough"] },
          "cert_source": { "type": "string" },
          "backend_mode": { "type": "string" },
          "target": { "type": "string" },
          "agent_config": { "type": "object", "additionalProperties": true },
          "enabled": { "type": "boolean" },
          "created_by_user_id": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "bytes_in_total": { "type": "integer", "format": "int64" },
          "bytes_out_total": { "type": "integer", "format": "int64" },
          "last_observed_at": { "type": "string", "format": "date-time" },
          "agent_router_id": { "type": "string" },
          "agent_started_at": { "type": "string", "format": "date-time" },
          "agent_last_seen": { "type": "string", "format": "date-time" },
          "transport_mode": { "enum": ["plain", "inner_tls"], "description": "Live agent session transport mode. inner_tls means the agent is using pinned inner TLS because the outer network performs TLS inspection." },
          "agent_token": { "type": "string", "description": "One-time plaintext kpt_ token returned on tunnel creation or token issue only." },
          "custom_domains": { "type": "array", "items": { "$ref": "#/components/schemas/CustomDomain" } }
        }
      },
      "ErrorText": { "type": "string" }
    }
  },
  "paths": {
    "/api/v1/healthz": {
      "get": {
        "summary": "Liveness probe",
        "security": [],
        "responses": { "200": { "description": "Service is up" } }
      }
    },
    "/api/public/recaptcha-config": {
      "get": {
        "summary": "Get public reCAPTCHA site key for anonymous forms",
        "security": [],
        "responses": { "200": { "description": "Site key or empty string" } }
      }
    },
    "/api/public/page-event": {
      "post": {
        "summary": "Submit first-party UI telemetry (closed-set labels)",
        "security": [],
        "responses": { "204": { "description": "Recorded" }, "400": { "description": "Unknown label or type" } }
      }
    },
    "/api/public/takedown": {
      "post": {
        "summary": "Submit public takedown report",
        "description": "Requires reCAPTCHA Enterprise token for action takedown_submit.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url", "reporter_email", "reason_text", "recaptcha_token"],
                "properties": {
                  "url": { "type": "string", "format": "uri" },
                  "reporter_email": { "type": "string", "format": "email" },
                  "reason_text": { "type": "string", "maxLength": 4096 },
                  "recaptcha_token": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Pending email verification" },
          "400": { "description": "Bad request" },
          "403": { "description": "Captcha rejected" },
          "429": { "description": "Rate limited" },
          "503": { "description": "Captcha not configured" }
        }
      }
    },
    "/api/public/waitlist": {
      "post": {
        "summary": "Join the launch waitlist",
        "description": "Records an email that asked to be notified when signup opens. Shown by the sign-up UI after auth/start returns access_required. Requires the same reCAPTCHA Enterprise token as auth_start.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "source": { "type": "string", "description": "Attribution slug carried from the ?src= query param, if present." },
                  "recaptcha_token": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Recorded (idempotent per email)" },
          "400": { "description": "Bad request" },
          "403": { "description": "Captcha rejected" },
          "429": { "description": "Rate limited" }
        }
      }
    },
    "/api/auth/start": {
      "post": {
        "summary": "Start email magic-link/code sign-in",
        "description": "Requires reCAPTCHA Enterprise token for action auth_start. Successful accepted responses are intentionally opaque.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "recaptcha_token"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "recaptcha_token": { "type": "string" },
                  "code": { "type": "string", "description": "Optional access code. A code carrying the `register` capability lets a brand-new email through invite-only signup; credit/feature capabilities are applied on first sign-in." },
                  "kind": { "enum": ["combined", "link", "code"] }
                }
              }
            }
          }
        },
        "responses": {
          "202": { "description": "Accepted; email may be sent if checks pass. If the reCAPTCHA token is missing, returns { status: \"captcha_required\", captcha_required: true } without sending email." },
          "400": { "description": "Bad request" },
          "403": { "description": "Captcha rejected, or access required: while registration is invite-only, a brand-new email that is not allow-listed and supplies no valid `register` code returns { \"error\": \"access_required\" } — the client offers the waitlist." },
          "429": { "description": "Rate limited" },
          "503": { "description": "Captcha not configured" }
        }
      }
    },
    "/api/auth/config": {
      "get": {
        "summary": "Read public auth provider configuration",
        "security": [],
        "responses": { "200": { "description": "{ google_oauth_configured: bool, email_auth_configured: bool }" } }
      }
    },
    "/api/auth/google/start": {
      "get": {
        "summary": "Start Google OAuth sign-in",
        "security": [],
        "responses": {
          "302": { "description": "Redirect to Google OAuth" },
          "503": { "description": "Google OAuth not configured" }
        }
      }
    },
    "/api/auth/google/callback": {
      "get": {
        "summary": "Complete Google OAuth sign-in",
        "security": [],
        "responses": {
          "302": { "description": "Redirect to management UI with session cookie or auth_error" }
        }
      }
    },
    "/api/auth/session": {
      "get": {
        "summary": "Check browser session state",
        "security": [],
        "responses": { "200": { "description": "{ authenticated: bool }" } }
      }
    },
    "/api/auth/verify-code": {
      "post": {
        "summary": "Complete email-code sign-in",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["challenge_id", "code"],
                "properties": {
                  "challenge_id": { "type": "string" },
                  "code": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Session created; cookie set" },
          "202": { "description": "MFA required; response includes mfa_challenge_id and methods" },
          "401": { "description": "Bad or expired code" }
        }
      }
    },
    "/api/auth/verify-link": {
      "get": {
        "summary": "Complete email magic-link sign-in",
        "description": "Browser-navigated redirect target from the sign-in email link. Sets the session cookie and redirects into the UI. Agents should use POST /api/auth/verify-code instead.",
        "security": [],
        "responses": { "302": { "description": "Session created; redirects into the management UI" }, "401": { "description": "Bad or expired link" } }
      }
    },
    "/api/auth/mfa/verify": {
      "post": {
        "summary": "Complete MFA challenge during sign-in",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["challenge_id", "method"],
                "properties": {
                  "challenge_id": { "type": "string" },
                  "method": { "enum": ["totp", "recovery_code"] },
                  "code": { "type": "string", "description": "6-digit TOTP code when method is totp" },
                  "recovery_code": { "type": "string", "description": "Recovery code when method is recovery_code" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Session created; cookie set" },
          "401": { "description": "Bad or expired MFA challenge" }
        }
      }
    },
    "/api/auth/token-login": {
      "post": {
        "summary": "Exchange a machine API token for a browser session",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token"],
                "properties": {
                  "token": { "type": "string", "description": "Active kpm_ machine API token" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Session created; cookie set" },
          "401": { "description": "Invalid or revoked token" },
          "403": { "description": "Token is not a machine API token" }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "summary": "Revoke current session or bearer token",
        "responses": { "204": { "description": "Revoked" } }
      }
    },
    "/api/v1/me": {
      "get": {
        "summary": "Get current account and workspace state",
        "responses": { "200": { "description": "Current account state" }, "401": { "description": "Invalid or missing token" } }
      }
    },
    "/api/v1/workspaces": {
      "get": { "summary": "List workspaces", "responses": { "200": { "description": "Workspace list" } } },
      "post": { "summary": "Create workspace", "responses": { "200": { "description": "Created workspace and session" } } }
    },
    "/api/v1/workspaces/{id}": {
      "patch": { "summary": "Rename workspace", "responses": { "200": { "description": "Renamed workspace" } } },
      "delete": { "summary": "Delete workspace", "description": "Disables the workspace and removes live serving state for its tunnels, agent tokens, routes, and custom domains. Requires owner or admin access to the target workspace.", "responses": { "204": { "description": "Workspace deleted" }, "403": { "description": "Owner or admin access required" }, "404": { "description": "Workspace not found" } } }
    },
    "/api/v1/workspaces/switch": {
      "post": { "summary": "Switch active browser workspace", "responses": { "200": { "description": "New browser session" } } }
    },
    "/api/v1/tunnels": {
      "get": { "summary": "List tunnels", "description": "Lists tunnels in the active workspace. Each entry carries sni/public_url/tls_mode/cert_source/enabled/target/backend_mode plus agent session and traffic rollups. For LLM automation: after creating a tunnel, fetch this to confirm routing; see also https://kepeink.hu/docs/run-the-agent-with-docker for Docker target networking (service DNS name, not localhost inside container).", "responses": { "200": { "description": "Tunnel list", "content": { "application/json": { "schema": { "type": "object", "required": ["tunnels"], "properties": { "tunnels": { "type": "array", "items": { "$ref": "#/components/schemas/Tunnel" } } } } } } } } },
      "post": {"summary": "Create tunnel", "description": "Creates a tunnel. Body { target, backend_mode=\"proxy\"|\"static\"|\"ssh\" }. Exact targets: proxy http://localhost:3000 or http://<lan-host>:<port>; host service behind Docker use http://host.docker.internal:<port>; containerized app use http://<service>:<port> (must resolve inside agent container — see https://kepeink.hu/docs/run-the-agent-with-docker); static absolute path /var/www/dist; ssh host:22. The server allocates sni/public_url; do not send sni. tls_mode must be omitted or terminated (end-to-end TLS uses POST /api/v1/tunnels/{id}/e2e). Returns the first agent_token (kpt_…) in plaintext shown once — save it before installing the agent (curl -fsSL https://cdn.kepeink.hu/install.sh | sudo sh -s -- --token=kpt_… or Docker image registry.kepeink.hu/agent:2 with KEPEINK_AGENT_TOKEN).", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "required": ["target"], "properties": {"target": {"type": "string", "description": "Local target the agent can reach: http(s) URL for proxy, absolute path for static, host:port for ssh"}, "backend_mode": {"type": "string", "enum": ["proxy", "static", "ssh"], "default": "proxy"}, "agent_config": {"type": "object", "description": "Optional provider-side config (ssh creds, etc.) — usually omitted"}}}}}}, "responses": {"201": {"description": "Created tunnel with plaintext agent_token (shown once)", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/Tunnel"}, {"type": "object", "required": ["token_id", "agent_token"], "properties": {"token_id": {"type": "string"}, "agent_token": {"type": "string", "description": "kpt_… bearer token — keep out of logs"}}}]}}}}}}
    },
    "/api/v1/tunnels/{id}": {
      "get": { "summary": "Get tunnel", "responses": { "200": { "description": "Tunnel", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Tunnel" } } } }, "404": { "description": "Not found" } } },
      "patch": { "summary": "Update tunnel", "responses": { "200": { "description": "Updated tunnel", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Tunnel" } } } } } },
      "delete": { "summary": "Delete tunnel", "responses": { "204": { "description": "Deleted" } } }
    },
    "/api/v1/tunnels/{id}/enabled": {
      "post": {
        "summary": "Enable or disable a tunnel",
        "description": "Lighter than PATCH; propagates to the data plane sub-second. Body {\"enabled\": true|false}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["enabled"],
                "properties": { "enabled": { "type": "boolean" } }
              }
            }
          }
        },
        "responses": { "200": { "description": "{ tunnel_id, enabled }" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/tunnels/{id}/e2e": {
      "post": {
        "summary": "Enable or disable end-to-end TLS for a tunnel",
        "description": "Owner-only. Enabling switches the tunnel to passthrough + agent-managed ACME: the agent terminates public TLS with a Let's Encrypt certificate it obtains itself, and the private key never leaves the customer machine. Enabling requires the workspace's e2e_tls feature grant, no cert_worker custom domains attached to the tunnel (409 — agent_managed_acme custom domains are compatible), per-registered-domain issuance headroom (429), and a connected agent new enough to serve it (409). Disabling reverts to the shared wildcard. Body {\"enabled\": true|false}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["enabled"],
                "properties": { "enabled": { "type": "boolean" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{ tunnel_id, e2e_enabled, tls_mode, cert_source }" },
          "403": { "description": "Workspace lacks the e2e_tls feature grant" },
          "404": { "description": "Not found" },
          "409": { "description": "Cert-worker custom domains attached, or connected agent too old" },
          "429": { "description": "Per-domain certificate issuance headroom exhausted; retry later" }
        }
      }
    },
    "/api/v1/tunnels/{id}/tokens": {
      "get": { "summary": "List tunnel agent tokens", "responses": { "200": { "description": "Token list" } } },
      "post": { "summary": "Issue tunnel agent token", "responses": { "201": { "description": "Created token with plaintext returned immediately and stored for email-code re-display" } } }
    },
    "/api/v1/tunnels/{id}/tokens/{token_id}/reveal/request": {
      "post": { "summary": "Email an 8-digit code for viewing a stored tunnel token", "responses": { "202": { "description": "Reveal challenge created" } }
      }
    },
    "/api/v1/tunnels/{id}/tokens/{token_id}/reveal/verify": {
      "post": {
        "summary": "Verify the 8-digit code and return the stored tunnel token",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["challenge_id", "code"],
                "properties": {
                  "challenge_id": { "type": "string" },
                  "code": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Stored token plaintext" } }
      }
    },
    "/api/v1/tunnels/{id}/tokens/{token_id}": {
      "delete": { "summary": "Revoke tunnel agent token", "responses": { "204": { "description": "Revoked" } } }
    },
    "/api/v1/tunnels/{id}/agents": {
      "get": {
        "summary": "List connected agents for a tunnel",
        "responses": { "200": { "description": "Agent session list (router, connected_at, version)" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/tunnels/{id}/agent-update": {
      "post": {
        "summary": "Ask the tunnel's agents to apply a pending self-update now",
        "description": "Bumps the server-owned apply counter; connected agents apply the staged update instead of waiting for their deferred window.",
        "responses": { "200": { "description": "Acknowledged" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/tunnels/{id}/agent-restart": {
      "post": {
        "summary": "Ask the tunnel's agents to restart",
        "description": "Bumps the server-owned restart counter; connected agents restart their worker process.",
        "responses": { "200": { "description": "Acknowledged" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/tunnels/{id}/access": {
      "get": {
        "summary": "Get a tunnel's basic-auth/MFA access-gate policy",
        "responses": { "200": { "description": "Access policy, including whether MFA is required" } }
      },
      "put": {
        "summary": "Replace a tunnel's access-gate policy",
        "description": "Body includes at least mfa_enabled; enabling MFA requires at least one access user already configured with a TOTP secret.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "mfa_enabled": { "type": "boolean" } } } } }
        },
        "responses": { "200": { "description": "Updated policy" }, "400": { "description": "Invalid policy, e.g. MFA enabled with no eligible access users" } }
      }
    },
    "/api/v1/tunnels/{id}/access/users": {
      "post": {
        "summary": "Add a basic-auth access user to a tunnel's access gate",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["username"], "properties": { "username": { "type": "string" }, "password": { "type": "string" }, "totp_secret_b32": { "type": "string" } } } } }
        },
        "responses": { "201": { "description": "Access user created" }, "400": { "description": "Invalid username/password/TOTP secret" } }
      }
    },
    "/api/v1/tunnels/{id}/access/users/{access_user_id}": {
      "patch": {
        "summary": "Update a tunnel access user",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "username": { "type": "string" }, "password": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "Updated" }, "404": { "description": "Not found" } }
      },
      "delete": {
        "summary": "Disable a tunnel access user",
        "responses": { "204": { "description": "Disabled" }, "404": { "description": "Not found or already disabled" } }
      }
    },
    "/api/v1/custom-domains": {
      "get": {
        "summary": "List workspace custom domains",
        "responses": {
          "200": { "description": "Custom domain list", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomainListResponse" } } } }
        }
      },
      "post": {
        "summary": "Create a custom domain desired-state row",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomainCreateRequest" } } }
        },
        "responses": {
          "201": { "description": "Created custom domain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "400": { "description": "Invalid hostname" },
          "404": { "description": "Requested tunnel not found" },
          "409": { "description": "Hostname or alias collision" }
        }
      }
    },
    "/api/v1/custom-domains/{id}": {
      "get": {
        "summary": "Get a custom domain",
        "responses": {
          "200": { "description": "Custom domain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "404": { "description": "Not found" }
        }
      },
      "patch": {
        "summary": "Update custom-domain desired state",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomainPatchRequest" } } }
        },
        "responses": {
          "200": { "description": "Updated custom domain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "404": { "description": "Custom domain or tunnel not found" }
        }
      },
      "delete": {
        "summary": "Disable a custom domain",
        "responses": {
          "204": { "description": "Disabled" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/v1/custom-domains/{id}/verify": {
      "post": {
        "summary": "Nudge DNS verification for a custom domain",
        "responses": {
          "200": { "description": "Verification queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/v1/custom-domains/{id}/e2e": {
      "post": {
        "summary": "Enable or disable end-to-end TLS for a custom domain",
        "description": "Owner-only and FREE. Enabling sets cert_source='agent_managed_acme': the customer's agent obtains and holds a Let's Encrypt certificate for the customer-owned hostname, edged pipes the raw ClientHello through, and the private key never leaves the customer machine. It is free because the certificate counts against the customer's own registered domain (not our shared pool) and passthrough is cheaper for us than cert-worker issuance — so there is no feature-grant check. Disabling reverts to the cert-worker flow (Kepeink issues and serves the cert; brief downtime while it is obtained), and is refused with 409 while the domain still points at a tunnel-level end-to-end (passthrough) tunnel. Body {\"enabled\": true|false}.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["enabled"],
                "properties": { "enabled": { "type": "boolean" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated custom domain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "404": { "description": "Not found" },
          "409": { "description": "Disable refused: domain still points at an end-to-end TLS tunnel" }
        }
      }
    },
    "/api/v1/custom-domains/{id}/retry-certificate": {
      "post": {
        "summary": "Nudge certificate issuance retry for a custom domain",
        "responses": {
          "200": { "description": "Certificate retry queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CustomDomain" } } } },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/v1/sessions": {
      "get": { "summary": "List browser sessions and API keys", "responses": { "200": { "description": "Access token list" } } }
    },
    "/api/v1/api-keys": {
      "post": {
        "summary": "Create non-expiring machine API key",
        "description": "Browser-session only. The new key inherits the current session's workspace role.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyCreateRequest" } } }
        },
        "responses": {
          "201": {
            "description": "Created API key. Plaintext is returned once.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/APIKeyCreateResponse" } } }
          },
          "403": { "description": "Called from a machine token or insufficient role" }
        }
      }
    },
    "/api/v1/sessions/{token_id}": {
      "delete": { "summary": "Revoke browser session or API key", "responses": { "204": { "description": "Revoked" }, "404": { "description": "Not found" } } }
    },
    "/api/v1/account/mfa": {
      "get": {
        "summary": "Read account MFA status",
        "description": "Browser-session only. Returns whether MFA is enabled, active/pending factors, and remaining recovery-code count.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "200": { "description": "MFA status" }, "403": { "description": "Machine token or non-browser session" } }
      }
    },
    "/api/v1/account/mfa/email/request": {
      "post": {
        "summary": "Request an MFA-enrollment email code",
        "description": "Browser-session only. Sends an 8-digit code to the account's current email address. Required before /api/v1/account/mfa/totp/start will provision a secret.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "202": { "description": "Code emailed; response includes challenge_id" }, "409": { "description": "MFA already enabled" } }
      }
    },
    "/api/v1/account/mfa/email/verify": {
      "post": {
        "summary": "Verify an MFA-enrollment email code",
        "description": "Browser-session only. Body: { challenge_id, code }. On success returns a short-lived enroll_challenge_id ticket that /api/v1/account/mfa/totp/start consumes.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "200": { "description": "enroll_challenge_id ticket" }, "403": { "description": "Invalid or expired code" } }
      }
    },
    "/api/v1/account/mfa/totp/start": {
      "post": {
        "summary": "Start TOTP MFA enrollment",
        "description": "Browser-session only. Body: { enroll_challenge_id } from /api/v1/account/mfa/email/verify — proves live mailbox control before a secret is provisioned. Creates a pending TOTP factor and returns the secret plus otpauth URI.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "200": { "description": "Pending TOTP factor" }, "403": { "description": "Email verification required" }, "409": { "description": "MFA already enabled" }, "503": { "description": "MFA unavailable" } }
      }
    },
    "/api/v1/account/mfa/totp/confirm": {
      "post": {
        "summary": "Confirm pending TOTP MFA enrollment",
        "description": "Browser-session only. Body: { factor_id, code }. Activates the factor and returns one-time recovery codes.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "200": { "description": "MFA enrolled with recovery codes" }, "400": { "description": "Invalid code or request" }, "404": { "description": "Pending factor not found" } }
      }
    },
    "/api/v1/account/mfa/recovery-codes/regenerate": {
      "post": {
        "summary": "Regenerate MFA recovery codes",
        "description": "Browser-session only. Requires recent MFA verification on the current session.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "200": { "description": "New recovery codes" }, "403": { "description": "Recent MFA required" } }
      }
    },
    "/api/v1/account/mfa/disable": {
      "post": {
        "summary": "Disable account MFA",
        "description": "Browser-session only. Requires recent MFA verification, disables MFA factors, consumes recovery codes, and revokes other active browser sessions.",
        "security": [{ "cookieAuth": [] }],
        "responses": { "204": { "description": "MFA disabled" }, "403": { "description": "Recent MFA required" } }
      }
    },
    "/api/v1/qr/svg": {
      "post": {
        "summary": "Render a QR code SVG",
        "description": "Authenticated helper used by the MFA enrollment UI. Body: { text }. The text must be an otpauth TOTP URI. Returns image/svg+xml.",
        "responses": { "200": { "description": "SVG image" }, "400": { "description": "Invalid request" }, "413": { "description": "Payload too large" }, "502": { "description": "QR renderer unavailable" }, "503": { "description": "QR renderer not configured" } }
      }
    },
    "/api/v1/account/delete/request": {
      "post": {
        "summary": "Request account deletion confirmation code",
        "description": "Browser session required; machine kpm_ tokens cannot delete or export the account and receive 403 Forbidden. Sends an 8-digit account deletion code to the account email.",
        "security": [{ "cookieAuth": [] }],
        "responses": {
          "202": {
            "description": "Deletion challenge created",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountDeleteRequestResponse" } } }
          },
          "403": { "description": "Machine token or non-browser session" },
          "404": { "description": "Account not found" }
        }
      }
    },
    "/api/v1/account/delete": {
      "post": {
        "summary": "Confirm account deletion",
        "description": "Browser session required; machine kpm_ tokens cannot delete or export the account and receive 403 Forbidden. Consumes an account_delete challenge code, soft-deletes the account, revokes sessions and machine tokens, and disables solely-owned workspaces. Remaining account residue is purged after a 30-day grace period.",
        "security": [{ "cookieAuth": [] }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountDeleteConfirmRequest" } } }
        },
        "responses": {
          "204": { "description": "Account deleted; no response body" },
          "400": { "description": "Bad JSON or missing challenge_id/code" },
          "403": { "description": "Machine token, invalid or expired challenge, wrong code, attempt-capped code, or wrong challenge purpose" }
        }
      }
    },
    "/api/v1/account/export": {
      "get": {
        "summary": "Download account data export",
        "description": "Browser session required; machine kpm_ tokens cannot delete or export the account and receive 403 Forbidden. Rate-limited to 2 exports per hour per user. Returns all account data as an application/json attachment; 204 is not used for this endpoint.",
        "security": [{ "cookieAuth": [] }],
        "responses": {
          "200": {
            "description": "Account export JSON attachment",
            "headers": {
              "Content-Disposition": {
                "description": "Attachment filename in the form kepeink-export-<user_id>-<YYYYMMDD>.json",
                "schema": { "type": "string" }
              }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AccountExport" } } }
          },
          "403": { "description": "Machine token or non-browser session" },
          "404": { "description": "Account not found" },
          "429": { "description": "Rate limited after 2 exports per hour" }
        }
      }
    },
    "/api/v1/audit": {
      "get": { "summary": "List audit events", "responses": { "200": { "description": "Audit event page" } } }
    },
    "/api/v1/management/events": {
      "get": {
        "summary": "SSE stream for UI state sync",
        "description": "Server-sent events stream for UI state sync.",
        "responses": { "200": { "description": "SSE stream" } }
      }
    },
    "/api/v1/billing/products": {
      "get": { "summary": "List billing products", "responses": { "200": { "description": "Product list" } } }
    },
    "/api/v1/billing/checkouts": {
      "post": { "summary": "Create checkout", "responses": { "200": { "description": "Checkout URL" } } }
    },
    "/api/v1/billing/checkouts/verify": {
      "post": { "summary": "Verify a returned checkout and enqueue credit", "description": "Owner-only. On post-purchase return, fetches the checkout from the provider and, if paid, enqueues the same webhook_events row the async webhook would — so credit lands within the reconciler tick instead of waiting for the (often slow) provider webhook. Idempotent; the reconciler dedups the grant on the order id.", "responses": { "200": { "description": "Accepted for processing" }, "202": { "description": "Not yet paid, or not the caller's checkout — keep polling" } } }
    },
    "/api/v1/billing/codes/redeem": {
      "post": {
        "summary": "Redeem a capability code",
        "description": "Owner-only. Applies every capability the code carries to the current workspace: credit tops up credit_bytes/entitlement_days, feature grants the named workspace feature. Returns a summary of what was applied and the new balance.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["code"],
                "properties": { "code": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "{ applied: { credit_bytes?, entitlement_days?, features? }, new_balance_bytes? }" },
          "400": { "description": "Invalid, expired, exhausted, revoked, or wrong-email code" },
          "403": { "description": "Not the workspace owner" }
        }
      }
    },
    "/api/v1/invites": {
      "get": {
        "summary": "List your invite codes",
        "description": "Returns the caller's own single-use invite codes newest-first, each with its status, validity, and redemption list, plus the pending-invite quota envelope. Any workspace member (owner/admin/member) may call.",
        "responses": {
          "200": { "description": "{ invites: [{ code_id, code, share_url, status, bound_email?, note?, max_uses, used_count, valid_until?, created_at, redemptions: [{ email, redeemed_at }] }], quota, used_slots, enabled }" }
        }
      },
      "post": {
        "summary": "Mint an invite code",
        "description": "Mints a single-use code carrying the register capability, owned by the caller, that admits one new person during invite-only registration. Any workspace member (owner/admin/member) may call. Fails with 403 invites_disabled when the operator has turned the surface off, or 429 invite_quota_reached when the caller is at their pending-invite limit.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bound_email": { "type": "string", "description": "Optional address the invite is locked to (lowercased)." },
                  "expires_in_days": { "type": "integer", "description": "Optional validity window in days (capped at 365); 0/absent means no expiry." },
                  "note": { "type": "string", "description": "Optional free-form memo (<= 500 chars)." }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "{ code_id, code, share_url, status, bound_email?, note?, max_uses, used_count, valid_until?, created_at, redemptions }" },
          "400": { "description": "Invalid bound_email or note too long" },
          "403": { "description": "Invites are disabled (invites_disabled)" },
          "429": { "description": "Pending-invite quota reached (invite_quota_reached)" }
        }
      }
    },
    "/api/v1/invites/{id}/revoke": {
      "post": {
        "summary": "Revoke one of your invite codes",
        "description": "Disables one of the caller's own invites so it can no longer admit a signup. Scoped to created_by_user_id: an unknown, already-revoked, or someone-else's code returns 404. Any workspace member (owner/admin/member) may call.",
        "responses": {
          "200": { "description": "{ code_id, status: \"revoked\" }" },
          "404": { "description": "Not found or already revoked (not_found)" }
        }
      }
    },
    "/api/v1/free-trial/activate": {
      "post": {
        "summary": "Activate free trial",
        "description": "Human-only. Requires reCAPTCHA Enterprise token for action free_trial_activate."
      }
    },
    "/api/v1/legal/current": {
      "get": { "summary": "Get dated legal releases and acceptance state", "responses": { "200": { "description": "Current Terms and Privacy release dates, accepted dates, and whether acceptance is required" } } }
    },
    "/api/v1/legal/accept": {
      "post": {
        "summary": "Accept the current dated Terms and Privacy releases",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["tos_release_date", "privacy_release_date"],
                "properties": {
                  "tos_release_date": { "type": "string", "format": "date", "example": "2026-07-15" },
                  "privacy_release_date": { "type": "string", "format": "date", "example": "2026-07-15" }
                }
              }
            }
          }
        },
        "responses": { "204": { "description": "Accepted" }, "409": { "description": "A newer release must be loaded" } }
      }
    },
    "/api/v1/legal/text/{kind}/{releaseDate}": {
      "get": {
        "summary": "Public legal document text",
        "security": [],
        "responses": { "200": { "description": "HTML body" } }
      }
    },
    "/api/v1/legal/text/{kind}": {
      "get": {
        "summary": "Public legal document text, latest version",
        "security": [],
        "responses": { "200": { "description": "HTML body" } }
      }
    },
    "/api/v1/agent-release": {
      "get": {
        "summary": "Current agent release manifest and download URLs",
        "responses": { "200": { "description": "Release info" } }
      }
    },
    "/api/v1/tls-advanced-interest": {
      "post": {
        "summary": "Register interest in advanced TLS features",
        "responses": { "204": { "description": "Recorded" } }
      }
    },
    "/api/v1/admin/admins": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List platform admins",
        "responses": { "200": { "description": "Admin list" } }
      },
      "post": {
        "tags": ["platform-admin"],
        "summary": "Reject platform-admin mutation",
        "description": "Platform admins are code-configured; DB/API mutations do not grant authority.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string" } } } } }
        },
        "responses": { "405": { "description": "Platform admins are code-configured" } }
      }
    },
    "/api/v1/admin/admins/{user_id}": {
      "delete": {
        "tags": ["platform-admin"],
        "summary": "Reject platform-admin removal",
        "description": "Platform admins are code-configured; DB/API mutations do not revoke authority.",
        "responses": { "405": { "description": "Platform admins are code-configured" } }
      }
    },
    "/api/v1/admin/stats": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Get platform-admin telemetry statistics",
        "description": "Full status snapshot. The e2e_tls block reports end-to-end TLS adoption: activations_7d (mgmt.tunnels_archive rows with archived_reason='e2e_enabled' in the trailing 7 days — the true activation count, surviving a later disable), active_tunnels (tunnels currently on tls_mode='passthrough'), custom_domains_free (custom domains on the free per-domain path, cert_source='agent_managed_acme'), issuance_headroom_limit (the per-domain enable-guard cap), and pool_domains[] (per unpaid_pool registered domain: base_domain, used = live agent-managed tunnels inside the issuance window matching the enable guard's own count, and limit).",
        "responses": { "200": { "description": "Platform stats" } }
      }
    },
    "/api/v1/admin/status": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Full platform status (authenticated equivalent of /status)",
        "responses": { "200": { "description": "Status snapshot" } }
      }
    },
    "/api/v1/admin/events": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Recent platform admin event log",
        "responses": { "200": { "description": "Event list" } }
      }
    },
    "/api/v1/admin/events/stream": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "SSE stream of platform admin events",
        "description": "text/event-stream; distinct from the customer-scoped GET /api/v1/management/events stream.",
        "responses": { "200": { "description": "SSE stream" } }
      }
    },
    "/api/v1/admin/users": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List platform users",
        "responses": { "200": { "description": "User list" } }
      }
    },
    "/api/v1/admin/workspaces": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List platform workspaces",
        "responses": { "200": { "description": "Workspace list" } }
      }
    },
    "/api/v1/admin/tunnels": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List all tunnels",
        "responses": { "200": { "description": "Tunnel list" } }
      }
    },
    "/api/v1/admin/grants": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List grant history",
        "responses": { "200": { "description": "Grant list" } }
      }
    },
    "/api/v1/admin/tunnels/{id}/rproxy-domain": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Migrate a tunnel to a reverse-proxy domain",
        "responses": { "200": { "description": "Migrated" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/alerts": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Current Alertmanager alerts",
        "responses": { "200": { "description": "Alert list" } }
      }
    },
    "/api/v1/admin/logs": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Query recent platform logs",
        "responses": { "200": { "description": "Log lines" } }
      }
    },
    "/api/v1/admin/logs/containers": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List log container names",
        "responses": { "200": { "description": "Container name list" } }
      }
    },
    "/api/v1/admin/logs/stream": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Live platform log stream (SSE)",
        "responses": { "200": { "description": "text/event-stream of log lines" } }
      }
    },
    "/api/v1/admin/metrics/panels": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Metric panel catalog for the admin graphs",
        "responses": { "200": { "description": "Panel groups and panel definitions" } }
      }
    },
    "/api/v1/admin/metrics/range": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "Metric range query for one panel",
        "responses": { "200": { "description": "Time series for the requested panel and range" } }
      }
    },
    "/api/v1/admin/takedowns": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List takedown requests",
        "responses": { "200": { "description": "Takedown list" } }
      }
    },
    "/api/v1/admin/users/{user_id}/suspend": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Suspend platform user",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "User suspended" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/users/{user_id}/activate": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Activate platform user",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "User activated" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/workspaces/{workspace_id}/suspend": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Suspend workspace",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "Workspace suspended" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/workspaces/{workspace_id}/activate": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Activate workspace",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "properties": { "reason": { "type": "string" } } } } }
        },
        "responses": { "200": { "description": "Workspace activated" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/workspaces/{workspace_id}/grants": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Issue workspace grant",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["reason"], "properties": { "delta_bytes": { "type": "integer", "format": "int64" }, "entitlement_days": { "type": "integer", "minimum": 0, "maximum": 3660 }, "reason": { "type": "string", "maxLength": 500 } } } } }
        },
        "responses": { "201": { "description": "Grant issued" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/workspaces/{workspace_id}/tokens": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Mint a machine API token for a user in a workspace",
        "description": "Platform-admin session only. Creates or updates the user's active workspace membership for the requested role, returns the plaintext kpm_ token once, and writes an operator audit event.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["user_id", "role", "reason"], "properties": { "user_id": { "type": "string" }, "role": { "type": "string", "enum": ["owner", "admin", "member", "viewer"] }, "label": { "type": "string", "maxLength": 80 }, "reason": { "type": "string", "maxLength": 500 } } } } }
        },
        "responses": {
          "201": { "description": "Token minted. Plaintext api_token is returned once." },
          "400": { "description": "Invalid request" },
          "404": { "description": "Active user/workspace pair not found" }
        }
      }
    },
    "/api/v1/admin/takedowns/{request_id}/action": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Take action on a takedown request",
        "responses": { "200": { "description": "Action applied" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/takedowns/{request_id}/dismiss": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Dismiss a takedown request",
        "responses": { "204": { "description": "Request dismissed" }, "404": { "description": "Not found" } }
      }
    },
    "/api/v1/admin/codes": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List capability codes",
        "description": "Paginated list of codes with their capabilities, usage, binding, validity window, and revocation state. Supports ?q= search over code, label, note, and bound email.",
        "responses": { "200": { "description": "Code list" } }
      },
      "post": {
        "tags": ["platform-admin"],
        "summary": "Mint a capability code",
        "description": "Creates a code carrying one or more capabilities: `register` (bypass invite-only signup), `credit` (credit_bytes and/or entitlement_days), `feature` (feature_name, e.g. e2e_tls). Body: { capabilities: [{ capability, credit_bytes?, entitlement_days?, feature_name? }], bound_email?, valid_from?, valid_until?, max_uses (default 1), label?, note? }. At least one capability is required. Returns the generated code.",
        "responses": { "201": { "description": "Code created" }, "400": { "description": "Invalid parameters or no capabilities" } }
      }
    },
    "/api/v1/admin/codes/{code_id}/revoke": {
      "post": {
        "tags": ["platform-admin"],
        "summary": "Revoke a capability code",
        "description": "Sets revoked_at so the code can no longer be redeemed. Idempotent target state; already-revoked or unknown codes return 404.",
        "responses": { "200": { "description": "Code revoked" }, "404": { "description": "Not found or already revoked" } }
      }
    },
    "/api/v1/admin/waitlist": {
      "get": {
        "tags": ["platform-admin"],
        "summary": "List launch-waitlist signups",
        "description": "Paginated list of waitlist entries (email, source, joined and notified timestamps). Supports ?q= search over email and source. Pass ?format=csv for a CSV export download.",
        "responses": { "200": { "description": "Waitlist entries (JSON) or CSV export" } }
      }
    },
    "/api/v1/billing/creem-webhook": {
      "post": {
        "summary": "Creem billing webhook receiver",
        "description": "Verifies the Creem webhook signature and records the raw event for the reconciler. Not a customer-facing endpoint. Creem is the only hosted-checkout provider.",
        "security": [],
        "responses": { "200": { "description": "Processed" }, "400": { "description": "Bad signature or payload" } }
      }
    },
    "/api/v1/email/ses-sns-webhook": {
      "post": {
        "summary": "Amazon SES feedback webhook",
        "description": "Receives Amazon SNS HTTPS notifications for SES bounce, complaint, reject, and delivery-delay events. The server verifies the SNS signature and configured topic ARN before processing.",
        "security": [],
        "responses": { "200": { "description": "SNS notification accepted or ignored" }, "403": { "description": "SNS signature or topic rejected" }, "410": { "description": "SES SNS feedback not configured" } }
      }
    }
  }
}
