openapi: 3.0.3
info:
  title: Biel.ai API
  version: 2.0.0
  description: |

    Biel.ai is an AI-powered documentation assistant. It indexes your technical documentation and provides AI chat and search capabilities through widgets, MCP servers, and this REST API.

    **Base URL:** `https://app.biel.ai`

    > The API is available on **Business** and **Enterprise** plans. [Contact us](https://biel.ai/contact) to upgrade.

    ## Versioning

    This document describes **API v2** (`/api/v2/`). If you have an existing v1 integration, see the [migration guide](/api/migrate-from-v1). v1 is scheduled for retirement in **January 2027**.

    ## Authorization & permissions

    Biel.ai uses API key authentication with granular permissions.

    - **Public projects**: Read operations don't require authentication. Write operations require an API key.
    - **Private projects**: All operations require an API key.

    > Projects are public by default. Configure this in **Project settings**.

    ### Available permissions

    | Permission | Description | Required For |
    |------------|-------------|--------------|
    | `projects_read` | Read project details | Getting project information, listing projects |
    | `projects_create` | Create projects | Creating new projects |
    | `projects_update` | Update project settings | Updating project configuration |
    | `projects_delete` | Delete projects | Removing projects |
    | `search` | Search documents within projects | Searching project content, tracking search clicks, submitting message feedback |
    | `sources_read` | View project sources | Listing project sources, getting source detail |
    | `sources_create` | Upload sources | Adding files, URLs, sitemaps, repositories, OpenAPI specs, and Confluence spaces to projects |
    | `sources_delete` | Delete sources | Removing sources from projects |
    | `sync_create` | Trigger a project sync | Re-crawling project sources |
    | `sync_read` | Read sync status | Polling sync runs |
    | `analytics_read` | Read project analytics | Reading chat, search, MCP, and insights analytics |
    | `chats_create` | Create chats and send messages | Interacting with the AI chat |
    | `chats_read` | Read conversations | Listing chats, retrieving a conversation |
    | `chats_delete` | Delete conversations | Deleting a conversation |
    | `teams_update` | Update team settings | Renaming a team, changing its slug, managing members |

    > Keys created before v2 may carry legacy permission names (`project_read`, `project_search`, `project_sources_*`, `project_sync`, `chat_create`). They remain valid as aliases of their v2 equivalents, so you don't need to re-issue keys.

    ### Create an API key

    1. In [app.biel.ai](https://app.biel.ai), go to **Team** > **API Keys**.
    2. Click **Create API Key**, enter a name, and select permissions.
    3. Click **Create API Key** and copy the key. You won't be able to view it again.

    ### Using your API key

    Include the key in the `Authorization` header:
    ```
    Authorization: Api-Key <your-api-key>
    ```

    > Read-only keys (`projects_read`, `search`) can be exposed client-side. Keys with write permissions should always be stored server-side.
servers:
  - url: https://app.biel.ai
    description: Production
tags:
  - name: Chat
    description: Send messages and retrieve conversations.
  - name: Projects
    description: Manage projects and search indexed content.
  - name: Sources
    description: Manage project sources (files, URLs, sitemaps, sites, repositories, OpenAPI specs, Confluence spaces).
  - name: Sync
    description: Trigger source re-crawls and track their progress.
  - name: Analytics
    description: Read aggregate metrics for chat, search, MCP usage, and insights.
  - name: Teams
    description: Manage the team that owns the API key.
paths:
  /api/v2/projects/:
    get:
      operationId: listProjects
      summary: List projects
      description: |
        List projects accessible to the API key. Results are paginated.

        **Required permission:** `projects_read`
      parameters:
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      tags:
        - Projects
      security:
        - ApiKeyAuth:
            - projects_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectList'
          description: Projects listed successfully
        '403':
          $ref: '#/components/responses/Forbidden'
    post:
      operationId: createProject
      summary: Create a project
      description: |
        Create a new project. The slug is auto-generated. Configure the project with [`PATCH /api/v2/projects/{slug}/`](/api/update-project) after creation.

        **Required permission:** `projects_create`
      tags:
        - Projects
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectInput'
      security:
        - ApiKeyAuth:
            - projects_create
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
          description: Project created successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v2/projects/{slug}/:
    get:
      operationId: getProject
      summary: Get project details
      description: |
        Retrieve project settings, filters, and metadata.

        **Required permission:** `projects_read` (private projects only).
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Projects
      security:
        - ApiKeyAuth:
            - projects_read
        - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
          description: Project details retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateProject
      summary: Update a project
      description: |
        Update one or more project settings. Only the supplied fields are modified. Feature configuration is grouped under `config` (for example, `config.mcp.enabled`).

        **Required permission:** `projects_update`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Projects
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectUpdate'
      security:
        - ApiKeyAuth:
            - projects_update
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
          description: Project updated successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteProject
      summary: Delete a project
      description: |
        Permanently delete a project, all its sources, indexed content, and chat history.

        **Required permission:** `projects_delete`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Projects
      security:
        - ApiKeyAuth:
            - projects_delete
      responses:
        '204':
          description: Project deleted successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/search/:
    get:
      operationId: searchProject
      summary: Search project content
      description: |
        Semantic search across indexed project content. Returns ranked results with snippets.

        **Required permission:** `search` (private projects only).
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: query
          name: q
          schema:
            type: string
          required: true
          description: Search query
        - in: query
          name: url
          schema:
            type: string
          description: URL from where the search was initiated.
        - in: query
          name: filter_title
          schema:
            type: string
          required: false
          description: Optional filter to restrict search results to documents matching this filter title.
      tags:
        - Projects
      security:
        - ApiKeyAuth:
            - search
        - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/SearchResult'
          description: Search results
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/search/click/:
    post:
      operationId: trackSearchClick
      summary: Track a search result click
      description: |
        Register a click on a search result for analytics. Call this after the user clicks on a result returned by [`GET /api/v2/projects/{slug}/search/`](/api/search-project).

        **Required permission:** `search` (private projects only).
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Projects
      security:
        - ApiKeyAuth:
            - search
        - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
          description: Click registered successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/chats/:
    get:
      operationId: listChats
      summary: List chats
      description: |
        List chats for a project. Results are paginated and ordered by `created_at`.

        **Required permission:** `chats_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
        - in: query
          name: order
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
          required: false
          description: Sort direction for `created_at`. Defaults to newest first.
        - in: query
          name: from
          schema:
            type: string
            format: date
          required: false
          description: Only include chats created on or after this date (ISO-8601, `YYYY-MM-DD`).
        - in: query
          name: to
          schema:
            type: string
            format: date
          required: false
          description: Only include chats created on or before this date (ISO-8601, `YYYY-MM-DD`).
        - in: query
          name: metadata
          schema:
            type: string
          required: false
          description: Filter by the `metadata` tag set in [`POST /api/v2/projects/{slug}/chats/`](/api/send-message) (for example, `web`, `mcp`, `slack`).
      tags:
        - Chat
      security:
        - ApiKeyAuth:
            - chats_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatList'
          description: Chats listed successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: sendMessage
      summary: Send a message
      description: |
        Send a message and get an AI response. Returns 201 for new chats, 200 for existing ones.

        **Required permission:** `chats_create`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Chat
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendMessageInput'
      security:
        - ApiKeyAuth:
            - chats_create
        - {}
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMessageResponse'
          description: Message sent successfully
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SendMessageResponse'
          description: New chat created successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/chats/{chat_uuid}/:
    get:
      operationId: getChat
      summary: Get a chat
      description: |
        Retrieve a single conversation, including all of its messages ordered chronologically.

        **Required permission:** `chats_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: chat_uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the chat to retrieve.
      tags:
        - Chat
      security:
        - ApiKeyAuth:
            - chats_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Chat'
          description: Chat retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteChat
      summary: Delete a chat
      description: |
        Delete a conversation.

        **Required permission:** `chats_delete`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: chat_uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the chat to delete.
      tags:
        - Chat
      security:
        - ApiKeyAuth:
            - chats_delete
      responses:
        '204':
          description: Chat deleted successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/messages/{uuid}/feedback/:
    post:
      operationId: submitMessageFeedback
      summary: Submit feedback for a message
      description: |
        Record a like (`1`) or dislike (`0`) reaction on an AI-generated message.

        **Auth:**
        - Public projects: no API key required (the widget calls this endpoint client-side).
        - Private projects: API key with `search` permission.
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the AI message to attach the feedback to.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageFeedback'
        required: true
      tags:
        - Chat
      security:
        - {}
        - ApiKeyAuth:
            - search
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Feedback recorded successfully
          description: Feedback recorded successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/sources/:
    get:
      operationId: listSources
      summary: List sources
      description: |
        List all sources associated with a project. Results are paginated.

        **Required permission:** `sources_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/Limit'
        - $ref: '#/components/parameters/Offset'
      tags:
        - Sources
      security:
        - ApiKeyAuth:
            - sources_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SourceList'
          description: Sources listed successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: addSource
      summary: Add source
      description: |
        Add sources to a project. Supports files, URLs, sitemaps, hosted sites (ZIP), GitHub repositories, OpenAPI specifications, and Confluence spaces. All sources are indexed for search and chat.

        Use `multipart/form-data` when uploading binary content (files or site ZIPs). Use `application/json` for everything else, including private GitHub repositories and private Confluence spaces that need credentials.

        **Required permission:** `sources_create`

        | Source type | Limits / notes |
        |-------------|----------------|
        | Files (PDF, MD, TXT, DOCX, XLSX, CSV, YAML, JSON, RST, HTML, CSS, JS, XML) | 10 MB per file. `multipart/form-data` only |
        | URLs | Must be publicly accessible |
        | Sitemaps | Must end in `.xml` |
        | Hosted sites (ZIP) | 250 MB per ZIP. `multipart/form-data` only. `site_name` is required |
        | GitHub repositories | Public repos work via either content type. Private repos require a personal access token (JSON only) |
        | OpenAPI specifications | JSON or YAML URL |
        | Confluence spaces | Public spaces work via either content type. Private spaces require username + API token (JSON only) |

        If a value is wrong, such as a sitemap URL that doesn't end in `.xml`, the request returns `400` with the errors listed by field name. If the values look right but the source still can't be added, such as an unsupported file type or a corrupt ZIP, the request returns `422` with a single `error` message explaining why.
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Sources
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                files:
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Files to upload (PDF, MD, TXT, DOCX, XLSX, CSV, YAML, JSON, RST, HTML, CSS, JS, XML)
                urls:
                  type: array
                  items:
                    type: string
                    format: uri
                  description: URLs to crawl and index
                sitemaps:
                  type: array
                  items:
                    type: string
                    format: uri
                  description: Sitemap URLs to crawl
                repositories:
                  type: array
                  items:
                    type: string
                    format: uri
                  description: Public GitHub repository URLs to ingest. For private repositories use `application/json` to provide a token.
                openapis:
                  type: array
                  items:
                    type: string
                    format: uri
                  description: OpenAPI specification URLs (JSON or YAML).
                confluences:
                  type: array
                  items:
                    type: string
                    format: uri
                  description: Confluence space URLs (e.g. `https://yourcompany.atlassian.net/wiki/spaces/DOCS`). For private spaces use `application/json` to provide credentials.
                site_zip:
                  type: string
                  format: binary
                  description: ZIP file containing a complete website to host and index
                site_name:
                  type: string
                  description: Name for the hosted site (required when uploading site_zip)
                base_url:
                  type: string
                  format: uri
                  description: Original base URL of the site (for example, `https://docs.example.com`). When you omit this field, source page URLs default to a placeholder pattern. Provide this value to ensure indexed pages link back to the correct production URLs.
          application/json:
            schema:
              $ref: '#/components/schemas/AddSourceJson'
      security:
        - ApiKeyAuth:
            - sources_create
      responses:
        '201':
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  sources:
                    type: array
                    items:
                      $ref: '#/components/schemas/Source'
                    description: Sources created by this request. Use the `uuid` to poll [`GET /api/v2/projects/{slug}/sources/{source_uuid}/`](/api/get-source) for ingestion status.
          description: Sources created successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/Unprocessable'
  /api/v2/projects/{slug}/sources/{source_uuid}/:
    get:
      operationId: getSource
      summary: Get source detail
      description: |
        Retrieve detail for a single source.

        **Required permission:** `sources_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: source_uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the source to retrieve
      tags:
        - Sources
      security:
        - ApiKeyAuth:
            - sources_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Source'
          description: Source detail retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteSource
      summary: Delete source
      description: |
        Remove a source and its indexed content from a project.

        **Required permission:** `sources_delete`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: source_uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the source to delete
      tags:
        - Sources
      security:
        - ApiKeyAuth:
            - sources_delete
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
          description: Source deleted successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/sync/:
    get:
      operationId: getSyncStatus
      summary: Get latest sync status
      description: |
        Inspect the most recent sync run for the project, including overall status, the URLs that were processed, and the event log for the run.

        Use this to:
        - Detect when an automated [`POST /api/v2/projects/{slug}/sources/`](/api/add-source) followed by [`POST /api/v2/projects/{slug}/sync/`](/api/sync-project) flow has finished (`status` transitions from `running` to `success`/`error`/`warning`).
        - Surface failures with actionable context. `events` includes per-URL warnings and errors.

        For lightweight polling, prefer [`GET /api/v2/projects/{slug}/`](/api/get-project) and read its `last_sync` summary instead. It returns the same `status` and timestamps without the full event log.

        Returns 404 when the project has never been synced.

        **Required permission:** `sync_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Sync
      security:
        - ApiKeyAuth:
            - sync_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncStatus'
          description: Sync status retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: syncProject
      summary: Start a sync
      description: |
        Re-crawl all project sources and update the search index. Returns immediately; the crawl runs in the background. Poll [`GET /api/v2/projects/{slug}/sync/`](/api/get-sync-status), or the `last_sync` field from [`GET /api/v2/projects/{slug}/`](/api/get-project), to track completion.

        Calling this endpoint while a sync is already running cancels the in-progress run and starts a fresh one.

        Rate limits may apply to this endpoint.

        **Required permission:** `sync_create`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
      tags:
        - Sync
      security:
        - ApiKeyAuth:
            - sync_create
      responses:
        '202':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncSummary'
          description: Sync started. Poll the returned `uuid` via [`GET /api/v2/projects/{slug}/sync/{sync_uuid}/`](/api/get-sync-run).
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/sync/{sync_uuid}/:
    get:
      operationId: getSyncRun
      summary: Get a sync run
      description: |
        Retrieve a specific sync run by UUID. Returns the same shape as [`GET /api/v2/projects/{slug}/sync/`](/api/get-sync-status), but addresses a single run instead of the latest one. Use the `uuid` returned by [`POST /api/v2/projects/{slug}/sync/`](/api/sync-project).

        **Required permission:** `sync_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - in: path
          name: sync_uuid
          schema:
            type: string
            format: uuid
          required: true
          description: UUID of the sync run to retrieve.
      tags:
        - Sync
      security:
        - ApiKeyAuth:
            - sync_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncStatus'
          description: Sync run retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/analytics/chat/:
    get:
      operationId: getChatAnalytics
      summary: Get chat analytics
      description: |
        Aggregate KPIs and daily activity for the AI chat over a date range. MCP-only conversations are excluded. For those, see [`GET /api/v2/projects/{slug}/analytics/mcp/`](/api/get-mcp-analytics).

        **Required permission:** `analytics_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/FromDate'
        - $ref: '#/components/parameters/ToDate'
      tags:
        - Analytics
      security:
        - ApiKeyAuth:
            - analytics_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatAnalytics'
          description: Chat analytics retrieved successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/analytics/search/:
    get:
      operationId: getSearchAnalytics
      summary: Get search analytics
      description: |
        Aggregate KPIs and daily activity for the search experience over a date range. Counts come from [`GET /api/v2/projects/{slug}/search/`](/api/search-project) calls and [`POST /api/v2/projects/{slug}/search/click/`](/api/track-search-click) events.

        **Required permission:** `analytics_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/FromDate'
        - $ref: '#/components/parameters/ToDate'
      tags:
        - Analytics
      security:
        - ApiKeyAuth:
            - analytics_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchAnalytics'
          description: Search analytics retrieved successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/analytics/mcp/:
    get:
      operationId: getMcpAnalytics
      summary: Get MCP analytics
      description: |
        Aggregate KPIs and daily activity for conversations that originated from the MCP server.

        **Required permission:** `analytics_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/FromDate'
        - $ref: '#/components/parameters/ToDate'
      tags:
        - Analytics
      security:
        - ApiKeyAuth:
            - analytics_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/McpAnalytics'
          description: MCP analytics retrieved successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/projects/{slug}/analytics/insights/:
    get:
      operationId: getInsightsAnalytics
      summary: Get advanced analytics insights
      description: |
        Surface conversation-driven insights for the project: top recurring questions (`faqs`) and content gaps where users asked something the assistant could not answer (`content_gaps`). Each item includes the related chats so you can drill into the original conversations.

        **Plan-gated:** requires a plan that includes advanced analytics. When your team does not have access, the endpoint returns 200 with `{ "has_access": false }` and no payload. Treat that as a successful response rather than an error, and prompt the user to upgrade.

        **Required permission:** `analytics_read`
      parameters:
        - $ref: '#/components/parameters/ProjectSlug'
        - $ref: '#/components/parameters/FromDate'
        - $ref: '#/components/parameters/ToDate'
      tags:
        - Analytics
      security:
        - ApiKeyAuth:
            - analytics_read
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsightsAnalytics'
          description: 'Insights retrieved successfully (or `has_access: false` if the team is on a plan without advanced analytics)'
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/teams/:
    get:
      operationId: listTeams
      summary: List teams accessible to the API key
      description: |
        Return the team this API key belongs to. Because every API key is scoped to a single team, the returned array always contains exactly one entry.

        Any valid API key authenticates this call, no specific scope is required.
      tags:
        - Teams
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Team'
          description: Teams listed successfully
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v2/teams/{slug}/:
    get:
      operationId: getTeam
      summary: Get a team
      description: |
        Retrieve the team identified by `slug`. The `slug` must match the team that owns the authenticating API key. Requesting any other team returns 404.

        Any valid API key authenticates this call, no specific scope is required.
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
      tags:
        - Teams
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Team'
          description: Team retrieved successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: updateTeam
      summary: Update team settings
      description: |
        Update the team's `name` and/or `slug`. The `slug` in the path must match the team that owns the authenticating API key. Include only the fields you want to change.

        **Required permission:** `teams_update`
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TeamUpdate'
        required: true
      tags:
        - Teams
      security:
        - ApiKeyAuth:
            - teams_update
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Team'
          description: Team updated successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/teams/{slug}/members/:
    get:
      operationId: listTeamMembers
      summary: List team members
      description: |
        List the active members of the team. The `slug` must match the team that owns the authenticating API key.

        Any valid API key authenticates this call, no specific scope is required.
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
      tags:
        - Teams
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamMemberList'
          description: Members listed successfully
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    post:
      operationId: inviteTeamMember
      summary: Invite a team member
      description: |
        Send an email invitation to join the team with the given role. The invitee becomes a member once they accept.

        **Required permission:** `teams_update`
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TeamMemberInvite'
      tags:
        - Teams
      security:
        - ApiKeyAuth:
            - teams_update
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamInvitation'
          description: Invitation sent successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/teams/{slug}/members/{member_id}/:
    patch:
      operationId: updateTeamMember
      summary: Update a team member
      description: |
        Change a member's role. The `id` is the membership id returned by [`GET /api/v2/teams/{slug}/members/`](/api/list-team-members).

        **Required permission:** `teams_update`
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
        - in: path
          name: member_id
          schema:
            type: integer
          required: true
          description: Membership id of the member to update.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TeamMemberUpdate'
      tags:
        - Teams
      security:
        - ApiKeyAuth:
            - teams_update
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TeamMember'
          description: Member updated successfully
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: removeTeamMember
      summary: Remove a team member
      description: |
        Remove a member from the team. The `id` is the membership id returned by [`GET /api/v2/teams/{slug}/members/`](/api/list-team-members). The team's last admin cannot be removed.

        **Required permission:** `teams_update`
      parameters:
        - $ref: '#/components/parameters/TeamSlug'
        - in: path
          name: member_id
          schema:
            type: integer
          required: true
          description: Membership id of the member to remove.
      tags:
        - Teams
      security:
        - ApiKeyAuth:
            - teams_update
      responses:
        '204':
          description: Member removed successfully
        '400':
          $ref: '#/components/responses/BadRequest'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v2/openapi/validate/:
    post:
      operationId: validateOpenApi
      summary: Validate an OpenAPI specification
      description: |
        Validate an OpenAPI specification against the same checks Biel runs when it indexes your spec, including `$ref` resolution. A `200` means the next sync will parse the spec; a `422` returns the validation errors it would fail with.

        Designed for CI/CD: publish your spec, call this endpoint, and fail the pipeline on a non-2xx status. This catches changes that would break API indexing before Biel's next sync does.

        Submit the spec either by `url` (Biel fetches it and resolves relative `$ref`s against it) or inline as `content` (JSON or YAML).

        **Required permission:** none. Any valid API key works.
      tags:
        - Sources
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OpenApiValidationInput'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenApiValidationSuccess'
          description: The specification is valid. A sync will parse it.
        '400':
          $ref: '#/components/responses/ValidationError'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OpenApiValidationFailure'
          description: The specification failed to fetch, parse, or validate.
components:
  schemas:
    Error:
      type: object
      description: Returned when the request fails as a whole, for example when the API key lacks a permission or the project does not exist. When individual fields are invalid, the response lists them by field name instead.
      properties:
        error:
          type: string
          description: Human-readable description of what went wrong.
      required:
        - error
    FieldErrors:
      type: object
      description: |
        Validation errors, keyed by the name of the field that failed. Each field maps to a list of messages.

        Nested settings are keyed the same way they are sent, so an invalid value in `config.search` is reported under `config`, then `search`. Errors that don't belong to a single field appear under `non_field_errors`.
      additionalProperties:
        oneOf:
          - type: array
            items:
              type: string
          - $ref: '#/components/schemas/FieldErrors'
    Filter:
      type: object
      description: A search filter configured for the project.
      properties:
        title:
          type: string
          description: Display title of the filter.
      required:
        - title
    ChatSettings:
      type: object
      description: Chat-related project configuration.
      properties:
        initial_message:
          type: string
          description: Greeting message shown when users open the chat.
        suggested_questions:
          type: array
          items:
            type: string
          readOnly: true
          description: Pre-configured suggested questions shown to users when they open the chat.
        think_mode:
          type: boolean
          readOnly: true
          description: Whether reasoning mode is enabled.
        actions:
          type: array
          items:
            $ref: '#/components/schemas/Action'
          readOnly: true
          description: Call-to-action buttons configured for the project, rendered by the chat widget.
    SearchSettings:
      type: object
      description: Search-related project configuration.
      properties:
        filters:
          type: array
          items:
            $ref: '#/components/schemas/Filter'
          readOnly: true
          description: Search filters configured for the project.
    MCPSettings:
      type: object
      description: MCP server configuration.
      properties:
        enabled:
          type: boolean
          description: Whether MCP server integration is enabled.
        server_name:
          type: string
          description: Display name of the MCP server. Defaults to the project name.
        metadata_tag:
          type: string
          description: Tag used to identify MCP-originated conversations in analytics.
    ProjectConfig:
      type: object
      description: |
        Project configuration, grouped by feature. Every group is optional: send only the groups and fields you want to change, and everything else keeps its current value.

        Read-only fields are returned in responses but rejected in requests. Sending one back, or misspelling a field name, returns a validation error naming the field, so a write that would not have taken effect fails loudly instead of appearing to succeed.
      properties:
        chat:
          $ref: '#/components/schemas/ChatSettings'
        search:
          $ref: '#/components/schemas/SearchSettings'
        mcp:
          $ref: '#/components/schemas/MCPSettings'
        prompt:
          $ref: '#/components/schemas/PromptSettings'
    Project:
      type: object
      properties:
        slug:
          type: string
          readOnly: true
          description: URL-safe project identifier. Auto-generated on creation and immutable thereafter.
        uuid:
          type: string
          format: uuid
          readOnly: true
          description: |
            Public project identifier used by the embeddable widget's `project` attribute. Returned by [`POST /api/v2/projects/`](/api/create-project), so an integration can embed the widget as soon as it creates a project.
        name:
          type: string
          description: Human-readable project name.
        is_private:
          type: boolean
          description: Whether the project is private. Private projects require an API key for read operations.
        created_at:
          type: string
          format: date-time
          readOnly: true
          description: ISO-8601 timestamp of project creation.
        updated_at:
          type: string
          format: date-time
          readOnly: true
          description: ISO-8601 timestamp of last project update.
        whitelabel:
          type: boolean
          readOnly: true
          description: Whether the 'Powered by Biel.ai' branding is hidden. Requires a Professional plan or above.
        config:
          $ref: '#/components/schemas/ProjectConfig'
        last_sync:
          allOf:
            - $ref: '#/components/schemas/SyncSummary'
          readOnly: true
          description: |
            Lightweight summary of the most recent sync run, or `null` if the project has never been synced. Use it to gate an automated workflow on indexing completion. When `status` is `running`, the crawl is still in progress; once it transitions to `success`, `error`, `warning`, or `canceled`, the run is done. For the full event log, call [`GET /api/v2/projects/{slug}/sync/`](/api/get-sync-status).
      required:
        - slug
        - name
        - is_private
        - whitelabel
        - config
    ProjectInput:
      type: object
      description: Body for creating a project. Deliberately minimal; configure everything else with [`PATCH /api/v2/projects/{slug}/`](/api/update-project) after creation.
      properties:
        name:
          type: string
          description: Human-readable project name.
        is_private:
          type: boolean
          default: false
          description: Whether the project is private. Private projects require an API key for read operations.
      required:
        - name
    ProjectUpdate:
      type: object
      description: Body for updating a project. All fields are optional; only supplied fields are modified.
      properties:
        name:
          type: string
          description: Human-readable project name.
        is_private:
          type: boolean
          description: Whether the project is private.
        config:
          $ref: '#/components/schemas/ProjectConfig'
    ProjectListItem:
      type: object
      description: Lean project representation returned by [`GET /api/v2/projects/`](/api/list-projects).
      properties:
        slug:
          type: string
          description: URL-safe project identifier.
        name:
          type: string
          description: Human-readable project name.
        is_private:
          type: boolean
          description: Whether the project is private.
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of project creation.
      required:
        - slug
        - name
        - is_private
        - created_at
    ProjectList:
      type: object
      description: Paginated list of projects.
      properties:
        count:
          type: integer
          description: Total number of projects matching the query.
        next:
          type: string
          format: uri
          nullable: true
          description: URL of the next page, or `null` if this is the last page.
        previous:
          type: string
          format: uri
          nullable: true
          description: URL of the previous page, or `null` if this is the first page.
        results:
          type: array
          items:
            $ref: '#/components/schemas/ProjectListItem'
      required:
        - count
        - results
    SearchResult:
      type: object
      properties:
        title:
          type: string
          description: The page title related to the document.
        fragment:
          type: string
          description: A snippet of text containing the search query or a relevant section of the document.
        url:
          type: string
          format: uri
          description: The URL to the original document containing the fragment.
        score:
          type: number
          description: Relevance score.
        domain:
          type: string
          format: uri
          description: The root domain name of the source.
        is_title_match:
          type: boolean
          description: Whether the page title contains the exact search query.
    SourceBase:
      type: object
      description: Properties shared by every source type.
      properties:
        uuid:
          type: string
          format: uuid
          description: Source UUID.
        type:
          type: string
          enum:
            - file
            - url
            - sitemap
            - site
            - hosted_site
            - repository
            - openapi
            - confluence
          description: Source type.
        name:
          type: string
          description: Display name (file name, URL, repository name, etc.).
        created_at:
          type: string
          format: date-time
          readOnly: true
          description: ISO-8601 timestamp of when the source was added.
      required:
        - uuid
        - type
        - name
    FileSource:
      description: An uploaded file (PDF, MD, TXT, DOCX, XLSX, CSV, YAML, JSON, RST, HTML, CSS, JS, XML).
      allOf:
        - $ref: '#/components/schemas/SourceBase'
    UrlSource:
      description: A single crawled URL.
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            url:
              type: string
              format: uri
              description: The crawled URL.
    SitemapSource:
      description: A sitemap whose URLs are crawled and indexed.
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            url:
              type: string
              format: uri
              description: The sitemap URL.
    SitePageSource:
      description: 'A single page of a hosted site (`type: site`). Created automatically when a site ZIP is uploaded.'
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            url:
              type: string
              format: uri
              description: Original URL the page maps to.
    HostedSiteSource:
      description: 'A complete website uploaded as a ZIP and hosted by Biel.ai (`type: hosted_site`). Groups the individual `site` pages.'
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            base_url:
              type: string
              format: uri
              description: Original base URL of the site.
            hosted_url:
              type: string
              format: uri
              description: URL where Biel.ai serves the uploaded site.
            page_count:
              type: integer
              description: Number of indexed pages.
    RepositorySource:
      description: A GitHub repository whose files are ingested.
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            url:
              type: string
              format: uri
              description: Repository URL.
    OpenApiSource:
      description: An OpenAPI specification ingested from a URL.
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            url:
              type: string
              format: uri
              description: Specification URL (JSON or YAML).
    ConfluenceSource:
      description: A Confluence space whose pages are ingested.
      allOf:
        - $ref: '#/components/schemas/SourceBase'
        - type: object
          properties:
            space_key:
              type: string
              description: Confluence space key.
            base_url:
              type: string
              format: uri
              description: Confluence base URL.
    Source:
      description: A single project source. The `type` field determines the concrete shape.
      oneOf:
        - $ref: '#/components/schemas/FileSource'
        - $ref: '#/components/schemas/UrlSource'
        - $ref: '#/components/schemas/SitemapSource'
        - $ref: '#/components/schemas/SitePageSource'
        - $ref: '#/components/schemas/HostedSiteSource'
        - $ref: '#/components/schemas/RepositorySource'
        - $ref: '#/components/schemas/OpenApiSource'
        - $ref: '#/components/schemas/ConfluenceSource'
      discriminator:
        propertyName: type
        mapping:
          file: '#/components/schemas/FileSource'
          url: '#/components/schemas/UrlSource'
          sitemap: '#/components/schemas/SitemapSource'
          site: '#/components/schemas/SitePageSource'
          hosted_site: '#/components/schemas/HostedSiteSource'
          repository: '#/components/schemas/RepositorySource'
          openapi: '#/components/schemas/OpenApiSource'
          confluence: '#/components/schemas/ConfluenceSource'
    RepositoryInput:
      type: object
      description: A GitHub repository to ingest.
      properties:
        url:
          type: string
          format: uri
          description: GitHub repository URL.
        token:
          type: string
          description: GitHub personal access token. Required for private repositories. Stored encrypted.
        include_patterns:
          type: string
          description: Comma-separated patterns to include (for example, `*.py, src/`).
        exclude_patterns:
          type: string
          description: Comma-separated patterns to exclude (for example, `*.log, temp/`).
        max_file_size:
          type: integer
          description: Maximum file size in bytes. Defaults to 10485760 (10 MB).
      required:
        - url
    ConfluenceInput:
      type: object
      description: A Confluence space to ingest.
      properties:
        confluence_url:
          type: string
          format: uri
          description: Confluence space URL (e.g. `https://yourcompany.atlassian.net/wiki/spaces/DOCS`). The base URL and space key are auto-extracted.
        username:
          type: string
          description: Confluence username. Required for private spaces. Stored encrypted.
        api_token:
          type: string
          description: Confluence API token. Required for private spaces. Stored encrypted.
      required:
        - confluence_url
    AddSourceJson:
      type: object
      description: |
        JSON body for adding non-binary sources. Use `multipart/form-data` instead when uploading files or site ZIPs.

        At least one of `urls`, `sitemaps`, `repositories`, `openapis`, or `confluences` must be supplied.
      properties:
        urls:
          type: array
          items:
            type: string
            format: uri
          description: URLs to crawl and index.
        sitemaps:
          type: array
          items:
            type: string
            format: uri
          description: Sitemap URLs to crawl.
        repositories:
          type: array
          items:
            $ref: '#/components/schemas/RepositoryInput'
          description: GitHub repositories to ingest.
        openapis:
          type: array
          items:
            type: string
            format: uri
          description: OpenAPI specification URLs (JSON or YAML).
        confluences:
          type: array
          items:
            $ref: '#/components/schemas/ConfluenceInput'
          description: Confluence spaces to ingest.
    SyncSummary:
      type: object
      description: |
        Compact summary of a sync run, embedded in the project's `last_sync` field for lightweight polling.
      properties:
        uuid:
          type: string
          format: uuid
          description: UUID of the sync run.
        status:
          type: string
          enum:
            - running
            - success
            - error
            - warning
            - canceled
          description: |
            - `running`: crawl is in progress.
            - `success`: crawl finished without errors.
            - `warning`: crawl finished with non-fatal warnings (some URLs may have failed).
            - `error`: crawl failed.
            - `canceled`: a newer [`POST /api/v2/projects/{slug}/sync/`](/api/sync-project) call superseded this run.
        started_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of when the run started.
        finished_at:
          type: string
          format: date-time
          nullable: true
          description: ISO-8601 timestamp of when the run finished, or `null` while `status` is `running`.
        total_sources:
          type: integer
          description: Total number of sources processed in this run, including expanded entries (for example, individual files of a repository).
      required:
        - uuid
        - status
        - started_at
        - total_sources
    SyncEvent:
      type: object
      description: A structured log entry emitted during a sync run.
      properties:
        level:
          type: string
          enum:
            - info
            - warning
            - error
          description: Severity of the event.
        message:
          type: string
          description: Human-readable event message. Typically references a URL or source name.
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of when the event was emitted.
      required:
        - level
        - message
        - created_at
    SyncStatus:
      description: |
        Detail of a sync run. Includes the summary plus the full event log and the list of processed URLs.
      allOf:
        - $ref: '#/components/schemas/SyncSummary'
        - type: object
          properties:
            urls:
              type: array
              items:
                type: string
                format: uri
              description: URLs processed in this run.
            events:
              type: array
              items:
                $ref: '#/components/schemas/SyncEvent'
              description: Structured events emitted during the run, in chronological order.
          required:
            - urls
            - events
    SendMessageInput:
      type: object
      description: Body for [`POST /api/v2/projects/{slug}/chats/`](/api/send-message).
      properties:
        message:
          type: string
          description: The message to send to the AI.
        chat_uuid:
          type: string
          format: uuid
          description: UUID of an existing chat to continue. Creates a new chat if omitted.
        url:
          type: string
          description: URL where the chat is initiated from.
        email:
          type: string
          description: Email or ID for conversation tracking in analytics.
        think_mode:
          type: boolean
          description: Enable reasoning mode for deeper analysis. Requires reasoning mode to be enabled for the project.
        metadata:
          type: string
          description: Tag to identify the conversation source (e.g., `mcp`, `slack`, `web`).
      required:
        - message
    SendMessageResponse:
      type: object
      description: AI response to a sent message.
      properties:
        user_message_id:
          type: string
        ai_message:
          type: object
          properties:
            from:
              type: string
            message:
              type: string
            messageId:
              type: string
            isPartial:
              type: boolean
            timestamp:
              type: string
            sources:
              type: array
              items:
                type: object
        chat_uuid:
          type: string
    MessageFeedback:
      type: object
      description: Reaction payload for [`POST /api/v2/projects/{slug}/messages/{uuid}/feedback/`](/api/submit-message-feedback).
      properties:
        reaction:
          type: integer
          enum:
            - 0
            - 1
          description: '`1` = like, `0` = dislike.'
      required:
        - reaction
    ChatMessage:
      type: object
      description: A single message inside a chat.
      properties:
        uuid:
          type: string
          format: uuid
          description: Message UUID.
        message_type:
          type: string
          enum:
            - HUMAN
            - AI
            - SYSTEM
          description: Author of the message.
        content:
          type: string
          description: Message body.
        sources:
          type: array
          description: Sources cited by an AI message. Empty for human and system messages.
          items:
            type: object
        is_mcp_message:
          type: boolean
          description: True if this message originated from the MCP server.
        feedback:
          type: integer
          enum:
            - 0
            - 1
          nullable: true
          description: |
            User reaction to this message: `1` = like, `0` = dislike, `null` = no feedback yet.
            Set via [`POST /api/v2/projects/{slug}/messages/{uuid}/feedback/`](/api/submit-message-feedback). Only meaningful for `AI` messages.
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of when the message was sent.
      required:
        - uuid
        - message_type
        - content
        - is_mcp_message
        - created_at
    ChatListItem:
      type: object
      description: Lean chat representation returned by [`GET /api/v2/projects/{slug}/chats/`](/api/list-chats). Does not include messages; call [`GET /api/v2/projects/{slug}/chats/{chat_uuid}/`](/api/get-chat) to retrieve them.
      properties:
        uuid:
          type: string
          format: uuid
          description: Chat UUID.
        status:
          type: string
          enum:
            - ACTIVE
            - ARCHIVED
            - DELETED
          description: Chat lifecycle status.
        url:
          type: string
          nullable: true
          description: URL where the chat was initiated, as supplied when the message was sent.
        email:
          type: string
          nullable: true
          description: Email or identifier supplied when the message was sent, for analytics tracking.
        metadata:
          type: string
          nullable: true
          description: Channel tag supplied when the message was sent (for example, `web`, `mcp`, `slack`).
        country:
          type: string
          nullable: true
          description: ISO-3166 alpha-2 country code resolved server-side from the originating IP.
        created_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of chat creation.
        updated_at:
          type: string
          format: date-time
          description: ISO-8601 timestamp of last activity in the chat.
      required:
        - uuid
        - status
        - created_at
        - updated_at
    Chat:
      description: A chat with its full message history.
      allOf:
        - $ref: '#/components/schemas/ChatListItem'
        - type: object
          properties:
            project_slug:
              type: string
              description: Slug of the project the chat belongs to.
            messages:
              type: array
              items:
                $ref: '#/components/schemas/ChatMessage'
              description: Messages in the chat, ordered by `created_at` ascending.
          required:
            - project_slug
            - messages
    ChatList:
      type: object
      description: Paginated list of chats.
      properties:
        count:
          type: integer
          description: Total number of chats matching the query.
        next:
          type: string
          format: uri
          nullable: true
          description: URL of the next page, or `null` if this is the last page.
        previous:
          type: string
          format: uri
          nullable: true
          description: URL of the previous page, or `null` if this is the first page.
        results:
          type: array
          items:
            $ref: '#/components/schemas/ChatListItem'
      required:
        - count
        - results
    DailyCount:
      type: object
      description: 'One day in a daily time series. Days with no events are returned with `count: 0`.'
      properties:
        day:
          type: string
          format: date
          description: ISO-8601 date (`YYYY-MM-DD`).
        count:
          type: integer
          description: Number of events recorded on this day.
      required:
        - day
        - count
    TopUrl:
      type: object
      description: Aggregated count for a URL.
      properties:
        url:
          type: string
          description: Page URL where the chat was initiated. `Unknown` when not reported.
        count:
          type: integer
      required:
        - url
        - count
    TopCountry:
      type: object
      description: Aggregated count for a country resolved server-side from the user's IP.
      properties:
        name:
          type: string
          description: Country name. `Unknown` when the IP could not be resolved.
        flag:
          type: string
          description: Country flag emoji. Empty string for `Unknown`.
        count:
          type: integer
      required:
        - name
        - flag
        - count
    TopSearchTerm:
      type: object
      description: Aggregated count for a search query.
      properties:
        query:
          type: string
        count:
          type: integer
      required:
        - query
        - count
    ChatAnalytics:
      type: object
      description: KPIs and daily activity for the AI chat in the requested date range. MCP-only conversations are excluded.
      properties:
        kpis:
          type: object
          properties:
            chats_count:
              type: integer
              description: Total number of chats in the date range.
            messages_count:
              type: integer
              description: Total number of messages (human + AI) across those chats.
            unique_users_count:
              type: integer
              description: Distinct end users (resolved by IP, with chats lacking an IP each counting as one user).
            positive_count:
              type: integer
              description: Number of AI messages with a thumbs-up reaction.
            negative_count:
              type: integer
              description: Number of AI messages with a thumbs-down reaction.
            satisfaction_rate:
              type: integer
              description: Percentage 0-100. Treats neutral (no feedback) AI messages as positive.
          required:
            - chats_count
            - messages_count
            - unique_users_count
            - positive_count
            - negative_count
            - satisfaction_rate
        activity:
          type: object
          properties:
            chats_per_day:
              type: array
              items:
                $ref: '#/components/schemas/DailyCount'
              description: Chats created per day. Always covers every day of the range.
            top_urls:
              type: array
              items:
                $ref: '#/components/schemas/TopUrl'
              description: Top 10 URLs from which chats were initiated.
            top_countries:
              type: array
              items:
                $ref: '#/components/schemas/TopCountry'
              description: Top 10 countries by chat volume.
          required:
            - chats_per_day
            - top_urls
            - top_countries
      required:
        - kpis
        - activity
    SearchAnalytics:
      type: object
      description: KPIs and daily activity for the search experience in the requested date range.
      properties:
        kpis:
          type: object
          properties:
            unique_users:
              type: integer
              description: Distinct end users issuing searches (resolved by IP).
            total_searches:
              type: integer
              description: Total search queries.
            satisfaction_rate:
              type: integer
              description: Percentage 0-100. Computed as clicks divided by searches.
          required:
            - unique_users
            - total_searches
            - satisfaction_rate
        activity:
          type: object
          properties:
            searches_per_day:
              type: array
              items:
                $ref: '#/components/schemas/DailyCount'
              description: Searches per day. Always covers every day of the range.
            top_search_terms:
              type: array
              items:
                $ref: '#/components/schemas/TopSearchTerm'
              description: Top 10 search queries by volume.
          required:
            - searches_per_day
            - top_search_terms
      required:
        - kpis
        - activity
    McpAnalytics:
      type: object
      description: KPIs and daily activity for conversations that originated from the MCP server.
      properties:
        kpis:
          type: object
          properties:
            chats_count:
              type: integer
              description: Total number of MCP chats in the date range.
            messages_count:
              type: integer
              description: Number of human queries received via MCP.
            unique_users_count:
              type: integer
              description: Distinct end users (resolved by IP, with chats lacking an IP each counting as one user).
          required:
            - chats_count
            - messages_count
            - unique_users_count
        activity:
          type: object
          properties:
            chats_per_day:
              type: array
              items:
                $ref: '#/components/schemas/DailyCount'
              description: MCP chats created per day. Always covers every day of the range.
            top_countries:
              type: array
              items:
                $ref: '#/components/schemas/TopCountry'
              description: Top 10 countries by MCP chat volume.
          required:
            - chats_per_day
            - top_countries
      required:
        - kpis
        - activity
    InsightsRelatedChat:
      type: object
      description: A chat that contributed to an insight item. Retrieve the underlying conversation with [`GET /api/v2/projects/{slug}/chats/{chat_uuid}/`](/api/get-chat).
      properties:
        chat_uuid:
          type: string
          format: uuid
          description: UUID of the chat.
        original_question:
          type: string
          description: The user's original message that triggered the analysis.
        date:
          type: string
          format: date
          nullable: true
          description: ISO-8601 date (`YYYY-MM-DD`) the chat was analysed on, or `null` if no date is available.
      required:
        - chat_uuid
        - original_question
    InsightsItem:
      type: object
      description: A single FAQ or content gap, aggregated across the date range.
      properties:
        question_text:
          type: string
          description: Canonical question text identified by the analyser.
        total_occurrences:
          type: integer
          description: Number of times this question (or close variants) appeared in the range.
        related_chats:
          type: array
          items:
            $ref: '#/components/schemas/InsightsRelatedChat'
          description: Chats that fed into this insight, with their original question wording.
      required:
        - question_text
        - total_occurrences
        - related_chats
    InsightsAnalytics:
      type: object
      description: |
        Conversation-driven insights for a project. When the team is not on a plan with advanced analytics, only `has_access: false` is returned and the `faqs` / `content_gaps` fields are omitted.
      properties:
        has_access:
          type: boolean
          description: '`true` when the team has advanced analytics access; `false` otherwise.'
        faqs:
          type: array
          items:
            $ref: '#/components/schemas/InsightsItem'
          description: Top 10 most frequently asked questions in the date range. Only present when `has_access` is `true`.
        content_gaps:
          type: array
          items:
            $ref: '#/components/schemas/InsightsItem'
          description: Top 10 questions the assistant could not answer satisfactorily in the date range. Only present when `has_access` is `true`.
      required:
        - has_access
    TeamMember:
      type: object
      description: A user who belongs to a team.
      properties:
        id:
          type: integer
          description: Membership id (not the user id).
        user_id:
          type: integer
          description: Underlying user id.
        first_name:
          type: string
          nullable: true
          description: Member first name.
        last_name:
          type: string
          nullable: true
          description: Member last name.
        display_name:
          type: string
          description: Display name (full name when available, otherwise email).
        role:
          type: string
          enum:
            - admin
            - maintainer
            - editor
            - viewer
          description: Member role in the team.
      required:
        - id
        - user_id
        - display_name
        - role
    Team:
      type: object
      description: A team groups users, projects, and API keys. Each API key is scoped to exactly one team.
      properties:
        id:
          type: integer
          description: Numeric team id.
        name:
          type: string
          description: Human-readable team name.
        slug:
          type: string
          description: URL slug used to address the team.
        members:
          type: array
          items:
            $ref: '#/components/schemas/TeamMember'
          description: Active team members.
        dashboard_url:
          type: string
          format: uri
          description: Web URL to the team dashboard.
        subscription:
          allOf:
            - $ref: '#/components/schemas/TeamSubscription'
          description: Subscription state for the team, including plan and trial status.
        trial:
          $ref: '#/components/schemas/TeamTrial'
      required:
        - id
        - name
        - slug
        - members
        - dashboard_url
        - subscription
        - trial
    TeamUpdate:
      type: object
      description: Mutable fields on a team. All fields are optional; include only what you want to change.
      properties:
        name:
          type: string
          description: New human-readable team name.
        slug:
          type: string
          description: New URL slug. Must be unique across all teams.
    TeamMemberInvite:
      type: object
      description: Body for inviting a new member to the team.
      properties:
        email:
          type: string
          format: email
          description: Email address to send the invitation to.
        role:
          type: string
          enum:
            - admin
            - maintainer
            - editor
            - viewer
          default: editor
          description: Role the invitee will have once they accept.
      required:
        - email
    TeamInvitation:
      type: object
      description: A pending invitation to join the team.
      properties:
        id:
          type: string
          description: Invitation id.
        email:
          type: string
          format: email
          description: Invited email address.
        role:
          type: string
          enum:
            - admin
            - maintainer
            - editor
            - viewer
          description: Role the invitee will have once they accept.
        is_accepted:
          type: boolean
          description: Whether the invitation has been accepted.
      required:
        - id
        - email
        - role
        - is_accepted
    TeamMemberUpdate:
      type: object
      description: Mutable fields on a team membership.
      properties:
        role:
          type: string
          enum:
            - admin
            - maintainer
            - editor
            - viewer
          description: New role for the member.
      required:
        - role
    OpenApiValidationInput:
      type: object
      description: The OpenAPI specification to validate. Provide exactly one of `url` or `content`.
      properties:
        url:
          type: string
          format: uri
          description: URL of the specification (JSON or YAML). Biel fetches it and resolves relative `$ref`s against it, exactly as a sync would.
          example: https://api.example.com/openapi.yaml
        content:
          type: string
          description: Raw specification content (JSON or YAML) submitted inline.
          example: |
            openapi: 3.0.3
            info:
              title: Example
              version: 1.0.0
            paths: {}
      oneOf:
        - required:
            - url
        - required:
            - content
    OpenApiValidationSuccess:
      type: object
      description: The specification parsed and validated successfully.
      properties:
        valid:
          type: boolean
          enum:
            - true
        openapi_version:
          type: string
          description: Value of the spec's `openapi` (or legacy `swagger`) field.
          example: 3.0.3
        title:
          type: string
          description: The API title declared in `info.title`.
          example: Petstore API
        version:
          type: string
          description: The API version declared in `info.version`.
          example: 1.2.0
        paths:
          type: integer
          description: Number of paths in the specification.
          example: 12
        operations:
          type: integer
          description: Number of operations across all paths.
          example: 34
      required:
        - valid
    OpenApiValidationFailure:
      type: object
      description: The specification failed to fetch, parse, or validate.
      properties:
        valid:
          type: boolean
          enum:
            - false
        errors:
          type: array
          description: Human-readable validation errors, matching what a sync would report.
          items:
            type: string
          example:
            - 'Unexpected property ''allow_empty_values'' at: paths, /pets/, get, parameters, 0'
      required:
        - valid
        - errors
    Action:
      type: object
      description: A call-to-action button the chat widget renders alongside AI answers.
      properties:
        name:
          type: string
          description: Identifier of the action, unique within the project.
        button_text:
          type: string
          description: Label shown on the button.
        url:
          type: string
          format: uri
          description: Destination the button links to.
      required:
        - name
        - button_text
        - url
    PromptSettings:
      type: object
      description: |
        Prompt customization. Use these settings to shape the assistant's behavior.
      properties:
        use_default:
          type: boolean
          description: Whether the built-in system prompt is used. Setting this to `false` without providing `extend` is a validation error.
        version:
          type: string
          enum:
            - v1
            - v2
            - v3
          description: Version of the built-in assistant prompt.
        extend:
          type: string
          nullable: true
          description: Custom instructions appended to (or, when `use_default` is `false`, replacing) the built-in prompt.
    SourceList:
      type: object
      description: Paginated list of sources.
      properties:
        count:
          type: integer
          description: Total number of sources matching the query.
        next:
          type: string
          format: uri
          nullable: true
          description: URL of the next page, or `null` if this is the last page.
        previous:
          type: string
          format: uri
          nullable: true
          description: URL of the previous page, or `null` if this is the first page.
        results:
          type: array
          items:
            $ref: '#/components/schemas/Source'
      required:
        - count
        - results
    TeamSubscription:
      type: object
      description: Subscription state of the team.
      properties:
        status:
          type: string
          enum:
            - active
            - trialing
            - none
          description: Whether the team has an active paid subscription, is on the free trial, or has neither.
        plan:
          type: string
          nullable: true
          description: Slug of the active plan (for example `professional`), or `null` without a paid subscription.
      required:
        - status
        - plan
    TeamTrial:
      type: object
      description: Free-trial state of the team.
      properties:
        active:
          type: boolean
          description: Whether the free trial is currently active.
        ends_at:
          type: string
          format: date-time
          nullable: true
          description: ISO-8601 end of the trial window while trialing, otherwise `null`.
      required:
        - active
        - ends_at
    TeamMemberList:
      type: object
      description: List of team members.
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/TeamMember'
      required:
        - results
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: |
        API Key authentication using the format: `Api-Key <your-api-key>`

        Create your API key in Team Settings and assign the required permissions for your use case.
  parameters:
    Limit:
      in: query
      name: limit
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
      description: Maximum number of items to return per page.
    Offset:
      in: query
      name: offset
      schema:
        type: integer
        minimum: 0
        default: 0
      description: Number of items to skip from the start of the list.
    ProjectSlug:
      in: path
      name: slug
      schema:
        type: string
      required: true
      description: Project slug identifier.
    FromDate:
      in: query
      name: from
      schema:
        type: string
        format: date
      required: false
      description: Start date (inclusive, ISO-8601, `YYYY-MM-DD`). Defaults to 30 days ago.
    ToDate:
      in: query
      name: to
      schema:
        type: string
        format: date
      required: false
      description: End date (inclusive, ISO-8601, `YYYY-MM-DD`). Defaults to today.
    TeamSlug:
      in: path
      name: slug
      schema:
        type: string
      required: true
      description: URL slug used to address the team.
  responses:
    Forbidden:
      description: Forbidden. Missing permissions.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Validation error. Field-level errors keyed by field name.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/FieldErrors'
    NotFound:
      description: Resource not found.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadRequest:
      description: Bad request. Invalid or missing data.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unprocessable:
      description: Unprocessable. The request was valid, but it cannot be completed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
