> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.privategpt.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.privategpt.dev/_mcp/server.

# Retrieve Chunks using Semantic, Keyword, or Hybrid Search

POST /v1/primitives/search
Content-Type: application/json

Perform document chunk search using semantic, keyword, or hybrid strategies.

This endpoint provides flexible search capabilities across ingested documents
with support for different search strategies based on use case requirements.

Search Types:
* **Semantic Search**: Uses vector embeddings to find chunks with similar meaning
  to the provided text query, regardless of exact keyword matches
* **Keyword Search**: Finds chunks containing specific keywords with exact or
  fuzzy matching capabilities (implementation pending)
* **Hybrid Search**: Combines semantic similarity with keyword matching for
  comprehensive results (implementation pending)

Key Features:
* **Score-based Ranking**: Results include similarity/relevance scores
* **Context Filtering**: Narrow search to specific collections,
artifacts, or metadata
* **Adjacent Context**: Optionally retrieve surrounding chunks for richer context
* **Configurable Limits**: Control result count (1-100 chunks)
* **Flexible Matching**: Choose optimal strategy based on query type

Search Process:
1. Parse request to determine search strategy and parameters
2. Apply context filters to narrow search scope
3. Execute search using appropriate algorithm (semantic/keyword/hybrid)
4. Rank results by relevance score
5. Optionally expand results with adjacent chunks for context

Current Implementation Status:
* ✅ Semantic Search: Fully implemented and production-ready
* 🚧 Keyword Search: Planned feature, returns 400 if requested
* 🚧 Hybrid Search: Planned feature, returns 400 if requested

Notes:
* Higher scores indicate better matches
* Expansion increases response time but provides richer context
* Use `/artifacts/list` to discover available collections and metadata
* Semantic search works best for conceptual queries
* Keyword search (when available) will excel at exact term matching

Reference: https://docs.privategpt.dev/api-reference/primitives/search-v-1-primitives-search-post

## Request

### Body (application/json)

This endpoint expects a Primitives_search_v1_primitives_search_post_Request.

- `Primitives_search_v1_primitives_search_post_Request`

## Response

### 200

Successful chunk retrieval

- `any`

## Errors

### 401 Unauthorized Error

Unauthorized

- `any`

## Types

### SemanticSearch

Represents a semantic search operation.

- `text` (string, required) — The text query to find relevant chunks
- `context_filter` (ContextFilter, required) — Filter to select specific context from ingested documents. Can filter by collection, artifacts, and metadata.
- `type` ("semantic_search", optional, default: semantic_search) — Type of search operation, always 'semantic_search' for semantic searches
- `limit` (integer, optional, default: 10) — Maximum number of chunks to return
- `score_threshold` (double, optional, default: 0) — Minimum similarity score threshold for returned chunks
- `expand` (boolean, optional, default: false) — Whether to include adjacent chunks for more context

### KeywordSearch

Represents a keyword-based search operation.

- `keywords` (list of string, required) — List of keywords to find relevant chunks
- `context_filter` (ContextFilter, required) — Filter to select specific context from ingested documents. Can filter by collection, artifacts, and metadata.
- `type` ("keywords_search", optional, default: keywords_search) — Type of search operation, always 'keywords_search' for keyword searches
- `limit` (integer, optional, default: 10) — Maximum number of chunks to return

### HybridSearch

Represents a hybrid search operation combining semantic and keyword search.

- `text` (string, required) — The text query to find relevant chunks
- `keywords` (list of string, required) — List of keywords to find relevant chunks
- `context_filter` (ContextFilter, required) — Filter to select specific context from ingested documents. Can filter by collection, artifacts, and metadata.
- `type` ("hybrid_search", optional, default: hybrid_search) — Type of search operation, always 'hybrid_search' for combined searches
- `limit` (integer, optional, default: 10) — Maximum number of chunks to return
- `expand` (boolean, optional, default: false) — Whether to include adjacent chunks for more context

