{
  "openapi": "3.0.3",
  "info": {
    "title": "Context Retriever API",
    "description": "Search over document chunks: semantic (vector), full-text (keyword), or hybrid (RRF) modes.",
    "version": "1.0.0"
  },
  "servers": [
    { "url": "/", "description": "Current host" }
  ],
  "paths": {
    "/api/search": {
      "post": {
        "summary": "Search context",
        "description": "Search document chunks by query. Use for retrieving relevant context before answering. Modes: **semantic** (vector similarity), **fulltext** (keyword/lexeme match), **hybrid** (RRF of both). Prefer hybrid or semantic for natural-language questions and conceptual search; fulltext matches normalized word forms, not exact phrases. Semantic and hybrid results may include adjacent chunks when similarity is above the threshold.",
        "operationId": "searchContext",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "Search query text. Required. Max 10000 characters.",
                    "maxLength": 10000
                  },
                  "search_type": {
                    "type": "string",
                    "enum": ["semantic", "fulltext", "hybrid"],
                    "default": "semantic",
                    "description": "Use hybrid or semantic for natural-language questions; fulltext matches normalized word forms rather than exact phrases."
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50,
                    "default": 10,
                    "description": "Maximum number of results to return."
                  },
                  "min_similarity": {
                    "type": "number",
                    "format": "float",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Minimum similarity/score threshold; results below are filtered out. Only for semantic and hybrid."
                  },
                  "rrf_k": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 60,
                    "description": "RRF constant for hybrid search (Reciprocal Rank Fusion). Used only when search_type is hybrid."
                  },
                  "hybrid_candidates": {
                    "type": "integer",
                    "minimum": 5,
                    "description": "Number of candidates from each method (semantic + fulltext) before RRF. Used only when search_type is hybrid. Default 20."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Search results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "query": { "type": "string", "description": "Echo of the search query." },
                    "search_type": { "type": "string", "enum": ["semantic", "fulltext", "hybrid"] },
                    "results_count": { "type": "integer", "description": "Number of chunks returned." },
                    "search_metadata": {
                      "type": "object",
                      "description": "Present for hybrid only.",
                      "properties": {
                        "rrf_k": { "type": "integer" },
                        "candidates_per_method": { "type": "integer" },
                        "semantic_results": { "type": "integer" },
                        "fulltext_results": { "type": "integer" },
                        "combined_unique": { "type": "integer" }
                      }
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "chunk_id": { "type": "string", "format": "uuid", "description": "Chunk ID." },
                          "content": { "type": "string", "description": "Chunk text content." },
                          "chunk_index": { "type": "integer", "description": "Index of chunk within the document." },
                          "token_count": { "type": "integer", "nullable": true },
                          "score": { "type": "number", "description": "Main relevance score (similarity, fulltext rank, or RRF)." },
                          "metadata": { "type": "object", "nullable": true, "description": "Chunk metadata." },
                          "created_at": { "type": "string", "format": "date-time", "nullable": true },
                          "document": {
                            "type": "object",
                            "properties": {
                              "id": { "type": "string", "format": "uuid", "nullable": true },
                              "file_name": { "type": "string", "nullable": true },
                              "file_type": { "type": "string", "nullable": true }
                            }
                          },
                          "scores": {
                            "type": "object",
                            "description": "Detailed scores when available (semantic_score, fulltext_score, ranks, etc.).",
                            "additionalProperties": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request (missing query, query too long, or invalid search_type/params).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "error": { "type": "string" } }
                }
              }
            }
          },
          "500": {
            "description": "Server error (e.g. search or vectorization failure).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": { "error": { "type": "string" } }
                }
              }
            }
          }
        }
      }
    }
  }
}
