{
  "openapi": "3.1.0",
  "info": {
    "title": "Tork Chat API",
    "version": "1.0.0",
    "description": "REST API for Tork Chat — a multi-tenant AI chat platform with RAG, governance, knowledge management, and billing. Public proxy endpoints support CORS for widget embedding. Admin endpoints require a valid Supabase session cookie.",
    "contact": {
      "name": "Tork Network",
      "url": "https://tork.network",
      "email": "support@tork.network"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://chat.tork.network/terms"
    }
  },
  "servers": [
    {
      "url": "https://chat.tork.network",
      "description": "Production"
    }
  ],
  "tags": [
    { "name": "Chat", "description": "Core chat and streaming endpoints" },
    { "name": "Tenants", "description": "Tenant configuration and onboarding" },
    { "name": "Widgets", "description": "Widget configuration and verification" },
    { "name": "Knowledge Base", "description": "Document upload, scanning, and management" },
    { "name": "Admin", "description": "Admin-only analytics, conversations, leads, and settings" },
    { "name": "Governance", "description": "Tork Govern PII scanning and audit receipts" },
    { "name": "Topic Guard", "description": "Blocked topic detection and analytics" },
    { "name": "MFA", "description": "Multi-factor authentication settings and backup codes" },
    { "name": "Billing", "description": "Stripe checkout, billing portal, and usage" },
    { "name": "Health", "description": "Service health check" }
  ],
  "security": [],
  "components": {
    "securitySchemes": {
      "BearerJWT": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Supabase JWT obtained via the login flow. Required for all admin and billing endpoints."
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "sb-access-token",
        "description": "Supabase session cookie set after authentication."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "description": "Human-readable error message" }
        }
      },
      "RateLimitError": {
        "allOf": [
          { "$ref": "#/components/schemas/Error" },
          {
            "type": "object",
            "properties": {
              "retryAfter": { "type": "integer", "description": "Seconds until the rate limit resets" },
              "limit": { "type": "integer", "description": "Requests per minute allowed for this plan" }
            }
          }
        ]
      },
      "ChatRequest": {
        "type": "object",
        "required": ["message", "org_id", "widget_id"],
        "properties": {
          "message": { "type": "string", "minLength": 1, "description": "User message text" },
          "org_id": { "type": "string", "format": "uuid", "description": "Tenant/organisation UUID" },
          "widget_id": { "type": "string", "description": "Widget identifier" },
          "session_id": { "type": "string", "format": "uuid", "description": "Existing session UUID. Omit to start a new session." }
        }
      },
      "ChatResponse": {
        "type": "object",
        "required": ["response", "session_id", "receipt_ids", "pii_detected"],
        "properties": {
          "response": { "type": "string", "description": "AI assistant response" },
          "session_id": { "type": "string", "format": "uuid", "description": "Session UUID (create or continue)" },
          "receipt_ids": {
            "type": "object",
            "properties": {
              "l3": { "type": "string", "description": "L3 input governance receipt ID" },
              "l6": { "type": "string", "description": "L6 output governance receipt ID" }
            }
          },
          "pii_detected": { "type": "boolean", "description": "Whether PII was detected and redacted in the input" },
          "topic_blocked": { "type": "boolean", "description": "Whether the message was blocked by Topic Guard" },
          "matched_topic": { "type": ["string", "null"], "description": "The blocked topic that was matched" },
          "data_connector_used": { "type": "boolean", "description": "Whether a live data connector was queried" }
        }
      },
      "ProxyChatRequest": {
        "type": "object",
        "required": ["message", "tenant_id"],
        "properties": {
          "message": { "type": "string", "minLength": 1 },
          "tenant_id": { "type": "string", "format": "uuid" },
          "session_id": { "type": "string", "format": "uuid" }
        }
      },
      "TenantConfig": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "bot_name": { "type": "string" },
          "welcome_message": { "type": "string" },
          "widget_config": { "type": "object", "additionalProperties": true }
        }
      },
      "WidgetConfig": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "bot_name": { "type": "string" },
          "welcome_message": { "type": "string" },
          "accent_color": { "type": "string" },
          "logo_url": { "type": ["string", "null"] },
          "suggested_questions": { "type": "array", "items": { "type": "string" } },
          "locale": { "type": "string", "default": "en" },
          "text_direction": { "type": "string", "enum": ["ltr", "rtl"] },
          "widget_config": { "type": "object", "additionalProperties": true }
        }
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "filename": { "type": "string" },
          "file_size": { "type": ["integer", "null"] },
          "file_type": { "type": ["string", "null"] },
          "chunk_count": { "type": "integer" },
          "status": { "type": "string", "enum": ["processing", "ready", "error"] },
          "visibility": { "type": "string", "enum": ["internal", "public"] },
          "created_at": { "type": "string", "format": "date-time" },
          "extracted_text": { "type": ["string", "null"] }
        }
      },
      "DocumentWithChunks": {
        "allOf": [
          { "$ref": "#/components/schemas/Document" },
          {
            "type": "object",
            "properties": {
              "chunks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string", "format": "uuid" },
                    "content": { "type": "string" },
                    "chunk_index": { "type": "integer" },
                    "token_count": { "type": ["integer", "null"] }
                  }
                }
              }
            }
          }
        ]
      },
      "IngestResponse": {
        "type": "object",
        "required": ["document_id", "chunk_count"],
        "properties": {
          "document_id": { "type": "string", "format": "uuid" },
          "chunk_count": { "type": "integer" }
        }
      },
      "ScanRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": { "type": "string", "format": "uri" }
        }
      },
      "AnalyticsResponse": {
        "type": "object",
        "properties": {
          "conversations": {
            "type": "object",
            "properties": {
              "today": { "type": "integer" },
              "week": { "type": "integer" },
              "month": { "type": "integer" }
            }
          },
          "messages": {
            "type": "object",
            "properties": {
              "today": { "type": "integer" },
              "week": { "type": "integer" },
              "month": { "type": "integer" }
            }
          },
          "avg_messages_per_conversation": { "type": "string" },
          "escalation_rate": { "type": "string" },
          "busiest_hour": { "type": "string" },
          "top_intents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "intent": { "type": "string" },
                "count": { "type": "integer" }
              }
            }
          }
        }
      },
      "TenantInfo": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "slug": { "type": "string" },
          "name": { "type": "string" },
          "plan": { "type": "string", "enum": ["free", "starter", "pro", "business", "enterprise"] },
          "domain": { "type": ["string", "null"] }
        }
      },
      "WidgetConfigInput": {
        "type": "object",
        "properties": {
          "primary_color": { "type": "string" },
          "bot_name": { "type": "string" },
          "logo_url": { "type": ["string", "null"] },
          "greeting_message": { "type": "string" },
          "suggested_questions": { "type": "array", "items": { "type": "string" } }
        },
        "additionalProperties": true
      },
      "BotConfigInput": {
        "type": "object",
        "properties": {
          "system_prompt": { "type": "string" },
          "temperature": { "type": "number", "minimum": 0, "maximum": 2 },
          "max_tokens": { "type": "integer" }
        },
        "additionalProperties": true
      },
      "TopicGuardConfig": {
        "type": "object",
        "properties": {
          "topic_guard_enabled": { "type": "boolean" },
          "topic_guard_sensitivity": { "type": "string", "enum": ["low", "medium", "high"] },
          "blocked_topics": { "type": "array", "items": { "type": "string" } }
        }
      },
      "TopicGuardCheckRequest": {
        "type": "object",
        "required": ["message"],
        "properties": {
          "message": { "type": "string" },
          "blocked_topics": { "type": "array", "items": { "type": "string" } },
          "sensitivity": { "type": "string", "enum": ["low", "medium", "high"], "default": "medium" }
        }
      },
      "TopicGuardCheckResponse": {
        "type": "object",
        "properties": {
          "blocked": { "type": "boolean" },
          "matchedTopic": { "type": ["string", "null"] },
          "confidence": { "type": ["number", "null"] }
        }
      },
      "TopicGuardAnalytics": {
        "type": "object",
        "properties": {
          "counts": {
            "type": "object",
            "properties": {
              "today": { "type": "integer" },
              "week": { "type": "integer" },
              "month": { "type": "integer" }
            }
          },
          "top_topics": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "topic": { "type": "string" },
                "count": { "type": "integer" }
              }
            }
          },
          "recent": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string", "format": "uuid" },
                "message": { "type": "string" },
                "matched_topic": { "type": ["string", "null"] },
                "confidence": { "type": ["number", "null"] },
                "created_at": { "type": "string", "format": "date-time" },
                "widget_id": { "type": ["string", "null"] }
              }
            }
          }
        }
      },
      "MfaBackupCodesResponse": {
        "type": "object",
        "properties": {
          "codes": {
            "type": "array",
            "items": { "type": "string" },
            "description": "Plaintext backup codes (shown once)"
          }
        }
      },
      "MfaOrgSettings": {
        "type": "object",
        "properties": {
          "mfa_required": { "type": "boolean" },
          "mfa_grace_period_hours": { "type": "integer" },
          "team_members": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": { "type": "string", "format": "uuid" },
                "email": { "type": "string", "format": "email" },
                "has_mfa": { "type": "boolean" }
              }
            }
          }
        }
      },
      "BillingCheckoutRequest": {
        "type": "object",
        "required": ["plan", "period", "tenantId"],
        "properties": {
          "plan": { "type": "string", "enum": ["starter", "pro", "business"] },
          "period": { "type": "string", "enum": ["monthly", "annual"] },
          "tenantId": { "type": "string", "format": "uuid" },
          "country": { "type": "string", "description": "ISO 3166-1 alpha-2 country code for PPP pricing" }
        }
      },
      "BillingCheckoutResponse": {
        "type": "object",
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Stripe Checkout session URL" }
        }
      },
      "BillingPortalRequest": {
        "type": "object",
        "required": ["tenantId"],
        "properties": {
          "tenantId": { "type": "string", "format": "uuid" },
          "tenant_id": { "type": "string", "format": "uuid", "description": "Alias for tenantId" }
        }
      },
      "UsageResponse": {
        "type": "object",
        "properties": {
          "plan": { "type": "string" },
          "subscription_status": { "type": ["string", "null"] },
          "conversations_used": { "type": "integer" },
          "conversations_limit": { "type": "integer" },
          "messages_used": { "type": "integer" },
          "messages_limit": { "type": "integer" },
          "documents_used": { "type": "integer" },
          "documents_limit": { "type": "integer" },
          "period_start": { "type": ["string", "null"], "format": "date" },
          "period_end": { "type": ["string", "null"], "format": "date" },
          "ppp_tier": { "type": "integer", "enum": [1, 2, 3] }
        }
      },
      "OnboardRequest": {
        "type": "object",
        "required": ["business_name"],
        "properties": {
          "business_name": { "type": "string" },
          "website_url": { "type": ["string", "null"], "format": "uri" },
          "industry": { "type": ["string", "null"] },
          "widget_config": { "$ref": "#/components/schemas/WidgetConfigInput" }
        }
      },
      "GovernanceStatus": {
        "type": "object",
        "properties": {
          "tenant_name": { "type": "string" },
          "governance_status": { "type": "string", "example": "active" },
          "total_interactions": { "type": "integer" },
          "pii_detections_redacted": { "type": "integer" },
          "last_receipt_timestamp": { "type": ["string", "null"], "format": "date-time" }
        }
      },
      "WidgetVerifyRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": { "type": "string", "description": "Website URL to check for widget installation" }
        }
      },
      "WidgetVerifyResponse": {
        "type": "object",
        "properties": {
          "installed": { "type": "boolean" },
          "url": { "type": "string", "format": "uri" },
          "checked_at": { "type": "string", "format": "date-time" },
          "error": { "type": "string", "description": "Present if the target URL was unreachable" }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "example": "ok" },
          "service": { "type": "string", "example": "tork-chat" },
          "engine": { "type": "object", "additionalProperties": true },
          "timestamp": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Unauthorized" }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Not found" }
          }
        }
      },
      "RateLimit": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "Retry-After": { "schema": { "type": "integer" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/RateLimitError" }
          }
        }
      },
      "InternalError": {
        "description": "Internal server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Internal server error" }
          }
        }
      }
    }
  },
  "paths": {
    "/api/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Service health check",
        "description": "Returns the health status of the Tork Chat service and the underlying chat engine.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" }
              }
            }
          },
          "503": {
            "description": "Chat engine is unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "unreachable" },
                    "service": { "type": "string" },
                    "message": { "type": "string" },
                    "timestamp": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/chat": {
      "post": {
        "tags": ["Chat"],
        "summary": "Send a chat message (full RAG pipeline)",
        "description": "Processes a user message through the complete Tork pipeline: IP/org rate limiting, L3 input governance (PII scanning), topic guard, data connector intent detection, vector retrieval, Claude LLM, L6 output governance, and usage metering. Returns the AI response with governance receipt IDs.",
        "operationId": "postChat",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ChatRequest" },
              "example": {
                "message": "What are your business hours?",
                "org_id": "550e8400-e29b-41d4-a716-446655440000",
                "widget_id": "my-widget",
                "session_id": "660e8400-e29b-41d4-a716-446655440001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ChatResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "402": {
            "description": "Plan conversation limit reached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": { "type": "string" },
                    "message": { "type": "string" },
                    "upgrade_url": { "type": "string" },
                    "used": { "type": "integer" },
                    "limit": { "type": "integer" },
                    "plan": { "type": "string" }
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimit" },
          "503": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/proxy/chat": {
      "post": {
        "tags": ["Chat"],
        "summary": "Proxy chat (widget-facing, CORS enabled)",
        "description": "CORS-enabled proxy endpoint for the embedded widget. Forwards the request to the internal chat engine. Rate limited to 60 req/min per IP.",
        "operationId": "postProxyChat",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ProxyChatRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Response from chat engine",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimit" },
          "502": {
            "description": "Chat engine unreachable",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "options": {
        "tags": ["Chat"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyChat",
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/chat-stream": {
      "post": {
        "tags": ["Chat"],
        "summary": "Streaming chat via SSE (widget-facing, CORS enabled)",
        "description": "Streams chat responses as Server-Sent Events. Rate limited to 60 req/min per IP. The response body is a text/event-stream.",
        "operationId": "postProxyChatStream",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ProxyChatRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SSE stream",
            "content": {
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimit" },
          "502": {
            "description": "Engine unreachable or no stream body",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "options": {
        "tags": ["Chat"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyChatStream",
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/ingest": {
      "post": {
        "tags": ["Knowledge Base"],
        "summary": "Ingest a document (direct upload with chunking + embedding)",
        "description": "Accepts a PDF, TXT, or MD file, extracts text, chunks it, embeds each chunk, and stores the vectors in the knowledge base. Returns the new document ID and chunk count.",
        "operationId": "postIngest",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "org_id", "widget_id"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF, TXT, or MD file"
                  },
                  "org_id": { "type": "string", "format": "uuid" },
                  "widget_id": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Document ingested",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IngestResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error (missing file, wrong type, no text extracted)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/onboard": {
      "post": {
        "tags": ["Tenants"],
        "summary": "Create a new tenant (onboarding)",
        "description": "Creates a new tenant record during signup. Derives a URL-safe slug from the business name. Optionally creates a tenant membership for the authenticated user.",
        "operationId": "postOnboard",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/OnboardRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tenant created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": { "type": "boolean" },
                    "slug": { "type": "string" },
                    "tenant": { "$ref": "#/components/schemas/TenantInfo" }
                  }
                }
              }
            }
          },
          "400": {
            "description": "business_name is required",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/proxy/tenant/{slug}": {
      "get": {
        "tags": ["Tenants"],
        "summary": "Get public tenant configuration by slug",
        "description": "Returns tenant name, bot name, welcome message, and widget config. Cached for 300 seconds. CORS enabled.",
        "operationId": "getProxyTenant",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Tenant URL slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Tenant config",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TenantConfig" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Tenants"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyTenant",
        "parameters": [
          { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/widget-config/{slug}": {
      "get": {
        "tags": ["Widgets"],
        "summary": "Get widget configuration by slug",
        "description": "Returns widget configuration including bot name, welcome message, accent color, suggested questions, and locale. Normalises widget_config JSONB fields to top-level. Falls back to Supabase direct query if the engine is unavailable. Cached 300 seconds.",
        "operationId": "getProxyWidgetConfig",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Widget configuration",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WidgetConfig" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Widgets"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyWidgetConfig",
        "parameters": [
          { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/verify-widget": {
      "post": {
        "tags": ["Widgets"],
        "summary": "Verify widget installation on a URL",
        "description": "Fetches the given URL and checks whether the Tork widget script tag is present in the HTML.",
        "operationId": "postProxyVerifyWidget",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WidgetVerifyRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/WidgetVerifyResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid or missing URL",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    },
    "/api/proxy/verify/{slug}": {
      "get": {
        "tags": ["Governance"],
        "summary": "Get governance status for a tenant",
        "description": "Returns Tork Govern activity for a tenant: total interactions, PII detections redacted, and the timestamp of the last receipt.",
        "operationId": "getProxyVerify",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Governance status",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GovernanceStatus" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Governance"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyVerify",
        "parameters": [
          { "name": "slug", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/knowledge/documents": {
      "get": {
        "tags": ["Knowledge Base"],
        "summary": "List knowledge base documents",
        "description": "Returns all documents for the current tenant, ordered by creation date descending. Requires admin session.",
        "operationId": "getProxyKnowledgeDocuments",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Document list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Document" }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Knowledge Base"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyKnowledgeDocuments",
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/knowledge/documents/{id}": {
      "get": {
        "tags": ["Knowledge Base"],
        "summary": "Get a document with its chunks",
        "description": "Returns a single document record plus all its text chunks, ordered by chunk index.",
        "operationId": "getProxyKnowledgeDocument",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Document with chunks",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DocumentWithChunks" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "patch": {
        "tags": ["Knowledge Base"],
        "summary": "Update document visibility",
        "operationId": "patchProxyKnowledgeDocument",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visibility": { "type": "string", "enum": ["internal", "public"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated document",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "delete": {
        "tags": ["Knowledge Base"],
        "summary": "Delete a document and its chunks",
        "operationId": "deleteProxyKnowledgeDocument",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Knowledge Base"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyKnowledgeDocument",
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/knowledge/upload": {
      "post": {
        "tags": ["Knowledge Base"],
        "summary": "Upload a document to the knowledge base",
        "description": "Accepts PDF, DOCX, XLSX, CSV, or TXT files up to 10 MB. Forwards to the chat engine for ingestion. CORS enabled.",
        "operationId": "postProxyKnowledgeUpload",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF, DOCX, XLSX, CSV, or TXT (max 10 MB)"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": ["internal", "public"],
                    "default": "internal"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload successful",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "400": { "description": "Missing file", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "File too large (> 10 MB)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "415": { "description": "Unsupported file type", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Knowledge Base"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyKnowledgeUpload",
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/knowledge/scan": {
      "post": {
        "tags": ["Knowledge Base"],
        "summary": "Start a URL scan",
        "description": "Initiates a web crawl of the given URL to extract content for the knowledge base. Returns a scan job ID.",
        "operationId": "postProxyKnowledgeScan",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScanRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan started",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "400": { "description": "url is required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "options": {
        "tags": ["Knowledge Base"],
        "summary": "CORS preflight",
        "operationId": "optionsProxyKnowledgeScan",
        "responses": { "204": { "description": "No content" } }
      }
    },
    "/api/proxy/knowledge/scan/{id}": {
      "get": {
        "tags": ["Knowledge Base"],
        "summary": "Get URL scan status",
        "description": "Returns the current status of an ongoing or completed URL scan job.",
        "operationId": "getProxyKnowledgeScan",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Scan job ID" }
        ],
        "responses": {
          "200": {
            "description": "Scan status",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/proxy/knowledge/summary": {
      "get": {
        "tags": ["Knowledge Base"],
        "summary": "Knowledge base summary",
        "description": "Returns aggregated document and chunk counts for the knowledge base.",
        "operationId": "getProxyKnowledgeSummary",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "document_count": { "type": "integer" },
                    "chunk_count": { "type": "integer" }
                  },
                  "additionalProperties": true
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/proxy/knowledge/approvals": {
      "get": {
        "tags": ["Knowledge Base"],
        "summary": "List documents pending approval",
        "description": "Returns documents in the knowledge base that require admin review before being used for retrieval.",
        "operationId": "getProxyKnowledgeApprovals",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Pending approvals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": { "$ref": "#/components/schemas/Document" }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/proxy/knowledge/approve": {
      "post": {
        "tags": ["Knowledge Base"],
        "summary": "Approve a knowledge base document",
        "description": "Marks a document as approved so it becomes available for RAG retrieval.",
        "operationId": "postProxyKnowledgeApprove",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["document_id"],
                "properties": {
                  "document_id": { "type": "string", "format": "uuid" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/topic-guard/check": {
      "post": {
        "tags": ["Topic Guard"],
        "summary": "Test a message against blocked topics",
        "description": "Checks whether a message would be blocked by the Topic Guard. Used by the admin test/preview feature.",
        "operationId": "postTopicGuardCheck",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TopicGuardCheckRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Check result",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TopicGuardCheckResponse" }
              }
            }
          },
          "400": { "description": "message is required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/topic-guard/analytics": {
      "get": {
        "tags": ["Topic Guard"],
        "summary": "Topic Guard analytics",
        "description": "Returns blocked message counts (today/week/month), top 5 blocked topics, and the 20 most recent blocked messages for the current tenant.",
        "operationId": "getTopicGuardAnalytics",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Analytics data",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TopicGuardAnalytics" }
              }
            }
          }
        }
      }
    },
    "/api/admin/analytics": {
      "get": {
        "tags": ["Admin"],
        "summary": "Conversation and message analytics",
        "description": "Returns conversation and message counts by time window (today/week/month), average messages per conversation, escalation rate, busiest hour, and top intents.",
        "operationId": "getAdminAnalytics",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Analytics",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AnalyticsResponse" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/tenant": {
      "get": {
        "tags": ["Admin"],
        "summary": "Get current tenant info",
        "description": "Returns the current tenant's metadata (id, slug, name, plan, domain).",
        "operationId": "getAdminTenant",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Tenant info",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TenantInfo" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/widget-config": {
      "put": {
        "tags": ["Widgets"],
        "summary": "Update widget configuration",
        "description": "Updates the widget_config JSONB on the tenant record and syncs top-level fields (accent_color, logo_url, welcome_message, bot_name) for backwards compatibility.",
        "operationId": "putAdminWidgetConfig",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WidgetConfigInput" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/bot-config": {
      "put": {
        "tags": ["Admin"],
        "summary": "Update bot configuration",
        "description": "Stores the bot_config JSONB on the tenant record (system prompt, temperature, max tokens, etc.).",
        "operationId": "putAdminBotConfig",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BotConfigInput" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/topic-guard-config": {
      "get": {
        "tags": ["Topic Guard"],
        "summary": "Get Topic Guard configuration",
        "operationId": "getAdminTopicGuardConfig",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Topic Guard config",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TopicGuardConfig" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      },
      "put": {
        "tags": ["Topic Guard"],
        "summary": "Update Topic Guard configuration",
        "description": "Updates topic_guard_enabled, topic_guard_sensitivity, and blocked_topics. Propagates blocked_topics to all widgets for this tenant.",
        "operationId": "putAdminTopicGuardConfig",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/TopicGuardConfig" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/conversations/{id}": {
      "get": {
        "tags": ["Admin"],
        "summary": "Get a conversation with messages and leads",
        "operationId": "getAdminConversation",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": {
            "description": "Conversation detail",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/messages": {
      "get": {
        "tags": ["Admin"],
        "summary": "Get messages for a conversation",
        "operationId": "getAdminMessages",
        "security": [{ "BearerJWT": [] }],
        "parameters": [
          {
            "name": "conversation_id",
            "in": "query",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Message list",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/leads": {
      "delete": {
        "tags": ["Admin"],
        "summary": "Delete leads",
        "operationId": "deleteAdminLeads",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": { "type": "array", "items": { "type": "string", "format": "uuid" } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/notifications": {
      "get": {
        "tags": ["Admin"],
        "summary": "Get recent escalations and leads (last 24 hours)",
        "operationId": "getAdminNotifications",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Notifications",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/documents": {
      "get": {
        "tags": ["Admin"],
        "summary": "List all documents (admin)",
        "operationId": "getAdminDocuments",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Document list",
            "content": {
              "application/json": {
                "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Document" } }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/chunks": {
      "delete": {
        "tags": ["Admin"],
        "summary": "Delete chunks by source document",
        "operationId": "deleteAdminChunks",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document_id": { "type": "string", "format": "uuid" },
                  "source": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deleted",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/tags": {
      "get": {
        "tags": ["Admin"],
        "summary": "Get intent tags with counts",
        "operationId": "getAdminTags",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Tags with message counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "tag": { "type": "string" },
                      "count": { "type": "integer" }
                    }
                  }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/export/conversations": {
      "get": {
        "tags": ["Admin"],
        "summary": "Export conversations as CSV",
        "operationId": "getAdminExportConversations",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "CSV file download",
            "content": {
              "text/csv": {
                "schema": { "type": "string" }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/briefing": {
      "post": {
        "tags": ["Admin"],
        "summary": "Generate weekly AI briefing summary",
        "operationId": "postAdminBriefing",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Briefing summary",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/audit-log": {
      "post": {
        "tags": ["Admin"],
        "summary": "Write an audit log entry",
        "operationId": "postAdminAuditLog",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": { "type": "string" },
                  "resource_type": { "type": "string" },
                  "resource_id": { "type": "string" },
                  "metadata": { "type": "object", "additionalProperties": true }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "type": "boolean" } } } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/retrieval": {
      "post": {
        "tags": ["Admin"],
        "summary": "Test retrieval pipeline (debug)",
        "description": "Runs a query through the vector retrieval pipeline and returns the top matching chunks. Used for debugging RAG quality.",
        "operationId": "postAdminRetrieval",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": { "type": "string" },
                  "top_k": { "type": "integer", "default": 5 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retrieved chunks",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/platform": {
      "get": {
        "tags": ["Admin"],
        "summary": "Platform admin — list all tenants",
        "description": "Super-admin endpoint that returns all tenants. Requires platform admin role.",
        "operationId": "getAdminPlatform",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Platform overview",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/stripe/overview": {
      "get": {
        "tags": ["Billing"],
        "summary": "Stripe MRR and active subscriber overview",
        "operationId": "getAdminStripeOverview",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Stripe overview",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/connectors/decrypt": {
      "post": {
        "tags": ["Admin"],
        "summary": "Decrypt connector auth header",
        "description": "Decrypts an AES-256-GCM encrypted connector auth header value for display in the admin UI.",
        "operationId": "postAdminConnectorsDecrypt",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["encrypted"],
                "properties": {
                  "encrypted": { "type": "string", "description": "Hex-encoded AES-256-GCM ciphertext" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decrypted value",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "decrypted": { "type": "string" }
                  }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/admin/ingest": {
      "post": {
        "tags": ["Knowledge Base"],
        "summary": "Admin ingest endpoint (placeholder)",
        "description": "Placeholder endpoint — direct admin ingestion is coming soon. Use /api/proxy/knowledge/upload instead.",
        "operationId": "postAdminIngest",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Coming soon",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          }
        }
      }
    },
    "/api/mfa/backup-codes": {
      "get": {
        "tags": ["MFA"],
        "summary": "Get remaining backup code count",
        "operationId": "getMfaBackupCodes",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Count of remaining backup codes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "remaining": { "type": "integer" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["MFA"],
        "summary": "Generate new backup codes",
        "description": "Generates 8 new TOTP backup codes, stores hashed versions, and returns the plaintext codes once.",
        "operationId": "postMfaBackupCodes",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "New backup codes (shown once)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MfaBackupCodesResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "tags": ["MFA"],
        "summary": "Verify a backup code",
        "description": "Verifies a backup code during an MFA challenge. Consumes the code if valid.",
        "operationId": "putMfaBackupCodes",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["code"],
                "properties": {
                  "code": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code valid",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } }
              }
            }
          },
          "400": { "description": "Code required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "description": "Invalid backup code", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      },
      "delete": {
        "tags": ["MFA"],
        "summary": "Delete all backup codes",
        "operationId": "deleteMfaBackupCodes",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/mfa/org-settings": {
      "get": {
        "tags": ["MFA"],
        "summary": "Get org MFA policy and team member status",
        "operationId": "getMfaOrgSettings",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "MFA org settings",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MfaOrgSettings" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "put": {
        "tags": ["MFA"],
        "summary": "Update org MFA policy",
        "operationId": "putMfaOrgSettings",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mfa_required": { "type": "boolean" },
                  "mfa_grace_period_hours": { "type": "integer", "default": 48 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/billing/checkout": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create a Stripe Checkout session",
        "description": "Creates a Stripe Checkout session for the specified plan and billing period. Supports Purchasing Power Parity (PPP) pricing across 3 tiers based on country.",
        "operationId": "postBillingCheckout",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BillingCheckoutRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout session URL",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BillingCheckoutResponse" }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/billing/portal": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create a Stripe Billing Portal session",
        "description": "Creates a Stripe Customer Portal session for managing subscriptions, invoices, and payment methods.",
        "operationId": "postBillingPortal",
        "security": [{ "BearerJWT": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BillingPortalRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Billing portal URL",
            "content": {
              "application/json": {
                "schema": { "type": "object", "properties": { "url": { "type": "string", "format": "uri" } } }
              }
            }
          },
          "400": {
            "description": "No billing account found",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/billing/tenant": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get tenant ID for authenticated user",
        "description": "Returns the tenant ID associated with the authenticated user. Falls back to owner lookup, membership lookup, and auto-creation if no tenant exists.",
        "operationId": "getBillingTenant",
        "security": [{ "BearerJWT": [] }],
        "responses": {
          "200": {
            "description": "Tenant ID",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenantId": { "type": ["string", "null"], "format": "uuid" },
                    "name": { "type": "string" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/usage/{tenantId}": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get usage metrics for a tenant",
        "description": "Returns plan, subscription status, and usage counts (conversations, messages, documents) for the current billing period.",
        "operationId": "getUsage",
        "parameters": [
          {
            "name": "tenantId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage metrics",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UsageResponse" }
              }
            }
          },
          "400": { "description": "tenantId required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/org-usage/{orgId}": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get org usage by plan (rate limited)",
        "operationId": "getOrgUsage",
        "parameters": [
          {
            "name": "orgId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "format": "uuid" }
          }
        ],
        "responses": {
          "200": {
            "description": "Usage by plan",
            "content": {
              "application/json": {
                "schema": { "type": "object", "additionalProperties": true }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimit" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    },
    "/api/webhooks/stripe": {
      "post": {
        "tags": ["Billing"],
        "summary": "Stripe webhook handler",
        "description": "Receives Stripe webhook events: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_succeeded, invoice.payment_failed. Includes idempotency via chat_webhook_events table.",
        "operationId": "postWebhooksStripe",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "type": "object", "additionalProperties": true }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Event processed",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "received": { "type": "boolean" } } } } }
          },
          "400": { "description": "Webhook signature verification failed" },
          "500": { "$ref": "#/components/responses/InternalError" }
        }
      }
    }
  }
}