### ContextFilter

Filter by collection, artifacts and metadata in the ingested context. The main filter is the collection the context is part of. All artifacts ids are ensured to be part of the context. If any of the ids is not usable in the context (i.e. doesn't exist), the related task will fail. The metadata filter will look for all context matching the metadata, if any, and add it to the context. The filter is a dictionary of key-value pairs. The key is the metadata key, and the value is the metadata value. If both artifacts and metadata filters are provided, the result will be the intersection of the two filters.

- `collection` (string, optional, default: pgpt_collection) — The name of the collection to filter on.
- `artifacts` (list of string, optional, nullable) — Artifacts ids to filter on.
- `metadata_filter` (list of map from string to any, optional, nullable) — Metadata filter to apply on the context.

## Examples

**Request**

```json
{
  "context_filter": {
    "collection": "reports"
  },
  "expand": true,
  "limit": 10,
  "text": "Q3 2023 sales performance",
  "type": "semantic_search"
}
```

**Response**

```json
{
  "data": [
    {
      "document": {
        "artifact": "q3_report",
        "doc_metadata": {
          "file_name": "Q3_Sales.pdf",
          "title": "Q3 2023 Report"
        },
        "object": "ingest.document"
      },
      "next_texts": [
        "Regional breakdown indicates strongest performance in North America."
      ],
      "object": "context.chunk",
      "previous_texts": [
        "Q2 comparison shows steady growth trends."
      ],
      "score": 0.89,
      "text": "Q3 sales increased by 25% compared to Q2, driven primarily by new customer acquisitions in the enterprise segment."
    }
  ],
  "model": "private-gpt",
  "object": "list"
}
```

**SDK Code**

```python
import requests

url = "https://api.example.com/v1/primitives/search"

payload = {
    "context_filter": { "collection": "reports" },
    "expand": True,
    "limit": 10,
    "text": "Q3 2023 sales performance",
    "type": "semantic_search"
}
headers = {"Content-Type": "application/json"}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.example.com/v1/primitives/search';
const options = {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: '{"context_filter":{"collection":"reports"},"expand":true,"limit":10,"text":"Q3 2023 sales performance","type":"semantic_search"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.example.com/v1/primitives/search"

	payload := strings.NewReader("{\n  \"context_filter\": {\n    \"collection\": \"reports\"\n  },\n  \"expand\": true,\n  \"limit\": 10,\n  \"text\": \"Q3 2023 sales performance\",\n  \"type\": \"semantic_search\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.example.com/v1/primitives/search")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n  \"context_filter\": {\n    \"collection\": \"reports\"\n  },\n  \"expand\": true,\n  \"limit\": 10,\n  \"text\": \"Q3 2023 sales performance\",\n  \"type\": \"semantic_search\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.example.com/v1/primitives/search")
  .header("Content-Type", "application/json")
  .body("{\n  \"context_filter\": {\n    \"collection\": \"reports\"\n  },\n  \"expand\": true,\n  \"limit\": 10,\n  \"text\": \"Q3 2023 sales performance\",\n  \"type\": \"semantic_search\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.example.com/v1/primitives/search', [
  'body' => '{
  "context_filter": {
    "collection": "reports"
  },
  "expand": true,
  "limit": 10,
  "text": "Q3 2023 sales performance",
  "type": "semantic_search"
}',
  'headers' => [
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.example.com/v1/primitives/search");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"context_filter\": {\n    \"collection\": \"reports\"\n  },\n  \"expand\": true,\n  \"limit\": 10,\n  \"text\": \"Q3 2023 sales performance\",\n  \"type\": \"semantic_search\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Content-Type": "application/json"]
let parameters = [
  "context_filter": ["collection": "reports"],
  "expand": true,
  "limit": 10,
  "text": "Q3 2023 sales performance",
  "type": "semantic_search"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.example.com/v1/primitives/search")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```