components:
  schemas:
    AddFileRequest:
      properties:
        file_content_type:
          description: "MIME type of the uploaded file."
          type: "string"
        file_name:
          description: "Display name for the file."
          type: "string"
        file_s3_key:
          description: "The `s3_key` returned by `createDemoSessionUpload`."
          type: "string"
        file_size:
          description: "Size in bytes."
          type: "integer"
      required:
        - "file_name"
        - "file_s3_key"
      type: "object"
    AddTextRequest:
      properties:
        text:
          description: "The content to train on. Counts against the demo's character cap."
          maxLength: 50000
          type: "string"
      required:
        - "text"
      type: "object"
    AddUrlRequest:
      properties:
        url:
          description: "Full public URL of the page to train on, scheme included. Must be reachable from the internet."
          format: "uri"
          type: "string"
      required:
        - "url"
      type: "object"
    CreateUploadRequest:
      properties:
        content_type:
          description: "MIME type of the file. Must be one of the accepted types."
          enum:
            - "application/pdf"
            - "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
            - "application/msword"
            - "text/plain"
            - "text/markdown"
          type: "string"
        filename:
          description: "Name of the file, used in the storage key."
          type: "string"
        size:
          description: "Size in bytes. Maximum 10,000,000."
          maximum: 10000000
          type: "integer"
      required:
        - "filename"
        - "content_type"
      type: "object"
    DemoCaps:
      description: "Hard limits on a demo session. Exceeding one is a 422, never a partial write."
      properties:
        max_chars:
          description: "Total training characters."
          type: "integer"
        max_files:
          description: "Uploaded files."
          type: "integer"
        max_messages:
          description: "Messages the demo will answer."
          type: "integer"
        max_urls:
          description: "Website URLs."
          type: "integer"
      required:
        - "max_messages"
        - "max_chars"
        - "max_urls"
        - "max_files"
      title: "DemoCaps"
      type: "object"
    DemoFile:
      properties:
        char_count:
          type:
            - "integer"
            - "null"
        file_name:
          type:
            - "string"
            - "null"
        id:
          format: "uuid"
          type: "string"
        status:
          description: "Extraction status of this file."
          type: "string"
        title:
          type:
            - "string"
            - "null"
      required:
        - "id"
        - "status"
      type: "object"
    DemoSession:
      description: "The full live state of an anonymous demo chatbot."
      properties:
        caps:
          "$ref": "#/components/schemas/DemoCaps"
        chars_used:
          description: "Training characters consumed so far."
          type: "integer"
        messages_used:
          description: "Demo messages answered so far."
          type: "integer"
        needs_retrain:
          description: "True when sources changed since the last training run."
          type: "boolean"
        session_id:
          description: "Opaque id that authorizes every other call in this group."
          type: "string"
        slug:
          description: "Slug of the demo chatbot."
          type: "string"
        sources:
          "$ref": "#/components/schemas/DemoSources"
        status:
          description: "Training status. `ready` means the chatbot can answer."
          examples:
            - "queued"
            - "training"
            - "ready"
          type: "string"
      required:
        - "session_id"
        - "slug"
        - "status"
        - "caps"
        - "sources"
      title: "DemoSession"
      type: "object"
    DemoSessionResponse:
      properties:
        demo:
          "$ref": "#/components/schemas/DemoSession"
        ok:
          const: true
          type: "boolean"
      required:
        - "ok"
        - "demo"
      title: "DemoSessionResponse"
      type: "object"
    DemoSources:
      description: "Everything attached to the demo, grouped by source type."
      properties:
        files:
          items:
            "$ref": "#/components/schemas/DemoFile"
          type: "array"
        texts:
          items:
            "$ref": "#/components/schemas/DemoText"
          type: "array"
        urls:
          items:
            "$ref": "#/components/schemas/DemoUrl"
          type: "array"
      required:
        - "urls"
        - "files"
        - "texts"
      title: "DemoSources"
      type: "object"
    DemoText:
      properties:
        char_count:
          type:
            - "integer"
            - "null"
        id:
          format: "uuid"
          type: "string"
        status:
          type: "string"
      required:
        - "id"
        - "status"
      type: "object"
    DemoUrl:
      properties:
        char_count:
          description: "Characters extracted."
          type:
            - "integer"
            - "null"
        domain:
          type:
            - "string"
            - "null"
        id:
          format: "uuid"
          type: "string"
        status:
          description: "Extraction status of this page."
          type: "string"
        url:
          format: "uri"
          type: "string"
      required:
        - "id"
        - "url"
        - "status"
      type: "object"
    Error:
      description: "Every error from every endpoint. `code` is the stable machine key; `message` says what happened; `hint` says what to do about it."
      properties:
        code:
          description: "Stable machine-readable key. Branch on this, never on `message`."
          examples:
            - "expired"
            - "url_cap"
            - "char_cap"
            - "global_cap"
            - "not_found"
            - "rate_limited"
          type: "string"
        documentation_url:
          description: "Where the failure is documented."
          format: "uri"
          type: "string"
        error:
          description: "Deprecated alias of `code`, kept for existing clients."
          type: "string"
        hint:
          description: "What to do next: retry, change an input, or stop."
          type: "string"
        message:
          description: "Human-readable description of the failure."
          type: "string"
        ok:
          const: false
          description: "Always false on an error."
          type: "boolean"
        status:
          description: "The HTTP status of this response."
          type: "integer"
      required:
        - "ok"
        - "code"
        - "status"
        - "message"
        - "hint"
      title: "Error"
      type: "object"
    JsonRpcRequest:
      description: "A JSON-RPC 2.0 message, or an array of them for a batch. Omit `id` to send a notification."
      properties:
        id:
          description: "Correlation id. Omit for a notification, which is answered with 202."
          type:
            - "string"
            - "integer"
        jsonrpc:
          const: "2.0"
          type: "string"
        method:
          description: "MCP method."
          examples:
            - "initialize"
            - "tools/list"
            - "tools/call"
          type: "string"
        params:
          description: "Method parameters."
          type: "object"
      required:
        - "jsonrpc"
        - "method"
      title: "JsonRpcRequest"
      type: "object"
    JsonRpcResponse:
      description: "A JSON-RPC 2.0 response. Exactly one of `result` or `error` is present."
      properties:
        error:
          description: "Present on failure."
          properties:
            code:
              description: "JSON-RPC error code."
              type: "integer"
            data:
              type: "object"
            message:
              type: "string"
          required:
            - "code"
            - "message"
          type: "object"
        id:
          type:
            - "string"
            - "integer"
            - "null"
        jsonrpc:
          const: "2.0"
          type: "string"
        result:
          description: "Present on success."
          type: "object"
      required:
        - "jsonrpc"
      title: "JsonRpcResponse"
      type: "object"
    McpManifest:
      description: "The descriptor served at /.well-known/mcp.json."
      properties:
        authentication:
          type: "object"
        description:
          type: "string"
        name:
          type: "string"
        protocolVersions:
          items:
            type: "string"
          type: "array"
        tools:
          description: "Tool definitions, each with its JSON Schema, as `tools/list` returns."
          items:
            type: "object"
          type: "array"
        transport:
          const: "streamable-http"
          type: "string"
        url:
          description: "Where to POST JSON-RPC messages."
          format: "uri"
          type: "string"
        version:
          type: "string"
      required:
        - "name"
        - "version"
        - "transport"
        - "url"
        - "tools"
      title: "McpManifest"
      type: "object"
    UploadTarget:
      description: "A presigned S3 PUT target. Expires 10 minutes after it is issued."
      properties:
        ok:
          const: true
          type: "boolean"
        s3_key:
          description: "Quote this back to `addDemoSessionFile` once the PUT succeeds."
          type: "string"
        upload_url:
          description: "PUT the file bytes here with the declared Content-Type."
          format: "uri"
          type: "string"
      required:
        - "ok"
        - "upload_url"
        - "s3_key"
      title: "UploadTarget"
      type: "object"
  securitySchemes:
    bearerAuth:
      bearerFormat: "cbg_..."
      description: "A ChatbotGen account API key, generated at https://www.chatbotgen.com/app/settings/account. Only the MCP endpoint accepts one; the demo API is authorized by its session id instead."
      scheme: "bearer"
      type: "http"
