openapi: 3.0.3
info:
  title: MDDB API
  description: |
    MDDB (Markdown Database) is a high-performance, version-controlled markdown database with triple protocol support.

    ## Features
    - Store and manage markdown documents with metadata
    - Full revision history for all documents
    - Multi-language support
    - Fast metadata-based search
    - **Vector search** with embedding providers (OpenAI, Cohere, Voyage AI, Ollama)
    - **Full-text search** with TF-IDF, BM25, BM25F, PMISparse scoring, 7 search modes (simple, boolean, phrase, wildcard, proximity, range, fuzzy), and multi-language stemming (18 languages)
    - **Hybrid search** combining FTS + vector search with alpha blending or RRF fusion
    - **Webhooks** for real-time event notifications
    - **TTL** (time-to-live) support for automatic document expiration
    - **JSON Schema validation** for document metadata
    - **URL import** with YAML frontmatter parsing
    - **Authentication** - JWT tokens and API keys
    - **Authorization** - Collection-level RBAC (Read/Write/Admin)
    - **Memory RAG** - Conversational memory system with session management, message storage, semantic/hybrid recall, and summarization
    - **User Management** - Multi-user with admin roles
    - **Group Permissions** - Organize users into groups
    - Template variable substitution
    - Export capabilities (NDJSON, ZIP)
    - Backup and restore operations
    - **Replication** - Leader-follower replication with binlog streaming
    - **Automation** - Webhooks, triggers (FTS/vector/hybrid + sentiment), crons
    - **Automation Logs** - Execution history with configurable TTL retention

    ## Protocols
    - **HTTP/JSON** - Port 11023 (documented here)
    - **gRPC/Protobuf** - Port 11024 (see gRPC documentation)
    - **GraphQL** - Port 11023/graphql (enable with MDDB_GRAPHQL_ENABLED=true)
    - **MCP** - Port 9000 (Streamable HTTP at `/mcp`, legacy SSE at `/sse`)
    - **HTTP/3 + QUIC** - Port 11443 (extreme mode only)

    ## Per-Protocol Access Modes
    Each protocol can have its own read/write mode: `MDDB_API_MODE`, `MDDB_GRPC_MODE`, `MDDB_MCP_MODE`, `MDDB_HTTP3_MODE`.
    Values: `read`, `write`, `wr`. Default: inherits from `MDDB_MODE`.
    Set `MDDB_MCP_BUILTIN_TOOLS=false` to hide built-in MCP tools.

    ## Authentication
    MDDB supports JWT tokens and API keys for authentication with collection-level RBAC authorization.

    ### JWT Authentication
    ```bash
    # Login to get JWT token
    curl -X POST http://localhost:11023/v1/auth/login \
      -H "Content-Type: application/json" \
      -d '{"username":"admin","password":"secret"}'

    # Use token in requests
    curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:11023/v1/stats
    ```

    ### API Key Authentication
    ```bash
    # Create API key (requires JWT first)
    curl -X POST http://localhost:11023/v1/auth/api-key \
      -H "Authorization: Bearer YOUR_JWT_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"description":"My API key"}'

    # Use API key in requests
    curl -H "X-API-Key: YOUR_API_KEY" http://localhost:11023/v1/stats
    ```

    See authentication endpoints below for user management, groups, and permissions.

  version: "2.15.0"
  contact:
    name: MDDB Team
    url: https://github.com/tradik/mddb
  license:
    name: BSD-3-Clause
    url: https://github.com/tradik/mddb/blob/main/LICENSE

servers:
  - url: http://localhost:11023
    description: Local development server
  - url: http://localhost:11023/v1
    description: Local development server (with version prefix)

tags:
  - name: Health
    description: Health check and monitoring endpoints
  - name: Authentication
    description: User authentication with JWT tokens and API keys
  - name: Users
    description: User management (admin only)
  - name: Groups
    description: Group management and permissions (admin only)
  - name: Embedding Config
    description: Embedding provider configuration management
  - name: Documents
    description: Document management operations
  - name: Search
    description: Search and query operations
  - name: Vector Search
    description: Semantic/vector search operations
  - name: Full-Text Search
    description: Keyword-based full-text search
  - name: Hybrid Search
    description: Combined sparse (FTS) + dense (vector) search with fusion strategies
  - name: Import
    description: Document import from URLs
  - name: TTL
    description: Document time-to-live management
  - name: Webhooks
    description: Webhook registration and management
  - name: Schema Validation
    description: JSON Schema validation for metadata
  - name: Export
    description: Export and backup operations
  - name: Maintenance
    description: Database maintenance operations
  - name: Telemetry
    description: Prometheus-compatible metrics endpoint
  - name: Replication
    description: Leader-follower replication status and monitoring
  - name: Synonyms
    description: FTS synonym dictionary management
  - name: Curation
    description: (v2.9.15+) Editorial overrides for FTS + Hybrid search — pin
      documents to fixed positions, hide others for specific queries
  - name: System
    description: System information and endpoint discovery
  - name: MCP
    description: Model Context Protocol 2026-07-28 and 2025-11-25 served side by
      side — Streamable HTTP transport, tools, resources, prompts, completion,
      logging, discovery
  - name: Memory RAG
    description: Conversational memory system for RAG — session management, message
      storage, semantic/hybrid recall, summarization
  - name: Configuration
    description: Server and MCP configuration
  - name: GraphQL
    description: GraphQL API and interactive playground
  - name: Automation
    description: Triggers, crons, and webhook targets for automated workflows
  - name: Revisions
    description: Document revision history and restore operations
  - name: Collection Config
    description: Per-collection type, description, icon, color, and custom metadata
  - name: Cross-Collection Search
    description: Search across multiple collections using embeddings or text queries
  - name: Temporal Tracking
    description: Document event history, hot-docs leaderboard and activity
      histograms (requires MDDB_TEMPORAL=true)
  - name: Spell Correction
    description: SymSpell-based FTS spell suggestions, text cleanup, and
      per-collection custom dictionaries (requires MDDB_SPELL=true)

