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

# API search

> Search the records of one collection or a whole project from your servers, with filters, sorting and related records.

Every collection is indexed for full-text search. With an API key that has the `search:read` scope, your servers, scripts and agents search through the API and get back full records, most relevant first. To search from a site's browser code, use [public search](https://docs.nohead.io/guides/public-search.md) instead: with a search token it goes straight to the search engine, needs no API key, and finds published records only.

## Search a collection

`GET /v1/collections/{collection_id}/search` searches one collection. Put the words in `q`; an empty `q` matches every record.

```bash theme={null}
curl -G "https://api.nohead.io/v1/collections/recipes/search" \
  -H "Authorization: Bearer $NOHEAD_API_KEY" \
  --data-urlencode "q=lentil soup" \
  --data-urlencode "filter[cuisine]=french" \
  --data-urlencode "sort=-published_at"
```

Results have the shape of a records list: `data` holds the current records, and `meta` has `next_cursor`, `has_more` and `total_estimate`, an approximate number of matches. Changes to records show up in results within seconds.

```ts TypeScript theme={null}
for await (const recipe of nohead.records.search("recipes", "lentil soup", {
  filter: { cuisine: "french", published_at: { exists: true } },
  sort: "-published_at",
  expand: ["author"],
})) {
  console.log(recipe.data.title)
}
```

```python Python theme={null}
for recipe in nohead.records.search(
    "recipes",
    "lentil soup",
    filter={"cuisine": "french", "published_at": {"exists": True}},
    sort="-published_at",
    expand=["author"],
):
    print(recipe.data["title"])
```

```ruby Ruby theme={null}
nohead.records.search("recipes", "lentil soup",
                      filter: { cuisine: "french", published_at: { exists: true } },
                      sort: "-published_at", expand: ["author"]).each do |recipe|
  puts recipe.data["title"]
end
```

## What words match

Words match the collection's `searchable` fields, in every search: with an API key, by members in the web app, and in [public search](https://docs.nohead.io/guides/public-search.md). Text, long text, rich text and enum fields are searchable unless you turn it off. You choose how each field takes part on the collection's **Search** tab, or in the field's options: see [Search options](https://docs.nohead.io/guides/content-modelling.md#search-options).

## Filter and sort

Filters work as in the records list, with the same operators (`eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `exists`), on fields marked `filterable`:

```text theme={null}
filter[price][lt]=50&filter[tags][in]=winter,beach
```

`filter[published_at][exists]=true` finds published records, and `false` unpublished ones. That is the only filter on `published_at` in search: other timestamps, ranges of `published_at` and `deleted` are refused with `400`, as is a filter on a field that isn't `filterable`. The error's `details` name the parameter, such as `filter[title]`.

Without `sort`, results are in order of relevance. `sort` takes `created_at`, `updated_at`, `published_at` or a field marked `sortable`, with `-` for descending.

## Search a project

`GET /v1/projects/{project_id}/search` returns one ranked list from all of the project's collections. Your project's ID (`prj_…`) is in `GET /v1/me`; the SDKs find it for you. Each record carries its `collection`.

```bash theme={null}
curl -G "https://api.nohead.io/v1/projects/$PROJECT_ID/search" \
  -H "Authorization: Bearer $NOHEAD_API_KEY" \
  --data-urlencode "q=lovelace" \
  --data-urlencode "filter[collection][in]=posts,authors" \
  --data-urlencode "filter[published_at][exists]=true"
```

A project search takes two filters: `filter[collection]` (a slug or ID, or up to 100 of them with `[in]`) and `filter[published_at][exists]`. It has no `sort` and no `expand`. Collections whose index is still being built for the first time are left out.

```ts TypeScript theme={null}
const results = await nohead.search("lovelace", {
  collections: ["posts", "authors"],
  filter: { published_at: { exists: true } },
})
results.meta.total_estimate
```

```python Python theme={null}
results = nohead.search(
    "lovelace",
    collections=["posts", "authors"],
    filter={"published_at": {"exists": True}},
)
results.meta.total_estimate
```

```ruby Ruby theme={null}
results = nohead.search("lovelace", collections: %w[posts authors],
                                    filter: { published_at: { exists: true } })
results.meta.total_estimate
```

## Pages and related records

Pass `limit` (1 to 100, 20 by default) and the `next_cursor` of the previous page as `cursor`. A cursor only works for the search it came from: with other words, filters or sort, it is `400`. Search pages through the first 1,000 hits; narrow the search with words or filters to reach the rest, or list the records instead.

In a collection search, `expand` embeds related records and assets under `expanded`, as in the [records list](https://docs.nohead.io/guides/reading-content.md#embed-related-records-and-assets). Embedding records needs `records:read`, and assets `assets:read`.

## Published-only keys

A published-only key (**Published records only** in the web app) searches published records only, whatever the filters ask for: `filter[published_at][exists]=false` finds nothing. Related records it embeds are published ones.

## Index status and rebuilds

Indexes follow record and schema changes on their own. `GET /v1/collections/{collection_id}/search-index` (`schema:read`) reports a collection's index:

* `status`: `building` until the index is first built, `ready`, `stale` from a change that needs a rebuild until the rebuild has run, or `failed` if the last rebuild failed (it is retried).
* `documents`: how many records it holds (for a published-only key, how many are published).
* `last_built_at` and `rebuild_requested_at`.
* `index`: the index's name in the search engine, which [public search](https://docs.nohead.io/guides/public-search.md#index-names) searches by. API searches don't need it.

The collection's **Search** tab shows the same status, with a **Rebuild index** button (`POST /v1/collections/{collection_id}/search-index/rebuild`, `schema:write`). You rarely need it, and it can run 3 times an hour per collection.

## When search answers 409

Search answers `409 conflict` while the index can't serve a request yet. The error's detail `code` says why:

* `index_building`: a new collection's index is being built for the first time.
* `rebuilding`: you filtered or sorted on a field you just made `filterable` or `sortable`, or searched words while none of the collection's searchable fields is in the index yet (its only one was just renamed or made searchable, say). The engine knows the field once the rebuild that adds it has run; meanwhile the index is `stale`.

Retry once the index status is `ready`. If the search service itself is unavailable, search answers `503`; reading records works as usual.

## Usage

Each search with an API key is one API request, counted like any other toward your plan, and it is also counted under **Search requests** on your organization's **Usage** page. Searches by members in the web app aren't counted, and neither are searches with a search token.

See the [API reference](https://docs.nohead.io/api-reference/overview.md) for every parameter and error.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.