externalDocs:
  description: "ChatbotGen developer resources"
  url: "https://www.chatbotgen.com/developers"
info:
  contact:
    email: "support@chatbotgen.com"
    name: "ChatbotGen support"
    url: "https://www.chatbotgen.com/developers"
  description: "ChatbotGen builds AI chatbots trained on your own content and deploys\nthem to a website widget, WhatsApp, and Telegram.\n\nTwo machine surfaces are public.\n\n**The MCP server** (`POST /mcp`) is the one most agents want. It speaks\nJSON-RPC 2.0 over the Model Context Protocol Streamable HTTP transport\nand exposes chatbot creation and training as tools. Anonymously it\nbuilds a demo chatbot from a website URL and hands back a chat link, an\nembed snippet, and a claim link a human opens to keep it. With an\naccount API key the same tools operate on that account's real chatbots.\nIts tool list is published at `/.well-known/mcp.json`.\n\n**The demo-tool API** (`/api/tools/*`) backs the free, no-signup tools\nat https://www.chatbotgen.com/tools. It needs no key: a session is created lazily and every\nlater call is authorized by the opaque `session_id` in the path.\n\nErrors are always JSON with a stable `code`, a human-readable `message`,\nand a `hint` describing the recovery. See the `Error` schema.\n\nPages on this site also answer `Accept: text/markdown` with a markdown\nrepresentation, and every documentation page answers on a `.md` URL.\n"
  license:
    name: "ChatbotGen Terms of Service"
    url: "https://www.chatbotgen.com/terms"
  summary: "The public HTTP API and MCP server for ChatbotGen."
  termsOfService: "https://www.chatbotgen.com/terms"
  title: "ChatbotGen API"
  version: "1.0.0"
