> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ndi.nace.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Search tool

> Direct search over the workspace index — hybrid-search

The workspace's one search method. Use it when you want predictable,
low-latency retrieval without an agentic loop. For a single-shot answer with
cited evidence, use [fact-search](/api-reference/intelligent-search); for
multi-step questions, use [deep-search](/api-reference/intelligent-search).

Results are filtered through the caller's access-label predicate.
Returns 404 if the workspace has not been indexed yet — ingest files first.

***

## Hybrid search

```
POST /v1/workspaces/{workspace_id}/tools/hybrid-search
```

Combines a BM25 keyword leg and a dense-vector leg with Reciprocal Rank Fusion,
so exact identifiers (ticker symbols, account codes, clause numbers) and
paraphrased concepts both land in one call. The retrieval configuration is fixed
server-side at the evaluated-best setting: there are no per-request retrieval
knobs and no single-leg variants to choose between.

**Request body:**

```json theme={"dark"}
{
  "query": "revenue recognition policy",
  "k": 10,
  "path_prefix": null,
  "categories": null
}
```

**Body fields:**

* **`query`** (required, max 4096 chars) — Search query.
* **`k`** (optional, default 10, max 100) — Number of hits to return.
* **`path_prefix`** (optional) — Restrict to files under this folder prefix.
* **`categories`** (optional) — Restrict to files assigned these document categories (from classification).

```bash theme={"dark"}
curl -X POST "$NDI_BASE_URL/v1/workspaces/$WS/tools/hybrid-search" \
  -H "X-API-Key: $NDI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "revenue recognition policy", "k": 5}'
```

**Response:**

```json theme={"dark"}
{
  "hits": [
    {
      "file_id": "550e8400-e29b-41d4-a716-446655440004",
      "path": "reports/annual-report-2025.pdf",
      "locator": {"kind": "text_range", "page": 12, "char_start": 430, "char_end": 560},
      "relevance_score": 0.92,
      "snippet": "Revenue is recognized when control of the promised goods or services is transferred to customers...",
      "why": "Direct match for revenue recognition criteria",
      "component": "revenue_recognition_policy"
    }
  ],
  "candidates_seen": 47,
  "truncated": false,
  "coverage": {"restricted_candidates": 0, "labels_required": []}
}
```

**Response fields:**

* **`hits`** — Ordered by score. Each `Evidence` item: `file_id`, `path`, `locator` (required — page/position in the source), `relevance_score`, `snippet` (verbatim passage), `why` (retrieval rationale), `component` (which public component of the file the hit came from — a spreadsheet tab name or a split document's section; `null` for single-component files or when attribution is unavailable). Act on a hit by passing `component` straight to [`qa-file` / `read-file`](/api-reference/file-tools) as `component=` (with `pages` from the locator for documents).
* **`candidates_seen`** — Pre-access-filter count; compare to `hits` length to tell when the access gate truncated results.
* **`truncated`** — `true` when the index found more than `k` candidates before access filtering.
* **`coverage`** — Labels required to see withheld candidates.

***

## Choosing a search method

| Method            | When to use                                                    |
| ----------------- | -------------------------------------------------------------- |
| **hybrid-search** | Ranked passages, no synthesis — you do the reading             |
| **fact-search**   | A cited answer in seconds — fixed retrieval pipeline, no agent |
| **deep-search**   | Multi-step questions requiring several tool calls              |

***

## Errors

* **`workspace_not_found`** (404) — No such workspace.
* **`invalid_request`** (422) — `query` is empty, `k` is out of range, or both `path_prefix` and `categories` produce an empty scope.