paths:
  /health:
    get:
      tags:
        - Health
      summary: Health check
      description: Simple health check endpoint that verifies database connectivity
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  mode:
                    type: string
                    enum: [ read, write, wr ]
                    example: wr
                  persistence:
                    $ref: "#/components/schemas/PersistenceStatus"
                  durable:
                    type: boolean
                    description: >-
                      Whether an acknowledged write can be expected to survive a
                      restart of the process and its container (GO-032). False
                      means the data directory is read-only or ephemeral.
                    example: true
                  mcpSessions:
                    type: object
                    description: >-
                      Open MCP sessions per transport (GO-021). Absent when the
                      MCP server is disabled — an absent field says "not
                      running" where a zero would say "idle". A count that only
                      grows is how a session leak becomes visible.
                    properties:
                      sse:
                        type: integer
                        description: Sessions on the legacy MCP-over-SSE transport
                        example: 0
                      streamable:
                        type: integer
                        description: Sessions on the Streamable HTTP transport
                        example: 2
        "503":
          description: Service is unhealthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/health:
    get:
      tags:
        - Health
      summary: Health check (versioned)
      description: Alias for /health endpoint
      operationId: getHealthV1
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  mode:
                    type: string
                    enum: [ read, write, wr ]
                    example: wr
                  persistence:
                    $ref: "#/components/schemas/PersistenceStatus"
                  durable:
                    type: boolean
                    description: >-
                      Whether an acknowledged write can be expected to survive a
                      restart of the process and its container (GO-032). False
                      means the data directory is read-only or ephemeral.
                    example: true
                  mcpSessions:
                    type: object
                    description: >-
                      Open MCP sessions per transport (GO-021). Absent when the
                      MCP server is disabled — an absent field says "not
                      running" where a zero would say "idle". A count that only
                      grows is how a session leak becomes visible.
                    properties:
                      sse:
                        type: integer
                        description: Sessions on the legacy MCP-over-SSE transport
                        example: 0
                      streamable:
                        type: integer
                        description: Sessions on the Streamable HTTP transport
                        example: 2
        "503":
          description: Service is unhealthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/stats:
    get:
      tags:
        - Health
      summary: Server statistics
      description: Get detailed server and database statistics
      operationId: getStats
      responses:
        "200":
          description: Statistics retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Stats"

  /v1/add:
    post:
      tags:
        - Documents
      summary: Add or update document
      description: |
        Add a new document or update an existing one. If a document with the same collection, key, and language exists, it will be updated and a new revision will be created.
      operationId: addDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddRequest"
      responses:
        "200":
          description: Document added/updated successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/add-batch:
    post:
      tags:
        - Documents
      summary: Batch add documents
      description: |
        Add multiple documents to a collection in a single request. Uses the optimized batch processor for high throughput.
        Fires all post-commit hooks (embedding, FTS indexing, webhooks, TTL, automation triggers).
      operationId: addBatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AddBatchRequest"
      responses:
        "200":
          description: Batch result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AddBatchResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/bulk-ingest-job:
    post:
      tags:
        - Documents
      summary: Submit async bulk ingest job
      description: |
        Queue a long-running bulk ingest job and return immediately with a
        job identifier. Clients should poll `/v1/bulk-ingest-job/{id}` for
        progress or supply `callbackUrl` to receive a `POST` with the final
        job record on completion.

        Jobs are processed by a single background worker in FIFO order.
        Payloads are kept in memory — if the server restarts mid-job, the
        status record is flipped to `failed` on the next start.
      operationId: submitBulkIngestJob
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkIngestSubmitRequest"
      responses:
        "202":
          description: Job accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkIngestJob"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/bulk-ingest-job/{id}:
    get:
      tags:
        - Documents
      summary: Get bulk ingest job status
      description: Returns the current status record — including counters, timestamps,
        and up to 50 error messages.
      operationId: getBulkIngestJob
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Job identifier returned from submit
      responses:
        "200":
          description: Job status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkIngestJob"
        "404":
          description: Job not found
    delete:
      tags:
        - Documents
      summary: Cancel pending bulk ingest job
      description: Only jobs in `pending` status can be cancelled. In-flight jobs must
        run to completion.
      operationId: cancelBulkIngestJob
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Job cancelled
        "400":
          description: Job already started or completed

  /v1/bulk-ingest-jobs:
    get:
      tags:
        - Documents
      summary: List bulk ingest jobs
      description: Returns all jobs newest-first, optionally filtered by collection.
      operationId: listBulkIngestJobs
      parameters:
        - name: collection
          in: query
          required: false
          schema:
            type: string
          description: Filter by collection name
      responses:
        "200":
          description: Job list
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs:
                    type: array
                    items:
                      $ref: "#/components/schemas/BulkIngestJob"
                  total:
                    type: integer

  /v1/ingest:
    post:
      tags:
        - Documents
      summary: Bulk ingest documents
      description: |
        Advanced bulk ingest endpoint for scraping pipelines and data import workflows.
        Supports URL key derivation, YAML frontmatter extraction, content deduplication,
        auto-metadata injection, and collection auto-configuration.
      operationId: ingestDocuments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IngestRequest"
      responses:
        "200":
          description: Ingest result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IngestResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/upload:
    post:
      tags:
        - Documents
      summary: Upload files with auto-conversion to Markdown
      description: |
        Upload files via multipart/form-data. Files are auto-converted
        to Markdown and stored as documents. Supports single and batch upload.

        Supported formats: `.md`, `.txt`, `.html`, `.htm`, `.pdf`, `.docx`, `.odt`, `.rtf`, `.yaml`, `.yml`, `.log`, `.lex`, `.tex`, `.latex`.
        Default (md/txt) is stored as-is with frontmatter extraction; others are converted.
        ODT and DOCX are ZIP-based with XML extraction. RTF is parsed to plain text.
        LaTeX/TeX is converted (sections, formatting, environments). YAML/LOG/LEX are wrapped in code blocks.
      operationId: uploadFile
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - collection
                - lang
              properties:
                file:
                  type: string
                  format: binary
                  description: Single file to upload
                "files[]":
                  type: array
                  items:
                    type: string
                    format: binary
                  description: Multiple files to upload
                collection:
                  type: string
                  description: Target collection name
                lang:
                  type: string
                  description: Document language code (e.g. en_US)
                key:
                  type: string
                  description: Document key (derived from filename if empty)
                meta:
                  type: string
                  description: JSON-encoded metadata map
                ttl:
                  type: integer
                  description: Time-to-live in seconds
                maxSize:
                  type: integer
                  description: Per-file size limit in bytes (default 10MB, max 100MB)
      responses:
        "200":
          description: File(s) uploaded and stored
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/UploadResponse"
                  - $ref: "#/components/schemas/UploadBatchResponse"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/get:
    post:
      tags:
        - Documents
      summary: Get document
      description: |
        Retrieve a document by collection, key, and language. Supports template variable substitution using the `env` parameter.
      operationId: getDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GetRequest"
      responses:
        "200":
          description: Document retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Document not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/search:
    post:
      tags:
        - Search
      summary: Search documents
      description: |
        Search documents with metadata filtering, sorting, and pagination.

        **Filtering:** AND logic between different metadata keys, OR logic between values of the same key.

        **Sorting:** Sort by `addedAt`, `updatedAt`, or `key`.
      operationId: searchDocuments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchRequest"
      responses:
        "200":
          description: Search results
          headers:
            X-Total-Count:
              description: Total number of matching documents (before pagination)
              schema:
                type: integer
                example: 234
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Document"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/delete:
    post:
      tags:
        - Documents
      summary: Delete document
      description: Delete a specific document by collection, key, and language
      operationId: deleteDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteRequest"
      responses:
        "200":
          description: Document deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: boolean
                    example: true
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/delete-batch:
    post:
      tags:
        - Documents
      summary: Batch delete documents
      description: Delete multiple documents in a single request
      operationId: deleteDocumentsBatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - documents
              properties:
                collection:
                  type: string
                  description: Target collection
                  example: blog
                documents:
                  type: array
                  items:
                    type: object
                    properties:
                      key:
                        type: string
                      lang:
                        type: string
                  example:
                    - key: post-1
                      lang: en
                    - key: post-2
                      lang: en
      responses:
        "200":
          description: Batch delete result
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                    example: 2
                  not_found:
                    type: integer
                    example: 0
                  failed:
                    type: integer
                    example: 0
                  errors:
                    type: array
                    items:
                      type: string
                    nullable: true
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/delete-collection:
    post:
      tags:
        - Documents
      summary: Delete collection
      description: Delete all documents in a collection
      operationId: deleteCollection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteCollectionRequest"
      responses:
        "200":
          description: Collection deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                    description: Number of documents deleted
                    example: 42
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/export:
    post:
      tags:
        - Export
      summary: Export documents
      description: |
        Export documents in NDJSON or ZIP format with optional metadata filtering.

        **NDJSON:** Newline-delimited JSON, one document per line.

        **ZIP:** Archive with markdown files named `{key}.{lang}.md`.
      operationId: exportDocuments
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ExportRequest"
      responses:
        "200":
          description: Export file
          content:
            application/x-ndjson:
              schema:
                type: string
                format: binary
            application/zip:
              schema:
                type: string
                format: binary
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/backup:
    get:
      tags:
        - Export
      summary: Create backup
      description: Create a backup of the database file
      operationId: createBackup
      parameters:
        - name: to
          in: query
          required: true
          description: Destination path for backup file
          schema:
            type: string
            example: backup-20250109.db
      responses:
        "200":
          description: Backup created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  backup:
                    type: string
                    example: backup-20250109.db
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/restore:
    post:
      tags:
        - Export
      summary: Restore from backup
      description: Restore database from a backup file
      operationId: restoreBackup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - from
              properties:
                from:
                  type: string
                  description: Path to backup file
                  example: backup-20250109.db
      responses:
        "200":
          description: Database restored successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  restored:
                    type: string
                    example: backup-20250109.db
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: >-
            Restore failed (unusable backup, copy or reopen error). The
            previous database is rolled back and keeps serving — the backup is
            validated before the live file is touched, and a pre-restore
            snapshot guards the swap.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/truncate:
    post:
      tags:
        - Maintenance
      summary: Truncate revision history
      description: |
        Remove old revisions to save disk space. Keeps the latest N revisions per document.

        **Warning:** This operation is irreversible.
      operationId: truncateRevisions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TruncateRequest"
      responses:
        "200":
          description: Revisions truncated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                    description: Number of revisions deleted
                    example: 150
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/vector-search:
    post:
      tags:
        - Vector Search
      summary: Semantic vector search
      description: |
        Search documents by semantic similarity using vector embeddings.

        Provide either a `query` string (which will be embedded automatically using the configured provider) or a raw `queryVector` array of floats.

        Results are ranked by cosine similarity score. Use `threshold` to filter out low-relevance results. Use `filterMeta` to narrow results by metadata before ranking.

        Use `algorithm` to select the search strategy: `flat` (exact), `hnsw` (fast approximate), `ivf` (clustered), or one of the quantized indexes `pq`/`opq`/`sq`/`sq4`/`bq` (compressed, smallest first). See [Search Algorithms](SEARCH.md) for details.
      operationId: vectorSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VectorSearchRequest"
      responses:
        "200":
          description: Vector search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VectorSearchResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Vector index is still loading
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/mcp/keys:
    post:
      tags:
        - MCP
      summary: Create MCP API key
      description: |
        Creates a named API key for MCP clients. The full key is returned
        **once, in this response only** — the listing exposes a fingerprint
        rather than key material, so it cannot be recovered afterwards.

        Requires admin authentication.
      operationId: createMCPKey
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Label identifying what the key is for.
                  example: claude-desktop
                expiresAt:
                  type: integer
                  format: int64
                  description: Unix timestamp; omit or 0 for a key that never expires.
                  example: 0
      responses:
        "200":
          description: Key created (plaintext returned once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  key:
                    type: string
                    description: The full key — store it now, it is not retrievable later.
                    example: mcp_4f3c2a1b8e7d6c5b4a39281706f5e4d3
                  name:
                    type: string
                    example: claude-desktop
                  createdAt:
                    type: integer
                    format: int64
                  expiresAt:
                    type: integer
                    format: int64
        "400":
          description: Invalid request body or key name
        "403":
          description: Admin permission required
        "503":
          description: MCP API key store not initialized
    get:
      tags:
        - MCP
      summary: List MCP API keys
      description: |
        Lists the registered MCP API keys. No key material is returned: each
        entry carries a `fingerprint` (four bytes of SHA-256, hex) that also
        identifies the key in the server log, while `keyPrefix` holds only the
        `mcp_` scheme marker.

        Requires admin authentication.
      operationId: listMCPKeys
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Key list
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/MCPAPIKeySummary"
                  total:
                    type: integer
                    example: 2
        "403":
          description: Admin permission required
        "503":
          description: MCP API key store not initialized
    delete:
      tags:
        - MCP
      summary: Delete MCP API key
      description: |
        Permanently removes a key, identified by its full value. The MCP auth
        cache is invalidated, so the key stops working immediately.

        Requires admin authentication.
      operationId: deleteMCPKey
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MCPAPIKeyRef"
      responses:
        "200":
          description: Key deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted
        "400":
          description: Invalid request body
        "403":
          description: Admin permission required
        "404":
          description: No such key
        "503":
          description: MCP API key store not initialized

  /v1/mcp/keys/disable:
    post:
      tags:
        - MCP
      summary: Disable MCP API key
      description: |
        Marks a key disabled without deleting it, so it stops authenticating
        but stays visible in the listing.

        Requires admin authentication.
      operationId: disableMCPKey
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MCPAPIKeyRef"
      responses:
        "200":
          description: Key disabled
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: disabled
        "400":
          description: Invalid request body
        "403":
          description: Admin permission required
        "404":
          description: No such key
        "503":
          description: MCP API key store not initialized


  /v1/vector-projection:
    post:
      tags:
        - Vector Search
      summary: Project embeddings to 2D (PCA)
      description: |
        Server-side PCA projection of a collection's embedding vectors to 2D
        for visual exploration (cluster structure, outliers, query
        diagnostics). Large collections are sampled deterministically; a text
        query or raw vector can be projected alongside the points.
      operationId: vectorProjection
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
              properties:
                collection:
                  type: string
                  description: Collection to project
                  example: kb
                sample:
                  type: integer
                  description: Maximum points to project (default 1000, capped at 2000)
                  default: 1000
                  example: 1000
                query:
                  type: string
                  description: Optional text query to embed and overlay on the projection
                queryVector:
                  type: array
                  items:
                    type: number
                    format: float
                  description: Optional pre-computed query vector to overlay
      responses:
        "200":
          description: Projected points
          content:
            application/json:
              schema:
                type: object
                properties:
                  points:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, description: "Index key (docID or docID#N)" }
                        key: { type: string, description: Document key }
                        docId: { type: string }
                        chunkIndex: { type: integer }
                        x: { type: number }
                        y: { type: number }
                  query:
                    type: object
                    nullable: true
                    description: Projected query point (when query/queryVector provided)
                  total: { type: integer, description: Vectors in the collection }
                  sampled: { type: integer, description: Vectors actually projected }
                  dimensions: { type: integer }
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/vector-reindex:
    post:
      tags:
        - Vector Search
      summary: Reindex collection embeddings
      description: |
        Trigger re-embedding of all documents in a collection. This is a write-protected endpoint.

        Use `force: true` to re-embed all documents even if their content has not changed since the last embedding.
      operationId: vectorReindex
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VectorReindexRequest"
      responses:
        "200":
          description: Reindex completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VectorReindexResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/vector-stats:
    get:
      tags:
        - Vector Search
      summary: Vector search statistics
      description: Get the current status of the vector search subsystem, including
        provider info, index readiness, and per-collection embedding counts.
      operationId: vectorStats
      responses:
        "200":
          description: Vector stats retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VectorStatsResponse"

  /v1/geo-search:
    post:
      tags:
        - Geo Search
      summary: Radius search (R-tree or geohash)
      description: |
        Find documents within `radiusMeters` of a (lat, lng) point. Documents
        must have `geo_lat`+`geo_lng`, `geo_hash`, or a resolvable
        `geo_postcode`+`geo_country` in their metadata. Results are sorted by
        ascending distance and composable with `filterMeta`.
      operationId: geoSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection, lat, lng, radiusMeters ]
              properties:
                collection: { type: string }
                lat: { type: number, format: double }
                lng: { type: number, format: double }
                radiusMeters: { type: number, format: double }
                topK: { type: integer, default: 10 }
                algorithm: { type: string, enum: [ rtree, geohash ], default: rtree }
                filterMeta:
                  {
                    type: object,
                    additionalProperties: { type: array, items: { type: string } },
                  }
                includeContent: { type: boolean, default: false }
      responses:
        "200": { description: OK }
        "400": { description: Invalid request }
        "503": { description: Index still loading }

  /v1/geo-within:
    post:
      tags:
        - Geo Search
      summary: Bounding-box search
      description: Find documents inside an axis-aligned bbox. Does not cross the
        anti-meridian.
      operationId: geoWithin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection, minLat, maxLat, minLng, maxLng ]
              properties:
                collection: { type: string }
                minLat: { type: number }
                maxLat: { type: number }
                minLng: { type: number }
                maxLng: { type: number }
                filterMeta:
                  {
                    type: object,
                    additionalProperties: { type: array, items: { type: string } },
                  }
                includeContent: { type: boolean }
      responses:
        "200": { description: OK }

  /v1/geo-polygon:
    post:
      tags:
        - Geo Search
      summary: Find documents inside a GeoJSON polygon or multi-polygon
      description: |
        Returns every indexed point whose coordinates fall inside the supplied
        GeoJSON Polygon (outer ring + optional holes) or MultiPolygon (union
        of polygons). Exactly one of `polygon` or `multiPolygon` must be set.

        The R-tree's bounding-box query prefilters candidates, so response
        time tracks the shape's bbox rather than the full collection size.
        GeoJSON coordinate order is `[lng, lat]` per RFC 7946. Rings may be
        open (first ≠ last) or closed (first == last); both forms work.

        Does not cross the anti-meridian — split the query into hemispheres
        if you need that.
      operationId: geoPolygon
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GeoPolygonRequest"
      responses:
        "200":
          description: Matches (empty array if none)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GeoPolygonResponse"
        "400":
          description: Invalid request — missing collection, both/neither shape supplied,
            or malformed geometry
        "503":
          description: Geo index still loading

  /v1/geo-reindex:
    post:
      tags:
        - Geo Search
      summary: Force-rebuild geo indexes
      description: Rebuild both R-tree and geohash indexes from the persisted `geo`
        bucket. Optionally load one or more postcode CSVs first.
      operationId: geoReindex
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                collection: { type: string, description: "Empty = rebuild all collections" }
                loadPostcodes:
                  type: array
                  items:
                    type: object
                    properties:
                      country: { type: string }
                      csvPath: { type: string }
      responses:
        "200": { description: OK }

  /v1/geo-stats:
    get:
      tags:
        - Geo Search
      summary: Geo index statistics
      operationId: geoStats
      responses:
        "200": { description: OK }

  /v1/geo-encode:
    post:
      tags:
        - Geo Search
      summary: Encode (lat, lng) → geohash string
      operationId: geoEncode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ lat, lng ]
              properties:
                lat: { type: number }
                lng: { type: number }
                precision: { type: integer, minimum: 1, maximum: 12, default: 12 }
      responses:
        "200": { description: OK }

  /v1/geo-decode:
    post:
      tags:
        - Geo Search
      summary: Decode geohash → centroid + bbox
      operationId: geoDecode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ geohash ]
              properties:
                geohash: { type: string }
      responses:
        "200": { description: OK }

  /v1/fts:
    post:
      tags:
        - Full-Text Search
      summary: Full-text search
      description: |
        Perform full-text search across documents in a collection. Supports 7 search modes with auto-detection and multi-language stemming (18 languages).

        **Search modes** (`mode` parameter, default `auto`):
        - `simple`: Classic keyword search scored by algorithm (TF-IDF, BM25, BM25F, PMISparse)
        - `boolean`: AND/OR/NOT operators, `+required`, `-excluded` terms
        - `phrase`: Exact word sequence matching using `"quoted phrases"`
        - `wildcard`: Pattern matching with `*` (any chars) and `?` (single char)
        - `proximity`: Terms within N words — `"term1 term2"~5`
        - `auto` (default): Auto-detects mode from query syntax (quotes → phrase, operators → boolean, etc.)

        **Algorithms** (for simple/boolean modes):
        - `tfidf` (default): Classic TF-IDF scoring
        - `bm25`: Okapi BM25 with document length normalization (k1=1.2, b=0.75)
        - `bm25f`: BM25F field-weighted scoring — applies different weights to title, metadata, and content fields
        - `pmisparse`: PMISparse — BM25 + PMI query expansion

        **Range filtering:** Use `rangeMeta` to filter by numeric, date, or string ranges on metadata fields.

        **In-graph filtering:** Use `filterMeta` to narrow results by metadata before scoring, same as vector search.
      operationId: fullTextSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FTSSearchRequest"
      responses:
        "200":
          description: Full-text search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FTSSearchResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/fts-reindex:
    post:
      tags:
        - Full-Text Search
      summary: Reindex FTS for a collection
      description: |
        Re-indexes all documents in a collection using their stored `lang` field for language-aware stemming and stop words.
        Use this after enabling multi-language support to update existing documents indexed with the default English pipeline.
      operationId: ftsReindex
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name to reindex
      responses:
        "200":
          description: Reindex completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  reindexed:
                    type: integer
                    description: Number of documents reindexed
                  collection:
                    type: string
        "400":
          description: Missing collection parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/autocomplete:
    get:
      tags:
        - Full-Text Search
      summary: Prefix autocomplete
      description: |
        Return up to `topN` terms starting with the given query, ranked by
        document frequency. Uses the existing FTS inverted index — no
        additional indexing is required.

        When `field` is supplied, the BM25F field-scoped index drives the
        scan so callers can autocomplete titles, tags, or body content
        independently. A single call scans at most 10000 index entries to
        keep latency bounded on pathological prefixes like "a".
      operationId: autocomplete
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
        - name: q
          in: query
          required: true
          schema:
            type: string
          description: Prefix query — lowercased and stripped of non-alphanumerics
          example: mar
        - name: field
          in: query
          required: false
          schema:
            type: string
          description: Optional field scope (e.g. `title`, `content`, `meta.tags`); empty
            means global
        - name: topN
          in: query
          required: false
          schema:
            type: integer
            default: 10
          description: Maximum number of suggestions returned
      responses:
        "200":
          description: Suggestions
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        term:
                          type: string
                          example: market
                        field:
                          type: string
                          example: title
                        docCount:
                          type: integer
                          example: 42
                  total:
                    type: integer
                  query:
                    type: string
                  field:
                    type: string

  /v1/fts-languages:
    get:
      tags:
        - Full-Text Search
      summary: List supported FTS languages
      description: Returns all supported languages for multi-language FTS with their
        codes, names, and the configured default language.
      operationId: ftsLanguages
      responses:
        "200":
          description: Supported languages
          content:
            application/json:
              schema:
                type: object
                properties:
                  languages:
                    type: array
                    items:
                      type: object
                      properties:
                        code:
                          type: string
                          example: en
                        name:
                          type: string
                          example: English
                  defaultLang:
                    type: string
                    example: en

  /v1/hybrid-search:
    post:
      tags:
        - Hybrid Search
      summary: Hybrid sparse+dense search
      description: |
        Combine FTS (keyword) and vector (semantic) search into a single query. Returns results ranked by a fused score.

        **Fusion strategies:**
        - `alpha` (default): Weighted alpha blending — `combined = (1-alpha) * normalizedFTS + alpha * vectorScore`. Alpha=0 is pure keyword, alpha=1 is pure semantic.
        - `rrf`: Reciprocal Rank Fusion — `score = 1/(k + rank_fts) + 1/(k + rank_vector)`. Rank-based, no alpha needed.

        **FTS algorithms:** `bm25` (default), `bm25f` (field-weighted).

        **Vector algorithms:** `flat` (default), `hnsw`, `ivf`, `pq`, `opq`, `sq`, `sq4`, `bq`.

        Use `filterMeta` to pre-filter by metadata before both FTS and vector scoring.
      operationId: hybridSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HybridSearchRequest"
      responses:
        "200":
          description: Hybrid search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HybridSearchResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/import-url:
    post:
      tags:
        - Import
      summary: Import document from URL
      description: |
        Fetch content from a URL, parse YAML frontmatter if present, merge metadata, and store the document. This is a write-protected endpoint.

        If `key` is not provided, it is derived from the URL path. YAML frontmatter metadata from the fetched content is merged with the `meta` parameter.
      operationId: importUrl
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ImportURLRequest"
      responses:
        "200":
          description: Document imported successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/import-wiki:
    post:
      tags:
        - Import
      summary: Import Wikipedia XML dump
      description: |
        Stream and import MediaWiki XML dumps (`.xml` or `.xml.bz2`) into a collection.
        Automatically converts wikitext to Markdown. Streams the XML — does not load the entire file into memory.
        Supports namespace filtering, redirect skipping, and batch processing with progress logging.
      operationId: importWiki
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - collection
                - lang
              properties:
                file:
                  type: string
                  format: binary
                  description: Wikipedia XML dump file (.xml or .xml.bz2)
                collection:
                  type: string
                  description: Target collection name
                lang:
                  type: string
                  description: Language code (e.g. en, de, pl)
                namespaces:
                  type: string
                  description: "Comma-separated namespace IDs (default: 0 = articles only)"
                skipRedirects:
                  type: string
                  enum: [ "true", "false" ]
                  description: Skip redirect pages
                skipFts:
                  type: string
                  enum: [ "true", "false" ]
                  description: Skip FTS indexing for faster bulk import
                maxPages:
                  type: string
                  description: Maximum number of pages to import
                batchSize:
                  type: string
                  description: "Pages per batch commit (default: 500)"
          application/octet-stream:
            schema:
              type: string
              format: binary
          application/x-bzip2:
            schema:
              type: string
              format: binary
      parameters:
        - name: collection
          in: query
          schema:
            type: string
          description: Target collection (for octet-stream/bzip2 uploads)
        - name: lang
          in: query
          schema:
            type: string
          description: Language code (for octet-stream/bzip2 uploads)
        - name: namespaces
          in: query
          schema:
            type: string
          description: "Comma-separated namespace IDs (default: 0)"
        - name: skipRedirects
          in: query
          schema:
            type: string
        - name: skipFts
          in: query
          schema:
            type: string
        - name: maxPages
          in: query
          schema:
            type: integer
        - name: batchSize
          in: query
          schema:
            type: integer
        - name: filename
          in: query
          schema:
            type: string
          description: Filename hint for compression detection (e.g. dump.xml.bz2)
      responses:
        "200":
          description: Import completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WikiImportResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/set-ttl:
    post:
      tags:
        - TTL
      summary: Set document TTL
      description: |
        Set a time-to-live (TTL) on a document. After the TTL expires, the document will be automatically deleted. This is a write-protected endpoint.

        Set `ttl` to `0` to remove the TTL and make the document permanent again.
      operationId: setTTL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetTTLRequest"
      responses:
        "200":
          description: TTL set successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks
      description: List all registered webhooks.
      operationId: listWebhooks
      responses:
        "200":
          description: List of registered webhooks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Webhook"
    post:
      tags:
        - Webhooks
      summary: Register webhook
      description: |
        Register a new webhook to receive event notifications. This is a write-protected endpoint.

        If `collection` is empty, the webhook will receive events for all collections.
      operationId: registerWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterWebhookRequest"
      responses:
        "200":
          description: Webhook registered successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/webhooks/delete:
    post:
      tags:
        - Webhooks
      summary: Delete webhook
      description: Delete a registered webhook by its ID. This is a write-protected
        endpoint.
      operationId: deleteWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteWebhookRequest"
      responses:
        "200":
          description: Webhook deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted
                  id:
                    type: string
                    example: abc123
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/schema/set:
    post:
      tags:
        - Schema Validation
      summary: Set collection schema
      description: |
        Set a JSON Schema for a collection's metadata validation. This is a write-protected endpoint.

        Once set, all documents added to the collection will have their metadata validated against this schema.
      operationId: setSchema
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetSchemaRequest"
      responses:
        "200":
          description: Schema set successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  collection:
                    type: string
                    example: blog
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/schema/get:
    post:
      tags:
        - Schema Validation
      summary: Get collection schema
      description: Retrieve the JSON Schema currently set for a collection.
      operationId: getSchema
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
              properties:
                collection:
                  type: string
                  description: Collection name
                  example: blog
      responses:
        "200":
          description: Schema retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SchemaResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/schema/delete:
    post:
      tags:
        - Schema Validation
      summary: Delete collection schema
      description: Remove the JSON Schema for a collection, disabling metadata
        validation. This is a write-protected endpoint.
      operationId: deleteSchema
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
              properties:
                collection:
                  type: string
                  description: Collection name
                  example: blog
      responses:
        "200":
          description: Schema deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  collection:
                    type: string
                    example: blog
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Read-only mode
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/schema/list:
    post:
      tags:
        - Schema Validation
      summary: List all schemas
      description: List all JSON Schemas set across all collections.
      operationId: listSchemas
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: List of schemas
          content:
            application/json:
              schema:
                type: object
                properties:
                  schemas:
                    type: array
                    items:
                      type: object
                      properties:
                        collection:
                          type: string
                          example: blog
                        schema:
                          type: string
                          description: JSON Schema string
                          example: "{\"type\":\"object\",\"required\":[\"author\"]}"

  /v1/validate:
    post:
      tags:
        - Schema Validation
      summary: Validate metadata against schema
      description: |
        Validate a metadata object against the JSON Schema set for a collection.

        This is a dry-run validation that does not modify any data. Useful for pre-validating metadata before adding a document.
      operationId: validateMetadata
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ValidateRequest"
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ValidateResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /metrics:
    get:
      tags:
        - Telemetry
      summary: Prometheus metrics
      description: |
        Returns server telemetry in Prometheus text exposition format.

        Includes HTTP request counters, latency histograms, database statistics,
        vector search status, and Go runtime metrics. Enabled by default
        (set `MDDB_METRICS=false` to disable).

        See [Telemetry documentation](TELEMETRY.md) for Grafana dashboards and alerting rules.
      operationId: getMetrics
      responses:
        "200":
          description: Prometheus metrics
          content:
            text/plain:
              schema:
                type: string
                example: |
                  # HELP mddb_uptime_seconds Time since server start in seconds.
                  # TYPE mddb_uptime_seconds gauge
                  mddb_uptime_seconds 3621.4
                  # HELP mddb_http_requests_total Total number of HTTP requests.
                  # TYPE mddb_http_requests_total counter
                  mddb_http_requests_total{method="POST",path="/v1/add",status="200"} 1523
        "404":
          description: Metrics disabled (MDDB_METRICS=false)

  /v1/replication/status:
    get:
      tags:
        - Replication
      summary: Replication status
      description: |
        Returns the replication status of this node, including role, LSN, binlog stats,
        and connected followers (leader only).

        Available for all roles: standalone, leader, and follower.

        ### Replication Architecture
        MDDB uses a pull-based, single-leader replication model. Wait for the `healthy` flag to determine if the node is fully synced.

        ```mermaid
        graph LR
            L[Leader] -->|Binlog Stream| F1[Follower 1]
            L -->|Binlog Stream| F2[Follower 2]
        ```

        See [Replication documentation](REPLICATION.md) for full setup instructions and monitoring.
      operationId: getReplicationStatus
      responses:
        "200":
          description: Replication status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReplicationStatusResponse"

  /v1/synonyms:
    get:
      tags:
        - Synonyms
      summary: List synonyms
      description: List all synonym entries for a collection. Synonyms are used to
        expand FTS queries with related terms.
      operationId: listSynonyms
      parameters:
        - name: collection
          in: query
          required: true
          description: Collection name
          schema:
            type: string
            example: blog
      responses:
        "200":
          description: Synonym list retrieved successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SynonymListResponse"
        "400":
          description: Invalid request (missing collection parameter)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Synonyms
      summary: Add or update synonym group
      description: |
        Add or update a synonym mapping for a term in a collection. Synonyms are bidirectional --
        if "big" has synonym "large", then querying "large" will also expand to include "big".
      operationId: setSynonym
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SynonymRequest"
      responses:
        "200":
          description: Synonym group set successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Synonyms
      summary: Delete synonym group
      description: Delete a synonym mapping for a term in a collection.
      operationId: deleteSynonym
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SynonymDeleteRequest"
      responses:
        "200":
          description: Synonym group deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/stopwords:
    get:
      tags:
        - Stop Words
      summary: List stop words
      description: List all stop word entries (default + custom) for a collection.
        Stop words are excluded from FTS indexing.
      operationId: listStopWords
      parameters:
        - name: collection
          in: query
          required: true
          description: Collection name
          schema:
            type: string
            example: blog
      responses:
        "200":
          description: Stop word list retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                    example: blog
                  entries:
                    type: array
                    items:
                      type: object
                      properties:
                        word:
                          type: string
                          example: the
                        isDefault:
                          type: boolean
                          example: true
                  total:
                    type: integer
                    example: 35
                  defaults:
                    type: integer
                    example: 33
                  custom:
                    type: integer
                    example: 2
        "400":
          description: Invalid request (missing collection parameter)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Stop Words
      summary: Add custom stop words
      description: Add custom stop words to a collection. These words will be excluded
        from FTS indexing.
      operationId: addStopWords
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - words
              properties:
                collection:
                  type: string
                  example: blog
                words:
                  type: array
                  items:
                    type: string
                  example: [ "foo", "bar" ]
      responses:
        "200":
          description: Stop words added successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  added:
                    type: integer
                    example: 2
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Stop Words
      summary: Delete custom stop words
      description: Delete custom stop words from a collection. Default stop words
        cannot be deleted.
      operationId: deleteStopWords
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - words
              properties:
                collection:
                  type: string
                  example: blog
                words:
                  type: array
                  items:
                    type: string
                  example: [ "foo" ]
      responses:
        "200":
          description: Stop words deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  deleted:
                    type: integer
                    example: 1
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/embedding-configs:
    get:
      tags:
        - Embedding Config
      summary: List embedding configurations
      description: List all embedding model configurations.
      operationId: listEmbeddingConfigs
      responses:
        "200":
          description: List of embedding configurations
          content:
            application/json:
              schema:
                type: object
                properties:
                  configs:
                    type: array
                    items:
                      $ref: "#/components/schemas/EmbeddingConfig"
    post:
      tags:
        - Embedding Config
      summary: Create embedding configuration
      description: |
        Create a new embedding model configuration. Required fields: id, name, provider, model, dimensions.

        Supported providers: `openai`, `ollama`, `cohere`, `voyage`.
      operationId: createEmbeddingConfig
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingConfig"
      responses:
        "201":
          description: Embedding configuration created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingConfig"
        "400":
          description: Invalid request (missing required fields or invalid provider)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/embedding-configs/{id}:
    get:
      tags:
        - Embedding Config
      summary: Get embedding configuration
      description: Retrieve a specific embedding configuration by ID.
      operationId: getEmbeddingConfig
      parameters:
        - name: id
          in: path
          required: true
          description: Embedding configuration ID
          schema:
            type: string
            example: openai-small
      responses:
        "200":
          description: Embedding configuration retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingConfig"
        "404":
          description: Configuration not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Embedding Config
      summary: Update embedding configuration
      description: |
        Update an existing embedding configuration. The ID in the path is used (body ID is ignored).

        Supported providers: `openai`, `ollama`, `cohere`, `voyage`.
      operationId: updateEmbeddingConfig
      parameters:
        - name: id
          in: path
          required: true
          description: Embedding configuration ID
          schema:
            type: string
            example: openai-small
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EmbeddingConfig"
      responses:
        "200":
          description: Embedding configuration updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmbeddingConfig"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Embedding Config
      summary: Delete embedding configuration
      description: Delete an embedding configuration. Cannot delete the default
        configuration.
      operationId: deleteEmbeddingConfig
      parameters:
        - name: id
          in: path
          required: true
          description: Embedding configuration ID
          schema:
            type: string
            example: openai-small
      responses:
        "204":
          description: Embedding configuration deleted
        "400":
          description: Cannot delete default config
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Configuration not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/embedding-configs/set-default:
    post:
      tags:
        - Embedding Config
      summary: Set default embedding configuration
      description: |
        Set an embedding configuration as the default. This will unset any previously default configuration
        and reinitialize the embedding system with the new default config.
      operationId: setDefaultEmbeddingConfig
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - id
              properties:
                id:
                  type: string
                  description: ID of the embedding configuration to set as default
                  example: openai-small
      responses:
        "200":
          description: Default configuration updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: default config updated
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Configuration not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/system/info:
    get:
      tags:
        - System
      summary: System information
      description: |
        Returns detailed system information including hostname, OS, architecture, CPU count,
        Go version, memory statistics, goroutine count, CPU usage, and network interfaces.
      operationId: getSystemInfo
      responses:
        "200":
          description: System information retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SystemInfoResponse"

  /v1/config:
    get:
      tags:
        - Configuration
      summary: Server configuration
      description: |
        Returns the current server configuration including version, database path, mode,
        protocol statuses, authentication state, and vector/chunk configuration.
      operationId: getConfig
      responses:
        "200":
          description: Server configuration retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConfigResponse"

  /v1/mcp/config:
    get:
      tags:
        - Configuration
      summary: MCP YAML configuration
      description: |
        Returns a YAML configuration template for the MCP (Model Context Protocol) server.
        Includes gRPC and REST connection settings and example custom tool configurations.
      operationId: getMCPConfig
      responses:
        "200":
          description: MCP configuration in YAML format
          content:
            text/yaml:
              schema:
                type: string
                example: |
                  mcp:
                    # listenAddress: "0.0.0.0:9000"
                  mddb:
                    grpcAddress: "localhost:11024"
                    restBaseUrl: "http://localhost:11023"
                    transportMode: "grpc_with_rest_fallback"
                    timeoutSeconds: 2
                    maxRetries: 1

  /v1/endpoints:
    get:
      tags:
        - System
      summary: List all endpoints
      description: |
        Returns a comprehensive list of all available HTTP endpoints, gRPC methods, and MCP tools
        supported by this server instance.
      operationId: listEndpoints
      responses:
        "200":
          description: Endpoint list retrieved
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EndpointsResponse"

  /v1/auth/login:
    post:
      tags:
        - Authentication
      summary: Login
      description: |
        Authenticate with username and password to receive a JWT token.
        The token should be included in subsequent requests via the `Authorization: Bearer <token>` header.
      operationId: authLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LoginRequest"
      responses:
        "200":
          description: Login successful
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LoginResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/register:
    post:
      tags:
        - Users
      summary: Register new user
      description: Create a new user account. Requires admin authentication.
      operationId: authRegister
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterRequest"
      responses:
        "200":
          description: User registered successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RegisterResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: User already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/api-key:
    post:
      tags:
        - Authentication
      summary: Create API key
      description: |
        Create a new API key for the authenticated user. The key is returned only once in the response.
        Use the `X-API-Key` header to authenticate with the key in subsequent requests.
      operationId: authCreateAPIKey
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAPIKeyRequest"
      responses:
        "200":
          description: API key created (key shown only once)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateAPIKeyResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/api-keys:
    get:
      tags:
        - Authentication
      summary: List API keys
      description: List all API keys for the authenticated user (key values are not
        included, only hashes).
      operationId: authListAPIKeys
      security:
        - BearerAuth: []
      responses:
        "200":
          description: List of API keys
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListAPIKeysResponse"
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/api-keys/{keyHash}:
    delete:
      tags:
        - Authentication
      summary: Delete API key
      description: Delete an API key by its hash. Users can only delete their own API keys.
      operationId: authDeleteAPIKey
      security:
        - BearerAuth: []
      parameters:
        - name: keyHash
          in: path
          required: true
          description: SHA256 hash of the API key
          schema:
            type: string
            example: a1b2c3d4e5f6...
      responses:
        "200":
          description: API key deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted
        "400":
          description: Key hash required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden (can only delete own keys)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: API key not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/me:
    post:
      tags:
        - Authentication
      summary: Get current user info
      description: Returns information about the currently authenticated user.
      operationId: authGetMe
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Current user info
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GetMeResponse"
        "401":
          description: Authentication required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/permissions:
    get:
      tags:
        - Users
      summary: Get user permissions
      description: Get all permissions for a specific user. Requires admin authentication.
      operationId: authGetPermissions
      security:
        - BearerAuth: []
      parameters:
        - name: username
          in: query
          required: true
          description: Username to get permissions for
          schema:
            type: string
            example: alice
      responses:
        "200":
          description: User permissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Permission"
        "400":
          description: Username parameter required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Users
      summary: Set user permissions
      description: Set permissions for a user on a specific collection. Requires admin
        authentication.
      operationId: authSetPermissions
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetPermissionRequest"
      responses:
        "200":
          description: Permission set successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SetPermissionResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/users:
    get:
      tags:
        - Users
      summary: List all users
      description: List all user accounts with their details. Requires admin authentication.
      operationId: authListUsers
      security:
        - BearerAuth: []
      responses:
        "200":
          description: List of users
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsersListResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/users/{username}:
    delete:
      tags:
        - Users
      summary: Delete user
      description: |
        Permanently deletes a user account and everything that grants it access:
        the record, its API keys, its per-collection permissions and its group
        memberships. The username becomes available again.

        Changed in 2.13.0. This used to disable the account and keep it, which
        held the name against every later registration while answering
        `{"status":"deleted"}`. A client that read the disabled record back
        after deleting will no longer find it.

        The audit log is unaffected — that is where the record of who existed
        and who removed them lives.

        Requires admin authentication.
      operationId: authDeleteUser
      security:
        - BearerAuth: []
      parameters:
        - name: username
          in: path
          required: true
          description: Username to delete
          schema:
            type: string
            example: alice
      responses:
        "200":
          description: User deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted
        "400":
          description: Username required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/groups:
    get:
      tags:
        - Groups
      summary: List all groups
      description: List all user groups. Requires admin authentication.
      operationId: authListGroups
      security:
        - BearerAuth: []
      responses:
        "200":
          description: List of groups
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupsListResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Groups
      summary: Create group
      description: Create a new user group. Requires admin authentication.
      operationId: authCreateGroup
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateGroupRequest"
      responses:
        "201":
          description: Group created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "400":
          description: Invalid request (missing name or group already exists)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/groups/{name}:
    get:
      tags:
        - Groups
      summary: Get group details
      description: Retrieve details for a specific group. Requires admin authentication.
      operationId: authGetGroup
      security:
        - BearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          description: Group name
          schema:
            type: string
            example: developers
      responses:
        "200":
          description: Group details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Groups
      summary: Update group
      description: Update a group's description and member list. Requires admin
        authentication.
      operationId: authUpdateGroup
      security:
        - BearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          description: Group name
          schema:
            type: string
            example: developers
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateGroupRequest"
      responses:
        "200":
          description: Group updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Groups
      summary: Delete group
      description: Delete a group and all its associated permissions. Requires admin
        authentication.
      operationId: authDeleteGroup
      security:
        - BearerAuth: []
      parameters:
        - name: name
          in: path
          required: true
          description: Group name
          schema:
            type: string
            example: developers
      responses:
        "200":
          description: Group deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/auth/group-permissions:
    get:
      tags:
        - Groups
      summary: Get group permissions
      description: Get all permissions for a specific group. Requires admin authentication.
      operationId: authGetGroupPermissions
      security:
        - BearerAuth: []
      parameters:
        - name: group
          in: query
          required: true
          description: Group name
          schema:
            type: string
            example: developers
      responses:
        "200":
          description: Group permissions
          content:
            application/json:
              schema:
                type: object
                properties:
                  permissions:
                    type: array
                    items:
                      $ref: "#/components/schemas/GroupPermission"
        "400":
          description: Group parameter required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Groups
      summary: Set group permission
      description: Set a collection-level permission for a group. Requires admin
        authentication.
      operationId: authSetGroupPermission
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GroupPermission"
      responses:
        "200":
          description: Permission set
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: permission set
        "400":
          description: Invalid request (missing fields or group not found)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Admin access required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /graphql:
    post:
      tags:
        - GraphQL
      summary: GraphQL API endpoint
      description: |
        Execute GraphQL queries and mutations against the MDDB API.
        Requires `MDDB_GRAPHQL_ENABLED=true` environment variable.

        Supports queries for documents, search, vector search, full-text search, and hybrid search.
        Supports mutations for adding, deleting, and managing documents.
      operationId: graphqlQuery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: GraphQL query or mutation string
                  example: "{ stats { totalDocuments } }"
                variables:
                  type: object
                  additionalProperties: true
                  description: GraphQL variables
                operationName:
                  type: string
                  description: Name of the operation to execute (if query contains multiple)
      responses:
        "200":
          description: GraphQL response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    description: Query result data
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        message:
                          type: string
                        path:
                          type: array
                          items:
                            type: string
                    description: GraphQL errors (if any)

  /playground:
    get:
      tags:
        - GraphQL
      summary: GraphQL Playground
      description: |
        Interactive GraphQL Playground UI for exploring and testing the MDDB GraphQL API.
        Requires `MDDB_GRAPHQL_ENABLED=true` environment variable.
      operationId: graphqlPlayground
      responses:
        "200":
          description: GraphQL Playground HTML page
          content:
            text/html:
              schema:
                type: string

  /v1/automation:
    get:
      tags: [ Automation ]
      summary: List automation rules
      description: Returns all automation rules (webhooks, triggers, crons).
        Optionally filter by type.
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [ webhook, trigger, cron ]
          description: Filter by rule type
      responses:
        "200":
          description: List of automation rules
          content:
            application/json:
              schema:
                type: object
                properties:
                  rules:
                    type: array
                    items:
                      $ref: "#/components/schemas/AutomationRule"
                  total:
                    type: integer
    post:
      tags: [ Automation ]
      summary: Create automation rule
      description: Create a new webhook target, trigger, or cron rule.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomationRule"
      responses:
        "201":
          description: Rule created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationRule"
        "400":
          description: Validation error

  /v1/automation/{id}:
    get:
      tags: [ Automation ]
      summary: Get automation rule
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Automation rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationRule"
        "404":
          description: Not found
    put:
      tags: [ Automation ]
      summary: Update automation rule
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AutomationRule"
      responses:
        "200":
          description: Rule updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AutomationRule"
    delete:
      tags: [ Automation ]
      summary: Delete automation rule
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Rule deleted

  /v1/automation/{id}/test:
    post:
      tags: [ Automation ]
      summary: Test trigger (dry run)
      description: Execute a trigger's search and return matching documents without
        firing the webhook.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Test results
          content:
            application/json:
              schema:
                type: object
                properties:
                  trigger:
                    type: object
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                      searchType:
                        type: string
                      query:
                        type: string
                      threshold:
                        type: number
                  matches:
                    type: array
                    items:
                      $ref: "#/components/schemas/TriggerMatch"
                  total:
                    type: integer

  /v1/automation-logs:
    get:
      tags: [ Automation ]
      summary: List automation execution logs
      description: Returns automation execution logs ordered newest-first with
        cursor-based pagination. Requires MDDB_AUTOMATION_LOGS != "disable".
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 500
          description: Max entries per page
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response
        - name: ruleId
          in: query
          schema:
            type: string
          description: Filter by rule ID
        - name: status
          in: query
          schema:
            type: string
            enum: [ success, error, skipped ]
          description: Filter by execution status
      responses:
        "200":
          description: Automation logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  logs:
                    type: array
                    items:
                      $ref: "#/components/schemas/AutomationLogEntry"
                  total:
                    type: integer
                  nextCursor:
                    type: string
                  hasMore:
                    type: boolean

  /v1/code-graph:
    get:
      tags:
        - Search
      summary: Code connection graph for one document
      description: >
        Answers the relational questions full-text search cannot: what breaks if
        this selector changes, which pages load this script, what does nothing
        reference any more. Edges are derived at query time from the
        `defines`/`uses`/`imports` metadata of code documents through the
        metadata index — nothing is stored, so a reindex reproduces the graph
        exactly. Resolution stays inside one collection.
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Document key, e.g. `theme/style.css`
        - name: direction
          in: query
          schema:
            type: string
            enum: [ in, out, both ]
            default: both
          description: >
            `in` = what depends on this document, `out` = what it depends on.
        - name: depth
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 3
            default: 1
          description: Hops to follow. Values above the maximum are clamped, not rejected.
        - name: maxDegree
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 100
          description: >
            Maximum neighbours per node. A selector such as `.title` appears in
            nearly every template, so an unbounded walk returns the whole theme.
        - name: lines
          in: query
          schema:
            type: boolean
            default: false
          description: >
            Include the first line each edge's symbol appears on, both sides.
            Off by default: it is the only part of a traversal that reads
            document content.
      responses:
        "200":
          description: The graph around the requested document
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodeGraphResponse"
        "400":
          description: Missing collection/key, or an unknown direction
        "404":
          description: No such document in the collection
    post:
      tags:
        - Search
      summary: Code connection graph for one document (JSON body)
      description: >
        Identical to the GET form. Accepts a JSON body for keys containing
        characters awkward in a query string.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection, key ]
              properties:
                collection:
                  type: string
                key:
                  type: string
                direction:
                  type: string
                  enum: [ in, out, both ]
                  default: both
                depth:
                  type: integer
                  minimum: 1
                  maximum: 3
                  default: 1
                maxDegree:
                  type: integer
                  minimum: 1
                  maximum: 100
                  default: 100
                lines:
                  type: boolean
                  default: false
                  description: Include first-occurrence line numbers for each edge.
              example:
                collection: theme
                key: theme/style.css
                direction: in
                depth: 2
      responses:
        "200":
          description: The graph around the requested document
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CodeGraphResponse"
        "400":
          description: Missing collection/key, or an unknown direction
        "404":
          description: No such document in the collection
  /v1/meta-keys:
    get:
      tags:
        - Search
      summary: List metadata keys and values for a collection
      description: Returns all unique metadata keys and their unique values for a
        given collection. Useful for building filter UIs.
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
      responses:
        "200":
          description: Metadata keys with their unique values
          content:
            application/json:
              schema:
                type: object
                properties:
                  meta:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: string
                example:
                  meta:
                    tag: [ "go", "rust", "python" ]
                    author: [ "alice", "bob" ]
        "400":
          description: Missing collection parameter

  /v1/checksum:
    get:
      tags:
        - Collections
      summary: Get collection checksum
      description: Returns a CRC32-based checksum for a collection that changes
        whenever documents are added, updated, or deleted. Useful for cache
        invalidation - clients can poll this lightweight endpoint to detect
        changes without downloading all documents.
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
      responses:
        "200":
          description: Collection checksum and document count
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                  checksum:
                    type: string
                    description: 8-character hex CRC32 checksum
                  documentCount:
                    type: integer
                example:
                  collection: "blog"
                  checksum: "a1b2c3d4"
                  documentCount: 42
        "400":
          description: Missing collection parameter

  /v1/update:
    patch:
      tags:
        - Documents
      summary: Partial document update
      description: |
        Update document metadata and/or content independently.
        Only provided fields are updated; omitted fields remain unchanged.
        Use `meta: {}` to clear all metadata.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - key
                - lang
              properties:
                collection:
                  type: string
                key:
                  type: string
                lang:
                  type: string
                meta:
                  type: object
                  description: "If provided, replaces all metadata. Use {} to clear."
                  additionalProperties:
                    type: array
                    items:
                      type: string
                contentMd:
                  type: string
                  description: "If provided, replaces document content."
                ttl:
                  type: integer
                  description: "If provided, updates TTL in seconds. 0 = no expiry."
            examples:
              meta-only:
                summary: Update only metadata
                value:
                  collection: "blog"
                  key: "p1"
                  lang: "en"
                  meta:
                    tag: [ "go", "updated" ]
              content-only:
                summary: Update only content
                value:
                  collection: "blog"
                  key: "p1"
                  lang: "en"
                  contentMd: "# New Content"
      responses:
        "200":
          description: Updated document
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Missing required fields or no fields to update
        "404":
          description: Document not found
        "405":
          description: Method not allowed

  /v1/doc-meta:
    get:
      tags:
        - Documents
      summary: Get document metadata only
      description: Returns metadata for a document without content. Lightweight read
        useful for checking tags without transferring document body.
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Document key
        - name: lang
          in: query
          schema:
            type: string
            default: "en"
          description: Language code (defaults to "en")
      responses:
        "200":
          description: Document metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  key:
                    type: string
                  lang:
                    type: string
                  meta:
                    type: object
                    additionalProperties:
                      type: array
                      items:
                        type: string
                  addedAt:
                    type: integer
                  updatedAt:
                    type: integer
                  expiresAt:
                    type: integer
                example:
                  key: "p1"
                  lang: "en"
                  meta:
                    tag: [ "go", "rust" ]
                    author: [ "alice" ]
                  addedAt: 1709600000
                  updatedAt: 1709610000
        "400":
          description: Missing required parameters
        "404":
          description: Document not found

  /v1/classify:
    post:
      tags:
        - Classification
      summary: Zero-shot document classification
      description: |
        Classify a document or text against candidate labels using embedding similarity.
        Provide either a document reference (collection + key + lang) or raw text, plus an array of candidate labels.
        Returns labels ranked by cosine similarity to the document/text embedding.
        Requires an embedding provider to be configured.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - labels
              properties:
                collection:
                  type: string
                  description: Collection name (for document reference)
                key:
                  type: string
                  description: Document key (for document reference)
                lang:
                  type: string
                  description: Language code (for document reference, defaults to "en")
                text:
                  type: string
                  description: Raw text to classify (alternative to document reference)
                labels:
                  type: array
                  items:
                    type: string
                  description: Candidate labels to rank by similarity (max 100)
                topK:
                  type: integer
                  description: Return top K labels (0 = all, default is all)
                multi:
                  type: boolean
                  description: If true, return all labels above threshold
                threshold:
                  type: number
                  description: Minimum similarity score (default 0.0)
            example:
              text: "Go is a statically typed, compiled language designed at Google"
              labels: [ "programming", "cooking", "sports", "music" ]
      responses:
        "200":
          description: Classification results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        label:
                          type: string
                        score:
                          type: number
                  model:
                    type: string
                  dimensions:
                    type: integer
              example:
                results:
                  - label: "programming"
                    score: 0.87
                  - label: "music"
                    score: 0.21
                  - label: "sports"
                    score: 0.18
                  - label: "cooking"
                    score: 0.12
                model: "text-embedding-3-small"
                dimensions: 1536
        "400":
          description: Missing labels, invalid request, or no embedding provider
        "404":
          description: Referenced document not found

  /v1/revisions:
    post:
      tags:
        - Revisions
      summary: List document revision history
      description: |
        Returns the revision history for a specific document, ordered by timestamp descending.
        Each revision includes the content and metadata at that point in time.
      operationId: listRevisions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - key
                - lang
              properties:
                collection:
                  type: string
                  description: Collection name
                  example: blog
                key:
                  type: string
                  description: Document key
                  example: hello-world
                lang:
                  type: string
                  description: Language code
                  example: en_US
      responses:
        "200":
          description: Revision history retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                    example: blog
                  key:
                    type: string
                    example: hello-world
                  lang:
                    type: string
                    example: en_US
                  revisions:
                    type: array
                    items:
                      $ref: "#/components/schemas/RevisionEntry"
                  total:
                    type: integer
                    description: Total number of revisions
                    example: 3
        "400":
          description: Missing required fields
        "404":
          description: Document not found

  /v1/revisions/restore:
    post:
      tags:
        - Revisions
      summary: Restore a document to a previous revision
      description: |
        Restores a document to the state it was in at a specific revision timestamp.
        The restored content becomes the current version of the document.
      operationId: restoreRevision
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
                - key
                - lang
                - timestamp
              properties:
                collection:
                  type: string
                  description: Collection name
                  example: blog
                key:
                  type: string
                  description: Document key
                  example: hello-world
                lang:
                  type: string
                  description: Language code
                  example: en_US
                timestamp:
                  type: integer
                  format: int64
                  description: Unix timestamp of the revision to restore
                  example: 1704844800
      responses:
        "200":
          description: Document restored successfully
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Document"
        "400":
          description: Missing required fields or invalid timestamp
        "404":
          description: Document or revision not found

  /mcp:
    post:
      tags:
        - MCP
      summary: MCP Streamable HTTP endpoint (2026-07-28 + 2025-11-25)
      description: |
        Streamable HTTP transport for both MCP revisions. Which one serves a request is
        decided per request: one carrying `io.modelcontextprotocol/protocolVersion` in
        `params._meta` is served statelessly under 2026-07-28, one without it under
        2025-11-25.

        Stateless (2026-07-28): server/discover, tools/list, tools/call, resources/list,
        resources/templates/list, resources/read, prompts/list, prompts/get,
        completion/complete, subscriptions/listen. Requires the MCP-Protocol-Version,
        Mcp-Method and (where a name or URI is addressed) Mcp-Name headers, which must
        match the body — a disagreement is 400 with JSON-RPC error -32020. No session is
        minted. An unimplemented method is 404 with a JSON-RPC -32601 body.

        Handshake (2025-11-25): initialize, tools/list, tools/call, resources/list,
        resources/templates/list, resources/read, prompts/list, prompts/get,
        completion/complete, logging/setLevel, ping, with session management via the
        MCP-Session-Id header.

        Notifications (no id) return 202 Accepted. Available on MCP port (default 9000).
        See docs/MCP-REVISIONS.md.
      operationId: mcpStreamableHTTP
      parameters:
        - name: MCP-Session-Id
          in: header
          description: >-
            Session identifier (assigned by the server at initialize). Handshake
            clients only; ignored, and never echoed, for a stateless request.
          schema:
            type: string
        - name: MCP-Protocol-Version
          in: header
          description: >-
            The revision this request is written in. Required for 2026-07-28, where
            it must equal params._meta['io.modelcontextprotocol/protocolVersion'];
            optional for 2025-11-25.
          schema:
            type: string
            example: "2026-07-28"
        - name: Mcp-Method
          in: header
          description: >-
            Mirrors the body's `method`. Required on every 2026-07-28 request so an
            intermediary can route without parsing the body.
          schema:
            type: string
            example: "tools/call"
        - name: Mcp-Name
          in: header
          description: >-
            Mirrors params.name (tools/call, prompts/get) or params.uri
            (resources/read). Required on those methods for 2026-07-28. A value
            outside plain ASCII is carried as =?base64?<base64>?=.
          schema:
            type: string
            example: "full_text_search"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                jsonrpc:
                  type: string
                  example: "2.0"
                id:
                  type: integer
                  example: 1
                method:
                  type: string
                  example: "tools/list"
                params:
                  type: object
      responses:
        "200":
          description: JSON-RPC response
          headers:
            MCP-Session-Id:
              description: Session identifier (present on initialize response)
              schema:
                type: string
          content:
            application/json:
              schema:
                type: object
        "202":
          description: Notification accepted (no response body)
        "400":
          description: >-
            Malformed or unservable request. The JSON-RPC body names which:
            -32020 (headers disagree with the body), -32022 (revision not served,
            with data.supported listing what is), or -32602 (a required _meta field
            is missing).
          content:
            application/json:
              schema:
                type: object
        "404":
          description: >-
            Method not implemented, with a JSON-RPC -32601 body. The body is what
            distinguishes this from a 404 at a URL hosting no MCP endpoint.
          content:
            application/json:
              schema:
                type: object
    get:
      tags:
        - MCP
      summary: MCP SSE stream for server-initiated messages (handshake clients)
      description: |
        Open an SSE stream for receiving server-initiated messages (notifications, progress).
        Requires MCP-Session-Id header from a previous initialize call.
        Belongs to 2025-11-25 only: 2026-07-28 removed the standalone stream in favour of
        subscriptions/listen, which answers on the POST that opened it.
      operationId: mcpStreamableSSE
      parameters:
        - name: MCP-Session-Id
          in: header
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string
    delete:
      tags:
        - MCP
      summary: Terminate MCP session
      description: Close an active MCP Streamable HTTP session.
      operationId: mcpTerminateSession
      parameters:
        - name: MCP-Session-Id
          in: header
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Session terminated

  /v1/events:
    get:
      tags:
        - Real-Time
      summary: Server-Sent Events stream
      description: |
        Subscribe to real-time events via SSE.

        Document events: `doc.added`, `doc.updated`, `doc.deleted`.

        Async job events: `job.started`, `job.progress`, `job.completed`,
        `job.failed`, `job.cancelled` — so a client can watch a bulk import
        finish instead of polling its status endpoint. Job events carry the
        same record `GET /v1/bulk/jobs/{id}` returns, under a `job` key.
        `job.progress` is throttled to one event per second per job; terminal
        events are not throttled.

        Default enabled (MDDB_SSE_ENABLED=false to disable).
      operationId: sseEvents
      parameters:
        - name: collection
          in: query
          description: Filter events to a specific collection (optional)
          schema:
            type: string
        - name: job
          in: query
          description: |
            Filter to one async job's events. A connection with this parameter
            receives that job's events only — no `doc.*` events — since a
            client asking for one job's stream wants the job, not every
            document change. Combines with `collection`.
          schema:
            type: string
      responses:
        "200":
          description: SSE event stream
          content:
            text/event-stream:
              schema:
                type: string

  /v1/audit:
    get:
      tags:
        - Audit
      summary: Query audit-log events (admin-only)
      description: |
        List structured audit-log events recorded by the server for ISO 27001
        A.8.15 / SOC 2 CC7.2 compliance. Admin JWT required. Returns 404 when
        `MDDB_AUDIT_ENABLED` is not set. Events are returned newest-first.
      operationId: auditQuery
      security:
        - bearerAuth: []
      parameters:
        - name: from
          in: query
          schema:
            type: string
            format: date-time
          description: RFC3339 lower bound (inclusive)
        - name: to
          in: query
          schema:
            type: string
            format: date-time
          description: RFC3339 upper bound (inclusive)
        - name: fromNanos
          in: query
          schema:
            type: integer
            format: int64
          description: Nanosecond-precision lower bound (overrides `from`)
        - name: toNanos
          in: query
          schema:
            type: integer
            format: int64
          description: Nanosecond-precision upper bound
        - name: actor
          in: query
          schema:
            type: string
          description: Filter by authenticated username
        - name: action
          in: query
          schema:
            type: string
          description: e.g. auth.login, auth.jwt, write./v1/add
        - name: result
          in: query
          schema:
            type: string
            enum: [ ok, fail ]
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
      responses:
        "200":
          description: Matching events
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      type: object
                      properties:
                        ts:
                          type: integer
                          format: int64
                        actor:
                          type: string
                        action:
                          type: string
                        resource:
                          type: string
                        collection:
                          type: string
                        key:
                          type: string
                        result:
                          type: string
                        ip:
                          type: string
                        userAgent:
                          type: string
                        detail:
                          type: string
                  count:
                    type: integer
                  dropped:
                    type: integer
                    format: int64
        "401":
          description: Missing or invalid token
        "403":
          description: Not an admin
        "404":
          description: Audit disabled
  /v1/audit/exporters:
    get:
      tags:
        - Audit
      summary: Per-sink audit export delivery counters (2.9.16+)
      description: |
        Returns a snapshot of every configured audit-log exporter (webhook,
        syslog) with delivered / failed / dropped counts and the last error.
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: Exporter status
          content:
            application/json:
              schema:
                type: object
                properties:
                  count:
                    type: integer
                  exporters:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        target:
                          type: string
                        queued:
                          type: integer
                          format: int64
                        delivered:
                          type: integer
                          format: int64
                        failed:
                          type: integer
                          format: int64
                        dropped:
                          type: integer
                          format: int64
                        lastError:
                          type: string
                        bufferSize:
                          type: integer
        "401":
          description: Missing or invalid token
        "403":
          description: Not an admin
        "404":
          description: Audit disabled
  /v1/encryption/status:
    get:
      tags:
        - Encryption
      summary: At-rest encryption posture (2.9.16+)
      description: |
        Primary keyID, configured previous keyIDs, and per-collection counts
        of how each document is sealed (primary / legacy / plaintext / unknown).
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: Encryption status
          content:
            application/json:
              schema:
                type: object
                properties:
                  enabled:
                    type: boolean
                  primaryKeyID:
                    type: integer
                  previousKeyIDs:
                    type: array
                    items:
                      type: integer
                  collections:
                    type: array
                    items:
                      type: object
                      properties:
                        collection:
                          type: string
                        encrypted:
                          type: boolean
                        total:
                          type: integer
                          format: int64
                        withPrimary:
                          type: integer
                          format: int64
                        withLegacy:
                          type: integer
                          format: int64
                        plaintext:
                          type: integer
                          format: int64
                        unknownKey:
                          type: integer
                          format: int64
                  currentJobID:
                    type: string
        "401":
          description: Missing or invalid token
        "403":
          description: Not an admin
  /v1/encryption/rotate:
    post:
      tags:
        - Encryption
      summary: Start a re-encryption job (2.9.16+)
      description: |
        Walks every encrypted entry sealed under a non-primary key and re-seals
        it with the current `MDDB_ENCRYPTION_KEY`. Plaintext entries and entries
        already under the primary keyID are skipped. Single-flight: calling
        rotate while a job is running returns the existing job's ID.
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                collection:
                  type: string
                  description: Optional scope; empty = all collections
      responses:
        "200":
          description: Rotation job descriptor
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RotationJob"
        "400":
          description: Encryption not configured
        "401":
          description: Missing or invalid token
        "403":
          description: Read-only mode or not an admin
  /v1/encryption/jobs:
    get:
      tags:
        - Encryption
      summary: List rotation jobs (2.9.16+)
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      responses:
        "200":
          description: Jobs list (newest first)
          content:
            application/json:
              schema:
                type: object
                properties:
                  jobs:
                    type: array
                    items:
                      $ref: "#/components/schemas/RotationJob"
  /v1/encryption/jobs/{id}:
    get:
      tags:
        - Encryption
      summary: Get a single key-rotation job
      description: Returns the status and progress of one encryption rotation job by ID.
      operationId: getEncryptionJob
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Rotation job ID
      responses:
        "200":
          description: Rotation job status
          content:
            application/json:
              schema:
                type: object
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/compliance-status:
    get:
      tags:
        - Compliance
      summary: Report the live state of the ISO 27001 / SOC 2 production guard
      description: |
        Returns the current compliance posture of the running server. No
        authentication required — designed for operator-facing liveness /
        readiness probes and external monitoring that must detect a drifted
        configuration. When `MDDB_PRODUCTION=true` is set and any guardrail is
        missing, the server refuses to boot, so a reachable endpoint with
        `production=true` implies `compliant=true`. When `MDDB_PRODUCTION` is
        unset the server runs with the existing defaults and this endpoint
        enumerates every missing control.
      operationId: complianceStatus
      responses:
        "200":
          description: Compliance posture
          content:
            application/json:
              schema:
                type: object
                required: [ production, compliant, missing, missingCount ]
                properties:
                  production:
                    type: boolean
                    description: True when MDDB_PRODUCTION=true was set at boot
                  compliant:
                    type: boolean
                    description: True when every guardrail is satisfied
                  missing:
                    type: array
                    items:
                      type: object
                      required: [ envVar, want, reason ]
                      properties:
                        envVar:
                          type: string
                          example: MDDB_AUDIT_ENABLED
                        want:
                          type: string
                          example: "true"
                        reason:
                          type: string
                          example: "A.8.15 / CC7.2 — audit trail"
                  missingCount:
                    type: integer
                    example: 0

  /v1/curation:
    get:
      tags:
        - Curation
      summary: List or fetch curation rules (v2.9.15+)
      description: |
        Pass `id` for a single rule, `collection` to list rules for one collection,
        or neither to list all rules (admin).
      parameters:
        - name: id
          in: query
          schema:
            type: string
        - name: collection
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Rule(s)
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/CurationRule"
                  - type: object
                    properties:
                      rules:
                        type: array
                        items:
                          $ref: "#/components/schemas/CurationRule"
                      total:
                        type: integer
                      collection:
                        type: string
    post:
      tags:
        - Curation
      summary: Create a curation rule (v2.9.15+)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CurationRule"
      responses:
        "200":
          description: Created rule with assigned id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurationRule"
    put:
      tags:
        - Curation
      summary: Replace an existing curation rule (v2.9.15+)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CurationRule"
      responses:
        "200":
          description: Updated rule
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurationRule"
    delete:
      tags:
        - Curation
      summary: Delete a curation rule (v2.9.15+)
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  id:
                    type: string

  /v1/search-advisor:
    get:
      tags:
        - Search
      summary: How should I search this collection?
      description: |
        Measures a collection and recommends the search type, ranking
        algorithm, vector index, fusion weights and result shape — with a
        plain-language reason for every choice (SRCH-010).

        Read-only by default. `apply=true` additionally writes the
        recommendation into the collection's retrieval profile, which needs
        write permission.

        Also available as the `search_advisor` MCP tool, annotated read-only,
        so an agent can ask before it searches instead of guessing from
        algorithm names.
      operationId: getSearchAdvisor
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
        - name: apply
          in: query
          required: false
          schema:
            type: boolean
            default: false
          description: Store the recommendation as the collection's retrieval profile.
      responses:
        "200":
          description: The recommendation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchRecommendation"
        "403":
          description: Forbidden, or read-only mode when apply=true
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags:
        - Search
      summary: How should I search this collection? (body form)
      operationId: postSearchAdvisor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection ]
              properties:
                collection:
                  type: string
                apply:
                  type: boolean
                  default: false
      responses:
        "200":
          description: The recommendation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchRecommendation"

  /v1/collection-config:
    get:
      tags:
        - Collection Config
      summary: Get collection configuration
      description: Get the configuration for a specific collection including type,
        description, icon, color, and custom metadata.
      operationId: getCollectionConfig
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
          example: blog
      responses:
        "200":
          description: Collection configuration retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                    example: blog
                  config:
                    $ref: "#/components/schemas/CollectionConfig"
                  configured:
                    type: boolean
                    description: Whether the collection has been explicitly configured
                    example: true
        "400":
          description: Missing collection parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    put:
      tags:
        - Collection Config
      summary: Set collection configuration
      description: Set or update the configuration for a collection.
      operationId: setCollectionConfig
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - collection
              properties:
                collection:
                  type: string
                  description: Collection name
                  example: blog
                type:
                  type: string
                  description: Collection type
                  example: website
                description:
                  type: string
                  description: Collection description
                  example: Blog posts and articles
                icon:
                  type: string
                  description: Collection icon (emoji or icon name)
                  example: "🌐"
                color:
                  type: string
                  description: Collection color (hex code)
                  example: "#3B82F6"
                customMeta:
                  type: object
                  additionalProperties: true
                  description: Custom key-value metadata for the collection
                wordpress:
                  $ref: "#/components/schemas/WordPressTargetConfig"
                retrieval:
                  $ref: "#/components/schemas/RetrievalProfile"
                responsePrompt:
                  type: string
                  maxLength: 4096
                  description: (v2.12.0+) How to format answers from this collection (RAG-002)
                trackAccess:
                  type: boolean
                  description: Record per-read access events for temporal analytics
                trackHot:
                  type: boolean
                  description: Maintain a hot-documents leaderboard for this collection
                spellCorrect:
                  type: boolean
                  description: Auto-correct FTS queries using the spell checker
                spellLang:
                  type: string
                  description: Override language for spell correction
                storageBackend:
                  type: string
                  enum: [ boltdb, memory, s3 ]
                  description: "Storage backend for this collection. Default: boltdb"
                  example: boltdb
                storageConfig:
                  $ref: "#/components/schemas/StorageConfigDef"
                quantization:
                  type: string
                  enum: [ float32, int8, int4 ]
                  description: "Vector quantization level for this collection. int8 gives 4x
                    compression (~1% recall drop), int4 gives 8x compression
                    (~2-3% recall drop). Default: float32 (no quantization).
                    Requires vector-reindex after changing."
                  example: int8
                diskOnlyVectors:
                  type: boolean
                  description: "Low-memory vector mode: RAM holds only the quantized
                    vectors while full-precision vectors stay on disk and are
                    read back to rescore the candidate set exactly. Requires
                    quantization (int8 or int4) and a vector-reindex after
                    enabling. Default: false"
                  example: true
      responses:
        "200":
          description: Collection configuration set successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  collection:
                    type: string
                    example: blog
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags:
        - Collection Config
      summary: Delete collection configuration
      description: Delete the configuration for a specific collection.
      operationId: deleteCollectionConfig
      parameters:
        - name: collection
          in: query
          required: true
          schema:
            type: string
          description: Collection name
          example: blog
      responses:
        "200":
          description: Collection configuration deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  collection:
                    type: string
                    example: blog
        "400":
          description: Missing collection parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/collection-configs:
    get:
      tags:
        - Collection Config
      summary: List all collection configurations
      description: Get all collection configurations.
      operationId: listCollectionConfigs
      responses:
        "200":
          description: All collection configurations retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  configs:
                    type: array
                    items:
                      type: object
                      properties:
                        collection:
                          type: string
                          example: blog
                        config:
                          $ref: "#/components/schemas/CollectionConfig"
                  total:
                    type: integer
                    description: Total number of configured collections
                    example: 3

  /v1/aggregate:
    post:
      tags:
        - Aggregations
      summary: Aggregate metadata facets and date histograms
      description: |
        Compute metadata value counts (facets) and date histograms for a collection.
        Supports optional metadata pre-filtering to scope the aggregation.
      operationId: aggregate
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AggregateRequest"
      responses:
        "200":
          description: Aggregation results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AggregateResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/cross-search:
    post:
      tags:
        - Cross-Collection Search
      summary: Cross-collection vector search
      description: |
        Search across multiple collections using a source document's embedding vector or a text query.
        Useful for finding related content across different collection types (e.g., images matching blog post content).
      operationId: crossSearch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CrossSearchRequest"
      responses:
        "200":
          description: Cross-collection search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CrossSearchResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/find-duplicates:
    post:
      tags:
        - Duplicate Detection
      summary: Find duplicate and similar documents
      description: |
        Scans a collection for duplicate and similar documents using content hashes and embedding vectors.
        Exact mode groups documents with identical content hashes. Similar mode compares embedding vectors
        pairwise and clusters documents above the similarity threshold using transitive clustering.
      operationId: findDuplicates
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FindDuplicatesRequest"
      responses:
        "200":
          description: Duplicate detection results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FindDuplicatesResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/memory/session:
    post:
      tags: [ Memory RAG ]
      summary: Create a new memory session
      description: Start a new conversation session for storing and recalling messages.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ userId ]
              properties:
                userId:
                  type: string
                  description: User identifier
                scenario:
                  type: string
                  description: Session context/scenario
                title:
                  type: string
                  description: Human-readable title
                meta:
                  type: object
                  additionalProperties:
                    type: string
                ttl:
                  type: integer
                  description: TTL in seconds (default 30 days)
      responses:
        "200":
          description: Session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  sessionId:
                    type: string
                  userId:
                    type: string
                  scenario:
                    type: string
                  title:
                    type: string
                  createdAt:
                    type: integer
                  expiresAt:
                    type: integer

  /v1/memory/message:
    post:
      tags: [ Memory RAG ]
      summary: Add a message to a session
      description: Store a message in a memory session. Messages are auto-embedded for
        semantic recall.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ sessionId, role, content ]
              properties:
                sessionId:
                  type: string
                role:
                  type: string
                  enum: [ user, assistant, system, tool ]
                content:
                  type: string
                meta:
                  type: object
                  additionalProperties:
                    type: string
      responses:
        "200":
          description: Message added
          content:
            application/json:
              schema:
                type: object
                properties:
                  messageId:
                    type: string
                  sessionId:
                    type: string
                  role:
                    type: string
                  createdAt:
                    type: integer
                  embedded:
                    type: boolean

  /v1/memory/recall:
    post:
      tags: [ Memory RAG ]
      summary: Recall relevant messages
      description: Semantically recall relevant messages from past conversations using
        hybrid search (vector + keyword).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ query ]
              properties:
                query:
                  type: string
                userId:
                  type: string
                sessionId:
                  type: string
                role:
                  type: string
                topK:
                  type: integer
                  default: 10
                threshold:
                  type: number
                strategy:
                  type: string
                  enum: [ hybrid, semantic, keyword ]
                  default: hybrid
                alpha:
                  type: number
                includeContent:
                  type: boolean
                filterMeta:
                  type: object
      responses:
        "200":
          description: Recall results
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        document:
                          $ref: "#/components/schemas/Document"
                        score:
                          type: number
                        rank:
                          type: integer
                        sessionId:
                          type: string
                        role:
                          type: string
                        matchStrategy:
                          type: string
                  total:
                    type: integer
                  strategy:
                    type: string
                  query:
                    type: string

  /v1/memory/summarize:
    post:
      tags: [ Memory RAG ]
      summary: Summarize a session
      description: Generate and store a summary of a session's conversation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ sessionId ]
              properties:
                sessionId:
                  type: string
                userId:
                  type: string
      responses:
        "200":
          description: Summary generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  summaryId:
                    type: string
                  sessionId:
                    type: string
                  summary:
                    type: string
                  createdAt:
                    type: integer
                  messages:
                    type: integer

  /v1/memory/sessions:
    post:
      tags: [ Memory RAG ]
      summary: List memory sessions
      description: List sessions with optional user/scenario filtering and pagination.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                userId:
                  type: string
                scenario:
                  type: string
                limit:
                  type: integer
                  default: 50
                offset:
                  type: integer
                sort:
                  type: string
                  enum: [ createdAt, updatedAt ]
                  default: createdAt
                asc:
                  type: boolean
      responses:
        "200":
          description: Session list
          content:
            application/json:
              schema:
                type: object
                properties:
                  sessions:
                    type: array
                    items:
                      type: object
                      properties:
                        sessionId:
                          type: string
                        userId:
                          type: string
                        scenario:
                          type: string
                        title:
                          type: string
                        createdAt:
                          type: integer
                        updatedAt:
                          type: integer
                        expiresAt:
                          type: integer
                        messageCount:
                          type: integer
                  total:
                    type: integer

  /v1/memory/history:
    post:
      tags: [ Memory RAG ]
      summary: Get session message history
      description: Retrieve chronologically ordered message history for a session.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ sessionId ]
              properties:
                sessionId:
                  type: string
                limit:
                  type: integer
                  default: 100
                offset:
                  type: integer
      responses:
        "200":
          description: Message history
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: "#/components/schemas/Document"
                  total:
                    type: integer

  /v1/temporal/query:
    post:
      tags: [ Temporal Tracking ]
      summary: Query document event history
      description: Returns create/update/access events for a specific document within
        a time range. Requires MDDB_TEMPORAL=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection, key, lang ]
              properties:
                collection:
                  type: string
                key:
                  type: string
                lang:
                  type: string
                eventType:
                  type: string
                  enum: [ create, update, access ]
                  description: Filter by event type; omit for all events
                from:
                  type: integer
                  format: int64
                  description: Unix timestamp; defaults to 30 days ago
                to:
                  type: integer
                  format: int64
                  description: Unix timestamp; defaults to now
                limit:
                  type: integer
                  default: 100
      responses:
        "200":
          description: Event history
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                  docId:
                    type: string
                  events:
                    type: array
                    items:
                      $ref: "#/components/schemas/TemporalEvent"
                  total:
                    type: integer
        "400":
          description: Missing required fields or temporal not available

  /v1/temporal/hot:
    post:
      tags: [ Temporal Tracking ]
      summary: Hot documents leaderboard
      description: Returns the top-N most accessed documents in a collection. Requires
        MDDB_TEMPORAL=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection ]
              properties:
                collection:
                  type: string
                topN:
                  type: integer
                  default: 10
                since:
                  type: integer
                  format: int64
                  description: Unix timestamp to filter events from
      responses:
        "200":
          description: Hot documents list
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                  entries:
                    type: array
                    items:
                      type: object
                      properties:
                        docId:
                          type: string
                        accessCount:
                          type: integer
                          format: int64
                        lastAccessAt:
                          type: integer
                          format: int64
                        document:
                          $ref: "#/components/schemas/Document"
        "400":
          description: Missing required field or temporal not available

  /v1/temporal/histogram:
    post:
      tags: [ Temporal Tracking ]
      summary: Event frequency histogram
      description: Returns a bucketed histogram of event frequency for a collection.
        Requires MDDB_TEMPORAL=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ collection ]
              properties:
                collection:
                  type: string
                eventType:
                  type: string
                  default: access
                  enum: [ create, update, access ]
                interval:
                  type: string
                  default: day
                  enum: [ day, week, month ]
                from:
                  type: integer
                  format: int64
                to:
                  type: integer
                  format: int64
      responses:
        "200":
          description: Histogram buckets
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                  eventType:
                    type: string
                  interval:
                    type: string
                  buckets:
                    type: array
                    items:
                      type: object
                      properties:
                        from:
                          type: integer
                          format: int64
                        to:
                          type: integer
                          format: int64
                        count:
                          type: integer
        "400":
          description: Missing required field or temporal not available

  /v1/spell-suggest:
    post:
      tags: [ Spell Correction ]
      summary: Spell correction suggestions
      description: Returns per-token spell suggestions and a corrected version of the
        input text. Requires MDDB_SPELL=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ text, lang ]
              properties:
                collection:
                  type: string
                  description: Optional — scope suggestions to collection dictionary
                text:
                  type: string
                lang:
                  type: string
                  example: en
                maxSuggestions:
                  type: integer
                  default: 5
      responses:
        "200":
          description: Spell suggestions
          content:
            application/json:
              schema:
                type: object
                properties:
                  originalText:
                    type: string
                  suggestedText:
                    type: string
                  tokenSuggestions:
                    type: array
                    items:
                      type: object
                      properties:
                        token:
                          type: string
                        suggestions:
                          type: array
                          items:
                            type: string
        "503":
          description: Spell index still loading — retry

  /v1/spell-cleanup:
    post:
      tags: [ Spell Correction ]
      summary: Apply spell corrections to text
      description: Applies the best corrections to each token and returns the cleaned
        text. Requires MDDB_SPELL=true.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ text, lang ]
              properties:
                collection:
                  type: string
                text:
                  type: string
                lang:
                  type: string
      responses:
        "200":
          description: Cleaned text
          content:
            application/json:
              schema:
                type: object
                properties:
                  original:
                    type: string
                  cleaned:
                    type: string
                  correctionsApplied:
                    type: integer
        "503":
          description: Spell index still loading — retry

  /v1/spell-dictionary:
    get:
      tags: [ Spell Correction ]
      summary: List custom dictionary words
      description: Returns words in the custom dictionary for the given collection and
        language.
      parameters:
        - in: query
          name: collection
          schema:
            type: string
        - in: query
          name: lang
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Dictionary words
          content:
            application/json:
              schema:
                type: object
                properties:
                  collection:
                    type: string
                  lang:
                    type: string
                  words:
                    type: array
                    items:
                      type: string
    put:
      tags: [ Spell Correction ]
      summary: Add words to custom dictionary
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ lang, words ]
              properties:
                collection:
                  type: string
                lang:
                  type: string
                words:
                  type: array
                  items:
                    type: string
                frequency:
                  type: integer
                  default: 100
                  description: Boost frequency for added words
      responses:
        "200":
          description: Words added
          content:
            application/json:
              schema:
                type: object
                properties:
                  added:
                    type: integer
    delete:
      tags: [ Spell Correction ]
      summary: Remove words from custom dictionary
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ lang, words ]
              properties:
                collection:
                  type: string
                lang:
                  type: string
                words:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Words removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  removed:
                    type: integer

