{
  "openapi": "3.1.0",
  "info": {
    "title": "OyeChats API",
    "version": "1.0.0",
    "description": "REST API for the OyeChats AI chatbot platform.\n\nAuthenticate workspace-scoped requests with `X-API-Key` (Settings \u2192 API Keys in the dashboard). Widget endpoints authenticate with the public `X-Bot-Key` embed key; live-chat operator endpoints authenticate with `X-Operator-Key`.\n\nThis document describes the customer-facing surface only. See https://www.oyechats.com/docs for guides.",
    "contact": {
      "name": "OyeChats Support",
      "email": "support@oyechats.com"
    },
    "termsOfService": "https://www.oyechats.com/legal/terms"
  },
  "paths": {
    "/auth/me/entitlements": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Get My Entitlements",
        "description": "Return the resolved plan entitlements for the authenticated workspace.\n\nUsed by the admin app's ``useEntitlements`` hook to drive every feature\ngate, limit display, and upgrade prompt without each component\nre-fetching the plan. Operators see the entitlements of the client\nthey belong to \u2014 that's the workspace they're acting in, not their\nown (operators don't have personal subscriptions).\n\nResponse shape mirrors ``PlanEntitlements.to_json_dict()`` plus a small\nset of derived booleans the UI uses heavily.",
        "operationId": "get_my_entitlements_auth_me_entitlements_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/auth/me": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Get Current User Endpoint",
        "description": "Return the authenticated principal's profile + workspace bot count.\n\nUsed by the admin TopBar to populate the user-menu dropdown (email,\njoining date, bots). Accepts BOTH ``X-API-Key`` (clients) and\n``X-Operator-Key`` (operators) so the dropdown works regardless of how\nthe user logged in. For an operator, ``bot_count`` is the count of bots\nin their workspace (the client they belong to) \u2014 that's what the user\nexpects to see for the \"X bots\" line, not zero.",
        "operationId": "get_current_user_endpoint_auth_me_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrentUserResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/auth/onboarding/complete": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Complete Onboarding",
        "description": "Mark the account's guided onboarding (Build Studio) as complete.\n\nCalled when the user finishes the Studio's Go-live milestone. Idempotent \u2014\nsafe to call more than once.",
        "operationId": "complete_onboarding_auth_onboarding_complete_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/auth/verify-email": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Verify Email",
        "description": "Verify a client's email using the 6-digit OTP sent at registration.\n\nWrong guesses are counted per ACCOUNT (see :mod:`app.core.otp_guard`), not\njust per IP: the ``@limiter.limit`` below keys on the caller's address, so\non its own it does nothing against a prober rotating through a proxy pool\nwith a 6-digit keyspace to cover. Once the per-account budget is spent the\ncode is burned and the user has to request a fresh one, which is the\nbehaviour the other OTP flows in this module already have.",
        "operationId": "verify_email_auth_verify_email_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/resend-verification": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Resend Verification",
        "description": "Re-send a fresh 6-digit verification OTP. Safe to call on unknown emails.",
        "operationId": "resend_verification_auth_resend_verification_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResendVerificationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Login",
        "description": "Authenticate a Client and return their permanent API key.\n\nTwo independent ceilings apply. ``@limiter.limit`` bounds one SOURCE\naddress; :func:`note_failed_login` bounds attempts against one TARGET\naccount, which is what password-spraying from a proxy pool defeats when\nonly the per-IP limit exists. The account ceiling is checked before the\npassword comparison so a throttled account costs an attacker a 429 rather\nthan a bcrypt verification.",
        "operationId": "login_auth_login_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/detect-country": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "Detect Country",
        "description": "Resolve the caller's country from edge headers, for the signup form.\n\nPublic (no auth) \u2014 the register page calls this on load to preselect the\nvisitor's country in the billing-country field. Returns the ISO 3166-1\nalpha-2 code, or ``null`` when no edge signal is present (local dev, direct\norigin hit) so the form can fall back to an unselected placeholder.",
        "operationId": "detect_country_auth_detect_country_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/auth/register": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Register",
        "description": "Self-service client registration.\nCreates a new client account and returns an API key for immediate login.",
        "operationId": "register_auth_register_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegisterResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/request-password-reset": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Request Password Reset",
        "description": "Generates an OTP and sends it via email.",
        "operationId": "request_password_reset_auth_request_password_reset_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestPasswordResetRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/reset-password": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Reset Password",
        "description": "Verifies OTP and resets the password.",
        "operationId": "reset_password_auth_reset_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/operator-login": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Operator Login",
        "description": "Authenticate an Operator via email and password.\nReturns the Operator's API Key for subsequent requests via X-Operator-Key header.",
        "operationId": "operator_login_auth_operator_login_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OperatorLoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperatorLoginResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/operator-change-password": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "Operator Change Password",
        "description": "Operator changes their own password.",
        "operationId": "operator_change_password_auth_operator_change_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OperatorChangePasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/auth/google/login": {
      "get": {
        "tags": [
          "oauth"
        ],
        "summary": "Google Login",
        "description": "Kick off the Google OAuth flow.\n\nIssues the state cookie and 302-redirects to Google's consent screen.\n``next`` is an optional relative path to land on after success (e.g.\n``/billing``). ``mode`` is telemetry only \u2014 backend behaviour is the\nsame for login and signup. ``client`` is ``\"web\"`` (default) or\n``\"mobile\"`` \u2014 the mobile app passes ``client=mobile`` so the callback\nredirects into the app's ``oyechats://`` scheme instead of the admin\nweb app once Google sends the user back.",
        "operationId": "google_login_auth_google_login_get",
        "parameters": [
          {
            "name": "next",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 2048
                },
                {
                  "type": "null"
                }
              ],
              "title": "Next"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "login",
                "register"
              ],
              "type": "string",
              "default": "login",
              "title": "Mode"
            }
          },
          {
            "name": "promo_code",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 64
                },
                {
                  "type": "null"
                }
              ],
              "title": "Promo Code"
            }
          },
          {
            "name": "referral_code",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 64
                },
                {
                  "type": "null"
                }
              ],
              "title": "Referral Code"
            }
          },
          {
            "name": "client",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "web",
                "mobile"
              ],
              "type": "string",
              "default": "web",
              "title": "Client"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/google/callback": {
      "get": {
        "tags": [
          "oauth"
        ],
        "summary": "Google Callback",
        "description": "Handle Google's redirect back into the app.\n\nValidates the CSRF state cookie, exchanges the code for a verified\nGoogle profile, then resolves the Client through three matching\nlayers (see ``_resolve_client_for_profile``) and issues a 302 to the\nfrontend with the api_key in the URL fragment.",
        "operationId": "google_callback_auth_google_callback_get",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 512
                },
                {
                  "type": "null"
                }
              ],
              "title": "Code"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 512
                },
                {
                  "type": "null"
                }
              ],
              "title": "State"
            }
          },
          {
            "name": "error",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 64,
                  "pattern": "^[A-Za-z0-9_.\\-]*$"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Error"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/google/id-token": {
      "post": {
        "tags": [
          "oauth"
        ],
        "summary": "Google Id Token Login",
        "description": "Handle native mobile Google Sign-In using an id_token directly.\n\nThe mobile app uses the native Google Sign-In SDK to fetch an id_token\nand POSTs it here. We verify the token signature and exchange it for a\nprofile, then create or return the API key in a JSON response.",
        "operationId": "google_id_token_login_auth_google_id_token_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdTokenRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/auth/google/status": {
      "get": {
        "tags": [
          "oauth"
        ],
        "summary": "Google Oauth Status",
        "description": "Tell the frontend whether the Google button should render.\n\nReturning a single boolean keeps the frontend logic trivial \u2014 if the\nserver hasn't been configured with credentials, the button hides\nitself rather than 503-ing on click.",
        "operationId": "google_oauth_status_auth_google_status_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/demo/{bot_key}": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "Get Bot Demo Page",
        "description": "Render a shareable demo page, or an iframe-based preview when *url* is supplied.\n\nWhen ``edit=1`` is passed, the page enables a postMessage bridge so the\nembedding dashboard can drive widget appearance in real time.",
        "operationId": "get_bot_demo_page_demo__bot_key__get",
        "parameters": [
          {
            "name": "bot_key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 4,
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_\\-]+$",
              "title": "Bot Key"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 2048
                },
                {
                  "type": "null"
                }
              ],
              "title": "Url"
            }
          },
          {
            "name": "edit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 1,
              "minimum": 0,
              "default": 0,
              "title": "Edit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/settings/public": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "Get Bot Settings Public",
        "description": "Public endpoint for the widget to fetch bot settings.\nAuthenticated via X-Bot-Key or X-API-Key (backward compat).\n\nIncludes the bot owner's subscription health so the widget can choose\nto suppress the launcher (or render an offline indicator) when the\nworkspace is not serving. ``is_offline=True`` means visitors who do\nopen the widget will only get the configured ``offline_message`` \u2014\nthe chat endpoint will not run RAG.",
        "operationId": "get_bot_settings_public_bots_settings_public_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/bots": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "List Bots",
        "description": "List bots the caller can act on.\n\nScoping matrix\n--------------\n* **Client / workspace owner (own workspace, no operator hat)** \u2014 every\n  bot in the workspace.\n* **Operator (X-Operator-Key or linked-operator via X-Workspace-Id)** \u2014\n  the single bot they're bound to (one-to-one operator\u2194bot binding).\n* **Owner acting as their own self-operator** \u2014 the auth resolver classifies\n  this as ``type=\"client\"`` because the caller is looking at their own\n  workspace, so the operator-scoping branch above wouldn't fire on its\n  own. When the frontend sends ``X-Acting-Role: operator`` (the workspace\n  switcher pill sets this whenever ``currentRole === 'operator'``), we\n  look up the caller's self-operator row and restrict to that bot too.\n  Falling back to the full workspace list on any lookup miss preserves\n  owner UX for clients whose frontend didn't send the hint.\n\nOperators must not see or switch to other bots in the workspace; the admin\nUI's bot switcher renders this list verbatim, so filtering here keeps\nunauthorised bots off the client entirely.",
        "operationId": "list_bots_bots_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "X-Acting-Role",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Acting-Role"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BotResponse"
                  },
                  "title": "Response List Bots Bots Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Create Bot",
        "description": "Create a new bot for the authenticated workspace.\n\nSubscription-gated: workspaces whose owner's trial has expired (or\nwhose subscription is otherwise inactive) get a 403 with\n``error: subscription_required``. The dashboard's read-only mode\nsurfaces a \"Reactivate to add a new bot\" banner instead of letting\nthe customer queue work they can't complete.",
        "operationId": "create_bot_bots_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBotRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/checkout": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Create Bot Checkout",
        "description": "Mint a Razorpay subscription for one new bot.\n\nReturns the Razorpay Checkout payload (``subscription_id``,\n``key_id``, prefill). The frontend opens Razorpay; on success it\ncalls ``POST /bots/checkout/verify`` (or the production webhook\narrives first) to materialise the new Bot row.\n\nFree / first-bot creation does NOT go through this endpoint \u2014 that\nkeeps using ``POST /bots`` directly. Use ``can_client_add_new_bot``\nto decide which path the frontend should take.",
        "operationId": "create_bot_checkout_bots_checkout_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BotCheckoutRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/bots/checkout/verify": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Verify Bot Checkout",
        "description": "Verify the Razorpay success callback and materialise the new bot.\n\nWebhook delivery is the source of truth in production; this endpoint\nis the sync fallback so the customer doesn't have to wait for the\nwebhook to land before seeing their new bot. Idempotent: if the\nsubscription's activation webhook arrived first, the local row\nalready exists and the handler short-circuits.",
        "operationId": "verify_bot_checkout_bots_checkout_verify_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BotCheckoutVerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/bots/{bot_id}/demo-share-click": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Track Demo Share Click",
        "description": "Record that an authenticated workspace user copied a bot demo link.",
        "operationId": "track_demo_share_click_bots__bot_id__demo_share_click_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/{bot_id}/framework-presets": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "Get Framework Presets",
        "operationId": "get_framework_presets_bots__bot_id__framework_presets_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/brand-tone-presets": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "List Brand Tone Presets",
        "description": "Return the curated brand-tone preset catalog for the AI & Personality tab.",
        "operationId": "list_brand_tone_presets_bots_brand_tone_presets_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/bots/{bot_id}/brand-tone/detect": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Detect Brand Tone",
        "description": "Re-classify the bot's tone from its already-crawled content (no re-crawl).\n\nWrites the detected preset's canonical text + key and *unlocks* ``brand_tone``\n(an explicit \"make it auto\" request), so future crawls keep it fresh until the\ncustomer edits again.",
        "operationId": "detect_brand_tone_bots__bot_id__brand_tone_detect_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/{bot_id}/seed-questions": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Get Seed Questions",
        "description": "Onboarding \"seed questions\" for the Build Studio Prove step.\n\nLLM-proposed from the bot's auto-extracted company context, then each is\nverified answerable by the same retrieval the live bot uses (see\n``seed_questions_service``). Cached on the bot after the first computation;\npass ``force=true`` to recompute. Returns ``{\"questions\": [...]}`` (0-3). An\nempty list is a normal outcome (\"show only the open input\"), never an error.\nVerified-email gated like the other resource endpoints.",
        "operationId": "get_seed_questions_bots__bot_id__seed_questions_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "force",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "title": "Force"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/{bot_id}/brand-tone/preview": {
      "post": {
        "tags": [
          "bots"
        ],
        "summary": "Preview Brand Tone",
        "description": "Generate a 1-2 sentence sample bot reply in the given (unsaved) tone.",
        "operationId": "preview_brand_tone_bots__bot_id__brand_tone_preview_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BrandTonePreviewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/{bot_id}": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "Get Bot",
        "description": "Get details of a specific bot owned by the authenticated workspace.",
        "operationId": "get_bot_bots__bot_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "bots"
        ],
        "summary": "Update Bot",
        "description": "Update settings for a specific bot.\n\nWritable under a super-admin impersonation session (design \u00a76.1, \"AI Agent\nconfig edits\") \u2014 this is the single endpoint behind name, greeting, tone and\nappearance/branding, which is the most common \"it looks wrong\" support\nreport. Nothing here touches billing, credits, or credentials \u2014 but\n``UpdateBotRequest`` is broader than the \u00a76.1 wording: it also carries the\nwidget's origin allowlist (a security control) and the Account's\nnotification/reply-to addresses, so those specific fields are rejected for\nan impersonated caller below rather than granted wholesale.",
        "operationId": "update_bot_bots__bot_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBotRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "bots"
        ],
        "summary": "Delete Bot",
        "description": "Delete a bot and all its data (documents, sessions, messages).\n\nWhen the bot has its own per-bot subscription, cancel that\nsubscription first (both with Razorpay and locally). Two reasons:\n\n1. **Stop the bill.** Leaving the subscription active after the bot\n   is gone would keep charging the customer for nothing.\n2. **Side-step the partial unique index.** ``subscriptions.bot_id``\n   is ``ON DELETE SET NULL``, so deleting the bot would otherwise\n   null the FK on an ``active`` subscription \u2014 which collides with\n   ``ix_subscriptions_client_legacy_active`` (only one client-level\n   active sub per client). Marking the sub ``canceled`` first takes\n   it out of that index's predicate before the row is touched.\n\nThe legacy / Free bot path (no ``subscription_id``) skips this and\njust deletes the bot as before.",
        "operationId": "delete_bot_bots__bot_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/bots/{bot_id}/recrawl": {
      "get": {
        "tags": [
          "bots"
        ],
        "summary": "Get Recrawl Status",
        "description": "Return the current auto-recrawl state + last-run summary for a bot.",
        "operationId": "get_recrawl_status_bots__bot_id__recrawl_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecrawlStatusResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "bots"
        ],
        "summary": "Update Recrawl",
        "description": "Toggle auto-recrawl on or off for a bot.\n\nEnabling requires the ``auto_recrawl`` feature flag on the client's\nplan \u2014 Free / Starter plans get a structured 403 the admin UI catches\nand routes to the upgrade flow. Disabling always succeeds so a\ncustomer can turn the feature off even after a plan downgrade left\nthem without the entitlement.",
        "operationId": "update_recrawl_bots__bot_id__recrawl_patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecrawlUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecrawlStatusResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Chat Endpoint",
        "description": "RAG Endpoint: Analyzes the question, retrieves relevant documents for the bot,\nand generates a standalone answer.\nAuthenticated via X-Bot-Key or X-API-Key (resolves default bot). Owner-preview\nrequests (Build Studio: ?preview=true&bot_id=) resolve any owned bot and are free.\n\nMarked writable for a super-admin impersonation session (design \u00a76.1,\n\"Preview-mode test chat\"): an owner-preview reply skips credit deduction\nentirely, so exercising the AI Agent costs the Account nothing.\n\nIMPORTANT \u2014 the write guard does **not** run on this endpoint. It lives in\nthe Client resolvers, and this route authenticates through\n``get_bot_for_chat`` instead, which resolves a Bot. The marker is therefore\nnot what makes this safe. The real constraint is enforced in\n``auth._resolve_preview_client``: an ``X-Impersonation-Token`` is accepted\n**only** on the owner-preview path (``preview=true`` + ``bot_id``, which\nsets ``_is_preview`` and skips deduction) and is never forwarded to\n``get_current_bot``, so the paid widget path on this same endpoint stays\nunreachable under impersonation. The kill switch is checked there too.",
        "operationId": "chat_endpoint_chat_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "preview",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Owner-preview mode (Build Studio)",
              "default": false,
              "title": "Preview"
            },
            "description": "Owner-preview mode (Build Studio)"
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Bot ID (owner-preview only)",
              "title": "Bot Id"
            },
            "description": "Bot ID (owner-preview only)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/stream": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Chat Stream Endpoint",
        "description": "Streaming RAG Endpoint: Streams the response token-by-token via SSE.\nProtocol: METADATA:{json} \u2192 text chunks \u2192 FINAL_METADATA:{json}\nAuthenticated via X-Bot-Key (widget) or X-API-Key. Owner-preview requests\n(Build Studio: ``?preview=true&bot_id=``) resolve any owned bot and are free\n\u2014 no credit deduction \u2014 exactly like the non-streaming ``POST /chat``.\n\nMarked writable for a super-admin impersonation session (design \u00a76.1,\n\"Preview-mode test chat\"), with the same mechanics documented on ``POST\n/chat``: the write guard does not run on this endpoint, and the\nowner-preview-only constraint is enforced in\n``auth._resolve_preview_client`` rather than by the marker.",
        "operationId": "chat_stream_endpoint_chat_stream_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "preview",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Owner-preview mode (Build Studio)",
              "default": false,
              "title": "Preview"
            },
            "description": "Owner-preview mode (Build Studio)"
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Bot ID (owner-preview only)",
              "title": "Bot Id"
            },
            "description": "Bot ID (owner-preview only)"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/validate-email": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Validate Email Endpoint",
        "description": "Real-time check the widget calls on email-field blur, before the\nvisitor can submit the handoff or offline-message form. Auth: X-Bot-Key.\n\nPaid plans only (every tier above Free) \u2014 gated per-bot via\n``is_email_validation_enabled_for_bot`` so a Free bot never fires the\npaid Reoon call (not just hides its result): its widget still submits\nthe form normally, exactly as it did before this feature existed.\n\nDeliberately lenient: blocks only unambiguous junk (bad syntax,\ndisposable addresses, spamtraps, domains with no working mail server).\nCatch-all and \"unknown\" results are let through \u2014 many real B2B\ncompanies run catch-all mail gateways that Reoon can't confirm\ndeliverability on either way, and this endpoint's job is to keep fake\nleads out, not to reject genuine visitors it can't fully verify. Fails\nopen (valid=True) if Reoon is unreachable, unconfigured, or the bot's\nplan doesn't include this feature \u2014 an infra hiccup or a lower tier\nmust never block a real visitor from talking to a human. See\ndocs/superpowers/plans/2026-08-08-visitor-intelligence.md.",
        "operationId": "validate_email_endpoint_chat_validate_email_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ValidateEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/lead-capture": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Lead Capture Endpoint",
        "description": "Capture lead contact info from pre-chat or handoff form. Auth: X-Bot-Key.\n\nEmail validation (Reoon) runs entirely in the background via\n``_enrich_lead_in_background`` \u2014 never here. Reoon's power mode can take\nseconds to over a minute; blocking this endpoint on it would hang the\nvisitor's live chat request. A possibly-invalid email is still captured:\nReoon has known false positives (confirmed empirically \u2014 see\ndocs/superpowers/plans/2026-08-08-visitor-intelligence.md \u00a704), so\nhard-rejecting a lead the visitor is actively submitting risks losing a\nreal one. The validation result instead gates the *manual* follow-up\nsend later (Gate 1 in ``lead_routes.send_manual_follow_up``).",
        "operationId": "lead_capture_endpoint_chat_lead_capture_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeadCaptureRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/behavioral-signals": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Behavioral Signals Endpoint",
        "description": "Receive behavioral signals from the widget and compute a behavioral score.\n\nCalled on session init with page context, and on beforeunload with time-on-page.\nAuth: X-Bot-Key.",
        "operationId": "behavioral_signals_endpoint_chat_behavioral_signals_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BehavioralSignalsRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/meeting-booked": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Meeting Booked Endpoint",
        "operationId": "meeting_booked_endpoint_chat_meeting_booked_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MeetingBookedRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/lead-info/{session_id}": {
      "get": {
        "tags": [
          "chat"
        ],
        "summary": "Get Lead Info Endpoint",
        "description": "Fetch existing lead info for a widget session. Auth: X-Bot-Key.\nAlways returns HTTP 200 \u2014 non-critical endpoint that must never block widget load.\nUsed by the widget to pre-fill HandoffForm fields and skip re-asking known info.",
        "operationId": "get_lead_info_endpoint_chat_lead_info__session_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/feedback/{message_id}": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Submit Feedback Endpoint",
        "description": "Submit feedback (thumbs up/down) for a specific bot reply. Also scores the Langfuse trace if available.",
        "operationId": "submit_feedback_endpoint_chat_feedback__message_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Message Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FeedbackRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/history/{session_id}": {
      "get": {
        "tags": [
          "chat"
        ],
        "summary": "Get History Endpoint",
        "description": "Retrieve chat history for a given session.\n\nAccepts both admin auth (X-API-Key / X-Operator-Key) and widget auth (X-Bot-Key).\nSupports cursor-based pagination via `before` param.",
        "operationId": "get_history_endpoint_chat_history__session_id__get",
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Cursor \u2014 return messages with id < this value",
              "title": "Before"
            },
            "description": "Cursor \u2014 return messages with id < this value"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "description": "Max messages to return",
              "default": 50,
              "title": "Limit"
            },
            "description": "Max messages to return"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/upload-url": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Get Visitor Upload Url",
        "description": "Return a presigned B2 PUT URL so the widget can upload a file directly.\n\nAuth: X-Bot-Key header. The widget uploads via PUT (no auth needed) then\nsends the file_url over the live-chat WebSocket.",
        "operationId": "get_visitor_upload_url_chat_upload_url_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UploadUrlRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/transcript": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Send Chat Transcript",
        "description": "Send the chat transcript for a session to the visitor's email.\n\nAuth: X-Bot-Key header (widget).\nRate limit: 3 per minute per bot key to prevent abuse.",
        "operationId": "send_chat_transcript_chat_transcript_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranscriptEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/chat/connect-request/{session_id}": {
      "get": {
        "tags": [
          "chat"
        ],
        "summary": "Get Pending Connect Request",
        "description": "Widget polls this while in bot mode to discover operator-initiated\nconnect invitations. Returns ``{ pending: false }`` when none.\n\nAuth: ``X-Bot-Key`` (visitor widget). The session must belong to the bot.",
        "operationId": "get_pending_connect_request_chat_connect_request__session_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/chat/connect-request/{session_id}/respond": {
      "post": {
        "tags": [
          "chat"
        ],
        "summary": "Respond To Connect Request",
        "description": "Visitor accepts or declines an operator's connect-request.\n\nOn accept we atomically promote the session to live chat and assign it to\nthe requesting operator. On decline (or stale ``request_id``) we just\nconsume the pending entry \u2014 the bot conversation continues unchanged.",
        "operationId": "respond_to_connect_request_chat_connect_request__session_id__respond_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectRequestResponseBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/documents": {
      "get": {
        "tags": [
          "documents"
        ],
        "summary": "Get Documents Endpoint",
        "description": "Retrieve a list of all ingested documents for the authenticated client.",
        "operationId": "get_documents_endpoint_documents_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/documents/knowledge-state": {
      "get": {
        "tags": [
          "documents"
        ],
        "summary": "Get Knowledge State Endpoint",
        "description": "Whether this bot's knowledge was deactivated by a plan lapse to Free.\n\n``deactivated`` is true when the bot has any inactive chunk \u2014 the signal the\nadmin uses to show the \"re-crawl / re-upload to reactivate on Free, or\nupgrade to restore\" banner. A brand-new Free bot has 0 inactive chunks and\ntherefore never sees it.",
        "operationId": "get_knowledge_state_endpoint_documents_knowledge_state_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/documents/pages": {
      "get": {
        "tags": [
          "documents"
        ],
        "summary": "Get Document Pages Endpoint",
        "description": "Return all crawled page URLs for a website source, with per-page chunk counts and titles.",
        "operationId": "get_document_pages_endpoint_documents_pages_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 253,
              "pattern": "^[A-Za-z0-9._\\-]+$",
              "description": "Normalized root domain (e.g. fynix.digital)",
              "title": "Source"
            },
            "description": "Normalized root domain (e.g. fynix.digital)"
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentPagesResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{document_name}": {
      "delete": {
        "tags": [
          "documents"
        ],
        "summary": "Delete Document Endpoint",
        "description": "Delete all documents associated with a document name for the authenticated client.",
        "operationId": "delete_document_endpoint_documents__document_name__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "document_name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 2048,
              "title": "Document Name"
            }
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/ingest/preview-cost": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Preview Ingest Cost",
        "description": "Return the credit cost the customer will pay if they upload ``files``.\n\nExtracts each file in-memory, counts words, and returns the tiered credit\ncharge per file plus the grand total \u2014 **without** saving anything,\ncharging credits, or touching the ingest pipeline. The admin UI calls\nthis after the user picks files so we can render a\n\"Upload for N credits\" confirm button.\n\nSame size + type validation as ``/ingest`` so the preview matches what\nwould actually happen. Files that fail extraction (scanned PDFs, empty\nDOCX) show ``words: 0, credits: 0`` and a diagnostic ``reason`` \u2014 matching\nthe \"not billed, will be quarantined\" behavior of the real upload path.\n\nNo credit gate here \u2014 the goal is a *preview*, not a hold. Confirming\nthe upload runs the real deduction on POST /ingest, which is where the\n402 for insufficient credits fires.",
        "operationId": "preview_ingest_cost_ingest_preview_cost_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_preview_ingest_cost_ingest_preview_cost_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/ingest": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Ingest Documents",
        "description": "Ingest multiple files (PDF, DOCX, TXT, MD) for a client.\n\nSubscription-gated \u2014 uploading new content into the knowledge base is\na paid-feature action. Customers with an expired trial can still see\nand delete what they already uploaded, just not add more until they\nreactivate.\n\nCredit-metered at ``credit_cost.document_upload`` per file (default 2).\nCost is calculated against the post-validation file count so unsupported\nextensions and oversize files don't burn credits. Deduction happens\nBEFORE the disk write so we never persist a file we can't bill for; if\na write later fails, the per-file cost is refunded.",
        "operationId": "ingest_documents_ingest_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_ingest_documents_ingest_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/ingest/status/{job_id}": {
      "get": {
        "tags": [
          "documents"
        ],
        "summary": "Ingest Status Endpoint",
        "description": "Poll the status of a background ingestion job.\n\nReturns the job's current state: queued, in_progress, complete, or failed.\nOnly available when WORKER_ENABLED=true (ARQ task queue).",
        "operationId": "ingest_status_endpoint_ingest_status__job_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Job Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/crawl/progress": {
      "get": {
        "tags": [
          "documents"
        ],
        "summary": "Crawl Progress Endpoint",
        "description": "Return live progress + terminal status for the caller's crawl.\n\nPolled by the frontend every few seconds. Reads from Redis so the same\nstate is visible whether the crawl is running in this API process or in\nthe ARQ worker. The response always contains ``status`` (one of\n``\"idle\" | \"running\" | \"cancelling\" | \"cancelled\" | \"done\" | \"failed\"``)\nand ``urls`` (list of URLs discovered so far). When ``status=\"running\"``\nthe response also contains ``pages_crawled``, ``max_pages``,\n``current_url``, ``started_at`` (epoch seconds), and ``cancellable``\n(bool) so the UI can render a real progress bar, an ETA, and a Cancel\nbutton. When ``status=\"done\"`` / ``\"cancelled\"`` the response contains\n``result`` with the ingestion payload; when ``\"failed\"`` it contains\n``error``.",
        "operationId": "crawl_progress_endpoint_crawl_progress_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/crawl/cancel": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Crawl Cancel Endpoint",
        "description": "Request cancellation of the caller's in-flight crawl.\n\nReturns 202 immediately. The orchestrator (running in the ARQ worker or\ninline) sees the cancel flag within ~1s, asks the crawler subprocess to\nstop cooperatively between URLs (fast, clean, no leaked Chromium), and\nfalls back to SIGTERM if the subprocess doesn't honour it within a few\nseconds. Any pages that were crawled before the cancel landed are still\ningested so we don't throw away work the customer already paid for.\n\nIdempotent: calling cancel twice is fine; the flag is a single Redis key\nthat auto-expires when the crawl finishes (or after ``CRAWL_SUBPROCESS\n_TIMEOUT + 60s`` if everything goes sideways).",
        "operationId": "crawl_cancel_endpoint_crawl_cancel_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/crawl/discover": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Crawl Discover Endpoint",
        "description": "Discover the number of crawlable pages on a site without ingesting content.\n\nFetches robots.txt \u2192 sitemaps \u2192 falls back to a 1-level HTML BFS if no\nsitemap is found. Returns within ~20 seconds. Used by the frontend to show\n\"Found X pages. Ready to crawl?\" before the user commits to a full crawl.\n\nThe ``total_found`` count is capped at the caller's plan ``max_crawl_pages``\nceiling so the number is always actionable and never exceeds what the plan\nallows. ``capped=true`` signals that there may be more pages than shown.\n\nPaid plans (Starter/Standard) carry an UNLIMITED (-1) page cap because\ncrawling is metered purely by credits; for them the discovery query falls\nback to a fixed 1000-URL ceiling so the preview stays bounded.",
        "operationId": "crawl_discover_endpoint_crawl_discover_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlDiscoverRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/crawl/diff": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Crawl Diff Endpoint",
        "description": "Diff a recrawl against the existing knowledge base for the given source.\n\nRuns the same robots.txt \u2192 sitemap \u2192 BFS discovery as ``/crawl/discover`` and\ncompares the resulting URL set against the pages already stored under\n``replace_source`` for this bot/client. Returns exact counts: ``unchanged``\n(URL present in both), ``new_pages`` (in sitemap but not stored), and\n``removed_pages`` (stored but no longer in sitemap).\n\nNotes:\n* URL-level diff only \u2014 actual content changes are detected per-page during\n  the crawl itself via the SHA-256 dedup hash in the ingestion pipeline. This\n  endpoint is fast (no page fetches) and is purely for the pre-recrawl\n  confirmation UI.\n* Numbers are exact within the discovery cap; if ``capped`` is true the\n  sitemap exceeded the plan ceiling and only the first ``plan_max`` URLs\n  were considered.",
        "operationId": "crawl_diff_endpoint_crawl_diff_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlDiffRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/crawl": {
      "post": {
        "tags": [
          "documents"
        ],
        "summary": "Crawl Endpoint",
        "description": "Start a crawl + ingestion job for a client.\n\nThe actual crawl runs in the ARQ worker (or as a FastAPI BackgroundTask\nwhen WORKER_ENABLED=false), so this endpoint returns 202 immediately\nwith a ``job_id``. Callers poll ``GET /crawl/progress`` to read live URL\ndiscovery and the terminal ``done`` / ``failed`` state.",
        "operationId": "crawl_endpoint_crawl_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrawlRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/qualification-funnel": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Qualification Funnel",
        "operationId": "get_qualification_funnel_analytics_qualification_funnel_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": false,
            "schema": {
              "enum": [
                "7d",
                "30d",
                "90d",
                "all"
              ],
              "type": "string",
              "default": "30d",
              "title": "Period"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/dashboard": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Dashboard Analytics Endpoint",
        "description": "Retrieve live aggregate statistics for the admin dashboard.",
        "operationId": "get_dashboard_analytics_endpoint_analytics_dashboard_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 365,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Restrict stats to the last N days",
              "title": "Days"
            },
            "description": "Restrict stats to the last N days"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/activity": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Activity Analytics Endpoint",
        "description": "Retrieve message activity over time for charts.",
        "operationId": "get_activity_analytics_endpoint_analytics_activity_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/top-questions": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Top Questions Endpoint",
        "description": "Retrieve the most common user queries.",
        "operationId": "get_top_questions_endpoint_analytics_top_questions_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/unanswered-questions": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Unanswered Questions Endpoint",
        "description": "Knowledge gaps: the questions the bot could not answer from its content.\n\nEach result is a distinct question the visitor asked when retrieval found\nnothing usable (the no-info pivot), with how often it was asked and when it\nwas last seen - so the customer knows which documents to add.",
        "operationId": "get_unanswered_questions_endpoint_analytics_unanswered_questions_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 365,
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "description": "Restrict to a trailing window of N days.",
              "title": "Days"
            },
            "description": "Restrict to a trailing window of N days."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/visitors": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Visitors Endpoint",
        "description": "Retrieve visitor sessions for the admin dashboard (most-recent first, paginated).",
        "operationId": "get_visitors_endpoint_analytics_visitors_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 1000,
              "minimum": 1,
              "default": 500,
              "title": "Limit"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0,
              "title": "Offset"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/ratings-summary": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Ratings Summary Endpoint",
        "description": "Retrieve post-chat visitor rating summary (avg, total, distribution).",
        "operationId": "get_ratings_summary_endpoint_analytics_ratings_summary_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/resolution-summary": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Resolution Summary Endpoint",
        "description": "Retrieve post-chat visitor resolution summary (resolved, unresolved, rate).",
        "operationId": "get_resolution_summary_endpoint_analytics_resolution_summary_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/feedback": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Feedback Endpoint",
        "description": "Retrieve all feedback for the admin dashboard.",
        "operationId": "get_feedback_endpoint_analytics_feedback_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/by-bot": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Per Bot Rollup Endpoint",
        "description": "Per-bot activity rollup for the account: credits, conversations, leads.\n\nBuilt for the agency case \u2014 many client sites on one account, one shared\ncredit pool \u2014 so each bot's spend is read from the ledger's\n``attributed_bot_id`` and stays correct even when the deduction came out\nof the pool. Bots with no activity in the window are omitted.",
        "operationId": "get_per_bot_rollup_endpoint_analytics_by_bot_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 365,
              "minimum": 1,
              "description": "Trailing window of N days",
              "default": 30,
              "title": "Days"
            },
            "description": "Trailing window of N days"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/by-bot.csv": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Per Bot Rollup Csv",
        "description": "The ``/analytics/by-bot`` rollup as a downloadable CSV.\n\nSame window and same auth as the JSON endpoint \u2014 an agency owner pulls one\nfile per reporting period and forwards it to the client it covers, so the\nfilename carries the window (``oyechats-report-2026-07-14-to-2026-08-13.csv``)\nand the rows arrive in the same order the dashboard shows them.\n\nNo totals row: a trailing aggregate in a CSV double-counts the moment\nanyone pivots or concatenates it, and a bot legitimately named \"Total\"\nwould be indistinguishable from it. The dashboard renders the totals.",
        "operationId": "get_per_bot_rollup_csv_analytics_by_bot_csv_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 365,
              "minimum": 1,
              "description": "Trailing window of N days",
              "default": 30,
              "title": "Days"
            },
            "description": "Trailing window of N days"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/journey/summary": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Journey Summary",
        "description": "Header-row totals: sessions with journey + per-conversion counts + leads.",
        "operationId": "get_journey_summary_analytics_journey_summary_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Calendar month as YYYY-MM (e.g. 2026-08)",
              "title": "Period"
            },
            "description": "Calendar month as YYYY-MM (e.g. 2026-08)"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/journey/top-pages": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Journey Top Pages",
        "description": "Ranked pages by distinct-session visits, optionally scoped by phase.",
        "operationId": "get_journey_top_pages_analytics_journey_top_pages_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Calendar month as YYYY-MM (e.g. 2026-08)",
              "title": "Period"
            },
            "description": "Calendar month as YYYY-MM (e.g. 2026-08)"
          },
          {
            "name": "phase",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "pre",
                    "chat",
                    "post"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "omit for all",
              "title": "Phase"
            },
            "description": "omit for all"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 20,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/journey/conversion-paths": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Journey Conversion Paths",
        "description": "Top pre-chat page sequences that preceded a given conversion event.",
        "operationId": "get_journey_conversion_paths_analytics_journey_conversion_paths_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "conversion_type",
            "in": "query",
            "required": true,
            "schema": {
              "enum": [
                "meeting_booked",
                "handoff_requested",
                "offline_message_sent"
              ],
              "type": "string",
              "title": "Conversion Type"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Calendar month as YYYY-MM (e.g. 2026-08)",
              "title": "Period"
            },
            "description": "Calendar month as YYYY-MM (e.g. 2026-08)"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 10,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/journey/post-chat": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Journey Post Chat",
        "description": "Where visitors go after the chat closes \u2014 first hops + full sequences.",
        "operationId": "get_journey_post_chat_analytics_journey_post_chat_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Calendar month as YYYY-MM (e.g. 2026-08)",
              "title": "Period"
            },
            "description": "Calendar month as YYYY-MM (e.g. 2026-08)"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 10,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/journey/pre-chat-sequences": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Get Journey Pre Chat Sequences",
        "description": "Top pre-chat page sequences across every session (converted or not).\n\nPowers the flow-diagram source rows on the Journey1 experiment: rather\nthan a bag of independent source pages, owners see the actual chained\nHome \u2192 About \u2192 Contact patterns visitors took before opening chat.",
        "operationId": "get_journey_pre_chat_sequences_analytics_journey_pre_chat_sequences_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          },
          {
            "name": "period",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "Calendar month as YYYY-MM (e.g. 2026-08)",
              "title": "Period"
            },
            "description": "Calendar month as YYYY-MM (e.g. 2026-08)"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 5,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/unsubscribe": {
      "get": {
        "tags": [
          "leads"
        ],
        "summary": "Unsubscribe Get",
        "description": "Handles direct clicks from email footers.",
        "operationId": "unsubscribe_get_leads_unsubscribe_get",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 512,
              "description": "Signed unsubscribe token from the email link",
              "title": "Token"
            },
            "description": "Signed unsubscribe token from the email link"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "leads"
        ],
        "summary": "Unsubscribe Post",
        "description": "API endpoint for a JS-driven unsubscribe confirmation button.",
        "operationId": "unsubscribe_post_leads_unsubscribe_post",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 512,
              "description": "Signed unsubscribe token from the email link",
              "title": "Token"
            },
            "description": "Signed unsubscribe token from the email link"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads": {
      "get": {
        "tags": [
          "leads"
        ],
        "summary": "List Leads",
        "description": "List leads with BANT data, scores, and optional filters.",
        "operationId": "list_leads_leads_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "unqualified",
                    "mql",
                    "sal",
                    "sql"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "unqualified|mql|sal|sql",
              "title": "Tier"
            },
            "description": "unqualified|mql|sal|sql"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "unqualified",
                    "mql",
                    "sal",
                    "sql"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "backward-compat alias for tier",
              "title": "Status"
            },
            "description": "backward-compat alias for tier"
          },
          {
            "name": "min_score",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "maximum": 100,
                  "minimum": 0
                },
                {
                  "type": "null"
                }
              ],
              "title": "Min Score"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/stats": {
      "get": {
        "tags": [
          "leads"
        ],
        "summary": "Lead Stats",
        "description": "Aggregate lead stats: total, unqualified, MQL, SAL, and SQL counts.",
        "operationId": "lead_stats_leads_stats_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/mark-all-viewed": {
      "post": {
        "tags": [
          "leads"
        ],
        "summary": "Mark All Leads Viewed",
        "description": "Bulk-clear the unread flag on every lead for the caller's bot(s).\n\nMatches the `PATCH /offline-messages/{id} \u2192 read` UX \u2014 a single\n\"Mark all as read\" click on the Leads page drops the sidebar badge\nto zero without opening every drawer.",
        "operationId": "mark_all_leads_viewed_leads_mark_all_viewed_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/{session_id}/view": {
      "post": {
        "tags": [
          "leads"
        ],
        "summary": "Mark Lead Viewed",
        "description": "Mark a single lead as viewed. Idempotent \u2014 subsequent calls are no-ops.\n\nReturns 204 (no body) so the frontend can fire-and-forget on drawer open.",
        "operationId": "mark_lead_viewed_leads__session_id__view_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/export": {
      "get": {
        "tags": [
          "leads"
        ],
        "summary": "Export Leads Csv",
        "description": "Export leads as a CSV file download. Paid plans only.\n\nThe CSV is the lead-intelligence layer in bulk (Score / Status / BANT /\nLocation / Device columns), so it is gated the same way the fields are\nstripped from the JSON responses \u2014 a Free API key gets a 403, not a\nfile with the locked columns filled in.",
        "operationId": "export_leads_csv_leads_export_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/{session_id}": {
      "get": {
        "tags": [
          "leads"
        ],
        "summary": "Get Lead Detail",
        "description": "Get full lead detail: BANT + contact info + chat history.",
        "operationId": "get_lead_detail_leads__session_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/leads/{session_id}/follow-up": {
      "post": {
        "tags": [
          "leads"
        ],
        "summary": "Send Manual Follow Up",
        "description": "Admin manually sends a follow-up email to a captured lead.\n\nThere is no automatic or timed send anywhere in this system \u2014 an\noperator triggers this explicitly, but every gate below still runs at\nclick time (not just when deciding whether to show the button), so a\nbad send stays structurally hard to make. See\ndocs/superpowers/plans/2026-08-08-visitor-intelligence.md \u00a702.",
        "operationId": "send_manual_follow_up_leads__session_id__follow_up_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/SendFollowUpRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Body"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/departments": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "List Departments",
        "description": "List all departments for the authenticated client/operator.",
        "operationId": "list_departments_operators_departments_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Create Department",
        "description": "Create a new department.",
        "operationId": "create_department_operators_departments_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDepartmentRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/departments/{department_id}": {
      "patch": {
        "tags": [
          "operators"
        ],
        "summary": "Update Department",
        "description": "Update a department.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Department edits (not invites)\") \u2014 name, description and business hours are\nconfiguration only. Creating and deleting departments stay denied: \u00a76.1 says\n*edits*, and a delete also re-parents every operator in the department.",
        "operationId": "update_department_operators_departments__department_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "department_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Department Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateDepartmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "operators"
        ],
        "summary": "Delete Department",
        "description": "Delete a department. Operators in this department are moved to no department.",
        "operationId": "delete_department_operators_departments__department_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "department_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Department Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "List Operators",
        "description": "List all operators for the authenticated client/operator.",
        "operationId": "list_operators_operators_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/create": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Create Operator",
        "description": "Create a new operator with login credentials.",
        "operationId": "create_operator_operators_create_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOperatorRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/{operator_id}": {
      "patch": {
        "tags": [
          "operators"
        ],
        "summary": "Update Operator",
        "description": "Update an operator's profile (owner/admin only).",
        "operationId": "update_operator_operators__operator_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "operator_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Operator Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOperatorRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "operators"
        ],
        "summary": "Delete Operator",
        "description": "Delete an operator (owner/admin only).",
        "operationId": "delete_operator_operators__operator_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "operator_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Operator Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/handoff": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Request Handoff",
        "description": "Visitor-initiated live chat request \u2014 runs through the state machine.\n\nThe state machine ``LiveChatAvailabilityService`` decides what the widget\nshould do based on the workspace's current live-chat reality (feature\nflag, operator presence, business hours, queue capacity). The endpoint\nreturns a structured response the widget reads to pick its UI mode:\n\n* ``suggested_action == \"route\"`` \u2014 queue + notify operators (current path)\n* ``suggested_action == \"wait\"``  \u2014 queue + tell widget to show queue UI\n  with auto-fallback timer\n* ``suggested_action == \"offline_form\"`` \u2014 do NOT queue, tell widget to\n  switch to the offline message form with the matching ``state`` as the\n  fallback reason\n\nSide effects (audit log, webhook, email notifications) only fire when\nthe visitor will actually be queued \u2014 no point waking operators when the\nstate machine has already decided to fall back to the form.",
        "operationId": "request_handoff_operators_handoff_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HandoffRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/cancel-handoff/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Cancel Handoff",
        "description": "Visitor cancels a waiting handoff request, returning session to bot mode.\n\nCalled by the widget when the visitor clicks \"Cancel and return to AI chat\"\nwhile still in the waiting state, especially if the WebSocket hasn't connected yet.",
        "operationId": "cancel_handoff_operators_cancel_handoff__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/session-status/{session_id}": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get Session Live Status",
        "description": "Get the current live chat status for a session.\n\nCalled by the widget on mount to restore chatMode across page navigations.\nReturns the session status and operator name if assigned.",
        "operationId": "get_session_live_status_operators_session_status__session_id__get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/queue": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get Queue",
        "description": "Get waiting chat queue from DB source-of-truth with visitor info.",
        "operationId": "get_queue_operators_queue_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/accept/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Accept Chat",
        "description": "Operator accepts a waiting chat.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Conversation status / assignment changes\") \u2014 claiming a queued conversation\nis the entry point for reproducing Support triage bugs. The visitor here has\nalready asked for a human, so this answers a request rather than initiating\ncontact (which is why ``/takeover`` and ``/connect-request`` stay denied).",
        "operationId": "accept_chat_operators_accept__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/AcceptChatRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/close/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Close Chat",
        "description": "Operator closes a live chat.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Conversation status / assignment changes\").",
        "operationId": "close_chat_operators_close__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/resolve/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Resolve Chat",
        "description": "Operator resolves and hard-closes a live chat.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Conversation status / assignment changes\").\n\n\nUnlike ``/close`` (which returns the visitor to bot mode, ``status='bot'``),\nthis marks the conversation ``status='closed'`` so it reads as *done* in\nreporting rather than an open bot conversation. The visitor-facing teardown\nis identical (the widget drops back to the bot via ``manager.close_chat``);\nonly the persisted status and the audit action differ.",
        "operationId": "resolve_chat_operators_resolve__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/transfer/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Transfer Chat",
        "description": "Transfer a live chat to another operator or department.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Conversation status / assignment changes\") \u2014 reassignment is the other half\nof Support triage, and every notification it fires goes to the Account's own\noperators, never to the Lead.",
        "operationId": "transfer_chat_operators_transfer__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TransferRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/me/status": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get My Operator Status",
        "description": "Get the caller's online status for a specific bot.\n\n``bot_id`` scopes the lookup to the caller's operator row bound to that\nbot \u2014 a workspace with two bots must not report ``is_online=True`` for\nbot B just because the admin is online as bot A's operator. Absent\n``bot_id`` the endpoint retains its historic \"any of my operator rows\"\nbehaviour for callers that haven't been updated yet.\n\nPreference order matches ``POST /operators/status`` so the two endpoints\ncan never disagree:\n    1. Self-op row (``linked_client_id == client.id``).\n    2. Legacy owner-role row.",
        "operationId": "get_my_operator_status_operators_me_status_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/me/notification-preferences": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get My Notification Preferences",
        "description": "Return the caller's own push preferences, fully defaulted.",
        "operationId": "get_my_notification_preferences_operators_me_notification_preferences_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "put": {
        "tags": [
          "operators"
        ],
        "summary": "Set My Notification Preferences",
        "description": "Replace the caller's own push preferences and echo the saved state.",
        "operationId": "set_my_notification_preferences_operators_me_notification_preferences_put",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationPreferencesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/status": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Set Operator Status",
        "description": "Set operator online/offline status explicitly.\n\nAccepts ``{\"is_online\": true/false}`` in the request body.\nFalls back to toggle behavior (backward compat) when no body is provided.\n\nWhen an operator transitions to offline, any sessions still assigned to\nthem are immediately re-queued and the affected visitors are notified \u2014\notherwise the visitor's widget would stay glued to a dead live session.",
        "operationId": "set_operator_status_operators_status_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/SetStatusRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/session/{session_id}/details": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get Session Details",
        "description": "Get full visitor/session details for the operator sidebar.",
        "operationId": "get_session_details_operators_session__session_id__details_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/session/{session_id}/qualification": {
      "patch": {
        "tags": [
          "operators"
        ],
        "summary": "Override Qualification Dimension",
        "description": "Manually correct or reset one qualification dimension's score (BR-03).\n\nThe automated extraction path (``rag_service._background_bant_extraction``)\ndeliberately never downgrades a dimension's score, and budget/authority\nscores never decay \u2014 by design, so a weak follow-up can't erase a strong\nearlier signal. But that also means a single false-positive extraction, or\na visitor typing an implausible statement in bad faith (\"we have a\n$50k/month budget approved\"), permanently misclassifies a lead with no\nremedy short of direct database editing. This gives operators an audited\nway to correct or reset (score=0) a dimension without weakening the\nnever-downgrade guarantee for the automated path \u2014 every override is\nstill logged as an append-only ``BANTSignal`` row, same as an LLM or\nCTA-click signal, just tagged ``source=\"operator_override\"``.",
        "operationId": "override_qualification_dimension_operators_session__session_id__qualification_patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QualificationOverrideRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/departments/public": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "List Departments Public",
        "description": "List departments for a bot (used by widget to show department picker).",
        "operationId": "list_departments_public_operators_departments_public_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/upload-chat-file": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Upload Chat File Route",
        "description": "Upload a file during live chat. Returns a URL to embed in messages.",
        "operationId": "upload_chat_file_route_operators_upload_chat_file_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_chat_file_route_operators_upload_chat_file_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/sessions/{session_id}/rating": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Submit Visitor Rating",
        "description": "Record a visitor's post-chat satisfaction rating and resolution status.\n\nAuth: X-Bot-Key header (widget). Both fields are optional \u2014 subsequent\ncalls silently overwrite previous values.",
        "operationId": "submit_visitor_rating_operators_sessions__session_id__rating_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VisitorRatingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/qualified-bot-sessions": {
      "get": {
        "tags": [
          "operators"
        ],
        "summary": "Get Qualified Bot Sessions",
        "description": "List visitors who are **currently** chatting with the AI and whose\nBANT qualification has captured at least 2 of 4 dimensions.\n\n\"Currently\" is enforced by a real-time presence heartbeat \u2014 the widget\npings the connect-request endpoint every 5s while in bot mode, and the\nin-memory manager tracks which sessions have a fresh ping. As soon as\nthe visitor closes the tab or navigates away, polling stops and the row\nauto-drops off the list within seconds. No time-window heuristic, no\nabandoned tabs ever shown.",
        "operationId": "get_qualified_bot_sessions_operators_qualified_bot_sessions_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/connect-request/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Operator Connect Request",
        "description": "Operator asks a bot-mode visitor whether they'd like to switch to a\nlive conversation. The visitor sees a Yes/No popup; nothing changes\nserver-side until they accept (then the takeover transition fires).\n\nIdempotent re-issuing for the same session simply refreshes the popup \u2014\ne.g. operator clicks Connect twice. The visitor only ever sees the latest\noperator's name.",
        "operationId": "operator_connect_request_operators_connect_request__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/AcceptChatRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/connect-request/{session_id}/cancel": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Operator Cancel Connect Request",
        "description": "Operator cancels a pending connect-request before the visitor responds.",
        "operationId": "operator_cancel_connect_request_operators_connect_request__session_id__cancel_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/takeover/{session_id}": {
      "post": {
        "tags": [
          "operators"
        ],
        "summary": "Takeover Bot Session",
        "description": "Proactively take over a session currently being handled by the AI.\n\nDistinct from ``/accept`` which only claims sessions already in the\n``waiting`` queue. Takeover transitions ``status='bot' \u2192 'live'`` atomically\nso two operators can't take over the same visitor at once.",
        "operationId": "takeover_bot_session_operators_takeover__session_id__post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:\\-]+$",
              "title": "Session Id"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/AcceptChatRequest"
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/operators/push/vapid-public-key": {
      "get": {
        "tags": [
          "push"
        ],
        "summary": "Get Vapid Public Key",
        "description": "Return the server's VAPID public key (URL-safe base64).\n\nPublic information \u2014 the frontend uses it as ``applicationServerKey`` when\ncalling ``pushManager.subscribe()``. Safe to expose without auth.",
        "operationId": "get_vapid_public_key_operators_push_vapid_public_key_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/operators/push/subscribe": {
      "post": {
        "tags": [
          "push"
        ],
        "summary": "Push Subscribe",
        "description": "Register a Web Push subscription for the calling user.\n\nAccepts both operator and workspace-owner (client) logins. The row's\n``operator_id`` or ``client_id`` is set depending on the auth type \u2014 a DB\nCHECK constraint guarantees exactly one is populated. Upsert by\n``endpoint`` so the same browser re-subscribing simply re-binds to the\ncurrent account (e.g. after re-login from the same machine, possibly as\na different role).",
        "operationId": "push_subscribe_operators_push_subscribe_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "delete": {
        "tags": [
          "push"
        ],
        "summary": "Push Unsubscribe",
        "description": "Remove a Web Push subscription previously registered for this user.\n\nThe frontend calls this when the user manually disables notifications or\nafter ``pushManager.unsubscribe()`` returns. Idempotent: a delete on a\nnon-existent endpoint is a no-op. Scoped to the calling user's own row \u2014\na subscriber cannot delete another account's subscription even if they\nhappen to know the endpoint.",
        "operationId": "push_unsubscribe_operators_push_subscribe_delete",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PushSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/operators/push/expo/subscribe": {
      "post": {
        "tags": [
          "push"
        ],
        "summary": "Expo Push Subscribe",
        "description": "Register an Expo Push token for the calling user.",
        "operationId": "expo_push_subscribe_operators_push_expo_subscribe_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpoPushSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "delete": {
        "tags": [
          "push"
        ],
        "summary": "Expo Push Unsubscribe",
        "description": "Remove an Expo Push token previously registered for this user.",
        "operationId": "expo_push_unsubscribe_operators_push_expo_subscribe_delete",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExpoPushSubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/invites": {
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Create Invite",
        "description": "Send an invitation to join the caller's workspace as an operator.",
        "operationId": "create_invite_invites_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInviteRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteCreatedResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "invites"
        ],
        "summary": "List Invites",
        "description": "List invites for the caller's workspace.\n\nRestricted to owners + admins (invite RBAC). Regular operators cannot\nenumerate pending invites.",
        "operationId": "list_invites_invites_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "status_filter",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "pending",
                    "accepted",
                    "revoked",
                    "expired",
                    "all"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status Filter"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/InviteView"
                  },
                  "title": "Response List Invites Invites Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/invites/{invite_id}/resend": {
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Resend Invite",
        "description": "Rotate the invite's token and resend the email.",
        "operationId": "resend_invite_invites__invite_id__resend_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "invite_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Invite Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InviteCreatedResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/invites/{invite_id}": {
      "delete": {
        "tags": [
          "invites"
        ],
        "summary": "Revoke Invite",
        "description": "Revoke a pending invite.",
        "operationId": "revoke_invite_invites__invite_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "invite_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Invite Id"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/invites/by-token/{token}": {
      "get": {
        "tags": [
          "invites"
        ],
        "summary": "Get Invite Public",
        "description": "Look up an invite by plaintext token.\n\nUnauthenticated \u2014 the airlock page uses this before login to render the\ncorrect state (signup vs login vs accept). Returns only workspace + inviter\nname + status + target email; no IDs or tokens are leaked.\n\nRate-limited by client IP as a soft defense against token enumeration\n(the token is 256-bit so brute force is infeasible, but the rate limit\nkeeps the surface honest).",
        "operationId": "get_invite_public_invites_by_token__token__get",
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 16,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_\\-]+$",
              "title": "Token"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublicInviteView"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/invites/by-token/{token}/accept": {
      "post": {
        "tags": [
          "invites"
        ],
        "summary": "Accept Invite Public",
        "description": "Accept the invite as the currently-authenticated Client.\n\n``X-API-Key`` ONLY \u2014 deliberately uses ``get_current_client_strict``\ninstead of ``get_current_client``. The latter accepts ``X-Operator-Key``\nand resolves it to the **workspace owner's** Client, which would silently\nbind the resulting linked-Operator row to the wrong identity if a legacy\noperator (whose Client is Acme's owner) clicked their own invite link.\n\nA legacy operator who genuinely wants to accept an invite has to sign\nout and log in as their personal Client account first \u2014 matches the\nairlock's own \"one identity per acceptance\" model.\n\nCase-insensitive email match between the invite target and the caller's\nClient email is enforced by :func:`invite_service.accept_invite`.",
        "operationId": "accept_invite_public_invites_by_token__token__accept_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 16,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_\\-]+$",
              "title": "Token"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/app__api__invite_routes__AcceptInviteResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/me/self-operator": {
      "post": {
        "tags": [
          "me"
        ],
        "summary": "Add Self As Operator",
        "description": "Add the calling Client as an operator in their own workspace.\n\nIdempotent \u2014 returns the existing row if the caller already self-added\n(reactivating a previously left row via ``DELETE /me/self-operator``).\nThe self-operator row has ``role='owner'`` and ``linked_client_id == id``\nwhere both point at the caller's Client identity (which is also the\nworkspace ID by design).\n\nFeature-gated by ``live_chat`` \u2014 a Free-tier owner CANNOT self-add.\n\nSeat-counted like every other operator \u2014 the workspace owner acting as\nan operator consumes 1 of their plan's operator seats, same as an\ninvited teammate. This matches industry-standard per-seat pricing\n(Slack, Intercom, Notion, Linear all count owner-as-agent). See\n``invite_service._active_operator_count`` for the full rationale.\n\nIdempotence detail: if a self-operator row already exists AND is active,\nthe endpoint returns without re-running the seat check. Only fresh\ncreation and reactivation of a deactivated row consume a seat, so a\ndouble-click on the CTA never over-allocates.\n\n``X-API-Key`` auth only (``get_current_client_strict``): a linked\noperator in some other workspace should not be able to promote\nthemselves to owner via a stray X-Workspace-Id header. Only a Client\nidentity authenticating as their true self can self-add.",
        "operationId": "add_self_as_operator_me_self_operator_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelfOperatorRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SelfOperatorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "delete": {
        "tags": [
          "me"
        ],
        "summary": "Remove Self As Operator",
        "description": "Deactivate the caller's self-operator row (owner leaves live chat).\n\nSets ``is_active = False`` rather than deleting the row so historical\nchat sessions and audit logs still reference a stable operator. A later\n``POST /me/self-operator`` reactivates the same row. Idempotent \u2014\ncalling this when no self-operator exists (or when it's already\ninactive) is a no-op.\n\nIn-flight live chats aren't force-closed: the assigned chat completes\nnaturally, and no new chats route to this operator once ``is_active``\nflips. If you specifically want to evict them from active chats, use\nthe revocation flow (v2, task #38).",
        "operationId": "remove_self_as_operator_me_self_operator_delete",
        "responses": {
          "204": {
            "description": "Successful Response"
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/me/workspaces": {
      "get": {
        "tags": [
          "me"
        ],
        "summary": "List My Workspaces",
        "description": "Return every workspace the caller can act in.\n\nSorted with the caller's owned workspace first, then linked-operator\nworkspaces alphabetically by workspace display name. Frontend uses this\nto populate the workspace switcher; the response is small (~1 row per\nworkspace) so we don't paginate.",
        "operationId": "list_my_workspaces_me_workspaces_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeWorkspacesResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/offline-messages": {
      "post": {
        "tags": [
          "offline-messages"
        ],
        "summary": "Submit Offline Message",
        "description": "Submit an offline message (called by widget when no agent is available).\n\nUnauthenticated by necessity \u2014 it is the out-of-hours form on a public\nwidget, and the bot key it carries is public too. Every accepted submission\nfans out to real inboxes: one e-mail per configured team recipient, PLUS a\nconfirmation to whatever address the CALLER typed. Ungated that is an\ne-mail amplifier \u2014 a script with a bot key lifted from any customer's page\ncould bury that customer's team in mail and, because the confirmation goes\nto an attacker-chosen recipient, use our sending domain to spray a third\nparty. The per-IP ceiling here is well above what a human filling in a form\ncan reach and turns the amplifier into a trickle.",
        "operationId": "submit_offline_message_offline_messages_post",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitOfflineMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "offline-messages"
        ],
        "summary": "List Offline Messages",
        "description": "List offline messages for the authenticated client / operator.\n\nClient / workspace-owner sessions see every bot in the workspace.\nOperator sessions are one-to-one with a bot \u2014 they see messages for that\nbot only, and the operator's ``bot_id`` overrides any ``bot_id`` query\nparameter so a modified request can't peek at a sibling bot's inbox.",
        "operationId": "list_offline_messages_offline_messages_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "enum": [
                    "new",
                    "read",
                    "replied"
                  ],
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status"
            }
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 20,
              "title": "Limit"
            }
          },
          {
            "name": "X-Acting-Role",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 32
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Acting-Role"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/offline-messages/{message_id}": {
      "patch": {
        "tags": [
          "offline-messages"
        ],
        "summary": "Update Offline Message",
        "description": "Update an offline message status (mark as read/replied).",
        "operationId": "update_offline_message_offline_messages__message_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Message Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateOfflineMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "offline-messages"
        ],
        "summary": "Delete Offline Message",
        "description": "Delete an offline message.",
        "operationId": "delete_offline_message_offline_messages__message_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "message_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Message Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/canned-responses": {
      "get": {
        "tags": [
          "canned-responses"
        ],
        "summary": "List Canned Responses",
        "description": "List canned responses for the client.",
        "operationId": "list_canned_responses_canned_responses_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 200
                },
                {
                  "type": "null"
                }
              ],
              "title": "Category"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "canned-responses"
        ],
        "summary": "Create Canned Response",
        "description": "Create a new canned response.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Canned-response CRUD\") \u2014 pure workspace content, and reversible.",
        "operationId": "create_canned_response_canned_responses_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCannedResponseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/canned-responses/{response_id}": {
      "patch": {
        "tags": [
          "canned-responses"
        ],
        "summary": "Update Canned Response",
        "description": "Update a canned response.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Canned-response CRUD\").",
        "operationId": "update_canned_response_canned_responses__response_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "response_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Response Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCannedResponseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "canned-responses"
        ],
        "summary": "Delete Canned Response",
        "description": "Delete a canned response.\n\nWritable under a super-admin impersonation session (design \u00a76.1,\n\"Canned-response CRUD\"). The deletion denied by \u00a76.2 is Account / AI Agent\ndeletion \u2014 a quick reply is neither, and re-creating one is trivial.",
        "operationId": "delete_canned_response_canned_responses__response_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "response_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Response Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/notifications": {
      "get": {
        "tags": [
          "notifications"
        ],
        "summary": "List Notifications",
        "operationId": "list_notifications_notifications_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 30,
              "title": "Limit"
            }
          },
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Before Id"
            }
          },
          {
            "name": "unread_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false,
              "title": "Unread Only"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "notifications"
        ],
        "summary": "Clear All",
        "operationId": "clear_all_notifications_delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/notifications/unread-count": {
      "get": {
        "tags": [
          "notifications"
        ],
        "summary": "Get Unread Count",
        "operationId": "get_unread_count_notifications_unread_count_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/notifications/mark-all-read": {
      "post": {
        "tags": [
          "notifications"
        ],
        "summary": "Mark All Read",
        "operationId": "mark_all_read_notifications_mark_all_read_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/notifications/{notification_id}/read": {
      "patch": {
        "tags": [
          "notifications"
        ],
        "summary": "Mark Read",
        "operationId": "mark_read_notifications__notification_id__read_patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "notification_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Notification Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/notifications/{notification_id}": {
      "delete": {
        "tags": [
          "notifications"
        ],
        "summary": "Delete One",
        "operationId": "delete_one_notifications__notification_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "notification_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Notification Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/client/settings": {
      "get": {
        "tags": [
          "client"
        ],
        "summary": "Get Client Settings",
        "description": "Retrieve chatbot customization settings.",
        "operationId": "get_client_settings_client_settings_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "client"
        ],
        "summary": "Update Client Settings",
        "description": "Update chatbot customization settings.",
        "operationId": "update_client_settings_client_settings_patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientSettingsUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/client/feedback": {
      "get": {
        "tags": [
          "client"
        ],
        "summary": "List My Feedback",
        "description": "List the logged-in client's own platform feedback, newest first.\n\nIncludes the resolution ``status`` and the superadmin's ``admin_response``\nso the customer can see that their issue was handled.",
        "operationId": "list_my_feedback_client_feedback_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Submit Platform Feedback",
        "description": "Save a classified feedback entry from an admin dashboard user.",
        "operationId": "submit_platform_feedback_client_feedback_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlatformFeedbackCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/feedback/upload": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Upload Feedback Attachment",
        "description": "Upload a feedback attachment (max 10MB) to R2 and return the URL.",
        "operationId": "upload_feedback_attachment_client_feedback_upload_post",
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_feedback_attachment_client_feedback_upload_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/upload-logo": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Upload Logo Endpoint",
        "description": "Upload a logo image to R2 and return its URL.\n\nThis endpoint previously read the whole body with no cap and passed\narbitrary bytes to ``Image.open`` \u2014 a decompression-bomb and Pillow-CVE\nsurface reachable by any authenticated customer, bounded only by nginx's\n50 MB body limit. Two sibling endpoints already had size and type checks;\nthis one had neither.",
        "operationId": "upload_logo_endpoint_client_upload_logo_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer",
                  "minimum": 1
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_logo_endpoint_client_upload_logo_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/client/profile": {
      "patch": {
        "tags": [
          "client"
        ],
        "summary": "Update Client Profile",
        "description": "Update the authenticated client's display name, company name, and website.\n\nEmail changes go through /client/change-email/* instead \u2014 that flow\nrequires the current password and confirms ownership of the new inbox\nvia OTP before the login email actually moves.",
        "operationId": "update_client_profile_client_profile_patch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientProfilePatch"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/change-password": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Change Client Password",
        "description": "Change the authenticated client's password (verifies the current one).\n\nRotates ``api_key`` in the same transaction. That key IS the session\ncredential (the dashboard stores it and sends it as ``X-API-Key``), it never\nexpires, and there is no server-side session table \u2014 so without rotation a\npassword change revoked nothing: a key lifted from a shared machine, a\nbrowser backup, or an XSS payload kept working forever, and the \"change your\npassword\" advice every incident response gives would have been false here.\n\nThe new key is returned so the caller's own tab can keep working; every\nOTHER holder of the old key is logged out on their next request. Callers\nthat ignore the field simply get bounced to /login by the 401 interceptor,\nwhich is also an acceptable outcome \u2014 the important half is the revocation.",
        "operationId": "change_client_password_client_change_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangePasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/change-email/request": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Request Client Email Change",
        "description": "Start an email change: verify the current password, then OTP-verify the new inbox.\n\nThe login email is NOT updated here \u2014 it only moves once\n``/change-email/confirm`` validates the code sent to ``new_email``. The\ncurrent (old) address also gets a notice, so an attacker who has\nhijacked the session can't quietly redirect account recovery without\nthe real owner finding out.",
        "operationId": "request_client_email_change_client_change_email_request_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/change-email/confirm": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Confirm Client Email Change",
        "description": "Verify the OTP sent to the pending new email and promote it to the login email.",
        "operationId": "confirm_client_email_change_client_change_email_confirm_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeEmailConfirm"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/change-email/cancel": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Cancel Client Email Change",
        "description": "Abandon a pending email change before it's confirmed.",
        "operationId": "cancel_client_email_change_client_change_email_cancel_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/api-key": {
      "get": {
        "tags": [
          "client"
        ],
        "summary": "Get Client Api Key",
        "description": "Return the authenticated client's API key in masked form.",
        "operationId": "get_client_api_key_client_api_key_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/client/api-key/regenerate": {
      "post": {
        "tags": [
          "client"
        ],
        "summary": "Regenerate Client Api Key",
        "description": "Rotate the client's API key. Returns the full new key ONCE for copy.",
        "operationId": "regenerate_client_api_key_client_api_key_regenerate_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "summary": "List Webhooks",
        "operationId": "list_webhooks_webhooks_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Create Webhook",
        "operationId": "create_webhook_webhooks_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Bot Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{webhook_id}": {
      "patch": {
        "tags": [
          "webhooks"
        ],
        "summary": "Update Webhook",
        "operationId": "update_webhook_webhooks__webhook_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Webhook Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "webhooks"
        ],
        "summary": "Delete Webhook",
        "operationId": "delete_webhook_webhooks__webhook_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{webhook_id}/deliveries": {
      "get": {
        "tags": [
          "webhooks"
        ],
        "summary": "Get Webhook Deliveries",
        "operationId": "get_webhook_deliveries_webhooks__webhook_id__deliveries_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Webhook Id"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{webhook_id}/test": {
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Test Webhook",
        "operationId": "test_webhook_webhooks__webhook_id__test_post",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Webhook Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/plans": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "List Plans",
        "description": "Return all active plans for the pricing page. No auth required.",
        "operationId": "list_plans_subscriptions_plans_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/subscriptions/promo": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Get Active Promotion",
        "description": "The launch promotion the current client qualifies for, for billing display.\n\nReturns ``{\"active\": false}`` when none applies, else a display projection\n(``free_cycles``, ``ends_at``, ``eligible_plan_ids``). This is DISPLAY ONLY \u2014\ncheckout independently re-validates eligibility server-side before deferring\nany charge, so a stale or forged response can never grant the offer.",
        "operationId": "get_active_promotion_subscriptions_promo_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/start-trial": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Start Trial Endpoint",
        "description": "Begin the paid plan's configured free trial (currently Standard, 7 days).\n\nTriggered when the customer clicks \"Start free trial\". No card is\nrequired; when the trial window elapses the expiry cron flips the\nsubscription to ``trial_expired`` and the customer must pick a plan\n+ enter a card to keep their bot live.\n\nTrial credits = the plan's full ``credits_per_month`` so the prospect\nexperiences the real product. The welcome email fires here, not on\nregistration, since registration now lands the customer on the free\ntier without a trial.\n\nError mapping (matches :class:`TrialUnavailable.reason`):\n\n* ``plan_not_found``           \u2192 404\n* ``plan_not_trialable``       \u2192 400\n* ``already_trialed``          \u2192 409\n* ``active_paid_subscription`` \u2192 409",
        "operationId": "start_trial_endpoint_subscriptions_start_trial_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StartTrialRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/current": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Get Current Subscription",
        "description": "Return the current workspace's subscription details + plan info.\n\nResolved via ``get_current_client_or_operator`` (not strict-client) so an\ninvited operator presenting their own ``X-API-Key`` together with the\nswitched workspace's ``X-Workspace-Id`` reads the WORKSPACE OWNER's plan,\nnot their own personal Free plan. The LiveChat UI feature-gates on this\nresponse \u2014 reading the wrong client's plan is what made \"Live chat isn't\nincluded in your plan\" appear on the operator's Support surface even\nthough the workspace owner is on Standard.\n\nWhen ``bot_id`` is given (the per-agent Billing overview), resolve that\nagent's OWN subscription + plan instead of the account default, so the\noverview shows the money attached to the selected agent. ``bot_id`` is\nscoped by ``client_id`` inside ``get_subscription_for_bot``, so a foreign\nid simply yields no subscription rather than another workspace's plan.",
        "operationId": "get_current_subscription_subscriptions_current_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/geo": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Get Billing Geo",
        "description": "Return the geo / currency profile the UI should render against.\n\nSingle call so the Billing page and PlanModal don't have to fan out a\nper-plan quote \u2014 the frontend converts INR paise to its local USD with\nthe returned ``display_rate`` (a paid plan's INR price is the source of\ntruth; USD is informational until international payments is live).\n\nIncludes the Razorpay public key so the React layer doesn't have to\nre-stuff it from a separate env / endpoint when opening the modal.\n\n``display_currency`` is NOT unconditionally the charge currency \u2014 the four\ncases, and what keeps each of them honest:\n\n* ``stored`` IN \u2192 INR shown, INR charged.\n* ``stored`` non-IN \u2192 USD shown, and no INR debit can contradict it because\n  the charge paths refuse instead of charging: top-up always 409s\n  ``intl_usd_pending`` (``create_topup_order`` is INR-only), checkout 409s\n  unless ``INTL_PAYMENTS_ENABLED`` puts it on the USD rail.\n* ``detected`` (IP geo) non-IN \u2192 USD shown while the charge path would\n  confirm IN. This divergence is DELIBERATE: an IP signal is display-grade\n  only \u2014 the frontend never echoes it back as ``billing_country``\n  (``usePlanCheckout.ts``) and ``_resolve_confirmed_billing_country_or_409``\n  never resolves on it. It cannot turn into a wrong charge because that same\n  gate 409s ``billing_country_required`` whenever a foreign IP is the ONLY\n  signal, so the customer confirms a country before any money moves.\n* ``unresolved`` (no stored country, no IP signal) \u2192 INR, because the gate\n  treats an unconfirmed buyer as domestic. Reporting USD here (the old rule)\n  was a live money bug: nothing 409s an unresolved country, so the top-up\n  modal rendered $13/$50/$125 and Razorpay debited \u20b91,000/\u20b94,000/\u20b910,000.\n\nThat last case is why the currency is taken from ``resolve_billing_context``\nrather than re-derived: it routes through ``charge_currency``, the same\nhelper the quote and the charge gate use, so this endpoint cannot drift\nfrom the rail it is describing.",
        "operationId": "get_billing_geo_subscriptions_geo_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/verify-razorpay-subscription": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Verify Razorpay Subscription",
        "description": "Verify the Razorpay Checkout return signature for a subscription.\n\nRazorpay's ``subscription.activated`` webhook is the canonical reconciler\n\u2014 this endpoint only exists so the UI can flip to a \"Subscription\nactive\" state the moment the modal closes, without waiting for the\nout-of-band webhook round-trip.\n\nResponse contract \u2014 read ``status`` carefully, it is the narrowest field:\n\n* ``status: \"verified\"`` refers to the SIGNATURE, and nothing else. It says\n  Razorpay signed this payment for this subscription. It does NOT say a\n  subscription exists locally, and misreading it as \"active\" is how a\n  caller ends up asserting a plan the app cannot see.\n* ``subscription_known`` \u2014 whether a local ``Subscription`` row exists yet.\n  The long-standing flag; unchanged, and callers built against it keep\n  working exactly as before.\n* ``activation_pending`` \u2014 the same fact stated in the affirmative, added\n  because \"``status`` is about the signature\" is a distinction a reader has\n  to notice, and the cost of not noticing it is a double charge. ``True``\n  means: the money is fine, the plan is not visible yet, keep polling.\n* ``retry_after_seconds`` \u2014 how long to wait before re-reading\n  ``GET /subscriptions/current``. Present only while activation is pending.\n  It is a CADENCE, not a deadline: poll until ``activation_pending`` clears\n  rather than giving up after a fixed number of attempts. The old frontend\n  re-fetched twice (immediately, then at 3s) and stopped \u2014 the prod mandate\n  was still non-billable well past that, so the customer saw no plan change\n  and bought it a second time.\n\nFailure modes (caller-facing):\n  * 400 \u2014 signature mismatch (replay / tampering).\n  * 409 \u2014 ``subscription_activation_conflict``: the money arrived but the\n    local row collided with another active subscription in this scope.\n  * 502 \u2014 Razorpay SDK error (network / quota).",
        "operationId": "verify_razorpay_subscription_subscriptions_verify_razorpay_subscription_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyRazorpaySubscriptionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/usage": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Get Subscription Usage",
        "description": "Backward-compat redirect to the credit-balance endpoint.\n\nThe legacy per-metric usage summary has been retired in favour of the\ncredit ledger. Kept here as a thin shim so older admin builds and any\nexternal consumers don't 404.",
        "operationId": "get_subscription_usage_subscriptions_usage_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/invoices": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "List Invoices",
        "description": "Return the client's payment history (most recent first).\n\nWhen ``bot_id`` is given AND that agent has its own subscription, the\nhistory is scoped to that agent's invoices (``Invoice.bot_id``). An agent\nwithout its own subscription falls back to the account-wide history,\nmatching how ``/current`` resolves the subscription it is shown beside \u2014\nsee ``_resolve_invoice_scope``. Omit ``bot_id`` for the account-wide\nhistory.",
        "operationId": "list_invoices_subscriptions_invoices_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/payment-recovery": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Payment Recovery",
        "description": "Recovery state + hosted link for a failing subscription.\n\nDrives the app-wide past-due banner and its CTA. Read-only and safe to\npoll: it resolves Razorpay's EXISTING hosted page and never mutates\nanything \u2014 in particular it never mints a second mandate, which would\ndouble-charge a customer whose original subscription is still recoverable\n(see ``dunning_service``).\n\nScope resolution mirrors ``/current`` and ``_resolve_invoice_scope``: an\nagent with no subscription of its own draws on the ACCOUNT plan, so we fall\nback to it. Without that fallback the banner would stay silent for a\npast-due account whenever an agent happened to be selected in the switcher.",
        "operationId": "payment_recovery_subscriptions_payment_recovery_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/billing-details": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Get Billing Details",
        "description": "The buyer identity used on tax invoices (invoicing v2 Phase 1).",
        "operationId": "get_billing_details_subscriptions_billing_details_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "put": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Update Billing Details",
        "operationId": "update_billing_details_subscriptions_billing_details_put",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingDetailsBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/billing-events": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Record Billing Funnel Event",
        "description": "Record a detected payment-funnel drop-off (customer closed the Razorpay\nsheet, or the gateway declined). Telemetry only \u2014 nothing downstream\ndepends on these rows, so the write is simple and unconditional; the app\ncalls this fire-and-forget and ignores failures.",
        "operationId": "record_billing_funnel_event_subscriptions_billing_events_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingFunnelEventBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/checkout/quote": {
      "get": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Checkout Quote",
        "description": "Single source of truth for what the checkout button will charge.\n\nThe admin UI calls this before opening any payment modal so it can\nrender the right currency, amount, payment methods, and CTA \u2014 without\nthe frontend having to know provider routing rules.\n\nResponse shape (always 200 unless inputs are invalid):\n\n``{\n    \"country\": \"IN\" | \"US\" | null,\n    \"currency\": \"INR\" | \"USD\",\n    \"amount_minor\": 149900,\n    \"amount_display\": \"\u20b91,499\",\n    \"billing_cycle\": \"monthly\",\n    \"provider\": \"razorpay\",\n    \"methods\": [\"card\", \"upi\"],\n    \"checkout_supported\": true,            # false \u2192 render Contact Sales\n    \"contact_sales\": null | \"developer@oyechats.com\",\n}``\n\nA ``checkout_supported: false`` response carries ``contact_sales`` so\nthe UI can surface a CTA instead of an empty button.",
        "operationId": "checkout_quote_subscriptions_checkout_quote_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "plan_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Plan Id"
            }
          },
          {
            "name": "billing_cycle",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "monthly",
              "title": "Billing Cycle"
            }
          },
          {
            "name": "billing_country",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Billing Country"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/subscriptions/checkout": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Create Checkout",
        "description": "Create a Razorpay checkout session for a paid plan.\n\nReturns ``{provider, subscription_id, key_id, name, description, prefill, theme}``\n\u2014 frontend opens ``new Razorpay({subscription_id, ...}).open()``.",
        "operationId": "create_checkout_subscriptions_checkout_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/change-plan": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Change Plan",
        "description": "Upgrade or downgrade the client's subscription to a different plan.\n\nResponse shape \u2014 the frontend branches on which key is present:\n\n* ``{\"status\": \"checkout_required\", ...}`` \u2014 Razorpay subscription auth required\n* ``{\"status\": \"downgrade_scheduled\", ...}`` \u2014 scheduled for next cycle\n* ``{\"status\": \"downgraded\", \"message\": \"...\"}`` \u2014 Free downgrade",
        "operationId": "change_plan_subscriptions_change_plan_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangePlanRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/cancel-scheduled-change": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Cancel Scheduled Change Endpoint",
        "description": "Clear a queued downgrade so the customer stays on their current plan.\n\nIdempotent: returns ``{\"status\": \"no_change_pending\"}`` when nothing is\nqueued. When a change WAS queued, this resets ``scheduled_*`` to NULL and\nleaves ``cancel_at_period_end`` alone \u2014 the gateway mandate was cancelled\nat-cycle-end when the downgrade was scheduled, so the customer must\nre-authorise to keep the current plan past cycle end. We surface that as\n``mandate_action`` in the response so the frontend can prompt accordingly.",
        "operationId": "cancel_scheduled_change_endpoint_subscriptions_cancel_scheduled_change_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/cancel": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Cancel Subscription",
        "description": "Cancel the client's subscription at the end of the current billing period.\n\nRecords the customer's INTENT to churn (``cancel_at_period_end=True``) and\n\u2014 deliberately \u2014 touches nothing at Razorpay yet. The irreversible gateway\ncancel is executed by ``task_execute_pending_cancellations`` a couple of\ndays before ``current_period_end`` (or inline below when the customer\ncancels inside that window).\n\nThat split is the whole point. Razorpay has NO un-cancel API, so issuing\nthe gateway cancel the moment the customer clicked Cancel destroyed the\nmandate ~30 days early: \"Reactivate\" then had to mint a brand-new\nsubscription, which Razorpay starts and charges immediately, so the\ncustomer paid a second time for days they had already bought. With the\ngateway call deferred, changing your mind before the sweep is a free,\ninstant flag flip (see ``/subscriptions/resume``).\n\nThe operator-seat add-on rides along with the deferred cancel for the same\nreason \u2014 cancelling it here took away seats the customer had paid for\nthrough period end while the plan itself kept running.\n\nPass ``bot_id`` to cancel a specific bot's subscription under the per-bot\nmodel (N3); omit it to act on the account.",
        "operationId": "cancel_subscription_subscriptions_cancel_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelSubscriptionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/resume": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Resume Subscription",
        "description": "Resume a subscription that was scheduled for cancellation.\n\nTwo modes, decided by whether the IRREVERSIBLE gateway cancel has been\nissued yet (``gateway_cancel_executed_at``).\n\n**Mode 1 \u2014 the mandate is still live.** ``/cancel`` now records only the\ncustomer's intent and leaves Razorpay alone until\n``task_execute_pending_cancellations`` runs near period end, so for almost\nevery customer who changes their mind there is nothing to re-authorise:\nclear the flags and the subscription simply keeps renewing. No checkout, no\npayment. We confirm liveness with the gateway first rather than trusting the\nlocal marker \u2014 if the customer cancelled from Razorpay's own emails, or a\nsweep half-completed, clearing the flag would promise a renewal that never\ncomes, which is the BL-3 lie this endpoint exists to avoid.\n\n**Mode 2 \u2014 the mandate is already dead.** Razorpay has NO un-cancel API for\nan at-cycle-end cancellation, so we re-authorise by minting a FRESH\nsubscription for the same plan/cycle (tagging the predecessor via\n``prev_razorpay_subscription_id`` so it is retired at the new sub's\nactivation webhook) and return ``mandate_action: \"reauthorise_required\"``\nwith the checkout payload. The local cancel flags are NOT cleared here \u2014 the\nrow must not pretend the cancellation was undone until the customer actually\nre-authorises and the activation webhook lands.\n\nMode 2 mints with ``start_at`` set to the current period end so the new\nmandate first charges when the paid period actually runs out. Without it\nRazorpay starts the subscription immediately and captures a full second\ncycle for days the customer had already bought.\n\nPass ``bot_id`` to resume a specific bot's subscription (N3); omit to act on\nthe account's highest-tier subscription.",
        "operationId": "resume_subscription_subscriptions_resume_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResumeSubscriptionRequest",
                "default": {}
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/subscriptions/seats": {
      "post": {
        "tags": [
          "subscriptions"
        ],
        "summary": "Change Seat Count",
        "description": "Add or remove operator seats from the active subscription.\n\nEach seat above ``plan.included_operator_seats`` is billed through a\nSEPARATE Razorpay add-on subscription (``RAZORPAY_SEAT_PLAN_ID``, whose\nplan amount IS the per-seat price). The main plan subscription is never\nedited for seats \u2014 Razorpay ``quantity`` multiplies the WHOLE plan\namount, which would overcharge the customer (P0-3). The local\n``operator_quantity`` mirror is updated immediately so live-chat seat\nenforcement sees the new limit without webhook latency.",
        "operationId": "change_seat_count_subscriptions_seats_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SeatChangeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/credits/balance": {
      "get": {
        "tags": [
          "credits"
        ],
        "summary": "Get Credit Balance",
        "description": "Return everything the Billing page needs in one round-trip:\n\n* Current credit balance (plan + top-up + soonest expiry).\n* Monthly grant + reset date (driven by the active subscription period).\n* Per-action credit costs from ``pricing_config`` so the UI can render\n  \"1 AI chat = 1 credit\" without baking the values in.\n* This-period usage (count of chats / URL crawls / customer emails the\n  customer has consumed since the last ``plan_grant``). Useful for the\n  \"How you're using credits\" panel.\n* Currency display info (symbol + code) so localisation is centralised.",
        "operationId": "get_credit_balance_credits_balance_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/credits/history": {
      "get": {
        "tags": [
          "credits"
        ],
        "summary": "Get Credit History",
        "description": "Return paginated ledger entries for the client (most recent first).\n\nWhen ``bot_id`` is given, the history is scoped to that agent's own ledger\n(entries carry ``CreditLedger.bot_id``); the ``client_id`` filter still\napplies, so a foreign ``bot_id`` simply matches nothing rather than leaking\nanother workspace's rows. Omit ``bot_id`` for the workspace-wide history.",
        "operationId": "get_credit_history_credits_history_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 1,
              "title": "Page"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/credits/daily": {
      "get": {
        "tags": [
          "credits"
        ],
        "summary": "Get Credit Daily",
        "description": "Daily credit consumption for the last ``days`` days (zero-filled).\n\nSums the magnitude of consumption debits per calendar day (UTC) so the\nUsage page can render a trend line. Returns a complete ascending series \u2014\none entry per day in the window, ``credits_used = 0`` on quiet days \u2014 so\nthe client can render without gap-filling.",
        "operationId": "get_credit_daily_credits_daily_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 30,
              "title": "Days"
            }
          },
          {
            "name": "bot_id",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Bot Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/credits/topup": {
      "post": {
        "tags": [
          "credits"
        ],
        "summary": "Initiate Topup",
        "description": "Initiate a top-up purchase via Razorpay.\n\nReturns a Razorpay order payload:\n``{provider, order_id, amount, currency, key_id, name, description, prefill, theme}``\n\u2014 frontend opens ``new Razorpay({order_id, ...}).open()``.\n\nCredits are granted asynchronously via the Razorpay webhook on payment\ncapture; the frontend should also call ``/credits/topup/verify`` so the\nsuccess modal is signature-verified server-side before showing confetti\n(defence-in-depth against tampered callbacks).",
        "operationId": "initiate_topup_credits_topup_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopupRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/credits/topup/verify": {
      "post": {
        "tags": [
          "credits"
        ],
        "summary": "Verify Topup Payment",
        "description": "Verify a Razorpay Checkout success callback and reconcile the grant.\n\nThe credit grant normally lands via the ``payment.captured`` / ``order.paid``\nwebhook. This endpoint signature-verifies the modal callback and then runs\nan idempotent reconcile (L3) so a dropped webhook still credits the customer\ninstead of leaving them paid-but-no-credits.",
        "operationId": "verify_topup_payment_credits_topup_verify_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopupVerifyRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/credits/packs": {
      "get": {
        "tags": [
          "credits"
        ],
        "summary": "List Topup Packs",
        "description": "Public list of currently-offered top-up packs (no auth).",
        "operationId": "list_topup_packs_credits_packs_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/payment-methods": {
      "get": {
        "tags": [
          "billing"
        ],
        "summary": "List Payment Methods",
        "description": "Saved instruments for one-off payments.\n\nServes the local mirror and only calls Razorpay when it is stale, or when\n``?refresh=true``. A naive read-through would fire one gateway call per\nBilling page load \u2014 burning our rate limit and turning a page refresh into\na DoS against our own Razorpay account. The rate limit is a second\nbackstop.\n\nA stale-mirror refresh that fails at the gateway degrades to the cached\nrows: showing a slightly old list beats blanking the page over a transient\nblip. Only an explicit ``?refresh=true`` surfaces the failure, because\nthere the customer asked and deserves to know it did not work.",
        "operationId": "list_payment_methods_payment_methods_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/payment-methods/{token_id}": {
      "delete": {
        "tags": [
          "billing"
        ],
        "summary": "Remove Payment Method",
        "description": "Revoke a saved instrument.\n\n``token_id`` is resolved against THIS client's Razorpay customer inside the\nservice, so a token belonging to another account fails at the gateway\nrather than deleting someone else's instrument.",
        "operationId": "remove_payment_method_payment_methods__token_id__delete",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "token_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Token Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/public/pricing-catalog": {
      "get": {
        "tags": [
          "public-pricing"
        ],
        "summary": "Pricing Catalog",
        "operationId": "pricing_catalog_public_pricing_catalog_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/affiliates/validate": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "Validate Code",
        "description": "Check whether ``code`` is a valid + active referral code.\n\nAggressively rate-limited (60/min/IP) so the endpoint cannot be used\nto enumerate the code namespace. Always returns 200 with\n``{valid: bool}`` \u2014 using HTTP status to leak validity would defeat\nthe rate-limit defense.",
        "operationId": "validate_code_affiliates_validate_get",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Code"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateCodeResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliates/click": {
      "post": {
        "tags": [
          "affiliate"
        ],
        "summary": "Record Click",
        "description": "Record a click on a referral link.\n\nFire-and-forget from the client perspective \u2014 we always return 204,\neven for invalid codes, so the caller cannot time-attack to enumerate\nvalid codes. The body's IP and User-Agent are extracted from the\nrequest headers (not the body) so callers cannot spoof them.",
        "operationId": "record_click_affiliates_click_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClickRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliate/referral-status": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "Referral Status",
        "description": "Return the account's standing referral attribution, if any.\n\nThe checkout modal calls this on open so an already-attributed account\nshows its permanent discount badge instead of an editable code field \u2014\nattribution is first-touch and cannot be removed, and the server applies\nthe discount at checkout regardless of what the UI shows.",
        "operationId": "referral_status_affiliate_referral_status_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReferralStatusResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/affiliate/apply-referral": {
      "post": {
        "tags": [
          "affiliate"
        ],
        "summary": "Apply Referral",
        "description": "Apply a referral code for the currently-authenticated customer.\n\nCalled from the checkout modal when the user enters a referral code before\nbuying a plan or top-up. Delegates to ``attribute_signup`` which enforces\nfirst-touch wins and self-referral prevention \u2014 idempotent, never blocks.\n\nReturns ``{ attributed, code, discount_pct }``:\n  * ``attributed=true`` \u2014 freshly attributed on this call.\n  * ``attributed=false`` with non-null ``code`` and ``discount_pct`` \u2014 the\n    account was already attributed to this same code; UX should still\n    show the discount applied (idempotent re-entry by the same user).\n  * ``attributed=false`` with ``code=None`` \u2014 invalid code OR account\n    is already attributed to a *different* code (collision).\nAlways returns 200 so checkout is never blocked.",
        "operationId": "apply_referral_affiliate_apply_referral_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApplyReferralRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApplyReferralResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/affiliate/me": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "Get Me",
        "operationId": "get_me_affiliate_me_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/affiliate/codes": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "List My Codes",
        "operationId": "list_my_codes_affiliate_codes_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/CodeRow"
                  },
                  "type": "array",
                  "title": "Response List My Codes Affiliate Codes Get"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      },
      "post": {
        "tags": [
          "affiliate"
        ],
        "summary": "Create My Code",
        "operationId": "create_my_code_affiliate_codes_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCodeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodeRow"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/affiliate/codes/{code_id}/referrals": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "List My Code Referrals",
        "description": "List the customers who signed up via one of this affiliate's codes.\n\nAuthorisation: the code must belong to the calling affiliate. We return\n404 (not 403) when the code exists but is owned by someone else so an\naffiliate can't probe the global code namespace.",
        "operationId": "list_my_code_referrals_affiliate_codes__code_id__referrals_get",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "code_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Code Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodeReferralsResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliate/codes/{code_id}": {
      "patch": {
        "tags": [
          "affiliate"
        ],
        "summary": "Update My Code",
        "operationId": "update_my_code_affiliate_codes__code_id__patch",
        "security": [
          {
            "APIKeyHeader": []
          }
        ],
        "parameters": [
          {
            "name": "code_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "title": "Code Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCodeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CodeRow"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliate/stats": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "Get My Stats",
        "operationId": "get_my_stats_affiliate_stats_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateStats"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/affiliate-invites/lookup": {
      "get": {
        "tags": [
          "affiliate"
        ],
        "summary": "Lookup Invite",
        "description": "Resolve a magic-link token to its target email + expiry.\n\nCalled by the ``/affiliate-invite`` landing page before deciding which\nbranch to render \u2014 the recipient sees their invited email + expiry\ndeadline, and (when logged in) the page auto-fires accept-existing.\nRate-limited to slow token enumeration. Returns 404/410 for invalid /\nused / expired tokens.",
        "operationId": "lookup_invite_affiliate_invites_lookup_get",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Token"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptInviteLookupResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliate-invites/accept": {
      "post": {
        "tags": [
          "affiliate"
        ],
        "summary": "Accept Invite",
        "description": "Accept a magic-link invite \u2014 atomically create Client + Affiliate.\n\nRate-limited (10/min/IP) on top of the token's natural one-shot\nconstraint. On success, returns the same shape as /auth/register so\nthe admin app can store the api_key and route the user straight to\n/affiliate without a separate login round-trip.",
        "operationId": "accept_invite_affiliate_invites_accept_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptInviteRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/app__api__affiliate_routes__AcceptInviteResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliate-invites/accept-existing": {
      "post": {
        "tags": [
          "affiliate"
        ],
        "summary": "Accept Invite Existing",
        "description": "Accept an invite while already signed in as an OyeChats client.\n\nThe other accept endpoint creates a brand-new Client+Affiliate pair \u2014\nthis one wires an Affiliate row to the client who's already\nauthenticated. Used by the unified `/affiliate-invite` landing page\nwhen the recipient already has an account.\n\nStatus codes:\n  * 200 \u2014 affiliate row created, fire the welcome email\n  * 403 \u2014 token's email doesn't match the logged-in client\n  * 404 \u2014 token doesn't exist\n  * 409 \u2014 client is already an active affiliate\n  * 410 \u2014 token expired or already used",
        "operationId": "accept_invite_existing_affiliate_invites_accept_existing_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AcceptInviteForExistingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcceptInviteForExistingResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "APIKeyHeader": []
          }
        ]
      }
    },
    "/health": {
      "get": {
        "tags": [
          "system"
        ],
        "summary": "Health Check",
        "description": "Readiness check for user-facing traffic.\n\nReturns **200** when the API can serve user requests (DB + Redis\nreachable). Returns **503** only when one of those is down. Worker\nstatus is reported in the body for ops visibility but does **not**\ngate the response code: a degraded worker means BANT extraction and\nasync email pause, while chats themselves still work \u2014 failing the\ndeploy gate or load-balancer probe in that case would cause\nuser-visible downtime that wasn't there.\n\nUsed by deploy scripts, Nginx upstream checks, and external uptime\nmonitors. For comprehensive checks (worker included), use\n``/health/full``.",
        "operationId": "health_check_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/health/live": {
      "get": {
        "tags": [
          "system"
        ],
        "summary": "Liveness Probe",
        "description": "Ultra-lightweight liveness probe. No DB/Redis calls.\n\nReturns 200 if the process is alive. Used by external uptime monitors\n(BetterStack, UptimeRobot) where low-latency checks are preferred.",
        "operationId": "liveness_probe_health_live_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/": {
      "get": {
        "summary": "Read Root",
        "description": "Liveness banner for the API root.\n\n``docs_url`` is reported only where the docs are actually mounted.\n``_docs_urls`` switches them off in production specifically so the schema\nis not free recon (F22); pointing at ``/docs`` anyway told a prober the\nroute exists and was merely withheld, which is the half of that decision\nworth keeping quiet.",
        "operationId": "read_root__get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AcceptChatRequest": {
        "properties": {
          "operator_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Operator Id"
          }
        },
        "type": "object",
        "title": "AcceptChatRequest"
      },
      "AcceptInviteForExistingRequest": {
        "properties": {
          "token": {
            "type": "string",
            "title": "Token"
          }
        },
        "type": "object",
        "required": [
          "token"
        ],
        "title": "AcceptInviteForExistingRequest"
      },
      "AcceptInviteForExistingResponse": {
        "properties": {
          "is_affiliate": {
            "type": "boolean",
            "title": "Is Affiliate",
            "default": true
          },
          "message": {
            "type": "string",
            "title": "Message"
          }
        },
        "type": "object",
        "required": [
          "message"
        ],
        "title": "AcceptInviteForExistingResponse"
      },
      "AcceptInviteLookupResponse": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expires At"
          }
        },
        "type": "object",
        "required": [
          "email",
          "expires_at"
        ],
        "title": "AcceptInviteLookupResponse"
      },
      "AcceptInviteRequest": {
        "properties": {
          "token": {
            "type": "string",
            "maxLength": 512,
            "title": "Token"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Password"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          }
        },
        "type": "object",
        "required": [
          "token",
          "name",
          "password"
        ],
        "title": "AcceptInviteRequest"
      },
      "AffiliateStats": {
        "properties": {
          "total_clicks": {
            "type": "integer",
            "title": "Total Clicks"
          },
          "total_signups": {
            "type": "integer",
            "title": "Total Signups"
          },
          "active_codes": {
            "type": "integer",
            "title": "Active Codes"
          },
          "max_active_codes": {
            "type": "integer",
            "title": "Max Active Codes"
          },
          "conversion_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Conversion Pct"
          }
        },
        "type": "object",
        "required": [
          "total_clicks",
          "total_signups",
          "active_codes",
          "max_active_codes",
          "conversion_pct"
        ],
        "title": "AffiliateStats"
      },
      "AnswerLink": {
        "properties": {
          "keyword": {
            "type": "string",
            "maxLength": 80,
            "minLength": 1,
            "title": "Keyword"
          },
          "url": {
            "type": "string",
            "title": "Url"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "keyword",
          "url"
        ],
        "title": "AnswerLink",
        "description": "A smart link: keyword trigger \u2192 destination."
      },
      "ApplyReferralRequest": {
        "properties": {
          "code": {
            "type": "string",
            "maxLength": 20,
            "minLength": 3,
            "title": "Code"
          }
        },
        "type": "object",
        "required": [
          "code"
        ],
        "title": "ApplyReferralRequest"
      },
      "ApplyReferralResponse": {
        "properties": {
          "attributed": {
            "type": "boolean",
            "title": "Attributed"
          },
          "message": {
            "type": "string",
            "title": "Message"
          },
          "code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Code"
          },
          "discount_pct": {
            "type": "number",
            "title": "Discount Pct",
            "default": 0.0
          }
        },
        "type": "object",
        "required": [
          "attributed",
          "message"
        ],
        "title": "ApplyReferralResponse"
      },
      "BehavioralSignalsRequest": {
        "properties": {
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          },
          "page_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Page Url"
          },
          "referrer": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Referrer"
          },
          "utm_params": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Utm Params"
          },
          "time_on_page": {
            "anyOf": [
              {
                "type": "number",
                "ge": 0,
                "le": 86400
              },
              {
                "type": "null"
              }
            ],
            "title": "Time On Page"
          },
          "pages_viewed": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 1000000.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Pages Viewed"
          },
          "is_return_visit": {
            "type": "boolean",
            "title": "Is Return Visit",
            "default": false
          },
          "journey": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array",
                "maxItems": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Journey"
          }
        },
        "type": "object",
        "required": [
          "session_id"
        ],
        "title": "BehavioralSignalsRequest"
      },
      "BillingAddress": {
        "properties": {
          "line1": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Line1"
          },
          "line2": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Line2"
          },
          "city": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ],
            "title": "City"
          },
          "state": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ],
            "title": "State"
          },
          "postal_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 20
              },
              {
                "type": "null"
              }
            ],
            "title": "Postal Code"
          },
          "country": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ],
            "title": "Country"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "BillingAddress",
        "description": "Postal address printed on the customer's tax invoice."
      },
      "BillingDetailsBody": {
        "properties": {
          "legal_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Legal Name"
          },
          "gstin": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 32
              },
              {
                "type": "null"
              }
            ],
            "title": "Gstin"
          },
          "billing_address": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BillingAddress"
              },
              {
                "type": "null"
              }
            ]
          },
          "billing_country": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Country"
          },
          "billing_state_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 8
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing State Code"
          },
          "billing_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Email"
          }
        },
        "type": "object",
        "title": "BillingDetailsBody"
      },
      "BillingFunnelEventBody": {
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "checkout_abandoned",
              "payment_failed"
            ],
            "title": "Event"
          },
          "surface": {
            "type": "string",
            "enum": [
              "plan",
              "topup",
              "seat",
              "resume"
            ],
            "title": "Surface"
          },
          "meta": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Meta"
          }
        },
        "type": "object",
        "required": [
          "event",
          "surface"
        ],
        "title": "BillingFunnelEventBody",
        "description": "Fire-and-forget drop-off signal from the app's Razorpay wrapper."
      },
      "Body_ingest_documents_ingest_post": {
        "properties": {
          "files": {
            "items": {
              "type": "string",
              "contentMediaType": "application/octet-stream"
            },
            "type": "array",
            "title": "Files"
          }
        },
        "type": "object",
        "required": [
          "files"
        ],
        "title": "Body_ingest_documents_ingest_post"
      },
      "Body_preview_ingest_cost_ingest_preview_cost_post": {
        "properties": {
          "files": {
            "items": {
              "type": "string",
              "contentMediaType": "application/octet-stream"
            },
            "type": "array",
            "title": "Files"
          }
        },
        "type": "object",
        "required": [
          "files"
        ],
        "title": "Body_preview_ingest_cost_ingest_preview_cost_post"
      },
      "Body_upload_chat_file_route_operators_upload_chat_file_post": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_upload_chat_file_route_operators_upload_chat_file_post"
      },
      "Body_upload_feedback_attachment_client_feedback_upload_post": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_upload_feedback_attachment_client_feedback_upload_post"
      },
      "Body_upload_logo_endpoint_client_upload_logo_post": {
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "application/octet-stream",
            "title": "File"
          }
        },
        "type": "object",
        "required": [
          "file"
        ],
        "title": "Body_upload_logo_endpoint_client_upload_logo_post"
      },
      "BotCheckoutRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 120,
            "minLength": 1,
            "title": "Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 269,
                "minLength": 1,
                "pattern": "^[\\x20-\\x7E]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "plan_slug": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z0-9_\\-]+$",
            "title": "Plan Slug"
          },
          "billing_cycle": {
            "type": "string",
            "pattern": "^(monthly|annual)$",
            "title": "Billing Cycle",
            "default": "monthly"
          },
          "allowed_domains": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Allowed Domains"
          },
          "domain_check_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Domain Check Enabled"
          }
        },
        "type": "object",
        "required": [
          "name",
          "plan_slug"
        ],
        "title": "BotCheckoutRequest",
        "description": "Body for ``POST /bots/checkout`` \u2014 start a per-bot subscription.\n\nThe bot row is NOT created here. We pass the bot's name + website +\ndomain settings in the Razorpay subscription notes so the activation\nwebhook (or the sync verify endpoint) can materialise the bot only\nafter payment captures. Dismissed checkouts leave no orphan rows."
      },
      "BotCheckoutVerifyRequest": {
        "properties": {
          "razorpay_payment_id": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Razorpay Payment Id"
          },
          "razorpay_subscription_id": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Razorpay Subscription Id"
          },
          "razorpay_signature": {
            "type": "string",
            "maxLength": 512,
            "minLength": 1,
            "pattern": "^[!-~]+$",
            "title": "Razorpay Signature"
          }
        },
        "type": "object",
        "required": [
          "razorpay_payment_id",
          "razorpay_subscription_id",
          "razorpay_signature"
        ],
        "title": "BotCheckoutVerifyRequest",
        "description": "Body for ``POST /bots/checkout/verify`` \u2014 sync fallback for localhost.\n\nWebhook delivery is the source of truth in production, but Razorpay\ncan't hit ``localhost`` so the success callback hits this endpoint to\ntrigger the activation handler synchronously. Idempotent: re-running\non an already-active subscription is a no-op (the handler short-\ncircuits when the local row already exists)."
      },
      "BotResponse": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "bot_key": {
            "type": "string",
            "title": "Bot Key"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "system_prompt": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "System Prompt"
          },
          "brand_tone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Brand Tone"
          },
          "brand_tone_preset": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Brand Tone Preset"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "company_description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Description"
          },
          "manual_field_overrides": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Manual Field Overrides",
            "default": []
          },
          "bot_logo": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Logo"
          },
          "bot_logo_source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Logo Source"
          },
          "launcher_name": {
            "type": "string",
            "title": "Launcher Name"
          },
          "launcher_logo": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Launcher Logo"
          },
          "primary_color": {
            "type": "string",
            "title": "Primary Color"
          },
          "background_color": {
            "type": "string",
            "title": "Background Color"
          },
          "header_color": {
            "type": "string",
            "title": "Header Color"
          },
          "recommended_colors": {
            "anyOf": [
              {
                "items": {},
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Recommended Colors"
          },
          "user_bubble_color": {
            "type": "string",
            "title": "User Bubble Color",
            "default": "#DBE9FF"
          },
          "bant_enabled": {
            "type": "boolean",
            "title": "Bant Enabled"
          },
          "bant_config": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bant Config"
          },
          "relevance_threshold": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Relevance Threshold"
          },
          "avatar_type": {
            "type": "string",
            "title": "Avatar Type"
          },
          "orb_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Orb Color"
          },
          "lead_form_enabled": {
            "type": "boolean",
            "title": "Lead Form Enabled",
            "default": false
          },
          "lead_form_fields": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Lead Form Fields"
          },
          "email_verification_enabled": {
            "type": "boolean",
            "title": "Email Verification Enabled",
            "default": false
          },
          "company_lookup_enabled": {
            "type": "boolean",
            "title": "Company Lookup Enabled",
            "default": false
          },
          "notification_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notification Email"
          },
          "notification_emails": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notification Emails"
          },
          "reply_to_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reply To Email"
          },
          "email_on_qualified": {
            "type": "boolean",
            "title": "Email On Qualified",
            "default": true
          },
          "email_on_handoff": {
            "type": "boolean",
            "title": "Email On Handoff",
            "default": true
          },
          "email_on_offline": {
            "type": "boolean",
            "title": "Email On Offline",
            "default": true
          },
          "email_visitor_confirmation": {
            "type": "boolean",
            "title": "Email Visitor Confirmation",
            "default": true
          },
          "live_chat_enabled": {
            "type": "boolean",
            "title": "Live Chat Enabled",
            "default": true
          },
          "widget_installed_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Widget Installed At"
          },
          "last_crawl_status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Crawl Status"
          },
          "crawl_completed_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Crawl Completed At"
          },
          "indexed_chunk_count": {
            "type": "integer",
            "title": "Indexed Chunk Count",
            "default": 0
          },
          "operator_timeout_seconds": {
            "type": "integer",
            "title": "Operator Timeout Seconds",
            "default": 120
          },
          "live_chat_queue_timeout_seconds": {
            "type": "integer",
            "title": "Live Chat Queue Timeout Seconds",
            "default": 20
          },
          "live_chat_max_queue_size": {
            "type": "integer",
            "title": "Live Chat Max Queue Size",
            "default": 10
          },
          "business_hours": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Business Hours"
          },
          "feature_flags": {
            "additionalProperties": true,
            "type": "object",
            "title": "Feature Flags",
            "default": {}
          },
          "widget_messages": {
            "additionalProperties": true,
            "type": "object",
            "title": "Widget Messages",
            "default": {}
          },
          "widget_config": {
            "additionalProperties": true,
            "type": "object",
            "title": "Widget Config",
            "default": {}
          },
          "branding_text": {
            "type": "string",
            "title": "Branding Text",
            "default": "Powered by OyeChats"
          },
          "branding_url": {
            "type": "string",
            "title": "Branding Url",
            "default": "https://www.oyechats.com"
          },
          "welcome_title": {
            "type": "string",
            "title": "Welcome Title",
            "default": "Hi there \ud83d\udc4b"
          },
          "welcome_subtitle": {
            "type": "string",
            "title": "Welcome Subtitle",
            "default": "How can we help you today?"
          },
          "waiting_message": {
            "type": "string",
            "title": "Waiting Message",
            "default": "Connecting you to support..."
          },
          "offline_message": {
            "type": "string",
            "title": "Offline Message",
            "default": "We'll be right back! Leave a message and we'll follow up shortly."
          },
          "handoff_delay_seconds": {
            "type": "integer",
            "title": "Handoff Delay Seconds",
            "default": 0
          },
          "calendly_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Calendly Url"
          },
          "meeting_booking_enabled": {
            "type": "boolean",
            "title": "Meeting Booking Enabled",
            "default": false
          },
          "meeting_provider": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Meeting Provider"
          },
          "zcal_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Zcal Url"
          },
          "calcom_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Calcom Url"
          },
          "services": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Services"
          },
          "services_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Services Url"
          },
          "answer_links": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Answer Links"
          },
          "allowed_domains": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Allowed Domains",
            "default": []
          },
          "domain_check_enabled": {
            "type": "boolean",
            "title": "Domain Check Enabled",
            "default": false
          },
          "session_share_domain": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Session Share Domain"
          },
          "is_active": {
            "type": "boolean",
            "title": "Is Active"
          },
          "created_at": {
            "type": "string",
            "title": "Created At"
          },
          "plan_slug": {
            "type": "string",
            "title": "Plan Slug",
            "default": "free"
          },
          "plan_name": {
            "type": "string",
            "title": "Plan Name",
            "default": "Free"
          }
        },
        "type": "object",
        "required": [
          "id",
          "bot_key",
          "name",
          "website",
          "system_prompt",
          "bot_logo",
          "launcher_name",
          "launcher_logo",
          "primary_color",
          "background_color",
          "header_color",
          "recommended_colors",
          "bant_enabled",
          "avatar_type",
          "orb_color",
          "is_active",
          "created_at"
        ],
        "title": "BotResponse"
      },
      "BrandTonePreviewRequest": {
        "properties": {
          "brand_tone": {
            "type": "string",
            "maxLength": 500,
            "minLength": 1,
            "title": "Brand Tone"
          }
        },
        "type": "object",
        "required": [
          "brand_tone"
        ],
        "title": "BrandTonePreviewRequest"
      },
      "BusinessHours": {
        "properties": {
          "mon": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "tue": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "wed": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "thu": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "fri": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "sat": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "sun": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DayHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "timezone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Timezone"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "BusinessHours",
        "description": "Weekly schedule keyed by three-letter day, plus an optional IANA zone.\n\n``extra=\"forbid\"`` matters here specifically: the availability service\nlooks days up by these exact keys, so a payload with ``\"monday\"`` instead\nof ``\"mon\"`` used to be stored intact and then silently ignored at\nruntime \u2014 the customer's agent stayed offline all Monday with a schedule\non screen that said otherwise. Now it is a 422 at save time."
      },
      "CancelSubscriptionRequest": {
        "properties": {
          "reason": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          }
        },
        "type": "object",
        "title": "CancelSubscriptionRequest"
      },
      "ChangeEmailConfirm": {
        "properties": {
          "otp": {
            "type": "string",
            "title": "Otp"
          }
        },
        "type": "object",
        "required": [
          "otp"
        ],
        "title": "ChangeEmailConfirm"
      },
      "ChangeEmailRequest": {
        "properties": {
          "new_email": {
            "type": "string",
            "title": "New Email"
          },
          "current_password": {
            "type": "string",
            "title": "Current Password"
          }
        },
        "type": "object",
        "required": [
          "new_email",
          "current_password"
        ],
        "title": "ChangeEmailRequest"
      },
      "ChangePasswordRequest": {
        "properties": {
          "current_password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Current Password"
          },
          "new_password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "New Password"
          }
        },
        "type": "object",
        "required": [
          "current_password",
          "new_password"
        ],
        "title": "ChangePasswordRequest"
      },
      "ChangePlanRequest": {
        "properties": {
          "plan_id": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Plan Id"
          },
          "billing_cycle": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "monthly",
                  "annual"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Cycle"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          }
        },
        "type": "object",
        "required": [
          "plan_id"
        ],
        "title": "ChangePlanRequest"
      },
      "ChatRequest": {
        "properties": {
          "question": {
            "type": "string",
            "maxLength": 5000,
            "minLength": 1,
            "title": "Question"
          },
          "session_id": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 128,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_.:\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Session Id"
          },
          "cta_dimension": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Cta Dimension"
          }
        },
        "type": "object",
        "required": [
          "question"
        ],
        "title": "ChatRequest"
      },
      "CheckoutRequest": {
        "properties": {
          "plan_id": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Plan Id"
          },
          "billing_cycle": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ],
            "title": "Billing Cycle",
            "default": "monthly"
          },
          "coupon_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Coupon Code"
          },
          "billing_country": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Country"
          }
        },
        "type": "object",
        "required": [
          "plan_id"
        ],
        "title": "CheckoutRequest"
      },
      "ClickRequest": {
        "properties": {
          "code": {
            "type": "string",
            "maxLength": 20,
            "minLength": 3,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Code"
          },
          "referrer": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Referrer"
          }
        },
        "type": "object",
        "required": [
          "code"
        ],
        "title": "ClickRequest",
        "description": "Unauthenticated click beacon fired from the marketing site."
      },
      "ClientProfilePatch": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 269
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          }
        },
        "type": "object",
        "title": "ClientProfilePatch"
      },
      "ClientSettingsUpdate": {
        "properties": {
          "bot_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Name"
          },
          "bot_logo": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2048
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Logo"
          },
          "launcher_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Launcher Name"
          },
          "launcher_logo": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2048
              },
              {
                "type": "null"
              }
            ],
            "title": "Launcher Logo"
          },
          "primary_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Primary Color"
          },
          "background_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Background Color"
          },
          "header_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Header Color"
          }
        },
        "type": "object",
        "title": "ClientSettingsUpdate",
        "description": "Legacy client-scoped widget settings (``PATCH /client/settings``).\n\nSuperseded by the bot-scoped ``PATCH /bots/{id}`` for workspaces that have\na bot, but still reachable, so it is held to the same constraints \u2014\notherwise it is simply the unvalidated way in to the same columns."
      },
      "CodeReferralsResponse": {
        "properties": {
          "code": {
            "type": "string",
            "title": "Code"
          },
          "breakdown": {
            "$ref": "#/components/schemas/CommissionBreakdown"
          },
          "distribution": {
            "$ref": "#/components/schemas/PricingDistribution"
          },
          "referrals": {
            "items": {
              "$ref": "#/components/schemas/ReferralRow"
            },
            "type": "array",
            "title": "Referrals"
          }
        },
        "type": "object",
        "required": [
          "code",
          "breakdown",
          "distribution",
          "referrals"
        ],
        "title": "CodeReferralsResponse"
      },
      "CodeRow": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "code": {
            "type": "string",
            "title": "Code"
          },
          "label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          },
          "active": {
            "type": "boolean",
            "title": "Active"
          },
          "affiliate_commission_pct": {
            "type": "number",
            "title": "Affiliate Commission Pct"
          },
          "customer_discount_pct": {
            "type": "number",
            "title": "Customer Discount Pct"
          },
          "affiliate_commission_bps": {
            "type": "integer",
            "title": "Affiliate Commission Bps"
          },
          "customer_discount_bps": {
            "type": "integer",
            "title": "Customer Discount Bps"
          },
          "clicks": {
            "type": "integer",
            "title": "Clicks"
          },
          "signups": {
            "type": "integer",
            "title": "Signups"
          },
          "conversion_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Conversion Pct"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          },
          "deactivated_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Deactivated At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "code",
          "label",
          "active",
          "affiliate_commission_pct",
          "customer_discount_pct",
          "affiliate_commission_bps",
          "customer_discount_bps",
          "clicks",
          "signups",
          "conversion_pct",
          "created_at",
          "deactivated_at"
        ],
        "title": "CodeRow"
      },
      "CommissionBreakdown": {
        "properties": {
          "pool_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pool Pct"
          },
          "affiliate_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Affiliate Pct"
          },
          "customer_discount_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Discount Pct"
          },
          "code_unused_pool_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Code Unused Pool Pct"
          },
          "platform_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Platform Pct"
          }
        },
        "type": "object",
        "required": [
          "pool_pct",
          "affiliate_pct",
          "customer_discount_pct",
          "code_unused_pool_pct"
        ],
        "title": "CommissionBreakdown"
      },
      "ConnectRequestResponseBody": {
        "properties": {
          "accepted": {
            "type": "boolean",
            "title": "Accepted"
          },
          "request_id": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 128,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_.:\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Request Id"
          }
        },
        "type": "object",
        "required": [
          "accepted"
        ],
        "title": "ConnectRequestResponseBody"
      },
      "CrawlDiffRequest": {
        "properties": {
          "url": {
            "type": "string",
            "title": "Url"
          },
          "replace_source": {
            "type": "string",
            "minLength": 1,
            "title": "Replace Source",
            "description": "Root domain whose existing pages should be diffed against the live sitemap (e.g. 'oyechats.com')."
          },
          "mode": {
            "type": "string",
            "title": "Mode",
            "description": "Recrawl preview mode. ``full`` shows the totals for a re-scrape of every URL (charged per page regardless of content change). ``delta`` shows the URL-level diff and defers content-change detection to the ingestion pipeline's SHA-256 dedup \u2014 only new or changed pages are billed. ``delta`` is gated to Standard+; Free/Starter callers get 403.",
            "default": "delta"
          }
        },
        "type": "object",
        "required": [
          "url",
          "replace_source"
        ],
        "title": "CrawlDiffRequest",
        "description": "Request body for POST /crawl/diff \u2014 diff a recrawl against existing pages."
      },
      "CrawlDiscoverRequest": {
        "properties": {
          "url": {
            "type": "string",
            "title": "Url"
          }
        },
        "type": "object",
        "required": [
          "url"
        ],
        "title": "CrawlDiscoverRequest",
        "description": "Request body for POST /crawl/discover \u2014 URL-only pre-crawl page count."
      },
      "CrawlRequest": {
        "properties": {
          "url": {
            "type": "string",
            "title": "Url"
          },
          "max_pages": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Max Pages"
          },
          "use_js": {
            "type": "boolean",
            "title": "Use Js",
            "description": "Enable JavaScript (browser) mode for all pages. Required for Next.js, React, and other SPA sites.",
            "default": false
          },
          "replace_source": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Replace Source",
            "description": "Root domain to atomically replace after a successful crawl (e.g. 'fynix.digital'). Old chunks for this source are deleted only after new ingestion succeeds \u2014 so the bot always has knowledge during the recrawl."
          },
          "expected_new_pages": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Expected New Pages",
            "description": "Optional client-supplied page count from a prior /crawl/diff call, used to right-size the credit pre-flight on a recrawl (only honored when ``replace_source`` is set). Per-page atomic deduction inside the ingestion pipeline remains authoritative \u2014 this only loosens the upfront ceiling so a 9-new-page recrawl isn't blocked by a 1200-page worst-case reservation."
          },
          "discovered_pages": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Discovered Pages",
            "description": "Optional client-supplied sitemap page count from a prior /crawl/discover call. Used to right-size the credit pre-flight on an INITIAL crawl (honored only when ``replace_source`` is NOT set) so a small site isn't gated at the plan's full max-pages ceiling \u2014 e.g. a 13-page site reserves 13\u00d7cost, not plan_max\u00d7cost. Per-page atomic deduction inside the ingestion pipeline stays authoritative \u2014 this only tightens the upfront reservation to the discovered count."
          },
          "ordered_urls": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ordered Urls",
            "description": "Explicit, pre-ordered list of URLs to crawl (from a prior /crawl/discover, sorted client-side by the user's chosen order and truncated to the affordable count). When set, the recursive crawl is skipped and exactly these URLs are fetched in order. Validated same-origin and capped server-side."
          },
          "mode": {
            "type": "string",
            "title": "Mode",
            "description": "Recrawl mode. ``full`` forces re-embed + charge for every discovered URL (Free/Starter's only option) \u2014 the ingestion pipeline's SHA-256 dedup is bypassed so unchanged pages still bill. ``delta`` uses the existing dedup so only new or content-changed pages bill. ``delta`` is gated to Standard+; Free/Starter callers get 403. First-time crawls (no ``replace_source``) always run as full \u2014 the mode field is ignored.",
            "default": "delta"
          }
        },
        "type": "object",
        "required": [
          "url"
        ],
        "title": "CrawlRequest"
      },
      "CreateBotRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "default": "AI Assistant"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 269,
                "minLength": 1,
                "pattern": "^[\\x20-\\x7E]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "system_prompt": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "System Prompt"
          },
          "bant_enabled": {
            "type": "boolean",
            "title": "Bant Enabled",
            "default": true
          },
          "allowed_domains": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array",
                "maxItems": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Allowed Domains"
          },
          "domain_check_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Domain Check Enabled"
          }
        },
        "type": "object",
        "title": "CreateBotRequest"
      },
      "CreateCannedResponseRequest": {
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Title"
          },
          "content": {
            "type": "string",
            "maxLength": 5000,
            "minLength": 1,
            "title": "Content"
          },
          "shortcut": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 40,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Shortcut"
          },
          "category": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Category"
          }
        },
        "type": "object",
        "required": [
          "title",
          "content"
        ],
        "title": "CreateCannedResponseRequest"
      },
      "CreateCodeRequest": {
        "properties": {
          "code": {
            "type": "string",
            "maxLength": 20,
            "minLength": 3,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Code"
          },
          "label": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          },
          "affiliate_commission_pct": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 100.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Affiliate Commission Pct"
          },
          "customer_discount_pct": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 100.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Discount Pct"
          }
        },
        "type": "object",
        "required": [
          "code"
        ],
        "title": "CreateCodeRequest"
      },
      "CreateDepartmentRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          }
        },
        "type": "object",
        "required": [
          "name"
        ],
        "title": "CreateDepartmentRequest"
      },
      "CreateInviteRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "bot_id": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Bot Id"
          },
          "role": {
            "type": "string",
            "enum": [
              "operator",
              "admin"
            ],
            "title": "Role",
            "default": "operator"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          }
        },
        "type": "object",
        "required": [
          "email",
          "bot_id"
        ],
        "title": "CreateInviteRequest"
      },
      "CreateOperatorRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Password"
          },
          "bot_id": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Bot Id"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "operator"
            ],
            "title": "Role",
            "default": "operator"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          }
        },
        "type": "object",
        "required": [
          "name",
          "email",
          "password",
          "bot_id"
        ],
        "title": "CreateOperatorRequest"
      },
      "CreateWebhookRequest": {
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2083,
            "minLength": 1,
            "format": "uri",
            "title": "Url"
          },
          "events": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "title": "Events"
          },
          "is_active": {
            "type": "boolean",
            "title": "Is Active",
            "default": true
          }
        },
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "title": "CreateWebhookRequest"
      },
      "CurrentUserResponse": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "kind": {
            "type": "string",
            "title": "Kind"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "avatar_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Url"
          },
          "pending_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pending Email"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "created_at": {
            "type": "string",
            "title": "Created At"
          },
          "bot_count": {
            "type": "integer",
            "title": "Bot Count"
          },
          "is_superadmin": {
            "type": "boolean",
            "title": "Is Superadmin",
            "default": false
          },
          "is_online": {
            "type": "boolean",
            "title": "Is Online",
            "default": false
          },
          "is_verified": {
            "type": "boolean",
            "title": "Is Verified",
            "default": false
          },
          "onboarding_complete": {
            "type": "boolean",
            "title": "Onboarding Complete",
            "default": false
          },
          "role": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Role"
          },
          "is_affiliate": {
            "type": "boolean",
            "title": "Is Affiliate",
            "default": false
          },
          "affiliate_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Affiliate Id"
          },
          "is_affiliate_only": {
            "type": "boolean",
            "title": "Is Affiliate Only",
            "default": false
          },
          "trial": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TrialStatePayload"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "id",
          "kind",
          "name",
          "email",
          "created_at",
          "bot_count"
        ],
        "title": "CurrentUserResponse",
        "description": "Profile payload for the authenticated principal (TopBar profile dropdown).\n\nWorks for both clients (admins) and operators. The ``kind`` discriminator\ntells the UI which fields are meaningful \u2014 operators don't own bots\ndirectly, so ``bot_count`` reflects the bots in their workspace (the\nclient they belong to). For clients ``role`` is None; for operators\n``role`` is one of ``owner | admin | operator``.\n\nExposes only the small set of profile fields the admin app needs to\nrender the user menu \u2014 never sensitive data (no api_key, no password\nhash)."
      },
      "DayHours": {
        "properties": {
          "start": {
            "type": "string",
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "title": "Start"
          },
          "end": {
            "type": "string",
            "pattern": "^(?:[01]\\d|2[0-3]):[0-5]\\d$",
            "title": "End"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "start",
          "end"
        ],
        "title": "DayHours",
        "description": "One day's open window, ``{\"start\": \"09:00\", \"end\": \"17:00\"}``.\n\nA day is marked closed by setting the whole day to ``null``, which is what\n``live_chat_availability_service._within_business_hours`` reads. No\n``closed`` flag is accepted here on purpose: a field the evaluator does\nnot consult would store fine and change nothing, which is worse than\nrejecting it."
      },
      "DocumentPageItem": {
        "properties": {
          "url": {
            "type": "string",
            "title": "Url"
          },
          "title": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Title"
          },
          "chunk_count": {
            "type": "integer",
            "title": "Chunk Count"
          },
          "ingested_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ingested At"
          }
        },
        "type": "object",
        "required": [
          "url",
          "title",
          "chunk_count",
          "ingested_at"
        ],
        "title": "DocumentPageItem"
      },
      "DocumentPagesResponse": {
        "properties": {
          "domain": {
            "type": "string",
            "title": "Domain"
          },
          "total_pages": {
            "type": "integer",
            "title": "Total Pages"
          },
          "total_chunks": {
            "type": "integer",
            "title": "Total Chunks"
          },
          "pages": {
            "items": {
              "$ref": "#/components/schemas/DocumentPageItem"
            },
            "type": "array",
            "title": "Pages"
          }
        },
        "type": "object",
        "required": [
          "domain",
          "total_pages",
          "total_chunks",
          "pages"
        ],
        "title": "DocumentPagesResponse"
      },
      "ExpoPushSubscribeRequest": {
        "properties": {
          "token": {
            "type": "string",
            "maxLength": 256,
            "minLength": 1,
            "title": "Token"
          }
        },
        "type": "object",
        "required": [
          "token"
        ],
        "title": "ExpoPushSubscribeRequest"
      },
      "FeedbackRequest": {
        "properties": {
          "feedback": {
            "type": "integer",
            "maximum": 1.0,
            "minimum": -1.0,
            "title": "Feedback",
            "description": "1 for positive, -1 for negative"
          }
        },
        "type": "object",
        "required": [
          "feedback"
        ],
        "title": "FeedbackRequest"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "HandoffRequest": {
        "properties": {
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          },
          "reason": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          }
        },
        "type": "object",
        "required": [
          "session_id"
        ],
        "title": "HandoffRequest"
      },
      "IdTokenRequest": {
        "properties": {
          "id_token": {
            "type": "string",
            "maxLength": 4096,
            "minLength": 1,
            "title": "Id Token"
          }
        },
        "type": "object",
        "required": [
          "id_token"
        ],
        "title": "IdTokenRequest"
      },
      "InviteCreatedResponse": {
        "properties": {
          "invite": {
            "$ref": "#/components/schemas/InviteView"
          }
        },
        "type": "object",
        "required": [
          "invite"
        ],
        "title": "InviteCreatedResponse"
      },
      "InviteView": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "bot_id": {
            "type": "integer",
            "title": "Bot Id"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "title": "Expires At"
          },
          "invited_by_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Invited By Name"
          },
          "resend_count": {
            "type": "integer",
            "title": "Resend Count"
          },
          "sent_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sent At"
          },
          "accepted_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Accepted At"
          },
          "revoked_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Revoked At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "bot_id",
          "department_id",
          "status",
          "created_at",
          "expires_at",
          "invited_by_name",
          "resend_count",
          "sent_at",
          "accepted_at",
          "revoked_at"
        ],
        "title": "InviteView"
      },
      "LeadCaptureRequest": {
        "properties": {
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          },
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 40
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "company": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Company"
          }
        },
        "type": "object",
        "required": [
          "session_id"
        ],
        "title": "LeadCaptureRequest"
      },
      "LeadFormField": {
        "properties": {
          "field": {
            "type": "string",
            "enum": [
              "name",
              "email",
              "phone",
              "company"
            ],
            "title": "Field"
          },
          "required": {
            "type": "boolean",
            "title": "Required",
            "default": false
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "field"
        ],
        "title": "LeadFormField",
        "description": "One row of the configurable pre-chat lead form.\n\nExactly the shape both clients produce and consume \u2014 the admin app writes\n``{field, required}`` and the widget's ``LeadCaptureForm`` renders those\nfour field names. Anything else was previously stored and then dropped on\nread, so it is rejected here instead."
      },
      "LoginRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Password"
          }
        },
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "title": "LoginRequest"
      },
      "LoginResponse": {
        "properties": {
          "access_token": {
            "type": "string",
            "title": "Access Token"
          },
          "token_type": {
            "type": "string",
            "title": "Token Type",
            "default": "bearer"
          },
          "client_id": {
            "type": "integer",
            "title": "Client Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "is_superadmin": {
            "type": "boolean",
            "title": "Is Superadmin"
          },
          "is_verified": {
            "type": "boolean",
            "title": "Is Verified",
            "default": true
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          }
        },
        "type": "object",
        "required": [
          "access_token",
          "client_id",
          "name",
          "is_superadmin"
        ],
        "title": "LoginResponse"
      },
      "MeResponse": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "max_active_codes": {
            "type": "integer",
            "title": "Max Active Codes"
          },
          "commission_pct": {
            "type": "number",
            "title": "Commission Pct"
          },
          "commission_bps": {
            "type": "integer",
            "title": "Commission Bps"
          },
          "created_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "max_active_codes",
          "commission_pct",
          "commission_bps",
          "created_at"
        ],
        "title": "MeResponse"
      },
      "MeWorkspacesResponse": {
        "properties": {
          "workspaces": {
            "items": {
              "$ref": "#/components/schemas/WorkspaceView"
            },
            "type": "array",
            "title": "Workspaces"
          }
        },
        "type": "object",
        "required": [
          "workspaces"
        ],
        "title": "MeWorkspacesResponse"
      },
      "MeetingBookedRequest": {
        "properties": {
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          },
          "booking_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Booking Url"
          },
          "meeting_time": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Meeting Time"
          },
          "attendee_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Attendee Email"
          }
        },
        "type": "object",
        "required": [
          "session_id"
        ],
        "title": "MeetingBookedRequest"
      },
      "NotificationEmailRouting": {
        "properties": {
          "default": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 20,
            "title": "Default",
            "default": []
          },
          "qualified_lead": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 20,
            "title": "Qualified Lead",
            "default": []
          },
          "handoff_request": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 20,
            "title": "Handoff Request",
            "default": []
          },
          "offline_message": {
            "items": {
              "type": "string"
            },
            "type": "array",
            "maxItems": 20,
            "title": "Offline Message",
            "default": []
          }
        },
        "additionalProperties": false,
        "type": "object",
        "title": "NotificationEmailRouting",
        "description": "Per-event notification recipients (``bot.notification_emails``).\n\nPreviously a bare ``dict``: whatever the caller sent was written to JSONB\nand later handed to the transactional email provider as a recipient list.\nAn unvalidated address there is not a cosmetic problem \u2014 it is who the\ncustomer's lead notifications get delivered to. Each bucket is now an\nallow-listed key holding validated, de-duplicated addresses."
      },
      "NotificationPreferencesRequest": {
        "properties": {
          "push": {
            "$ref": "#/components/schemas/PushPreferencesModel"
          }
        },
        "type": "object",
        "title": "NotificationPreferencesRequest"
      },
      "OperatorChangePasswordRequest": {
        "properties": {
          "current_password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Current Password"
          },
          "new_password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "New Password"
          }
        },
        "type": "object",
        "required": [
          "current_password",
          "new_password"
        ],
        "title": "OperatorChangePasswordRequest"
      },
      "OperatorLoginRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Password"
          }
        },
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "title": "OperatorLoginRequest"
      },
      "OperatorLoginResponse": {
        "properties": {
          "access_token": {
            "type": "string",
            "title": "Access Token"
          },
          "token_type": {
            "type": "string",
            "title": "Token Type",
            "default": "bearer"
          },
          "operator_id": {
            "type": "integer",
            "title": "Operator Id"
          },
          "client_id": {
            "type": "integer",
            "title": "Client Id"
          },
          "default_bot_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Default Bot Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          }
        },
        "type": "object",
        "required": [
          "access_token",
          "operator_id",
          "client_id",
          "name",
          "role"
        ],
        "title": "OperatorLoginResponse"
      },
      "PlatformFeedbackCreate": {
        "properties": {
          "message": {
            "type": "string",
            "maxLength": 5000,
            "minLength": 1,
            "title": "Message"
          },
          "attachment_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Attachment Url"
          },
          "category": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Category"
          },
          "type": {
            "type": "string",
            "title": "Type",
            "default": "other"
          },
          "area": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Area"
          },
          "severity": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Severity"
          },
          "context": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Context"
          },
          "attachments": {
            "anyOf": [
              {
                "items": {},
                "type": "array",
                "maxItems": 10
              },
              {
                "type": "null"
              }
            ],
            "title": "Attachments"
          }
        },
        "type": "object",
        "required": [
          "message"
        ],
        "title": "PlatformFeedbackCreate"
      },
      "PricingDistribution": {
        "properties": {
          "currency": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Currency"
          },
          "paying_referrals": {
            "type": "integer",
            "title": "Paying Referrals"
          },
          "monthly_total_cents": {
            "type": "integer",
            "title": "Monthly Total Cents"
          },
          "monthly_affiliate_cents": {
            "type": "integer",
            "title": "Monthly Affiliate Cents"
          },
          "monthly_customer_saved_cents": {
            "type": "integer",
            "title": "Monthly Customer Saved Cents"
          },
          "monthly_platform_cents": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Monthly Platform Cents"
          }
        },
        "type": "object",
        "required": [
          "currency",
          "paying_referrals",
          "monthly_total_cents",
          "monthly_affiliate_cents",
          "monthly_customer_saved_cents"
        ],
        "title": "PricingDistribution",
        "description": "Aggregate monthly $ rolled up across every paying referral.\n\nLets the affiliate see \"your code is pulling ~$X/mo from N customers\"\nat a glance without summing the per-row cards by eye."
      },
      "PublicInviteView": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "workspace_name": {
            "type": "string",
            "title": "Workspace Name"
          },
          "inviter_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Inviter Name"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "expires_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expires At"
          }
        },
        "type": "object",
        "required": [
          "email",
          "workspace_name",
          "inviter_name",
          "role",
          "status",
          "expires_at"
        ],
        "title": "PublicInviteView",
        "description": "Metadata surfaced to the (unauthenticated) airlock page.\n\nDeliberately narrow: workspace name + inviter display name + status +\ntarget email. No IDs, no tokens, no personal data beyond what the invitee\nalready knows (their own email)."
      },
      "PushPreferencesModel": {
        "properties": {
          "enabled": {
            "type": "boolean",
            "title": "Enabled",
            "default": true
          },
          "events": {
            "additionalProperties": {
              "type": "boolean"
            },
            "type": "object",
            "title": "Events"
          },
          "quiet_hours": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/QuietHoursModel"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "title": "PushPreferencesModel"
      },
      "PushSubscribeRequest": {
        "properties": {
          "endpoint": {
            "type": "string",
            "title": "Endpoint"
          },
          "keys": {
            "$ref": "#/components/schemas/PushSubscriptionKeys"
          }
        },
        "type": "object",
        "required": [
          "endpoint",
          "keys"
        ],
        "title": "PushSubscribeRequest"
      },
      "PushSubscriptionKeys": {
        "properties": {
          "p256dh": {
            "type": "string",
            "maxLength": 256,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-=]+$",
            "title": "P256Dh"
          },
          "auth": {
            "type": "string",
            "maxLength": 256,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-=]+$",
            "title": "Auth"
          }
        },
        "type": "object",
        "required": [
          "p256dh",
          "auth"
        ],
        "title": "PushSubscriptionKeys"
      },
      "QualificationOverrideRequest": {
        "properties": {
          "dimension": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z][a-z0-9_]*$",
            "title": "Dimension"
          },
          "score": {
            "type": "integer",
            "maximum": 100.0,
            "minimum": 0.0,
            "title": "Score"
          }
        },
        "type": "object",
        "required": [
          "dimension",
          "score"
        ],
        "title": "QualificationOverrideRequest"
      },
      "QuietHoursModel": {
        "properties": {
          "start": {
            "type": "string",
            "title": "Start"
          },
          "end": {
            "type": "string",
            "title": "End"
          },
          "tz": {
            "type": "string",
            "title": "Tz",
            "default": "UTC"
          }
        },
        "type": "object",
        "required": [
          "start",
          "end"
        ],
        "title": "QuietHoursModel"
      },
      "RecrawlStatusResponse": {
        "properties": {
          "enabled": {
            "type": "boolean",
            "title": "Enabled"
          },
          "cadence_days": {
            "type": "integer",
            "title": "Cadence Days"
          },
          "feature_available": {
            "type": "boolean",
            "title": "Feature Available"
          },
          "current_plan": {
            "type": "string",
            "title": "Current Plan"
          },
          "next_recrawl_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Recrawl At"
          },
          "last_recrawl_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Recrawl At"
          },
          "last_recrawl_status": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Recrawl Status"
          },
          "last_recrawl_summary": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Recrawl Summary"
          },
          "sources_count": {
            "type": "integer",
            "title": "Sources Count"
          },
          "recrawl_history": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Recrawl History"
          }
        },
        "type": "object",
        "required": [
          "enabled",
          "cadence_days",
          "feature_available",
          "current_plan",
          "next_recrawl_at",
          "last_recrawl_at",
          "last_recrawl_status",
          "last_recrawl_summary",
          "sources_count",
          "recrawl_history"
        ],
        "title": "RecrawlStatusResponse"
      },
      "RecrawlUpdateRequest": {
        "properties": {
          "enabled": {
            "type": "boolean",
            "title": "Enabled"
          }
        },
        "type": "object",
        "required": [
          "enabled"
        ],
        "title": "RecrawlUpdateRequest"
      },
      "ReferralPricing": {
        "properties": {
          "plan_slug": {
            "type": "string",
            "title": "Plan Slug"
          },
          "currency": {
            "type": "string",
            "title": "Currency"
          },
          "full_price_cents": {
            "type": "integer",
            "title": "Full Price Cents"
          },
          "paid_cents": {
            "type": "integer",
            "title": "Paid Cents"
          },
          "affiliate_earns_cents": {
            "type": "integer",
            "title": "Affiliate Earns Cents"
          },
          "customer_saved_cents": {
            "type": "integer",
            "title": "Customer Saved Cents"
          },
          "platform_cents": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Platform Cents"
          }
        },
        "type": "object",
        "required": [
          "plan_slug",
          "currency",
          "full_price_cents",
          "paid_cents",
          "affiliate_earns_cents",
          "customer_saved_cents"
        ],
        "title": "ReferralPricing",
        "description": "Dollar-amount split for a single referred customer's monthly bill.\n\nCents are the minor unit of ``currency``. Values are 0 when the customer\nhas no paid subscription yet (Free tier, never converted, or paused)."
      },
      "ReferralRow": {
        "properties": {
          "client_id": {
            "type": "integer",
            "title": "Client Id"
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "attributed_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Attributed At"
          },
          "pricing": {
            "$ref": "#/components/schemas/ReferralPricing"
          }
        },
        "type": "object",
        "required": [
          "client_id",
          "name",
          "email",
          "attributed_at",
          "pricing"
        ],
        "title": "ReferralRow"
      },
      "ReferralStatusResponse": {
        "properties": {
          "attributed": {
            "type": "boolean",
            "title": "Attributed"
          },
          "code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Code"
          },
          "discount_pct": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Discount Pct"
          }
        },
        "type": "object",
        "required": [
          "attributed"
        ],
        "title": "ReferralStatusResponse"
      },
      "RegisterRequest": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Password"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "billing_country": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 8
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Country"
          },
          "referral_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Referral Code"
          },
          "promo_code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Promo Code"
          }
        },
        "type": "object",
        "required": [
          "name",
          "email",
          "password"
        ],
        "title": "RegisterRequest"
      },
      "RegisterResponse": {
        "properties": {
          "access_token": {
            "type": "string",
            "title": "Access Token"
          },
          "token_type": {
            "type": "string",
            "title": "Token Type",
            "default": "bearer"
          },
          "client_id": {
            "type": "integer",
            "title": "Client Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "is_superadmin": {
            "type": "boolean",
            "title": "Is Superadmin",
            "default": false
          },
          "is_verified": {
            "type": "boolean",
            "title": "Is Verified",
            "default": false
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "website": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "message": {
            "type": "string",
            "title": "Message",
            "default": "Account created successfully."
          },
          "trial": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/TrialStatePayload"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "required": [
          "access_token",
          "client_id",
          "name"
        ],
        "title": "RegisterResponse"
      },
      "RequestPasswordResetRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "RequestPasswordResetRequest"
      },
      "ResendVerificationRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "ResendVerificationRequest"
      },
      "ResetPasswordRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "otp": {
            "type": "string",
            "pattern": "^\\d{6}$",
            "title": "Otp"
          },
          "new_password": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "New Password"
          }
        },
        "type": "object",
        "required": [
          "email",
          "otp",
          "new_password"
        ],
        "title": "ResetPasswordRequest"
      },
      "ResumeSubscriptionRequest": {
        "properties": {
          "bot_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          }
        },
        "type": "object",
        "title": "ResumeSubscriptionRequest"
      },
      "SeatChangeRequest": {
        "properties": {
          "delta": {
            "type": "integer",
            "maximum": 500.0,
            "minimum": -500.0,
            "title": "Delta"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          }
        },
        "type": "object",
        "required": [
          "delta"
        ],
        "title": "SeatChangeRequest"
      },
      "SelfOperatorRequest": {
        "properties": {
          "bot_id": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Bot Id"
          }
        },
        "type": "object",
        "required": [
          "bot_id"
        ],
        "title": "SelfOperatorRequest",
        "description": "Body for ``POST /me/self-operator``.\n\n``bot_id`` is required because operators are bot-scoped \u2014 the owner must\npick which bot they'll take chats for. Reactivating a previously-added\nself-op row with a different ``bot_id`` reassigns it."
      },
      "SelfOperatorResponse": {
        "properties": {
          "operator_id": {
            "type": "integer",
            "title": "Operator Id"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "bot_id": {
            "type": "integer",
            "title": "Bot Id"
          },
          "is_active": {
            "type": "boolean",
            "title": "Is Active"
          },
          "was_existing": {
            "type": "boolean",
            "title": "Was Existing"
          }
        },
        "type": "object",
        "required": [
          "operator_id",
          "role",
          "bot_id",
          "is_active",
          "was_existing"
        ],
        "title": "SelfOperatorResponse",
        "description": "Response for ``POST /me/self-operator`` \u2014 the owner-as-operator row."
      },
      "SendFollowUpRequest": {
        "properties": {
          "confirm_override": {
            "type": "boolean",
            "title": "Confirm Override",
            "default": false
          }
        },
        "type": "object",
        "title": "SendFollowUpRequest"
      },
      "ServiceEntry": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          }
        },
        "additionalProperties": false,
        "type": "object",
        "required": [
          "name"
        ],
        "title": "ServiceEntry",
        "description": "``{\"name\": ..., \"url\": ...}`` \u2014 a service the agent can talk about."
      },
      "SetStatusRequest": {
        "properties": {
          "is_online": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Is Online"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          }
        },
        "type": "object",
        "title": "SetStatusRequest"
      },
      "StartTrialRequest": {
        "properties": {
          "plan_slug": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z0-9_\\-]+$",
            "title": "Plan Slug"
          }
        },
        "type": "object",
        "required": [
          "plan_slug"
        ],
        "title": "StartTrialRequest",
        "description": "Body for ``POST /subscriptions/start-trial``.\n\nThe slug is the public plan identifier the pricing page renders against\n(``starter`` / ``standard``). The slug must point at an active plan with\n``trial_days > 0`` \u2014 the free plan is intentionally excluded."
      },
      "SubmitOfflineMessageRequest": {
        "properties": {
          "bot_key": {
            "type": "string",
            "maxLength": 64,
            "minLength": 4,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Bot Key"
          },
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 40
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "message": {
            "type": "string",
            "maxLength": 5000,
            "minLength": 1,
            "title": "Message"
          },
          "session_id": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 128,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_.:\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Session Id"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          },
          "reason": {
            "type": "string",
            "maxLength": 200,
            "title": "Reason",
            "default": "manual"
          },
          "transcript": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/TranscriptTurn"
                },
                "type": "array",
                "maxItems": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Transcript"
          }
        },
        "type": "object",
        "required": [
          "bot_key",
          "name",
          "email",
          "message"
        ],
        "title": "SubmitOfflineMessageRequest",
        "description": "Unauthenticated widget submission \u2014 the bot key is the only credential.\n\n``reason`` and ``transcript`` were undeclared here while the widget has\nbeen sending both since the fallback-reason feature shipped. Pydantic's\ndefault is to ignore unknown keys, so on this path they were parsed and\ndropped \u2014 the columns stayed null and the admin inbox showed no fallback\ncause, while the WebSocket fallback (``submit_offline_form``) stored them\ncorrectly. Declaring them is the fix: same fields, same bounds, same\nbehaviour on both paths."
      },
      "TopupRequest": {
        "properties": {
          "amount": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Amount"
          },
          "pack_usd": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Pack Usd"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          },
          "billing_country": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Country"
          }
        },
        "type": "object",
        "title": "TopupRequest",
        "description": "Top-up purchase request.\n\n``amount`` is the pack amount matching one of the configured packs in\n``pricing_config.topup_packs``. ``pack_usd`` is kept as a backward-compat\nalias for older admin builds \u2014 at least one of the two must be provided.\n\n``bot_id`` scopes the purchase to a specific per-bot ledger. Omit\n(or pass null) to top up the account-level client pool \u2014 that's the\ncorrect shape for Free / legacy-pooled bots whose usage drains\nshared credits. Per-bot subscriptions must always pass their bot_id\nso the credits land in the right isolated bucket."
      },
      "TopupVerifyRequest": {
        "properties": {
          "razorpay_order_id": {
            "type": "string",
            "title": "Razorpay Order Id"
          },
          "razorpay_payment_id": {
            "type": "string",
            "title": "Razorpay Payment Id"
          },
          "razorpay_signature": {
            "type": "string",
            "title": "Razorpay Signature"
          }
        },
        "type": "object",
        "required": [
          "razorpay_order_id",
          "razorpay_payment_id",
          "razorpay_signature"
        ],
        "title": "TopupVerifyRequest",
        "description": "Razorpay Checkout success callback verification.\n\nThe frontend sends the trio Razorpay returns in its handler callback;\nwe verify the HMAC server-side to make sure the success was genuinely\nsigned by Razorpay (defence against tampered modal responses).\n\nThe credit grant itself happens via the Razorpay webhook \u2014 this\nendpoint just confirms the modal closure to the user."
      },
      "TranscriptEmailRequest": {
        "properties": {
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          },
          "recipient_email": {
            "type": "string",
            "title": "Recipient Email"
          }
        },
        "type": "object",
        "required": [
          "session_id",
          "recipient_email"
        ],
        "title": "TranscriptEmailRequest"
      },
      "TranscriptTurn": {
        "properties": {
          "role": {
            "type": "string",
            "maxLength": 20,
            "title": "Role",
            "default": "user"
          },
          "content": {
            "type": "string",
            "maxLength": 5000,
            "title": "Content",
            "default": ""
          },
          "ts": {
            "type": "string",
            "maxLength": 40,
            "title": "Ts",
            "default": ""
          }
        },
        "type": "object",
        "title": "TranscriptTurn"
      },
      "TransferRequest": {
        "properties": {
          "target_operator_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Target Operator Id"
          },
          "target_department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Target Department Id"
          }
        },
        "type": "object",
        "title": "TransferRequest"
      },
      "TrialStatePayload": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status"
          },
          "trial_end_at": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Trial End At"
          },
          "days_remaining": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Days Remaining"
          },
          "credits_granted": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Credits Granted"
          }
        },
        "type": "object",
        "required": [
          "status"
        ],
        "title": "TrialStatePayload",
        "description": "Subset of subscription state the dashboard needs to render the trial banner.\n\nAlways populated for clients that landed on a trialing subscription at\nsignup; ``None`` for accounts created without a trial (super-admin\nseeded, legacy free-tier, etc). The admin app treats ``None`` as\n\"no trial UI\" rather than zero-day urgency."
      },
      "UpdateBotRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "system_prompt": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "System Prompt"
          },
          "brand_tone": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Brand Tone"
          },
          "brand_tone_preset": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ],
            "title": "Brand Tone Preset"
          },
          "company_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Name"
          },
          "company_description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Description"
          },
          "website": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 269,
                "minLength": 1,
                "pattern": "^[\\x20-\\x7E]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          },
          "bot_logo": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Logo"
          },
          "launcher_name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Launcher Name"
          },
          "launcher_logo": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Launcher Logo"
          },
          "primary_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Primary Color"
          },
          "background_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Background Color"
          },
          "header_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Header Color"
          },
          "user_bubble_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "User Bubble Color"
          },
          "bant_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bant Enabled"
          },
          "bant_config": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Bant Config"
          },
          "qualification_framework": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "bant",
                  "meddic"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Qualification Framework"
          },
          "relevance_threshold": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 1.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Relevance Threshold"
          },
          "avatar_type": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "upload",
                  "orb",
                  "mascot"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Type"
          },
          "orb_color": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Orb Color"
          },
          "lead_form_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Lead Form Enabled"
          },
          "lead_form_fields": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/LeadFormField"
                },
                "type": "array",
                "maxItems": 10
              },
              {
                "type": "null"
              }
            ],
            "title": "Lead Form Fields"
          },
          "notification_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notification Email"
          },
          "notification_emails": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/NotificationEmailRouting"
              },
              {
                "type": "null"
              }
            ]
          },
          "reply_to_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reply To Email"
          },
          "email_on_qualified": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email On Qualified"
          },
          "email_on_handoff": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email On Handoff"
          },
          "email_on_offline": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email On Offline"
          },
          "email_visitor_confirmation": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email Visitor Confirmation"
          },
          "email_verification_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email Verification Enabled"
          },
          "company_lookup_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Company Lookup Enabled"
          },
          "live_chat_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Live Chat Enabled"
          },
          "operator_timeout_seconds": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 3600.0,
                "minimum": 5.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Operator Timeout Seconds"
          },
          "live_chat_queue_timeout_seconds": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 600.0,
                "minimum": 5.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Live Chat Queue Timeout Seconds"
          },
          "live_chat_max_queue_size": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 100.0,
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Live Chat Max Queue Size"
          },
          "business_hours": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BusinessHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "feature_flags": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Feature Flags"
          },
          "widget_messages": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Widget Messages"
          },
          "widget_config": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Widget Config"
          },
          "branding_text": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Branding Text"
          },
          "branding_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Branding Url"
          },
          "welcome_title": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Welcome Title"
          },
          "welcome_subtitle": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Welcome Subtitle"
          },
          "waiting_message": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ],
            "title": "Waiting Message"
          },
          "offline_message": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Offline Message"
          },
          "handoff_delay_seconds": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 3600.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Handoff Delay Seconds"
          },
          "calendly_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Calendly Url"
          },
          "meeting_booking_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Meeting Booking Enabled"
          },
          "meeting_provider": {
            "anyOf": [
              {
                "type": "string",
                "pattern": "^(calendly|zcal|calcom)$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Meeting Provider"
          },
          "zcal_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Zcal Url"
          },
          "calcom_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Calcom Url"
          },
          "services": {
            "anyOf": [
              {
                "items": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/ServiceEntry"
                    },
                    {
                      "type": "string"
                    }
                  ]
                },
                "type": "array",
                "maxItems": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Services"
          },
          "services_url": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2048
              },
              {
                "type": "null"
              }
            ],
            "title": "Services Url"
          },
          "answer_links": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/AnswerLink"
                },
                "type": "array",
                "maxItems": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Answer Links"
          },
          "allowed_domains": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array",
                "maxItems": 50
              },
              {
                "type": "null"
              }
            ],
            "title": "Allowed Domains"
          },
          "domain_check_enabled": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Domain Check Enabled"
          },
          "session_share_domain": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 253
              },
              {
                "type": "null"
              }
            ],
            "title": "Session Share Domain"
          }
        },
        "type": "object",
        "title": "UpdateBotRequest"
      },
      "UpdateCannedResponseRequest": {
        "properties": {
          "title": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Title"
          },
          "content": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 5000,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Content"
          },
          "shortcut": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 40,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9_\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Shortcut"
          },
          "category": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Category"
          }
        },
        "type": "object",
        "title": "UpdateCannedResponseRequest"
      },
      "UpdateCodeRequest": {
        "properties": {
          "code": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 20,
                "minLength": 3,
                "pattern": "^[A-Za-z0-9_\\-]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Code"
          },
          "label": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 120
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          },
          "active": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Active"
          },
          "affiliate_commission_pct": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 100.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Affiliate Commission Pct"
          },
          "customer_discount_pct": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 100.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Customer Discount Pct"
          }
        },
        "type": "object",
        "title": "UpdateCodeRequest"
      },
      "UpdateDepartmentRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "business_hours": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/BusinessHours"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "type": "object",
        "title": "UpdateDepartmentRequest"
      },
      "UpdateOfflineMessageRequest": {
        "properties": {
          "status": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "new",
                  "read",
                  "replied"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Status"
          }
        },
        "type": "object",
        "title": "UpdateOfflineMessageRequest"
      },
      "UpdateOperatorRequest": {
        "properties": {
          "name": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200,
                "minLength": 1
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "role": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "owner",
                  "admin",
                  "operator"
                ]
              },
              {
                "type": "null"
              }
            ],
            "title": "Role"
          },
          "bot_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Bot Id"
          },
          "department_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Department Id"
          },
          "avatar_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Avatar Url"
          },
          "max_concurrent_chats": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 100.0,
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Max Concurrent Chats"
          },
          "notification_preferences": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notification Preferences"
          }
        },
        "type": "object",
        "title": "UpdateOperatorRequest"
      },
      "UpdateWebhookRequest": {
        "properties": {
          "url": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2083,
                "minLength": 1,
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          },
          "events": {
            "anyOf": [
              {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Events"
          },
          "is_active": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Is Active"
          }
        },
        "type": "object",
        "title": "UpdateWebhookRequest"
      },
      "UploadUrlRequest": {
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 255,
            "minLength": 1,
            "title": "Filename"
          },
          "content_type": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "title": "Content Type"
          },
          "size": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Size"
          },
          "session_id": {
            "type": "string",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_.:\\-]+$",
            "title": "Session Id"
          }
        },
        "type": "object",
        "required": [
          "filename",
          "content_type",
          "size",
          "session_id"
        ],
        "title": "UploadUrlRequest"
      },
      "ValidateCodeResponse": {
        "properties": {
          "valid": {
            "type": "boolean",
            "title": "Valid"
          },
          "label": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          }
        },
        "type": "object",
        "required": [
          "valid"
        ],
        "title": "ValidateCodeResponse"
      },
      "ValidateEmailRequest": {
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 254,
            "minLength": 1,
            "title": "Email"
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "ValidateEmailRequest"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "VerifyEmailRequest": {
        "properties": {
          "email": {
            "type": "string",
            "title": "Email"
          },
          "otp": {
            "type": "string",
            "pattern": "^\\d{6}$",
            "title": "Otp"
          }
        },
        "type": "object",
        "required": [
          "email",
          "otp"
        ],
        "title": "VerifyEmailRequest"
      },
      "VerifyRazorpaySubscriptionRequest": {
        "properties": {
          "razorpay_payment_id": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Razorpay Payment Id"
          },
          "razorpay_subscription_id": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9_\\-]+$",
            "title": "Razorpay Subscription Id"
          },
          "razorpay_signature": {
            "type": "string",
            "maxLength": 512,
            "minLength": 1,
            "pattern": "^[!-~]+$",
            "title": "Razorpay Signature"
          }
        },
        "type": "object",
        "required": [
          "razorpay_payment_id",
          "razorpay_subscription_id",
          "razorpay_signature"
        ],
        "title": "VerifyRazorpaySubscriptionRequest"
      },
      "VisitorRatingRequest": {
        "properties": {
          "rating": {
            "anyOf": [
              {
                "type": "integer",
                "maximum": 5.0,
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Rating"
          },
          "resolved": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Resolved"
          }
        },
        "type": "object",
        "title": "VisitorRatingRequest"
      },
      "WorkspaceView": {
        "properties": {
          "id": {
            "type": "integer",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "operator"
            ],
            "title": "Role"
          },
          "operator_role": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Operator Role"
          },
          "bot_count": {
            "type": "integer",
            "title": "Bot Count",
            "default": 0
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "role"
        ],
        "title": "WorkspaceView"
      },
      "app__api__affiliate_routes__AcceptInviteResponse": {
        "properties": {
          "access_token": {
            "type": "string",
            "title": "Access Token"
          },
          "token_type": {
            "type": "string",
            "title": "Token Type",
            "default": "bearer"
          },
          "client_id": {
            "type": "integer",
            "title": "Client Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "is_affiliate": {
            "type": "boolean",
            "title": "Is Affiliate",
            "default": true
          }
        },
        "type": "object",
        "required": [
          "access_token",
          "client_id",
          "name"
        ],
        "title": "AcceptInviteResponse"
      },
      "app__api__invite_routes__AcceptInviteResponse": {
        "properties": {
          "workspace_id": {
            "type": "integer",
            "title": "Workspace Id"
          },
          "workspace_name": {
            "type": "string",
            "title": "Workspace Name"
          },
          "operator_id": {
            "type": "integer",
            "title": "Operator Id"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "redirect_url": {
            "type": "string",
            "title": "Redirect Url"
          }
        },
        "type": "object",
        "required": [
          "workspace_id",
          "workspace_name",
          "operator_id",
          "role",
          "redirect_url"
        ],
        "title": "AcceptInviteResponse"
      }
    },
    "securitySchemes": {
      "APIKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Impersonation-Token"
      }
    }
  },
  "servers": [
    {
      "url": "https://api.oyechats.com",
      "description": "Production"
    }
  ]
}
