> ## 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 a workspace

> Choose hybrid, fact, deep, or filtered search

Files must be [ingested](/guides/ingest-reconcile) before any of these work.
They are not aliases — see [Search methods](/concepts/search-methods).

## Hybrid search

Inline, not a job. Ranked snippets from BM25 + dense vectors.

<Tabs>
  <Tab title="Python">
    ```python theme={"dark"}
    from ndi_sdk import NdiClient

    with NdiClient() as client:
        workspace_id = "550e8400-e29b-41d4-a716-446655440001"
        hits = client.tools.hybrid_search(
            workspace_id,
            query="revenue by segment",
            k=10,
            path_prefix="reports/",
        )
        for hit in hits.hits:
            print(hit.path, hit.snippet)
        print(hits.coverage)
    ```
  </Tab>

  <Tab title="TypeScript">
    ```ts theme={"dark"}
    import { NdiClient } from "ndi-sdk";

    const client = new NdiClient();
    const workspaceId = "550e8400-e29b-41d4-a716-446655440001";
    const hits = await client.tools.hybridSearch(workspaceId, {
      query: "revenue by segment",
      k: 10,
      path_prefix: "reports/",
    });
    for (const hit of hits.hits ?? []) {
      console.log(hit.path, hit.snippet);
    }
    ```
  </Tab>

  <Tab title="curl">
    ```bash theme={"dark"}
    curl -s -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 by segment","k":10,"path_prefix":"reports/"}'
    ```
  </Tab>
</Tabs>

## Fact search

One-pass lookup. Returns a job with an answer and evidence.

```python theme={"dark"}
from ndi_sdk import NdiClient

with NdiClient() as client:
    workspace_id = "550e8400-e29b-41d4-a716-446655440001"
    job = client.jobs.wait(
        client.search.fact(
            workspace_id,
            query="What were total liabilities at year end?",
            wait_seconds=30,
        ).job_id,
        timeout=300,
    )
    print(job.result)
```

## Deep search

Agent loop. One question per `session_id`. Chain follow-ups with that turn's
session; do not share a session across unrelated questions.

```python theme={"dark"}
from ndi_sdk import NdiClient

with NdiClient() as client:
    workspace_id = "550e8400-e29b-41d4-a716-446655440001"
    first = client.jobs.wait(
        client.search.deep(
            workspace_id,
            query="Which subsidiaries missed covenant tests?",
            wait_seconds=30,
        ).job_id,
        timeout=600,
    )
    session_id = first.result.session_id
    follow = client.jobs.wait(
        client.search.deep(
            workspace_id,
            query="Show the supporting numbers for the first one.",
            session_id=session_id,
            wait_seconds=30,
        ).job_id,
        timeout=600,
    )
    print(follow.result)
```

The SDK has no `tier` argument. Deep search is not chat.

## Filtered search

Catalog-first filter / rank / count. **Python SDK only** —
`client.search.filtered` and `client.search.filtered_page`. TypeScript callers
POST `/v1/workspaces/{id}/filtered-search` with `{ "type": "query", ... }`
then `{ "type": "page", "cursor": "..." }`.

```python theme={"dark"}
from ndi_sdk import NdiClient

with NdiClient() as client:
    workspace_id = "550e8400-e29b-41d4-a716-446655440001"
    job = client.jobs.wait(
        client.search.filtered(
            workspace_id,
            query="invoices over 10000 from Q4",
            wait_seconds=30,
        ).job_id
    )
    print(job.result.total_matches, job.result.exhaustive)
    if job.result.next_cursor:
        page = client.jobs.wait(
            client.search.filtered_page(
                workspace_id,
                cursor=job.result.next_cursor,
            ).job_id
        )
        print(page.result)
```

Read `exhaustive` before treating a result as a census. `catalog_drifted` on
a later page means the catalog changed since the first page.

## Coverage

Every tool response can include `coverage.restricted_candidates` and
`labels_required` when your key cannot see every matching file. See
[Access labels](/guides/access-labels).