components:
  schemas:
    CodeGraphResponse:
      type: object
      description: >
        One traversal of the code connection graph. Nodes and edges are sorted,
        so two responses can be diffed to see what a change did.
      properties:
        collection:
          type: string
        root:
          type: string
          description: The key the traversal started from
        direction:
          type: string
          enum: [ in, out, both ]
        depth:
          type: integer
        nodes:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
              docId:
                type: string
              lang:
                type: string
              language:
                type: string
                description: Source language, e.g. `css`, `javascript`, `html`
              depth:
                type: integer
                description: Hops from the root, by the shortest path
        edges:
          type: array
          items:
            type: object
            properties:
              from:
                type: string
              to:
                type: string
              kind:
                type: string
                enum: [ uses-selector, imports ]
              symbol:
                type: string
                description: >
                  The selector or path that justifies this edge. Without it the
                  answer is only "these files are related", which is not
                  actionable.
              direction:
                type: string
                enum: [ in, out ]
              lines:
                type: object
                description: >
                  Present only when `lines` was requested. A symbol usually
                  occurs more than once, so these are the first occurrence — a
                  place to jump to, not a complete answer. Zero means the symbol
                  is not literal text in that file.
                properties:
                  fromLine:
                    type: integer
                  toLine:
                    type: integer
        truncated:
          type: boolean
          description: >
            A depth or degree limit cut the walk short, so "nothing else depends
            on this" cannot be read from a truncated result.
      example:
        collection: theme
        root: theme/style.css
        direction: in
        depth: 1
        nodes:
          - key: theme/style.css
            docId: rng|doc-0001|en
            language: css
            depth: 0
          - key: theme/index.html
            docId: rng|doc-0002|en
            language: html
            depth: 1
        edges:
          - from: theme/index.html
            to: theme/style.css
            kind: uses-selector
            symbol: .hero-banner
            direction: in
        truncated: false
    MCPAPIKeySummary:
      type: object
      description: |
        Public view of an MCP API key. Carries no key material: `fingerprint`
        is four bytes of SHA-256 over the key, and `keyPrefix` holds only the
        scheme marker every key starts with.
      properties:
        keyPrefix:
          type: string
          description: Scheme marker only — never a slice of the key.
          example: mcp_
        fingerprint:
          type: string
          description: Stable, non-reversible identifier; matches what the server logs.
          example: 9f2a41c7
        name:
          type: string
          example: claude-desktop
        createdAt:
          type: integer
          format: int64
        expiresAt:
          type: integer
          format: int64
          description: Unix timestamp; absent when the key never expires.
        disabled:
          type: boolean

    MCPAPIKeyRef:
      type: object
      description: Identifies an existing key by its full value.
      required:
        - key
      properties:
        key:
          type: string
          example: mcp_4f3c2a1b8e7d6c5b4a39281706f5e4d3

    RotationJob:
      type: object
      description: Encryption re-keying job descriptor (2.9.16+).
      properties:
        id:
          type: string
        status:
          type: string
          enum: [queued, running, completed, failed]
        primaryKeyID:
          type: integer
        startedAt:
          type: integer
          format: int64
          description: Unix nanoseconds
        finishedAt:
          type: integer
          format: int64
        scanned:
          type: integer
          format: int64
        reencrypted:
          type: integer
          format: int64
        skipped:
          type: integer
          format: int64
        errors:
          type: integer
          format: int64
        lastError:
          type: string
    WikiImportResponse:
      type: object
      properties:
        imported:
          type: integer
          description: Number of pages successfully imported
        skipped:
          type: integer
          description: Number of pages skipped (wrong namespace, redirects, empty)
        failed:
          type: integer
          description: Number of pages that failed to import
        errors:
          type: array
          items:
            type: string
          description: Error messages (capped at 10)
        collection:
          type: string
          description: Target collection name
        durationMs:
          type: integer
          format: int64
          description: Total import duration in milliseconds
    TemporalEvent:
      type: object
      properties:
        docId:
          type: string
        collection:
          type: string
        eventType:
          type: string
          enum: [ create, update, access ]
        timestamp:
          type: integer
          format: int64
    Document:
      type: object
      properties:
        id:
          type: string
          description: Auto-generated document ID
          example: doc|blog|hello-world|en_US
        key:
          type: string
          description: Document key (unique within collection and language)
          example: hello-world
        lang:
          type: string
          description: Language code (e.g., en_US, pl_PL, de_DE)
          example: en_US
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata key-value pairs (values are arrays)
          example:
            category: [ blog, tutorial ]
            author: [ John Doe ]
            tags: [ markdown, database ]
        contentMd:
          type: string
          description: Markdown content
          example: "# Hello World\n\nWelcome to MDDB!"
        addedAt:
          type: integer
          format: int64
          description: Unix timestamp when document was created
          example: 1704844800
        updatedAt:
          type: integer
          format: int64
          description: Unix timestamp when document was last updated
          example: 1704931200

    AddRequest:
      type: object
      required:
        - collection
        - key
        - lang
        - contentMd
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        key:
          type: string
          description: Document key
          example: hello-world
        lang:
          type: string
          description: Language code
          example: en_US
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata key-value pairs
          example:
            category: [ blog ]
            author: [ John Doe ]
        contentMd:
          type: string
          description: Markdown content
          example: "# Hello World\n\nWelcome to MDDB!"

    AddBatchRequest:
      type: object
      required:
        - collection
        - documents
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        documents:
          type: array
          items:
            $ref: "#/components/schemas/AddBatchDocument"
          description: Documents to add

    AddBatchDocument:
      type: object
      required:
        - key
        - lang
        - contentMd
      properties:
        key:
          type: string
          description: Document key
          example: post1
        lang:
          type: string
          description: Language code
          example: en
        contentMd:
          type: string
          description: Markdown content
          example: "# Hello World"
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata key-value pairs
          example:
            category: [ blog ]
        saveRevision:
          type: boolean
          description: Whether to save a revision
          default: false

    AddBatchResponse:
      type: object
      properties:
        added:
          type: integer
          description: Number of documents added
          example: 2
        updated:
          type: integer
          description: Number of documents updated
          example: 0
        failed:
          type: integer
          description: Number of documents that failed
          example: 0
        errors:
          type: array
          items:
            type: string
          description: Error messages for failed documents
        embeddingDropped:
          type: integer
          description: >-
            (v2.15.0+) Documents stored without a vector because the embedding
            queue was full and the write gave up waiting. Present only when
            non-zero. `failed` stays 0: the documents ARE stored, they are just
            unreachable by vector and hybrid search until POST /v1/vector-reindex
            fills them in. See MDDB_EMBEDDING_QUEUE_SIZE / MDDB_EMBEDDING_QUEUE_WAIT.
          example: 0

    BulkIngestSubmitRequest:
      type: object
      required:
        - collection
        - documents
      properties:
        collection:
          type: string
          description: Target collection
          example: blog
        documents:
          type: array
          items:
            $ref: "#/components/schemas/AddBatchDocument"
          description: Documents to ingest; processed in chunks of 500
        callbackUrl:
          type: string
          description: Optional URL that receives a POST with the final job record on
            completion
          example: https://example.com/webhook

    BulkIngestJob:
      type: object
      properties:
        id:
          type: string
          description: Unique job identifier
          example: bulk_1776539903516445000_849876
        collection:
          type: string
          example: blog
        status:
          type: string
          enum: [ pending, processing, completed, failed, cancelled ]
          description: |
            - `pending`: queued, worker has not picked it up yet
            - `processing`: worker is actively committing chunks
            - `completed`: finished (may include per-document failures)
            - `failed`: all documents failed or the server restarted mid-job
            - `cancelled`: user cancelled while still pending
        total:
          type: integer
          description: Total documents submitted
        processed:
          type: integer
          description: Documents processed so far (includes successes and failures)
        added:
          type: integer
          description: Documents added to the collection
        updated:
          type: integer
          description: Documents updated
        failed:
          type: integer
          description: Documents that failed to commit
        errors:
          type: array
          items:
            type: string
          description: Up to 50 error messages recorded during processing
        callbackUrl:
          type: string
          description: Webhook URL supplied at submit time
        submittedAt:
          type: integer
          format: int64
          description: Unix timestamp (seconds) when the job was submitted
        startedAt:
          type: integer
          format: int64
          description: Unix timestamp when the worker picked up the job
        completedAt:
          type: integer
          format: int64
          description: Unix timestamp when the job reached a terminal status

    GeoJSONPolygon:
      type: object
      required:
        - coordinates
      properties:
        type:
          type: string
          enum: [ Polygon ]
          description: GeoJSON geometry type. Optional — the handler fills it in when
            omitted.
        coordinates:
          type: array
          description: |
            Array of linear rings. The first ring is the outer boundary; any
            subsequent rings are holes. Each ring is an array of `[lng, lat]`
            pairs (RFC 7946 order) with at least 3 points.
          items:
            type: array
            items:
              type: array
              minItems: 2
              maxItems: 3
              items:
                type: number

    GeoJSONMultiPolygon:
      type: object
      required:
        - coordinates
      properties:
        type:
          type: string
          enum: [ MultiPolygon ]
        coordinates:
          type: array
          description: Array of Polygon coordinate arrays. Union semantics — a document
            matches if it lies inside any member polygon.
          items:
            type: array
            items:
              type: array
              items:
                type: array
                minItems: 2
                maxItems: 3
                items:
                  type: number

    GeoPolygonRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          example: venues
        polygon:
          $ref: "#/components/schemas/GeoJSONPolygon"
        multiPolygon:
          $ref: "#/components/schemas/GeoJSONMultiPolygon"
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Optional metadata pre-filter (AND across keys, OR within key)
        includeContent:
          type: boolean
          default: false
      description: Exactly one of `polygon` or `multiPolygon` must be set.

    GeoPolygonResponse:
      type: object
      properties:
        results:
          type: array
          items:
            type: object
            properties:
              document:
                type: object
              rank:
                type: integer
        total:
          type: integer
        shape:
          type: string
          enum: [ polygon, multiPolygon ]
        algorithm:
          type: string
          example: rtree

    IngestRequest:
      type: object
      required:
        - collection
        - documents
      properties:
        collection:
          type: string
          description: Collection name
          example: imported
        documents:
          type: array
          items:
            $ref: "#/components/schemas/IngestDocument"
          description: Documents to ingest
        options:
          $ref: "#/components/schemas/IngestOptions"

    IngestDocument:
      type: object
      required:
        - lang
        - contentMd
      properties:
        url:
          type: string
          format: uri
          description: Source URL (used for key derivation and auto-injected as source_url
            metadata)
          example: https://example.com/page1
        key:
          type: string
          description: Document key (derived from URL if empty)
          example: page1
        lang:
          type: string
          description: Language code
          example: en
        contentMd:
          type: string
          description: Markdown content
          example: "# Page 1\n\nContent here."
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata key-value pairs
        extractFrontmatter:
          type: boolean
          description: Parse YAML frontmatter from content and merge into metadata
          default: false
        scrapedAt:
          type: integer
          format: int64
          description: Unix timestamp of when the content was scraped (auto-injected as
            scraped_at metadata)
          example: 1709500000
        scraper:
          type: string
          description: Scraper identifier (auto-injected as scraper metadata)
          example: my-crawler
        ttl:
          type: integer
          format: int64
          description: Time-to-live in seconds
          example: 86400

    IngestOptions:
      type: object
      properties:
        skipDuplicates:
          type: boolean
          description: Skip documents whose content hasn't changed (CRC32 hash comparison)
          default: false
        skipEmbeddings:
          type: boolean
          description: Skip embedding generation for this batch
          default: false
        skipFts:
          type: boolean
          description: Skip FTS indexing for this batch
          default: false
        skipWebhooks:
          type: boolean
          description: Skip webhook firing for this batch
          default: false
        autoConfigureCollection:
          type: boolean
          description: Auto-configure collection as "scraping" type if it doesn't exist
          default: false
        saveRevision:
          type: boolean
          description: Save revision history for all documents in this batch
          default: false
        profile:
          type: string
        sanitized:
          type: integer
          description: >-
            How many documents had undecodable bytes dropped because their text
            was not valid UTF-8 (GO-036). A bulk import sanitises rather than
            failing, so one page in Windows-1250 cannot abort the other twenty
            thousand — but the count says how many were changed. Single-document
            writes refuse instead, with an error naming the encoding.
          enum: [ default, fast ]
          default: default
          description: >
            (v2.12.0+) Named preset of the flags above. `default` keeps every
            step. `fast` trades parsing fidelity and per-document bookkeeping
            for throughput: text-only parsing, no webhooks, no revisions,
            duplicates skipped. Notably `fast` does NOT skip embeddings or FTS —
            that is a separate decision with its own flag. Any flag set
            explicitly overrides the preset, so `fast` plus `saveRevision: true`
            keeps revisions. An unknown name is rejected with 400 rather than
            falling back silently.
        keyCollisions:
          type: array
          items:
            type: string
          description: |
            (v2.12.0+) Input keys that differ only in letter case and therefore
            name one document — `README.md` and `readme.md` are the same
            document, and the last write wins.

            The write still happens; refusing a large import over a spelling
            would be worse. Absent when there are no collisions.
          example:
            - '"README.md" and "readme.md" name one document (keys are case-insensitive); the last write wins'
        textOnly:
          type: boolean
          default: false
          description: >
            (v2.12.0+) Extract plain text from html/docx/odt instead of
            rebuilding their structure as Markdown. Implied by `profile: fast`.
            PDF has no text-only variant: its extractor already produces plain
            text and builds no structure.

    IngestResponse:
      type: object
      properties:
        added:
          type: integer
          description: Number of documents added
          example: 2
        updated:
          type: integer
          description: Number of documents updated
          example: 0
        skipped:
          type: integer
          description: Number of documents skipped (deduplication)
          example: 0
        failed:
          type: integer
          description: Number of documents that failed
          example: 0
        errors:
          type: array
          items:
            type: string
          description: Error messages for failed documents
        collection:
          type: string
          description: Collection name
          example: imported
        durationMs:
          type: integer
          format: int64
          description: Total processing time in milliseconds
          example: 45
        profile:
          type: string
          description: >
            (v2.12.0+) Which ingest profile actually applied, so a corpus
            loaded months ago can be explained without guessing which flags the
            caller sent.

    GetRequest:
      type: object
      required:
        - collection
        - key
        - lang
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        key:
          type: string
          description: Document key
          example: hello-world
        lang:
          type: string
          description: Language code
          example: en_US
        env:
          type: object
          additionalProperties:
            type: string
          description: Template variables for substitution
          example:
            siteName: My Blog
            year: "2025"

    SearchRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata filters (AND between keys, OR between values)
          example:
            category: [ blog, tutorial ]
            status: [ published ]
        sort:
          type: string
          enum: [ addedAt, updatedAt, key ]
          description: Sort field
          example: updatedAt
        asc:
          type: boolean
          description: Sort ascending (default false)
          example: false
        limit:
          type: integer
          description: Maximum number of results
          example: 10
        offset:
          type: integer
          description: Number of results to skip
          example: 0

    DeleteRequest:
      type: object
      required:
        - collection
        - key
        - lang
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        key:
          type: string
          description: Document key
          example: hello-world
        lang:
          type: string
          description: Language code
          example: en_US

    DeleteCollectionRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          description: Collection name to delete
          example: blog

    ExportRequest:
      type: object
      required:
        - collection
        - format
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Optional metadata filters
          example:
            status: [ published ]
        format:
          type: string
          enum: [ ndjson, zip ]
          description: Export format
          example: ndjson

    TruncateRequest:
      type: object
      required:
        - collection
        - keepRevs
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        keepRevs:
          type: integer
          description: Number of revisions to keep per document (0 = delete all history)
          example: 3
        dropCache:
          type: boolean
          description: Clear document cache after truncation
          example: true

    Stats:
      type: object
      properties:
        databasePath:
          type: string
          example: /data/mddb.db
        databaseSize:
          type: integer
          format: int64
          description: Database file size in bytes
          example: 10485760
        mode:
          type: string
          enum: [ read, write, wr ]
          example: wr
        collections:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                example: blog
              documentCount:
                type: integer
                example: 42
              revisionCount:
                type: integer
                example: 156
              metaIndexCount:
                type: integer
                example: 84
              checksum:
                type: string
                description: 8-character hex CRC32 checksum of the collection
                example: a1b2c3d4
        totalDocuments:
          type: integer
          example: 42
        totalRevisions:
          type: integer
          example: 156
        totalMetaIndices:
          type: integer
          example: 84
        indexQueue:
          type: object
          description: >-
            Async metadata-indexing queue health. `fallbacks` counts jobs that
            were indexed synchronously because the queue was full (they are
            never dropped). Omitted when the queue is not initialised.
          properties:
            processed:
              type: integer
              example: 12034
            failed:
              type: integer
              example: 0
            fallbacks:
              type: integer
              example: 3
            queueLen:
              type: integer
              example: 0
        uptime:
          type: string
          example: 2h15m30s

    VectorSearchRequest:
      type: object
      required:
        - collection
      properties:
        oversample:
          type: number
          minimum: 1.0
          maximum: 10.0
          description: >
            (v2.12.0+) Recall/latency knob: candidates fetched per requested
            result before deduplication, merging or rescoring trims them.
            Omitted, uses the collection's retrieval profile then MDDB's default
            of 3.0. Out of range returns 422 rather than clamping. See
            SEARCH.md#oversampling-v2120 for measured effect.
        collection:
          type: string
          description: Collection name to search
          example: blog
        query:
          type: string
          description: Natural language query string (required if queryVector is not
            provided)
          example: How to set up a CI/CD pipeline?
        queryVector:
          type: array
          items:
            type: number
            format: float
          description: Raw embedding vector (optional, use instead of query for
            pre-computed embeddings)
        topK:
          type: integer
          description: Number of top results to return (default 5)
          default: 5
          example: 5
        threshold:
          type: number
          format: float
          description: Minimum cosine similarity score (0.0 to 1.0)
          minimum: 0
          maximum: 1
          example: 0.7
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata filters to narrow results before ranking
          example:
            category: [ devops, tutorial ]
        includeContent:
          type: boolean
          description: Whether to include document content in results (default false)
          default: false
          example: false
        algorithm:
          type: string
          enum: [ flat, hnsw, ivf, pq, opq, sq, sq4, bq, quantized ]
          description: |
            Search algorithm to use (default "flat").
            - **flat**: Exact brute-force cosine similarity (most accurate, slower for large datasets)
            - **hnsw**: Hierarchical Navigable Small World graph (fast approximate, best general-purpose)
            - **ivf**: Inverted File Index with k-means clustering (good for large datasets)
            - **pq**: Product Quantization (compressed, lowest memory, slightly less accurate)
            - **opq**: Optimised Product Quantization — PQ with a learned rotation
            - **sq**: Scalar Quantization to 8 bits per dimension (4x smaller than float32)
            - **sq4**: Scalar Quantization to 4 bits, two dimensions per byte (8x smaller).
              Measured at 99.5% of `sq`'s recall@10 for half its storage — see
              `TestQuantizerRecallCurve`. Reach for it when a collection no longer
              fits in RAM at `sq`.
            - **bq**: Binary Quantization to 1 bit per dimension (32x smaller, much coarser)
            - **quantized**: Follows the collection's own `quantization` setting
          default: flat
          example: flat
        distanceMetric:
          type: string
          enum: [ cosine, dot_product, euclidean ]
          description: Distance metric for similarity computation
          default: cosine
          example: cosine
        retrievalMode:
          type: string
          enum: [ parent, chunk, window, graph ]
          description: |
            Result granularity (default "parent").
            - **parent**: One result per document, scored by its best-matching chunk.
            - **chunk**: One result per matching chunk; each result carries
              `chunkIndex` and `chunkText` with the exact matching passage —
              ideal for feeding precise context to an LLM.
            - **window**: Like `chunk`, but the passage is widened with
              `windowSize` neighboring chunks on each side.
          default: parent
          example: chunk
        windowSize:
          type: integer
          description: Neighbor chunks per side in "window" retrieval mode (default 1)
          default: 1
          example: 1
        mmr:
          type: boolean
          description: >-
            Diversify results with Maximal Marginal Relevance reranking.
            Selection balances query relevance against similarity to already
            selected results, so near-duplicate documents stop crowding out
            distinct ones.
          default: false
          example: true
        mmrLambda:
          type: number
          format: float
          description: >-
            MMR relevance/diversity balance in [0,1] — 1.0 keeps the pure
            relevance order, 0.0 maximizes diversity (default 0.5).
          minimum: 0
          maximum: 1
          default: 0.5
          example: 0.5

    VectorSearchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/VectorSearchResultItem"
        total:
          type: integer
          description: Number of results returned
          example: 5
        model:
          type: string
          description: Embedding model used
          example: text-embedding-3-small
        dimensions:
          type: integer
          description: Embedding vector dimensions
          example: 1536
        algorithm:
          type: string
          description: Algorithm used for this search
          example: flat
        distanceMetric:
          type: string
          description: Distance metric used for this search
          example: cosine
        searchStats:
          $ref: "#/components/schemas/SearchStats"

    VectorSearchResultItem:
      type: object
      properties:
        document:
          $ref: "#/components/schemas/Document"
        score:
          type: number
          format: float
          description: Cosine similarity score (0.0 to 1.0)
          example: 0.92
        rank:
          type: integer
          description: Result rank (1-based)
          example: 1
        chunkIndex:
          type: integer
          description: Index of the matching chunk (only in chunk/window retrieval modes)
          example: 2
        chunkText:
          type: string
          description: The matching passage text (only in chunk/window retrieval modes)
          example: "## Deployment\nTo deploy the service..."

    VectorReindexRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          description: Collection name to reindex
          example: blog
        force:
          type: boolean
          description: If true, re-embed even if content is unchanged
          default: false
          example: false

    VectorReindexResponse:
      type: object
      properties:
        embedded:
          type: integer
          description: Number of documents embedded
          example: 42
        skipped:
          type: integer
          description: Number of documents skipped (content unchanged)
          example: 10
        failed:
          type: integer
          description: Number of documents that failed to embed
          example: 0
        totalChunks:
          type: integer
          description: Total number of chunks embedded across all documents
          example: 128
        errors:
          type: array
          items:
            type: string
          description: Error messages for failed embeddings

    VectorStatsResponse:
      type: object
      properties:
        enabled:
          type: boolean
          description: Whether vector search is enabled
          example: true
        provider:
          type: string
          description: Embedding provider name
          example: text-embedding-3-small
        model:
          type: string
          description: Embedding model name
          example: text-embedding-3-small
        dimensions:
          type: integer
          description: Embedding vector dimensions
          example: 1536
        index_ready:
          type: boolean
          description: Whether the vector index is loaded and ready
          example: true
        collections:
          type: object
          additionalProperties:
            type: object
            properties:
              total_documents:
                type: integer
                description: Total documents in collection
                example: 100
              embedded_documents:
                type: integer
                description: Documents with embeddings
                example: 95
              total_chunks:
                type: integer
                description: Total embedding chunks (including multi-chunk documents)
                example: 280
          description: Per-collection embedding statistics
          example:
            blog:
              total_documents: 100
              embedded_documents: 95
              total_chunks: 280
        queue:
          type: object
          description: >-
            (v2.15.0+) Embedding queue state. It answers the question the two
            document counts cannot: whether embedding is still running or gave
            up. Absent when no embedding worker is configured.
          properties:
            size:
              type: integer
              description: Queue capacity (MDDB_EMBEDDING_QUEUE_SIZE)
              example: 1000
            depth:
              type: integer
              description: Jobs waiting now; above zero means embedding is still running
              example: 0
            dropped:
              type: integer
              format: int64
              description: >-
                Documents stored without a vector since startup, because the
                queue was full and the write gave up waiting. Repair with
                POST /v1/vector-reindex.
              example: 0
            wait:
              type: string
              description: How long a write waits for queue room (MDDB_EMBEDDING_QUEUE_WAIT)
              example: "5s"
        chunking:
          type: object
          properties:
            enabled:
              type: boolean
              description: Whether document chunking is enabled
              example: true
            chunkSize:
              type: integer
              description: Maximum characters per chunk
              example: 1500
          description: Chunking configuration

    FTSSearchRequest:
      type: object
      required:
        - collection
        - query
      properties:
        collection:
          type: string
          description: Collection name to search
          example: blog
        query:
          type: string
          description: Full-text search query string
          example: cancel subscription
        limit:
          type: integer
          description: Maximum number of results (default 50)
          default: 50
          example: 50
        algorithm:
          type: string
          enum: [ tfidf, bm25, bm25f, pmisparse ]
          description: |
            Scoring algorithm to use (default "tfidf").
            - **tfidf**: Classic TF-IDF scoring
            - **bm25**: Okapi BM25 with document length normalization (k1=1.2, b=0.75)
            - **bm25f**: BM25F field-weighted scoring — applies different weights to title, metadata, and content
            - **pmisparse**: PMISparse — BM25 + PMI query expansion (invented by Tradik Limited)
          default: tfidf
          example: bm25
        fuzzy:
          type: integer
          enum: [ 0, 1, 2 ]
          description: |
            Typo tolerance level (default 0).
            - **0**: Off — exact term matching only
            - **1**: Low — matches terms within 1 edit distance (e.g. "javascrip" → "javascript")
            - **2**: Medium — matches terms within 2 edit distances
          default: 0
          example: 0
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata filters to narrow results before scoring (AND between
            keys, OR between values)
          example:
            category: [ tutorial ]
        fieldWeights:
          type: object
          additionalProperties:
            type: number
          description: Field weights for BM25F algorithm (keys are "title", "meta",
            "content")
          example:
            title: 3.0
            meta: 2.0
            content: 1.0
        disableStem:
          type: boolean
          description: Disable stemming for this query
          default: false
        disableSynonyms:
          type: boolean
          description: Disable synonym expansion for this query
          default: false
        mode:
          type: string
          enum: [ auto, simple, boolean, phrase, wildcard, proximity, expression ]
          description: |
            Search mode (default "auto"). Auto-detect parses the query to determine the appropriate mode.
            - **auto**: Auto-detect from query syntax
            - **simple**: Classic keyword search
            - **boolean**: AND/OR/NOT operators, +required, -excluded
            - **phrase**: Exact word sequence (`"quoted phrases"`)
            - **wildcard**: Pattern matching (`*` any chars, `?` single char)
            - **proximity**: Terms within N words (`"term1 term2"~5`)
            - **expression**: Full query DSL with nested parens, precedence, and mixed atoms (v2.9.15+)
          default: auto
          example: auto
        distance:
          type: integer
          description: Maximum word distance for proximity search (default 5)
          default: 5
          example: 5
        lang:
          type: string
          description: |
            Language code for query tokenization (e.g., "en", "pl", "de"). Uses language-specific stemmer and stop words.
            Language codes are normalized: "en_US" → "en", "pl_PL" → "pl". Falls back to server default if omitted.
          example: en
        rangeMeta:
          type: array
          description: Range filters on metadata or timestamp fields
          items:
            $ref: "#/components/schemas/RangeFilter"
        boost:
          type: object
          additionalProperties:
            type: number
          description: |
            Per-query boost map. Keys follow the format `"metaKey:metaValue"`.
            Positive values multiply the score of documents matching that
            metadata pair (e.g. `5.0` = 5× boost); negative values demote
            (e.g. `-2.0` = ½× penalty). Zero is ignored. Multipliers from
            multiple matching entries combine multiplicatively; the final
            per-document multiplier is floored at `0.001` to prevent total
            score collapse.
          example:
            "tag:breed": 5.0
            "category:health": -2
        highlight:
          type: boolean
          default: false
          description: |
            When true (v2.9.15+), each result gains a `highlights` array with
            text snippets around matched terms. Works uniformly across every
            FTS mode.
        highlightTag:
          type: string
          default: "<mark>"
          description: Wrap tag for matched terms (close tag derived automatically)
        maxHighlights:
          type: integer
          default: 3
          description: Cap fragments per result
        fragmentSize:
          type: integer
          default: 150
          description: Approximate chars per fragment (snapped to word boundaries)
        facetBy:
          type: array
          items:
            type: string
          description: |
            (v2.9.15+) Metadata keys to aggregate into `facets` on the response.
            Counts are computed over the documents actually returned (after
            filtering, boosts, curation).
          example: [ category, lang ]
        facetMaxValues:
          type: integer
          description: (v2.9.15+) Cap per-key bucket count; 0 = unlimited
          default: 0

        cacheTtl:
          type: integer
          description: |
            Serve this search from the result cache when a matching response is
            no older than this many seconds, and store the response for that
            long. Omitted or 0 means no caching at all — neither served nor
            stored — so a request that says nothing keeps the default
            behaviour and the default freshness.

            Writes to the collection invalidate its cached results
            immediately. The response carries `X-MDDB-Cache: hit|miss`.
            Capped at 3600; `MDDB_SEARCH_CACHE_SIZE=0` disables the cache
            server-wide.
          minimum: 0
          maximum: 3600
          example: 60
    Highlight:
      type: object
      properties:
        fragment:
          type: string
          description: Snippet with matched terms wrapped in the configured tag; ellipsis
            `…` markers denote truncation
        matchedTerms:
          type: array
          items:
            type: string
        startOffset:
          type: integer
          description: Byte offset into the raw ContentMD where the fragment begins
        endOffset:
          type: integer

        startLine:
          type: integer
          description: |
            (v2.12.0+) 1-based, inclusive line where the fragment begins. This
            is what makes a result a place to edit rather than a document to
            read — see docs/API.md#finding-the-place-not-just-the-file.
        endLine:
          type: integer
          description: (v2.12.0+) 1-based, inclusive line where the fragment ends
    RangeFilter:
      type: object
      properties:
        field:
          type: string
          description: Field name — metadata key, or built-in "addedAt" / "updatedAt"
          example: price
        gte:
          type: string
          description: Greater than or equal (numeric, date string, or unix timestamp)
          example: "10"
        lte:
          type: string
          description: Less than or equal
          example: "100"
        gt:
          type: string
          description: Greater than
        lt:
          type: string
          description: Less than

    FTSSearchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/FTSResultItem"
        total:
          type: integer
          description: Number of results returned
          example: 3
        algorithm:
          type: string
          description: Algorithm used for this search
          example: tfidf
        mode:
          type: string
          description: Search mode used (simple, boolean, phrase, wildcard, proximity)
          example: simple
        lang:
          type: string
          description: Language used for query tokenization
          example: en
        fuzzy:
          type: integer
          description: Typo tolerance level used
          example: 0
        searchStats:
          $ref: "#/components/schemas/SearchStats"
        facets:
          $ref: "#/components/schemas/FacetMap"
        stemmingActive:
          type: boolean
          description: Whether stemming was applied to this query
        synonymsActive:
          type: boolean
          description: Whether synonym expansion was applied to this query
        fieldWeights:
          type: object
          additionalProperties:
            type: number
          description: Effective per-field weights used for scoring (BM25F)
        spellCorrected:
          type: object
          nullable: true
          description: >-
            Spell-correction info when the collection has spellCorrect enabled
            and the query was auto-corrected (original and corrected terms).

    FacetBucket:
      type: object
      properties:
        value:
          type: string
        count:
          type: integer
      required: [ value, count ]

    FacetMap:
      type: object
      description: |
        (v2.9.15+) Per-key facet counts. Buckets are ordered by count desc,
        value asc. Missing keys produce an empty bucket list so UIs can
        render a stable facet group layout across queries.
      additionalProperties:
        type: array
        items:
          $ref: "#/components/schemas/FacetBucket"

    FTSResultItem:
      type: object
      properties:
        document:
          $ref: "#/components/schemas/Document"
        score:
          type: number
          format: float
          description: TF-IDF relevance score
          example: 2.45
        matchedTerms:
          type: array
          items:
            type: string
          description: Terms from the query that matched
          example: [ cancel, subscription ]
        pinned:
          type: boolean
          description: |
            (v2.9.15+) Marked `true` when a curation rule injected this row at
            a fixed position. Clients can use this for distinct styling.
          default: false

    ImportURLRequest:
      type: object
      required:
        - collection
        - url
        - lang
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        url:
          type: string
          format: uri
          description: URL to fetch content from
          example: https://example.com/post.md
        key:
          type: string
          description: Document key (derived from URL if empty)
          example: my-post
        lang:
          type: string
          description: Language code
          example: en_US
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Additional metadata to merge with frontmatter
          example:
            source: [ imported ]
        ttl:
          type: integer
          description: Time-to-live in seconds (0 = no expiration)
          example: 3600

    SetTTLRequest:
      type: object
      required:
        - collection
        - key
        - lang
        - ttl
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        key:
          type: string
          description: Document key
          example: hello-world
        lang:
          type: string
          description: Language code
          example: en_US
        ttl:
          type: integer
          description: Time-to-live in seconds (0 = remove TTL)
          example: 3600

    Webhook:
      type: object
      properties:
        id:
          type: string
          description: Unique webhook ID
          example: abc123
        url:
          type: string
          format: uri
          description: Webhook callback URL
          example: https://example.com/hook
        events:
          type: array
          items:
            type: string
            enum: [ doc.added, doc.updated, doc.deleted ]
          description: Event types to listen for
          example: [ doc.added, doc.updated ]
        collection:
          type: string
          description: Collection filter (empty = all collections)
          example: blog
        createdAt:
          type: integer
          format: int64
          description: Unix timestamp when the webhook was created
          example: 1704844800

    RegisterWebhookRequest:
      type: object
      required:
        - url
        - events
      properties:
        url:
          type: string
          format: uri
          description: Webhook callback URL
          example: https://example.com/hook
        events:
          type: array
          items:
            type: string
            enum: [ doc.added, doc.updated, doc.deleted ]
          description: Event types to listen for
          example: [ doc.added, doc.updated ]
        collection:
          type: string
          description: Collection filter (empty = all collections)
          example: blog

    DeleteWebhookRequest:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          description: Webhook ID to delete
          example: abc123

    SetSchemaRequest:
      type: object
      required:
        - collection
        - schema
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        schema:
          type: string
          description: JSON Schema string for metadata validation
          example: "{\"type\":\"object\",\"required\":[\"author\"],\"properties\":{\"auth\
            or\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}"

    SchemaResponse:
      type: object
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        schema:
          type: string
          description: JSON Schema string
          example: "{\"type\":\"object\",\"required\":[\"author\"]}"
        enabled:
          type: boolean
          description: Whether schema validation is enabled for this collection
          example: true

    ValidateRequest:
      type: object
      required:
        - collection
        - meta
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata to validate against the collection schema
          example:
            category: [ blog ]
            author: [ John Doe ]

    ValidateResponse:
      type: object
      properties:
        valid:
          type: boolean
          description: Whether the metadata is valid according to the schema
          example: true
        errors:
          type: array
          items:
            type: string
          description: Validation error messages (empty if valid)
          example: []
        warnings:
          type: array
          items:
            type: string
          description: >
            Advisory findings that do not make the document invalid. Today:
            metadata values that look like a structure lost on the way in —
            Go's `%v` rendering of a map or a list of maps. A warning rather
            than an error, because such a value is a valid string and rejecting
            it would break callers legitimately storing text that looks like it.
          example: []

    ReplicationStatusResponse:
      type: object
      properties:
        node_id:
          type: string
          description: Unique node identifier
          example: leader-1
        role:
          type: string
          enum: [ standalone, leader, follower ]
          description: Replication role of this node
          example: leader
        current_lsn:
          type: integer
          format: uint64
          description: Current Log Sequence Number
          example: 45230
        binlog_oldest_lsn:
          type: integer
          format: uint64
          description: Oldest LSN still in the binlog (leader only)
          example: 40000
        binlog_size_bytes:
          type: integer
          format: int64
          description: Binlog file size in bytes (leader only)
          example: 15728640
        leader_addr:
          type: string
          description: Address of the leader node (follower only)
          example: leader:11024
        replication_lag_ms:
          type: integer
          format: int64
          description: Replication lag in milliseconds (follower only)
          example: 12
        healthy:
          type: boolean
          description: Whether this node is healthy
          example: true
        followers:
          type: array
          items:
            $ref: "#/components/schemas/FollowerInfo"
          description: Connected followers (leader only)
        uptime_seconds:
          type: integer
          format: int64
          description: Node uptime in seconds
          example: 86400

    FollowerInfo:
      type: object
      properties:
        follower_id:
          type: string
          description: Follower node identifier
          example: follower-1
        address:
          type: string
          description: Follower network address
          example: "10.0.0.2:11034"
        confirmed_lsn:
          type: integer
          format: uint64
          description: Last LSN confirmed by this follower
          example: 45228
        lag_ms:
          type: integer
          format: int64
          description: Estimated replication lag in milliseconds
          example: 12
        last_seen_at:
          type: integer
          format: int64
          description: Unix timestamp of last communication
          example: 1709500000
        status:
          type: string
          enum: [ healthy, warning, unhealthy ]
          description: "Follower health status (healthy: <1s lag, warning: 1-30s,
            unhealthy: >30s)"
          example: healthy

    HybridSearchRequest:
      type: object
      required:
        - collection
        - query
      properties:
        oversample:
          type: number
          minimum: 1.0
          maximum: 10.0
          description: >
            (v2.12.0+) Recall/latency knob: candidates fetched per requested
            result before deduplication, merging or rescoring trims them.
            Omitted, uses the collection's retrieval profile then MDDB's default
            of 3.0. Out of range returns 422 rather than clamping. See
            SEARCH.md#oversampling-v2120 for measured effect.
        collection:
          type: string
          description: Collection name to search
          example: blog
        query:
          type: string
          description: Search query string (used for both FTS and embedding)
          example: how to deploy with Docker
        topK:
          type: integer
          description: Number of top results to return (default 10)
          default: 10
          example: 10
        algorithm:
          type: string
          enum: [ bm25, bm25f, pmisparse ]
          description: FTS algorithm (default "bm25")
          default: bm25
          example: bm25
        vectorAlgorithm:
          type: string
          enum: [ flat, hnsw, ivf, pq, opq, sq, sq4, bq ]
          description: Vector search algorithm (default "flat")
          default: flat
          example: flat
        signals:
          $ref: "#/components/schemas/FusionSignals"
        strategy:
          type: string
          enum: [ alpha, rrf, weighted ]
          description: |
            Fusion strategy (default "alpha").
            - **alpha**: Weighted blending — `combined = (1-alpha) * normalizedFTS + alpha * vectorScore`
            - **rrf**: Reciprocal Rank Fusion — `score = 1/(k + rank_fts) + 1/(k + rank_vector)`
            - **weighted** (SRCH-002): alpha's blend, then adjusted by the
              signals in `signals`. Nothing changes unless a weight is set, and
              `alpha`/`rrf` are unaffected.
          default: alpha
          example: alpha
        alpha:
          type: number
          format: float
          description: >-
            Alpha blending weight: 0 = keyword only, 1 = semantic only. Used by
            the "alpha" and "weighted" strategies.

            Omit the field to take the collection's retrieval profile, or 0.5.
            An explicit 0 means pure keyword search and is honoured as such
            (SRCH-007) — it used to be indistinguishable from an omitted field
            and was silently replaced with 0.5, the opposite of the request.
          minimum: 0
          maximum: 1
          default: 0.5
          example: 0.5
        rrfK:
          type: integer
          description: RRF parameter k (default 60). Only used with "rrf" strategy.
          default: 60
          example: 60
        fuzzy:
          type: integer
          enum: [ 0, 1, 2 ]
          description: Typo tolerance for FTS (0=off, 1=1 edit, 2=2 edits)
          default: 0
          example: 0
        threshold:
          type: number
          format: float
          description: Minimum vector similarity score (0.0 to 1.0)
          minimum: 0
          maximum: 1
          example: 0.0
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata filters applied to both FTS and vector results
          example:
            category: [ tutorial ]
        geo:
          type: object
          description: |
            Optional spatial pre-filter. When set, only documents present in the
            in-memory geo index AND within `radiusMeters` of the given point are
            considered by both FTS and vector search; each result then carries a
            `distanceMeters` value. Required when `sort` is `distance`.
          required:
            - lat
            - lng
            - radiusMeters
          properties:
            lat:
              type: number
              format: double
              description: Latitude of the center point
              example: 51.5074
            lng:
              type: number
              format: double
              description: Longitude of the center point
              example: -0.1278
            radiusMeters:
              type: number
              format: double
              description: Search radius in meters
              example: 5000
        includeContent:
          type: boolean
          description: Include document content in results
          default: false
        fieldWeights:
          type: object
          additionalProperties:
            type: number
          description: Field weights for BM25F (keys are "title", "meta", "content")
          example:
            title: 3.0
            meta: 2.0
            content: 1.0
        disableStem:
          type: boolean
          description: Disable stemming for FTS
          default: false
        disableSynonyms:
          type: boolean
          description: Disable synonym expansion for FTS
          default: false
        lang:
          type: string
          description: Language for FTS query tokenization and stemming (e.g. "en", "pl", "de")
          example: en
        distanceMetric:
          type: string
          enum: [ cosine, dot_product, euclidean ]
          description: Distance metric for similarity computation
          default: cosine
          example: cosine
        boost:
          type: object
          additionalProperties:
            type: number
          description: |
            Per-query boost map applied to the fused combined score (not raw FTS/vector sub-scores).
            Keys follow the format `"metaKey:metaValue"`; positive values multiply the score of
            matching documents, negative values demote. See the same field on `FTSSearchRequest`
            for full semantics.
          example:
            "tag:featured": 2.0
            "status:archived": -3
        sort:
          type: string
          enum: [ combined, distance ]
          default: combined
          description: |
            Result ordering. `combined` (default) ranks by the fused score — keyword relevance
            and semantic similarity blended per `strategy`. `distance` re-sorts the post-merge
            result set by `distanceMeters` ascending so the nearest documents surface first.
            Requires a `geo` filter on the request; otherwise the server rejects the call
            because distances would all be zero.
          example: distance
        facetBy:
          type: array
          items:
            type: string
          description: (v2.9.15+) Metadata keys to aggregate into `facets` on the response
        facetMaxValues:
          type: integer
          description: (v2.9.15+) Cap per-key bucket count; 0 = unlimited

    HybridSearchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/HybridSearchResultItem"
        total:
          type: integer
          description: Number of results returned
          example: 5
        strategy:
          type: string
          description: Fusion strategy used
          example: alpha
        alpha:
          type: number
          description: Alpha value used (only for alpha strategy)
          example: 0.5
        rrfK:
          type: integer
          description: RRF k value used (only for rrf strategy)
          example: 60
        ftsAlgorithm:
          type: string
          description: FTS algorithm used
          example: bm25
        vectorAlgorithm:
          type: string
          description: Vector algorithm used
          example: flat
        distanceMetric:
          type: string
          description: Distance metric used for this search
          example: cosine
        searchStats:
          $ref: "#/components/schemas/SearchStats"
        facets:
          $ref: "#/components/schemas/FacetMap"

    HybridSearchResultItem:
      type: object
      properties:
        document:
          $ref: "#/components/schemas/Document"
        combinedScore:
          type: number
          format: float
          description: Fused score from both FTS and vector
          example: 0.78
        ftsScore:
          type: number
          format: float
          description: Normalized FTS score component
          example: 0.65
        vectorScore:
          type: number
          format: float
          description: Vector similarity score component
          example: 0.91
        matchedTerms:
          type: array
          items:
            type: string
          description: FTS matched terms
          example: [ deploy, docker ]
        rank:
          type: integer
          description: Result rank (1-based)
          example: 1
        pinned:
          type: boolean
          description: (v2.9.15+) Injected by a curation rule
          default: false

    PinnedDoc:
      type: object
      properties:
        key:
          type: string
        lang:
          type: string
        position:
          type: integer
          description: 1-based; <=0 appends after organic results
      required: [ key, position ]

    CurationRule:
      type: object
      description: (v2.9.15+) Pinned + hidden document overrides for FTS/Hybrid search
      properties:
        id:
          type: string
          description: Server-assigned. Omit on POST.
        collection:
          type: string
        query:
          type: string
        matchMode:
          type: string
          enum: [ exact, contains ]
          default: exact
        pins:
          type: array
          items:
            $ref: "#/components/schemas/PinnedDoc"
        hides:
          type: array
          items:
            type: string
          description: Document keys to drop from results
        enabled:
          type: boolean
        createdAt:
          type: integer
          format: int64
        updatedAt:
          type: integer
          format: int64
      required: [ collection, query ]

    SearchRecommendation:
      type: object
      description: |
        How to search a collection, measured rather than guessed (SRCH-010).

        MDDB offers eight vector algorithms, four ranking algorithms, three
        fusion strategies and four retrieval modes. An agent connecting over
        MCP has no basis for choosing among them. This measures the collection
        — length, vocabulary, whether it is embedded, whether it is code — and
        recommends, with a reason for every choice.

        It changes nothing. `retrievalProfile` is the advice encoded as a
        RAG-001 profile, ready to `PUT` to `/v1/collection-config` if you agree.
      properties:
        profile:
          $ref: "#/components/schemas/CollectionProfile"
        searchType:
          type: string
          enum: [ fts, vector, hybrid ]
        ftsAlgorithm:
          type: string
          enum: [ tfidf, bm25, bm25f, pmisparse ]
        vectorAlgorithm:
          type: string
          enum: [ flat, hnsw, ivf, pq, opq, sq, sq4, bq ]
        hybridStrategy:
          type: string
          enum: [ alpha, rrf, weighted ]
        hybridAlpha:
          type: number
        retrievalMode:
          type: string
          enum: [ parent, chunk, window, graph ]
        topK:
          type: integer
        signals:
          $ref: "#/components/schemas/FusionSignals"
        reasons:
          type: array
          description: >-
            One line per decision, in plain language. The part worth reading:
            a recommendation nobody can argue with is one nobody should follow.
          items:
            type: string
        warnings:
          type: array
          description: What would stop the recommendation working — a
            half-embedded collection, a missing provider.
          items:
            type: string
        retrievalProfile:
          $ref: "#/components/schemas/RetrievalProfile"

    CollectionProfile:
      type: object
      description: What the advisor measured, from a sample of up to 2000 documents.
      properties:
        collection:
          type: string
        documents:
          type: integer
        sampled:
          type: integer
          description: How many were actually read. A recommendation from a
            sample should say so.
        embeddedDocuments:
          type: integer
        vectorDimensions:
          type: integer
        medianWords:
          type: integer
          description: Median, not mean — one long manual among ten thousand
            notes moves a mean and describes nothing.
        longDocumentRatio:
          type: number
          description: Share above 500 words, where returning whole documents
            starts wasting a prompt.
        distinctTerms:
          type: integer
        termsPerDocument:
          type: number
          description: >-
            Vocabulary divided by documents sampled — how much new vocabulary
            each document contributes. This decides whether keyword search can
            discriminate. Type-token ratio would not: pooled over a corpus it
            falls towards zero as the corpus grows, so every large collection
            would look repetitive.
        typeTokenRatio:
          type: number
          description: Reported because it is familiar; not used for any decision.
        codeDocuments:
          type: integer
          description: How many carry extracted symbols, which is what makes
            the connection graph useful.
        estimatedVectorBytes:
          type: integer
          format: int64

    FusionSignals:
      type: object
      description: |
        Ranking signals for the `weighted` hybrid strategy (SRCH-002).

        **A weight is a fraction of the result's own score**: `proximity: 0.1`
        means "up to 10% better", `diversity: 0.8` means "up to 80% worse".
        That keeps the signals scale-free, and makes large weights genuinely
        powerful — at 1.0 a proximity bonus can carry a result past one scoring
        half again as much. Weights are clamped to [0, 1].

        Every weight defaults to 0, so a `weighted` request with no signals
        behaves exactly like `alpha`.
      properties:
        diversity:
          type: number
          minimum: 0
          maximum: 1
          description: >-
            Penalise a result that is a near-copy of a higher-ranked one,
            measured by text overlap. A vector score cannot do this: near-copies
            have near-identical embeddings, so similarity rewards the
            duplication. Suggested range 0.5-1.0.
          example: 0.8
        diversityThreshold:
          type: number
          minimum: 0
          maximum: 1
          default: 0.5
          description: Overlap below which two documents count as unrelated.
        proximity:
          type: number
          minimum: 0
          maximum: 1
          description: >-
            Reward a result whose key shares a directory with a higher-ranked
            one. Suggested range 0.05-0.15.
          example: 0.1
        freshness:
          type: number
          minimum: 0
          maximum: 1
          description: >-
            Reward recently updated documents on an exponential decay. Off by
            default because it is wrong for reference material — an API
            specification does not become less true with age. Suggested range
            0.05-0.2.
          example: 0.1
        freshnessHalfLifeDays:
          type: number
          default: 180
          description: How long the freshness bonus takes to halve.

    SignalBreakdown:
      type: object
      description: >-
        What each signal contributed to one result, in the same order as
        `results`. Returned by the `weighted` strategy, because a reranking
        nobody can explain is a reranking nobody trusts.
      properties:
        base:
          type: number
          description: The score before any signal applied
        diversity:
          type: number
          description: Negative — the near-duplicate penalty
        proximity:
          type: number
        freshness:
          type: number
        final:
          type: number

    PersistenceStatus:
      type: object
      description: >-
        What the data directory can promise about durability (GO-032). Reported
        by /health so an operator sees whether acknowledged writes will survive,
        not only that the process is up.
      properties:
        path:
          type: string
          description: The data directory the server writes to
          example: /var/lib/mddb
        filesystem:
          type: string
          description: Detected filesystem type, when it could be determined
          example: ext4
        ephemeral:
          type: boolean
          description: >-
            True when the directory lives on storage that does not survive a
            container restart (tmpfs, overlayfs upper layer).
          example: false
        writable:
          type: boolean
          example: true
        freeBytes:
          type: integer
          format: int64
          description: Free space on the filesystem holding the data directory
          example: 21474836480
        warnings:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
                example: EPHEMERAL_FS
              detail:
                type: string
                example: data directory is on tmpfs; writes will not survive a restart

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Error message
          example: document not found

    UploadResponse:
      type: object
      properties:
        key:
          type: string
          description: Document key
        format:
          type: string
          description: Detected file format (md, txt, html, pdf, docx, odt, rtf, yaml,
            tex, log, lex)
        converted:
          type: boolean
          description: Whether the file was converted to markdown
        document:
          $ref: "#/components/schemas/Document"

    UploadBatchResponse:
      type: object
      properties:
        added:
          type: integer
        updated:
          type: integer
        failed:
          type: integer
        errors:
          type: array
          items:
            type: string
        results:
          type: array
          items:
            $ref: "#/components/schemas/UploadResponse"

    SynonymRequest:
      type: object
      required:
        - collection
        - term
        - synonyms
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        term:
          type: string
          description: Base term for the synonym group
          example: big
        synonyms:
          type: array
          items:
            type: string
          description: List of synonyms for the term
          example: [ large, huge, enormous ]

    SynonymDeleteRequest:
      type: object
      required:
        - collection
        - term
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        term:
          type: string
          description: Term whose synonyms to delete
          example: big

    SynonymEntry:
      type: object
      properties:
        term:
          type: string
          description: Base term
          example: big
        synonyms:
          type: array
          items:
            type: string
          description: List of synonyms
          example: [ large, huge, enormous ]

    SynonymListResponse:
      type: object
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        entries:
          type: array
          items:
            $ref: "#/components/schemas/SynonymEntry"
        total:
          type: integer
          description: Total number of synonym entries
          example: 10

    EmbeddingConfig:
      type: object
      required:
        - id
        - name
        - provider
        - model
        - dimensions
      properties:
        id:
          type: string
          description: Unique identifier
          example: openai-small
        name:
          type: string
          description: Display name
          example: OpenAI Small
        provider:
          type: string
          enum: [ openai, ollama, cohere, voyage ]
          description: Embedding provider
          example: openai
        model:
          type: string
          description: Model name
          example: text-embedding-3-small
        dimensions:
          type: integer
          description: Embedding vector dimensions
          example: 1536
        apiKey:
          type: string
          description: API key (for OpenAI, Cohere, Voyage)
          example: sk-...
        apiUrl:
          type: string
          description: API URL (for Ollama or custom endpoints)
          example: http://localhost:11434
        isDefault:
          type: boolean
          description: Whether this is the default configuration
          example: false
        createdAt:
          type: integer
          format: int64
          description: Creation timestamp (Unix seconds)
          example: 1704844800

    SystemInfoResponse:
      type: object
      properties:
        hostname:
          type: string
          description: Server hostname
          example: mddb-server
        os:
          type: string
          description: Operating system
          example: linux
        arch:
          type: string
          description: CPU architecture
          example: amd64
        numCPU:
          type: integer
          description: Number of logical CPUs
          example: 8
        goVersion:
          type: string
          description: Go runtime version
          example: go1.27.0
        version:
          type: string
          description: MDDB server version
          example: 2.9.15
        uptimeSeconds:
          type: integer
          format: int64
          description: Server uptime in seconds
          example: 86400
        memoryTotal:
          type: integer
          format: uint64
          description: Total allocated memory in bytes (cumulative)
          example: 104857600
        memoryUsed:
          type: integer
          format: uint64
          description: Currently allocated memory in bytes
          example: 52428800
        memorySystem:
          type: integer
          format: uint64
          description: System memory obtained from OS in bytes
          example: 73400320
        memoryHeap:
          type: integer
          format: uint64
          description: Heap memory in use in bytes
          example: 41943040
        numGoroutines:
          type: integer
          description: Number of active goroutines
          example: 42
        cpuUsagePercent:
          type: number
          format: float
          description: CPU usage percentage (0-100)
          example: 12.5
        network:
          type: array
          items:
            $ref: "#/components/schemas/NetworkInterface"

    NetworkInterface:
      type: object
      properties:
        name:
          type: string
          description: Interface name
          example: eth0
        addresses:
          type: array
          items:
            type: string
          description: IP addresses assigned to this interface
          example: [ "192.168.1.10/24", "fe80::1/64" ]
        flags:
          type: string
          description: Interface flags
          example: up|broadcast|multicast

    ConfigResponse:
      type: object
      properties:
        version:
          type: string
          description: MDDB server version
          example: 2.9.15
        databasePath:
          type: string
          description: Path to the database file
          example: /data/mddb.db
        mode:
          type: string
          description: Server mode
          example: wr
        panelMode:
          type: string
          description: Panel mode (internal or external)
          example: internal
        protocols:
          type: object
          properties:
            http:
              type: object
              properties:
                enabled:
                  type: boolean
                  example: true
                addr:
                  type: string
                  example: ":11023"
            grpc:
              type: object
              properties:
                enabled:
                  type: boolean
                  example: true
                addr:
                  type: string
                  example: ":11024"
            mcp:
              type: object
              properties:
                enabled:
                  type: boolean
                  example: false
                addr:
                  type: string
                  example: ""
                stdio:
                  type: boolean
                  example: false
                revision:
                  type: string
                  description: Newest MCP spec revision this server serves.
                  example: "2026-07-28"
                supportedRevisions:
                  type: array
                  description: Every MCP spec revision this server serves, newest first.
                  items:
                    type: string
                  example: ["2026-07-28", "2025-11-25"]
                revisionPinned:
                  type: boolean
                  description: >-
                    True when MDDB_MCP_PROTOCOL_VERSION fixes the revision and
                    version negotiation is disabled.
                  example: false
                handshakeRevision:
                  type: string
                  description: >-
                    Revision an `initialize` is answered with, absent when this
                    server serves no handshake revision at all.
                  example: "2025-11-25"
            http3:
              type: object
              properties:
                enabled:
                  type: boolean
                  example: false
                addr:
                  type: string
                  example: ""
        authEnabled:
          type: boolean
          description: Whether authentication is enabled
          example: false
        metricsEnabled:
          type: boolean
          description: Whether Prometheus metrics are enabled
          example: true
        searchStatsEnabled:
          type: boolean
          description: Whether search statistics are included in search responses
          example: false
        replicationRole:
          type: string
          description: Replication role of this node
          example: standalone
        vectorConfig:
          type: object
          description: Vector search configuration (if enabled)
          properties:
            enabled:
              type: boolean
              example: true
            provider:
              type: string
              example: openai
            model:
              type: string
              example: text-embedding-3-small
            dimensions:
              type: integer
              example: 1536
            apiUrl:
              type: string
              example: https://api.openai.com/v1
        chunkConfig:
          type: object
          description: Document chunking configuration
          properties:
            enabled:
              type: boolean
              example: true
            chunkSize:
              type: integer
              example: 1500

    EndpointsResponse:
      type: object
      properties:
        http:
          type: array
          items:
            type: object
            properties:
              method:
                type: string
                description: HTTP method(s)
                example: POST
              path:
                type: string
                description: URL path
                example: /v1/add
              description:
                type: string
                description: Endpoint description
                example: Add/update document
              requiresAuth:
                type: boolean
                description: Whether authentication is required
                example: true
        grpc:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: gRPC method name
                example: Add
              description:
                type: string
                description: Method description
                example: Add/update document
        mcp:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
                description: MCP tool name
                example: add_document
              description:
                type: string
                description: Tool description
                example: Add/update document
              inputSchema:
                type: object
                description: JSON Schema for the tool input

    LoginRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          description: Username
          example: admin
        password:
          type: string
          description: Password
          example: secret

    LoginResponse:
      type: object
      properties:
        token:
          type: string
          description: JWT token
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
        expiresAt:
          type: integer
          format: int64
          description: Token expiration timestamp (Unix seconds)
          example: 1704931200

    RegisterRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          description: Username for the new user
          example: alice
        password:
          type: string
          description: Password for the new user
          example: securepassword123
        tenant:
          type: string
          description: >-
            Optional tenant namespace for the new user. A tenant user can only
            access collections named "<tenant>/<name>" and can never hold the
            global admin role. Omit for an unrestricted (global) user.
          example: acme

    RegisterResponse:
      type: object
      properties:
        username:
          type: string
          description: Created username
          example: alice
        tenant:
          type: string
          description: Tenant namespace the user is confined to (empty for global users)
          example: acme
        createdAt:
          type: integer
          format: int64
          description: Account creation timestamp (Unix seconds)
          example: 1704844800

    CreateAPIKeyRequest:
      type: object
      properties:
        description:
          type: string
          description: Optional label for the API key
          example: CI/CD pipeline key
        expiresAt:
          type: integer
          format: int64
          description: Expiration timestamp (Unix seconds, 0 = never)
          example: 0

    CreateAPIKeyResponse:
      type: object
      properties:
        key:
          type: string
          description: The API key value (shown only once)
          example: mddb_live_a1b2c3d4e5f6...
        description:
          type: string
          description: API key description
          example: CI/CD pipeline key
        expiresAt:
          type: integer
          format: int64
          description: Expiration timestamp (Unix seconds, 0 = never)
          example: 0
        createdAt:
          type: integer
          format: int64
          description: Creation timestamp (Unix seconds)
          example: 1704844800

    ListAPIKeysResponse:
      type: object
      properties:
        keys:
          type: array
          items:
            type: object
            properties:
              keyHash:
                type: string
                description: SHA256 hash of the API key (for deletion)
                example: a1b2c3d4e5f6...
              description:
                type: string
                description: API key description
                example: CI/CD pipeline key
              createdAt:
                type: integer
                format: int64
                description: Creation timestamp (Unix seconds)
                example: 1704844800
              expiresAt:
                type: integer
                format: int64
                description: Expiration timestamp (Unix seconds, 0 = never)
                example: 0

    GetMeResponse:
      type: object
      properties:
        username:
          type: string
          description: Current user's username
          example: admin
        admin:
          type: boolean
          description: Whether the user has admin privileges
          example: true
        createdAt:
          type: integer
          format: int64
          description: Account creation timestamp (Unix seconds)
          example: 1704844800

    Permission:
      type: object
      properties:
        username:
          type: string
          description: Username
          example: alice
        collection:
          type: string
          description: Collection name ("*" for all collections)
          example: blog
        read:
          type: boolean
          description: Read permission
          example: true
        write:
          type: boolean
          description: Write permission
          example: true
        admin:
          type: boolean
          description: Admin permission
          example: false

    SetPermissionRequest:
      type: object
      required:
        - username
        - collection
      properties:
        username:
          type: string
          description: Username
          example: alice
        collection:
          type: string
          description: Collection name ("*" for all collections)
          example: blog
        read:
          type: boolean
          description: Read permission
          example: true
        write:
          type: boolean
          description: Write permission
          example: true
        admin:
          type: boolean
          description: Admin permission
          example: false

    SetPermissionResponse:
      type: object
      properties:
        status:
          type: string
          example: ok

    UsersListResponse:
      type: object
      properties:
        users:
          type: array
          items:
            type: object
            properties:
              username:
                type: string
                description: Username
                example: alice
              createdAt:
                type: integer
                format: int64
                description: Account creation timestamp (Unix seconds)
                example: 1704844800
              disabled:
                type: boolean
                description: Whether the user is disabled
                example: false
              admin:
                type: boolean
                description: Whether the user has admin privileges
                example: false
              groups:
                type: array
                items:
                  type: string
                description: Groups the user belongs to
                example: [ developers, editors ]

    Group:
      type: object
      properties:
        name:
          type: string
          description: Group name
          example: developers
        description:
          type: string
          description: Group description
          example: Development team
        members:
          type: array
          items:
            type: string
          description: List of usernames in the group
          example: [ alice, bob ]
        createdAt:
          type: integer
          format: int64
          description: Creation timestamp (Unix seconds)
          example: 1704844800

    CreateGroupRequest:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Group name
          example: developers
        description:
          type: string
          description: Group description
          example: Development team
        members:
          type: array
          items:
            type: string
          description: Initial list of member usernames
          example: [ alice, bob ]

    UpdateGroupRequest:
      type: object
      properties:
        description:
          type: string
          description: Updated group description
          example: Development team (updated)
        members:
          type: array
          items:
            type: string
          description: Updated list of member usernames
          example: [ alice, bob, charlie ]

    GroupsListResponse:
      type: object
      properties:
        groups:
          type: array
          items:
            $ref: "#/components/schemas/Group"

    GroupPermission:
      type: object
      required:
        - groupName
        - collection
      properties:
        groupName:
          type: string
          description: Group name
          example: developers
        collection:
          type: string
          description: Collection name ("*" for all collections)
          example: blog
        read:
          type: boolean
          description: Read permission
          example: true
        write:
          type: boolean
          description: Write permission
          example: true
        admin:
          type: boolean
          description: Admin permission
          example: false

    AutomationRule:
      type: object
      required: [ type, name ]
      properties:
        id:
          type: string
          readOnly: true
        type:
          type: string
          enum: [ webhook, trigger, cron ]
        name:
          type: string
        enabled:
          type: boolean
          default: true
        createdAt:
          type: integer
          readOnly: true
        updatedAt:
          type: integer
          readOnly: true
        url:
          type: string
          description: Webhook URL (type=webhook)
        method:
          type: string
          enum: [ POST, GET, PUT ]
          default: POST
          description: HTTP method (type=webhook)
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom HTTP headers (type=webhook)
        collection:
          type: string
          description: Target collection (type=trigger)
        searchType:
          type: string
          enum: [ fts, vector, hybrid ]
          description: Search type (type=trigger)
        query:
          type: string
          description: Search query (type=trigger)
        threshold:
          type: number
          minimum: 0
          maximum: 100
          description: Minimum score threshold 0-100 (type=trigger)
        webhookId:
          type: string
          description: Target webhook ID (type=trigger, type=cron)
        searchParams:
          type: object
          description: Extra search parameters (type=trigger)
        events:
          type: array
          items:
            type: string
            enum: [ insert, update, delete ]
          description: Events that fire the trigger (type=trigger, default
            ["insert","update"])
        sentimentEnabled:
          type: boolean
          description: Enable keyword-based sentiment condition (type=trigger)
        sentimentMin:
          type: number
          minimum: -1
          maximum: 1.0
          description: Minimum sentiment score -1.0 to 1.0 (type=trigger)
        sentimentMax:
          type: number
          minimum: -1
          maximum: 1.0
          description: Maximum sentiment score -1.0 to 1.0 (type=trigger)
        conditionLogic:
          type: string
          enum: [ and, or ]
          default: and
          description: How to combine search + sentiment conditions (type=trigger)
        schedule:
          type: string
          description: Cron expression e.g. "0 9 * * *" (type=cron)
        triggerId:
          type: string
          description: Target trigger ID (type=cron)
        lastRun:
          type: integer
          readOnly: true
          description: Last execution timestamp (type=cron)
        nextRun:
          type: integer
          readOnly: true
          description: Next scheduled execution (type=cron)

    TriggerMatch:
      type: object
      properties:
        docId:
          type: string
        collection:
          type: string
        score:
          type: number

    AutomationLogEntry:
      type: object
      properties:
        id:
          type: string
        timestamp:
          type: integer
        ruleId:
          type: string
        ruleName:
          type: string
        ruleType:
          type: string
          enum: [ trigger, cron ]
        webhookId:
          type: string
        webhookUrl:
          type: string
        status:
          type: string
          enum: [ success, error, skipped ]
        httpStatus:
          type: integer
        durationMs:
          type: integer
        error:
          type: string
        attempt:
          type: integer

    SearchStats:
      type: object
      description: Statistics about a search execution
      properties:
        durationMs:
          type: integer
          description: Search execution time in milliseconds
          example: 12
        queryTerms:
          type: array
          items:
            type: string
          description: Tokenized query terms
          example: [ cancel, subscription ]
        indexSize:
          type: integer
          description: Documents in search scope
          example: 150
        totalTokens:
          type: integer
          description: Number of tokens in query
          example: 2

    RevisionEntry:
      type: object
      properties:
        timestamp:
          type: integer
          format: int64
          description: Unix timestamp of the revision
          example: 1704844800
        updatedAt:
          type: integer
          format: int64
          description: Unix timestamp when the revision was saved
          example: 1704844800
        contentMd:
          type: string
          description: Markdown content at this revision
          example: "# Hello World\n\nOriginal content."
        meta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata at this revision
          example:
            category: [ blog ]
            author: [ John Doe ]

    RetrievalProfile:
      type: object
      description: >
        (v2.12.0+) Per-collection retrieval defaults. Every field is optional;
        an unset field means "not configured". Precedence is fixed everywhere:
        an explicit request parameter wins, then this profile, then MDDB's own
        default for that endpoint — so a collection without a profile behaves
        exactly as it did before this existed.
      properties:
        defaultSearchType:
          type: string
          enum: [ fts, vector, hybrid ]
          description: >
            Which search this collection expects. Consumed by clients reading
            the config (mddb-chat, MCP agents); the server does not reroute a
            request to a different endpoint because of it.
        topK:
          type: integer
          minimum: 0
          maximum: 1000
          description: Results per search. 0 = unset.
        retrievalMode:
          type: string
          enum: [ parent, chunk, window, graph ]
          description: Granularity — whole document, matching passage, or passage with neighbours.
        hybridStrategy:
          type: string
          enum: [ alpha, rrf, weighted ]
          description: Hybrid fusion strategy.
        hybridAlpha:
          type: number
          minimum: 0
          maximum: 1
          description: >
            Keyword-to-semantic weight: 0 = keyword only, 1 = semantic only.
            Requires hybridAlphaSet, because 0.0 is a meaningful weight and
            cannot double as "unset".
        hybridAlphaSet:
          type: boolean
          description: Whether hybridAlpha was configured.
        oversample:
          type: number
          minimum: 1.0
          maximum: 10.0
          description: >
            (SRCH-005) Default recall/latency knob for this collection:
            candidates fetched per requested result before deduplication,
            merging or rescoring. A request parameter overrides it.
        contextTokenBudget:
          type: integer
          minimum: 0
          maximum: 1000000
          description: >
            Cap on the total context a search returns, so a RAG caller cannot be
            handed more than its model holds. Results are dropped from the tail,
            never truncated mid-document — half a document still costs tokens and
            no longer says anything reliable. Approximated as bytes/4 rather than
            tokenised: a real tokeniser would tie the budget to one model family.
            Responses set `contextTruncated` when this applied. 0 = no cap.
      example:
        defaultSearchType: hybrid
        topK: 40
        retrievalMode: chunk
        contextTokenBudget: 8000

    CollectionConfig:
      type: object
      properties:
        type:
          type: string
          description: Collection type (e.g., default, website, images, audio, documents)
          example: website
        description:
          type: string
          description: Collection description
          example: Blog posts and articles
        icon:
          type: string
          description: Collection icon (emoji or icon name)
          example: "🌐"
        color:
          type: string
          description: Collection color (hex code)
          example: "#3B82F6"
        customMeta:
          type: object
          additionalProperties: true
          description: Custom key-value metadata for the collection
        trackAccess:
          type: boolean
          description: Record per-read access events for temporal analytics
        trackHot:
          type: boolean
          description: Maintain a hot-documents leaderboard for this collection
        spellCorrect:
          type: boolean
          description: Auto-correct FTS queries using the spell checker
        spellLang:
          type: string
          description: Override language for spell correction (defaults to query language)
        encrypted:
          type: boolean
          description: >-
            Encrypt documents and revisions of this collection at rest
            (AES-256-GCM; requires MDDB_ENCRYPTION_KEY on the server)
        storageBackend:
          type: string
          enum: [ boltdb, memory, s3 ]
          description: >
            (Honoured from v2.12.0; accepted and ignored before that.) Where
            this collection's document *bodies* are stored. The metadata index,
            revisions, search indexes and replication binlog stay in the
            server's own database in every configuration — they are written in
            one transaction that a remote object store cannot join. A backend
            that cannot be reached is refused at configuration time, and writes
            to a collection whose backend fails later error rather than falling
            back to local disk.
          example: boltdb
        storageConfig:
          $ref: "#/components/schemas/StorageConfigDef"
        quantization:
          type: string
          enum: [ float32, int8, int4 ]
          description: "Vector quantization level. int8 = 4x compression, int4 = 8x
            compression. Default: float32."
          example: float32
        maxRevisions:
          type: integer
          description: |
            (v2.9.15+) Revision retention cap per document. `0` (default) = unlimited.
            When `> 0`, older revisions are trimmed synchronously on every write so
            history stays capped even under high write-churn.
          default: 0
        retrieval:
          $ref: "#/components/schemas/RetrievalProfile"
        responsePrompt:
          type: string
          maxLength: 4096
          description: >
            (v2.12.0+) How answers drawn from this collection should be
            formatted — numbered steps for runbooks, code blocks for API docs.
            Applied automatically by mddb-chat and returned alongside MCP search
            results, so the instruction travels with the data instead of living
            in every client. Supports `{{collection}}` and `{{query}}`
            placeholders. Plain text for a model; never rendered as markup.
            Capped at 4 KiB because it is prepended to prompts automatically and
            would otherwise eat the context the answer needs.
          example: "Answer as numbered steps. Quote the exact command for each step."
          example: 20
        wordpress:
          $ref: "#/components/schemas/WordPressTargetConfig"

    WordPressTargetConfig:
      type: object
      description: |
        (v2.11.0+) WordPress remote-publishing target used by the
        wordpress_publish / wordpress_set_status MCP tools. Points at a site
        running the mddb-sync plugin with "Remote publishing" enabled.
      required:
        - url
      properties:
        url:
          type: string
          description: Site base URL. Must be https:// (http only for localhost).
          example: https://blog.example.com
        apiKey:
          type: string
          writeOnly: true
          description: |
            mddb-sync publish key (Settings → MDDB Sync), sent as Authorization Bearer.

            (v2.12.0+) Write-only. Reads never return it — see `apiKeySet`. On
            write, an empty value keeps the stored key; remove the whole
            `wordpress` block to clear it.
        apiKeySet:
          type: boolean
          readOnly: true
          description: |
            (v2.12.0+) Read-only. `true` when a publish key is stored.
          example: true

    StorageConfigDef:
      type: object
      description: Backend-specific configuration for non-default storage backends
        (required when storageBackend is 's3').
      properties:
        endpoint:
          type: string
          description: S3-compatible endpoint URL
          example: s3.amazonaws.com
        bucket:
          type: string
          description: S3 bucket name
          example: my-mddb-bucket
        region:
          type: string
          description: AWS region
          example: us-east-1
        accessKey:
          type: string
          description: Access key for authentication
          example: AKIAIOSFODNN7EXAMPLE
        secretKey:
          type: string
          writeOnly: true
          description: |
            (v2.12.0+) Write-only. Reads never return it — see `secretKeySet`.

            Reading a collection's configuration requires read permission on
            that collection, which is permission to read its documents, not to
            collect the credentials for the bucket underneath them.

            On write, an empty value keeps the stored secret, so the usual
            read-modify-write loop does not erase it. Send a new value to
            replace it; remove the whole `storageConfig` block to clear it.
          example: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        secretKeySet:
          type: boolean
          readOnly: true
          description: |
            (v2.12.0+) Read-only. `true` when a secret key is stored — so a
            blank `secretKey` field can be told apart from no credential at all.
          example: true
        prefix:
          type: string
          description: Key prefix within the bucket
          example: mddb/
        useTLS:
          type: boolean
          description: Use HTTPS for S3 connections
          example: true

    AggregateRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          description: Collection name
          example: blog
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Optional metadata pre-filter (AND over keys, OR over values)
        facets:
          type: array
          items:
            type: object
            required:
              - field
            properties:
              field:
                type: string
                description: Metadata key to aggregate
                example: category
              orderBy:
                type: string
                enum: [ count, value ]
                description: Sort order for facet buckets (default count descending)
                example: count
        histograms:
          type: array
          items:
            type: object
            required:
              - field
            properties:
              field:
                type: string
                description: Time field to histogram (addedAt or updatedAt)
                example: addedAt
              interval:
                type: string
                enum: [ day, week, month, year ]
                description: Histogram bucket interval (default month)
                example: month
        maxFacetSize:
          type: integer
          description: Maximum number of values per facet (default 50)
          example: 50

    AggregateResponse:
      type: object
      properties:
        collection:
          type: string
          example: blog
        totalDocs:
          type: integer
          description: Total documents matching the filter (or all docs if no filter)
          example: 42
        facets:
          type: object
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                value:
                  type: string
                count:
                  type: integer
          description: Facet results keyed by metadata field name
        histograms:
          type: object
          additionalProperties:
            type: array
            items:
              type: object
              properties:
                key:
                  type: string
                  description: Formatted date label
                from:
                  type: integer
                  description: Unix timestamp start
                to:
                  type: integer
                  description: Unix timestamp end (exclusive)
                count:
                  type: integer
          description: Histogram results keyed by field name
        durationMs:
          type: integer
          description: Query execution time in milliseconds
          example: 3

    CrossSearchRequest:
      type: object
      properties:
        oversample:
          type: number
          minimum: 1.0
          maximum: 10.0
          description: >
            (v2.12.0+) Recall/latency knob: candidates fetched per requested
            result before deduplication, merging or rescoring trims them.
            Omitted, uses the collection's retrieval profile then MDDB's default
            of 3.0. Out of range returns 422 rather than clamping. See
            SEARCH.md#oversampling-v2120 for measured effect.
        sourceCollection:
          type: string
          description: Collection containing the source document (for document-as-query)
          example: content
        sourceDocID:
          type: string
          description: ID of the source document whose embedding to use as query
          example: post-123
        query:
          type: string
          description: Text query (alternative to sourceCollection/sourceDocID)
          example: machine learning tutorial
        queryVector:
          type: array
          items:
            type: number
          description: Pre-computed query vector (alternative to query or source document)
        targetCollections:
          type: array
          items:
            type: string
          description: Collections to search in
          example: [ "images", "audio" ]
        topK:
          type: integer
          description: Number of results to return
          default: 10
          example: 10
        threshold:
          type: number
          description: Minimum similarity score
          default: 0.0
          example: 0.5
        algorithm:
          type: string
          description: Vector search algorithm
          enum: [ flat, hnsw, ivf, pq, opq, sq, bq ]
          default: flat
          example: flat
        distanceMetric:
          type: string
          description: Distance metric for similarity computation
          enum: [ cosine, dot_product, euclidean ]
          default: cosine
          example: cosine
        filterMeta:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: Metadata filters applied to target documents
        includeContent:
          type: boolean
          description: Include document content in results
          default: false
          example: false

    CrossSearchResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: "#/components/schemas/CrossSearchResultItem"
        total:
          type: integer
          description: Total number of results
          example: 5
        sourceCollection:
          type: string
          description: Source collection (if document-as-query was used)
          example: content
        sourceDocID:
          type: string
          description: Source document ID (if document-as-query was used)
          example: post-123
        targetCollections:
          type: array
          items:
            type: string
          description: Collections that were searched
          example: [ "images", "audio" ]
        algorithm:
          type: string
          description: Vector search algorithm used
          example: flat
        distanceMetric:
          type: string
          description: Distance metric used
          example: cosine
        searchStats:
          $ref: "#/components/schemas/SearchStats"

    CrossSearchResultItem:
      type: object
      properties:
        collection:
          type: string
          description: Collection the result came from
          example: images
        document:
          $ref: "#/components/schemas/Document"
        score:
          type: number
          format: float
          description: Similarity score
          example: 0.89
        rank:
          type: integer
          description: Result rank (1-based)
          example: 1

    FindDuplicatesRequest:
      type: object
      required:
        - collection
      properties:
        collection:
          type: string
          description: Collection to scan
        mode:
          type: string
          enum: [ exact, similar, minhash, both ]
          default: both
          description: |
            Detection mode.
            - **exact**: identical content hashes
            - **similar**: close embeddings — finds documents on the same
              topic, and needs an embedding provider configured
            - **minhash**: overlapping text (SRCH-002) — finds a page that was
              forked and lightly edited, which `exact` misses and `similar`
              cannot distinguish from two independent pages on one subject.
              Needs no embeddings.
            - **both**: exact and similar. Deliberately excludes `minhash`,
              which reads every document body.
        threshold:
          type: number
          minimum: 0
          maximum: 1
          default: 0.9
          description: Similarity threshold for similar mode
        maxDocs:
          type: integer
          default: 5000
          description: Maximum documents to process (safety bound)
        distanceMetric:
          type: string
          enum: [ cosine, dot_product, euclidean ]
          default: cosine
        includeContent:
          type: boolean
          default: false
    FindDuplicatesResponse:
      type: object
      properties:
        collection:
          type: string
        mode:
          type: string
        threshold:
          type: number
        distanceMetric:
          type: string
        totalDocuments:
          type: integer
        totalEmbedded:
          type: integer
        exactGroups:
          type: array
          items:
            $ref: "#/components/schemas/DuplicateGroup"
        similarGroups:
          type: array
          items:
            $ref: "#/components/schemas/DuplicateGroup"
        exactDuplicates:
          type: integer
        similarPairs:
          type: integer
        searchStats:
          $ref: "#/components/schemas/SearchStats"
    DuplicateGroup:
      type: object
      properties:
        groupId:
          type: integer
        type:
          type: string
          enum: [ exact, similar, minhash ]
        documents:
          type: array
          items:
            $ref: "#/components/schemas/DuplicateDocInfo"
        score:
          type: number
          description: Average similarity score for the group
    DuplicateDocInfo:
      type: object
      properties:
        docId:
          type: string
        key:
          type: string
        contentHash:
          type: string
        contentMd:
          type: string
        score:
          type: number
          description: Max similarity to another document in the group

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token obtained from POST /v1/auth/login
    APIKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key obtained from POST /v1/auth/api-key
