# Filter and refine search
Source: https://nordvec.com/docs/guides/how-to/filter-search

Narrow a document search with datasource, provider, type and date filters, and read the ranked results.

`/documents/search` runs full-text search over the title and the whole text of
your documents and returns the best matches, each with a relevance score and the
source document it came from. This guide shows how the query matches, how to
narrow the results with filters, and how to read the response.

## The request [#the-request]

Only `query` is required. Everything else narrows or bounds the results.

```bash
curl https://nordvec.com/api/v1/documents/search \
  -H "Authorization: Bearer $NORDVEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "renewal terms",
    "limit": 20,
    "datasource": "contracts",
    "sourceProvider": "google",
    "createdAfter": "2026-01-01T00:00:00Z",
    "createdBefore": "2026-07-01T00:00:00Z"
  }'
```

| Field | Type | Notes |
|---|---|---|
| `query` | string | Required. 1 to 500 characters. Quoted phrases, `or` and a leading `-` to exclude a word are understood. |
| `limit` | integer | Optional. 1 to 50, default 20. How many results to return. |
| `datasource` | string | Optional. Restrict to one datasource by its slug (up to 200 characters). |
| `sourceProvider` | string | Optional. Restrict to one connector provider, for example `google`, `sharepoint` or `slack`. |
| `createdAfter` | string | Optional. ISO 8601 timestamp with an offset; only documents created at or after it. |
| `createdBefore` | string | Optional. ISO 8601 timestamp with an offset; only documents created at or before it. |
| `contentType` | string | Optional. Restrict to one knowledge type, the `type` a pushed document or a knowledge file declares (for example `policy` or `runbook`). |

<Callout>
Every filter is combined with AND: a document must match the query **and** every
filter you supply. Leave a filter out to widen the search.
</Callout>

## How the query matches [#how-the-query-matches]

- **The whole document is searched.** The title and every passage of the text
  count, however long the document is.
- **Words match in the form you write them, in any language.** There is no
  stemming: `invoice` does not match `invoices`, and `tilbagebetaling` does not
  match `tilbagebetalingen`. To catch several forms, join them with `or`.
- **Accents are ignored on both sides.** `cafe` finds `café`, and `børnehave`
  and `bornehave` find each other. Case is ignored too.
- **Operators.** Put words in double quotes to match them as a phrase, write
  `or` between words to match either, and put `-` before a word to leave out
  documents that contain it.

## Try it [#try-it]

Signed in, you can run a search over your own documents from this page. Change
the query in the API reference to try your own.

Try it in the API reference: [`POST /api/v1/documents/search`](https://nordvec.com/docs/api#tag/documents/POST/documents/search) (Search documents).

## The response [#the-response]

```json
{
  "results": [
    {
      "id": "0198f2a4-6c1e-7d30-b6a1-2f9d54c08a11",
      "title": "Acme Corp Master Services Agreement",
      "snippet": "Automatic **renewal**: the agreement continues for successive twelve month **terms** unless…",
      "score": 0.82,
      "datasource": "contracts",
      "source_provider": "google",
      "source_type": null,
      "mime_type": "application/pdf",
      "content_type": null,
      "status": "indexed",
      "created_at": "2026-02-14T09:00:00Z",
      "updated_at": "2026-02-14T09:00:00Z"
    }
  ],
  "totalCount": 7
}
```

Each result is one document. Its `snippet` is cut from the passage that matched
best, wherever that passage is in the text, with the matched words wrapped in
`**`; a word you typed without its accents is found and ranked, but may appear
unmarked in the snippet. The `score` runs from 0 up to, but never reaching, 1
(higher is more relevant), and the source document's metadata lets you trace
the result back. `totalCount` is how many documents matched in total, which may
be larger than the number of `results` you asked for with `limit`.

## Reading the results [#reading-the-results]

- **Results are ranked by relevance**, most relevant first. A document ranks by
  its best-matching passage. Use `score` to drop weak matches within one set of
  results; scores from different queries are not on the same scale.
- **`totalCount` vs `results.length`**: `results` holds up to `limit` items;
  `totalCount` is the full match count. If `totalCount` is much larger than your
  `limit`, tighten the `query` or add a filter; there is no second page of
  search results.
- **`status` tells you where the document is in processing.** A document is
  matched on its stored text, so one still `processing` can appear; `indexed`
  means every step finished. See
  [Documents & search](/docs/guides/concepts/documents) for the lifecycle.

<Callout>
Search only ever returns documents the caller is allowed to see. Access is
enforced in the database, not in application code, so a filter can never widen
what the caller sees. For an API key that is what the workspace shares; see
[Who sees a document](/docs/guides/concepts/documents#who-sees-a-document).
</Callout>

## Next steps [#next-steps]

<Cards>
  <Card title="Documents & search" href="/docs/guides/concepts/documents" />
  <Card title="List and retrieve documents" href="/docs/guides/how-to/list-documents" />
  <Card title="API reference" href="/docs/api" />
</Cards>