openapi: "3.1.0"
paths:
  "/.well-known/mcp.json":
    get:
      description: "Name, version, transport, endpoint, authentication, and the full\nanonymous tool list with each tool's JSON Schema. Lets a client decide\nwhether to connect before it opens a session.\n"
      operationId: "getMcpManifest"
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/McpManifest"
          description: "The MCP server descriptor."
      summary: "Descriptor for the ChatbotGen MCP server"
      tags:
        - "Discovery"
  "/api/tools/sessions":
    post:
      description: "Provisions an ephemeral demo chatbot and returns its state, including the\n`session_id` every other call in this group needs. No account and no key.\n\nCall this once and reuse the `session_id`; there is a global cap on\nconcurrent demos, and `503 global_cap` means the free demo is saturated\nrather than that anything is broken — retry in a few minutes, or point\nthe person at https://www.chatbotgen.com/app/register.\n\nRate limit: 30 requests per minute per IP address.\n"
      operationId: "createDemoSession"
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The new demo session."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
        "503":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`global_cap` — the demo pool is full right now."
      summary: "Create an anonymous demo session"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}":
    get:
      description: "Returns sources, character and message usage against the caps, and the\ntraining status. Poll this while training runs: `status` moves to\n`ready` when the chatbot can answer.\n\nA demo that has expired answers `404 expired`; create a new session\nrather than retrying this one.\n"
      operationId: "getDemoSession"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The demo session's current state."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
      summary: "Read a demo session's live state"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}/files":
    post:
      description: "Step two of the file flow. Registers a file already uploaded with the\npresigned URL from `createDemoSessionUpload`. Text extraction runs in the\nbackground; poll `getDemoSession` and watch the file's `status`.\n"
      operationId: "addDemoSessionFile"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      requestBody:
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddFileRequest"
        required: true
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The demo session, including the new file."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "422":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "A cap was reached or the input was unusable — see `code` for which: `url_cap`, `file_cap`, `char_cap`, `empty`."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
        "503":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`global_cap` — the demo pool is full right now."
      summary: "Attach an uploaded file to a demo session"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}/texts":
    post:
      description: "Attaches free text — a policy, opening hours, a product description — to\nthe demo. Counts against the same 50000-character budget\nas crawled pages and uploaded files.\n"
      operationId: "addDemoSessionText"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      requestBody:
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddTextRequest"
        required: true
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The demo session, including the new text source."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "422":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "A cap was reached or the input was unusable — see `code` for which: `url_cap`, `file_cap`, `char_cap`, `empty`."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
        "503":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`global_cap` — the demo pool is full right now."
      summary: "Train a demo session on a block of text"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}/training":
    post:
      description: "Chunks and embeds everything attached to the demo. Training is\nasynchronous: this returns immediately and `status` moves to `ready`\nwhen it finishes, so poll `getDemoSession`.\n\n`422 empty` means no source produced readable text — a URL that returned\nno body, or a PDF with no text layer. Add a different source rather than\nretrying.\n"
      operationId: "trainDemoSession"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The demo session, now training."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "422":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`empty` — there is nothing trainable in this session yet."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
      summary: "Embed a demo session's sources so the chatbot can answer"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}/uploads":
    post:
      description: "Step one of the two-step file flow. Returns a short-lived (10 minute) S3\n`PUT` URL and the `s3_key` to quote afterwards. `PUT` the file bytes to\n`upload_url` with the same `Content-Type` you declared here, then call\n`addDemoSessionFile` with the returned `s3_key`.\n\nAccepted types: PDF, DOCX, DOC, plain text, Markdown. Maximum 10 MB, and\nat most 3 files per demo.\n"
      operationId: "createDemoSessionUpload"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      requestBody:
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CreateUploadRequest"
        required: true
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/UploadTarget"
          description: "The presigned upload target."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "422":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`file_cap`, `file_type` or `file_size` — see `code` for which constraint failed."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
        "503":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`global_cap` — the demo pool is full right now."
      summary: "Get a presigned URL for a file upload"
      tags:
        - "Demo tools"
  "/api/tools/sessions/{session_id}/urls":
    post:
      description: "Fetches the page, extracts its text, and attaches it to the demo. The\ncontent is not embedded until `trainDemoSession` runs.\n\nA demo accepts at most 3 URLs and\n50000 characters in total; past either you get\n`422 url_cap` or `422 char_cap`. Both are permanent for this session —\nremove a source or start a new one.\n"
      operationId: "addDemoSessionUrl"
      parameters:
        -
          description: "The opaque session id returned by `createDemoSession`."
          in: "path"
          name: "session_id"
          required: true
          schema:
            type: "string"
      requestBody:
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/AddUrlRequest"
        required: true
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DemoSessionResponse"
          description: "The demo session, including the new URL."
        "404":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`expired` — no live demo has that session id."
        "422":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "A cap was reached or the input was unusable — see `code` for which: `url_cap`, `file_cap`, `char_cap`, `empty`."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded."
        "503":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "`global_cap` — the demo pool is full right now."
      summary: "Train a demo session on a web page"
      tags:
        - "Demo tools"
  "/docs/{category}/{slug}.md":
    get:
      description: "Append `.md` to any documentation URL to get its markdown source with the\nfrontmatter stripped and no HTML shell around it. Requires no `Accept`\nheader. The page list lives in `/llms.txt`.\n\nSpanish pages fall back to English when no translation exists.\n"
      operationId: "getDocPageAsMarkdown"
      parameters:
        -
          description: "Documentation category slug, e.g. `getting-started`."
          in: "path"
          name: "category"
          required: true
          schema:
            type: "string"
        -
          description: "Page slug, e.g. `welcome`."
          in: "path"
          name: "slug"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          content:
            "text/plain":
              schema:
                type: "string"
          description: "The markdown source of the page."
        "404":
          description: "No documentation page has that category and slug."
      summary: "Raw markdown source of one documentation page"
      tags:
        - "Discovery"
  "/llms-full.txt":
    get:
      description: "The whole product documentation set concatenated, for a client that would rather make one request than crawl the docs tree."
      operationId: "getLlmsFullTxt"
      responses:
        "200":
          content:
            "text/plain":
              schema:
                type: "string"
          description: "The full documentation corpus."
      summary: "Every documentation page inlined as one file"
      tags:
        - "Discovery"
  "/llms.txt":
    get:
      description: "A curated map of chatbotgen.com for language models: one line per page with a written description, and markdown URLs for the documentation."
      operationId: "getLlmsTxt"
      responses:
        "200":
          content:
            "text/plain":
              schema:
                type: "string"
          description: "The llms.txt index."
      summary: "llmstxt.org index of the site"
      tags:
        - "Discovery"
  "/mcp":
    get:
      description: "`GET /mcp` means \"open a server-initiated SSE stream\" in the Streamable\nHTTP transport. This server does not offer one, so it answers `405` with\n`Allow: POST` rather than a `404` that would read like a wrong URL.\n"
      operationId: "describeMcpTransport"
      responses:
        "405":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/JsonRpcResponse"
          description: "Always. Use POST."
      summary: "Reports that this MCP endpoint is POST-only"
      tags:
        - "MCP"
    post:
      description: "The Model Context Protocol Streamable HTTP transport. Send `initialize`,\nthen `tools/list`, then `tools/call`. Supported protocol versions:\n2025-06-18, 2025-03-26, 2024-11-05.\n\nA request (a message with an `id`) is answered with a single JSON body —\nthis server never opens an SSE stream, so `application/json` is always\nthe response media type. A notification (no `id`) is answered with\n`202 Accepted` and an empty body, as the transport requires. A JSON-RPC\nbatch is a top-level array and is answered with an array.\n\nAnonymous callers see `create_chatbot`, `get_chatbot`,\n`add_knowledge_url`, `add_knowledge_text` and `add_qa`, operating on\nsandbox chatbots: 5 pages crawled,\n10 messages answered, 50000\ncharacters of training content, deleted after 8\ndays unless a human opens the returned `claim_url`.\n\nSend `Authorization: Bearer cbg_...` to operate on a real account's\nchatbots under its plan limits; `list_chatbots` is then also available.\nAn invalid key is rejected at the transport layer with `401` and a\n`WWW-Authenticate` header, not as a JSON-RPC error.\n\nRate limit: 60 requests per minute per IP address, with a tighter per-tool budget on\n`create_chatbot` because it triggers a live crawl.\n"
      operationId: "callMcpServer"
      requestBody:
        content:
          "application/json":
            examples:
              createChatbot:
                summary: "Build a demo chatbot for a website"
                value:
                  id: 2
                  jsonrpc: "2.0"
                  method: "tools/call"
                  params:
                    arguments:
                      language: "en"
                      website_url: "https://example.com"
                    name: "create_chatbot"
              initialize:
                summary: "Handshake"
                value:
                  id: 1
                  jsonrpc: "2.0"
                  method: "initialize"
                  params:
                    capabilities: {}
                    clientInfo:
                      name: "example-client"
                      version: "1.0.0"
                    protocolVersion: "2025-06-18"
            schema:
              "$ref": "#/components/schemas/JsonRpcRequest"
        required: true
      responses:
        "200":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/JsonRpcResponse"
          description: "The JSON-RPC result or error for the request."
        "202":
          description: "The message was a notification; there is no body to return."
        "401":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/JsonRpcResponse"
          description: "The `Authorization` header carried a key we do not recognise."
        "429":
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
          description: "Rate limit exceeded. Wait for the window to roll over."
      security:
        - {}
        -
          bearerAuth: []
      summary: "Send a JSON-RPC 2.0 message to the ChatbotGen MCP server"
      tags:
        - "MCP"
  "/openapi.json":
    get:
      description: "The OpenAPI 3.1 description of the ChatbotGen API. `/openapi.yaml` serves the same document YAML-encoded."
      operationId: "getOpenApiDocument"
      responses:
        "200":
          content:
            "application/json":
              schema:
                type: "object"
          description: "The OpenAPI document."
      summary: "This document"
      tags:
        - "Discovery"
  "/sitemap.xml":
    get:
      description: "Standard sitemaps.org XML, including hreflang alternates for the bilingual pages."
      operationId: "getSitemap"
      responses:
        "200":
          content:
            "application/xml":
              schema:
                type: "string"
          description: "The sitemap."
      summary: "XML sitemap of every indexable URL"
      tags:
        - "Discovery"
  "/v2/health":
    get:
      description: "Returns `ok` when the application is running and its database answers. Used by the deployment proxy; safe to poll."
      operationId: "getHealth"
      responses:
        "200":
          content:
            "text/plain":
              schema:
                type: "string"
          description: "`ok`."
        "503":
          content:
            "text/plain":
              schema:
                type: "string"
          description: "`db unreachable` — the app is up but its database is not."
      summary: "Liveness and database reachability"
      tags:
        - "Operations"
security:
  - {}
servers:
  -
    description: "Production"
    url: "https://www.chatbotgen.com"
tags:
  -
    description: "Model Context Protocol server. Lets an AI assistant create and train chatbots directly."
    externalDocs:
      description: "MCP details"
      url: "https://www.chatbotgen.com/ai-info#mcp"
    name: "MCP"
  -
    description: "The free \"Chat With Your Content\" demo API. No account, no key; a session id authorizes each call."
    name: "Demo tools"
  -
    description: "Machine-readable descriptions of this site: OpenAPI, the MCP manifest, llms.txt, the sitemap."
    name: "Discovery"
  -
    description: "Service health."
    name: "Operations"
webhooks:
  stripe:
    post:
      description: "Subscription lifecycle and checkout completion, verified by HMAC signature. Documented so it is not mistaken for a callable endpoint."
      operationId: "receiveStripeWebhook"
      responses:
        "200":
          description: "Event accepted."
      summary: "Stripe billing events (received at /webhooks/stripe)"
  telegram:
    post:
      description: "Inbound Telegram messages, authorized by the unguessable uuid in the path."
      operationId: "receiveTelegramWebhook"
      responses:
        "200":
          description: "Update accepted."
      summary: "Telegram bot updates (received at /webhooks/telegram/{uuid})"
  whatsapp:
    post:
      description: "QR, pairing, connected, disconnected and inbound message events from the WhatsApp service, signature-verified in production."
      operationId: "receiveWhatsappWebhook"
      responses:
        "200":
          description: "Event accepted."
      summary: "WhatsApp session and message events (received at /webhooks/whatsapp/{chatbot_id})"
