<!-- Source: https://docs.squirro.com/en/latest/technical/api/search-api.html -->
# Search API

The Squirro search API returns the items held in a project and lets you choose exactly which item fields come back with each result.

The platform stores items in Elasticsearch, but you do not query Elasticsearch directly. You send the request to the search API using Squirro field and label names, and the platform translates it into an index query for you. That translation is what makes project permissions, access filtering, and the item format consistent across every client.

This page covers how to run a query, how to select the fields returned with each item, and how to find out which fields a project holds. For counts, statistics, and grouped breakdowns over the matching items, see the [Search Aggregations](aggregations.md#api-aggregations) page.

> **Note**
>
> Items carry two kinds of fields. Built-in fields such as `title`, `body`, and `created_at` exist on every item of every project. Labels are the structured metadata that comes from the data loaded into a specific project, such as a `country` or an `author` label.
>
>
>
> The API uses two older names for labels, and both appear in this page because they are the literal names you send and receive. An item holds its labels in a field called `keywords`, and the resource that defines which labels a project has is called `facets`. For the concept itself, see the [Labels](../data-loading/labels.md#data-loading-labels) page, or the Label entry on the [Squirro Glossary](../../a-z/squirro-glossary.md#glossary-squirro) page.

## Before You Start

Every request needs an access token in the `Authorization` header. To learn how to obtain one, see the [Authentication](authentication.md#api-authentication) page.

The authenticated user also needs read access to the project being queried. For how Squirro roles and project permissions govern API access, see the [Permissions Reference](squirro_client/sc-api-permissions.md#sc-api-permissions) page.

The examples on this page use the Python SDK. For how to install it and create an authenticated client, see the [SquirroClient Tutorial](squirro_client/tutorial.md#api-squirro-client-tutorial) page.

## Querying Items

Items are returned by the following endpoint:

```text
POST /api/topic/v0/{tenant}/projects/{project_id}/items/query
```

The two placeholders in the path are:

- `{tenant}`

  The tenant of the authenticated user. A tenant keeps the data of one organization separate from the others running on the same Squirro cluster, which matters above all in cloud deployments. The authentication response returns it in the `tenant` field, for example `squirro_demo`. For more information, see the [Authentication](authentication.md#api-authentication) page.
- `{project_id}`

  The identifier of the project to query. To find it, list the projects the authenticated user can access:

  ```text
  GET /api/topic/v0/{tenant}/projects
  ```

  The response is an array of the matching projects, each carrying its identifier in the `id` field:

  ```text
  [{'id': 'Sz7LLLbyTzy_SddblwIxaA',
    'title': 'Market Intelligence',
    'project_type': 'normal'},
   {'id': 'DSuNrcnlSc6x5SJZh02IyQ',
    'title': 'Support Tickets',
    'project_type': 'normal'}]
  ```

  The Python SDK returns the same list through [`get_projects()`](../../api/squirro_client.topic.TopicApiBaseMixin.md#squirro_client.topic.TopicApiBaseMixin.get_projects):

  ```python
  for project in client.get_projects():
      print(project["id"], project["title"])
  ```

The request body is JSON. Using the Python SDK ([`SquirroClient`](squirro_client/squirroclient-class.md#squirro_client.SquirroClient)), the same call is the [`query()`](../../api/squirro_client.topic.TopicApiBaseMixin.md#squirro_client.topic.TopicApiBaseMixin.query) method. The SDK stores the tenant when you authenticate and adds it to every request, so you only pass the project identifier:

```python
client.query(
    project_id,
    query="market risk",
    count=10,
    fields=["title", "created_at", "keywords"],
)
```

The equivalent HTTP request:

```bash
curl -X POST "https://<squirro-server>/api/topic/v0/<tenant>/projects/<project_id>/items/query" \
    -H "Authorization: Bearer <access_token>" \
    -H "Content-Type: application/json" \
    -d '{"query": "market risk", "count": 10, "fields": ["title", "created_at", "keywords"]}'
```

For the query string syntax accepted by the `query` parameter, see the [Query Syntax](../search/features/query-syntax.md#search-query-syntax) page.

### Response Envelope

The matching items are returned in `items`, wrapped in an envelope that describes the result set as a whole. Take the following query, which asks for the first ten matches of `market risk` with three fields on each item:

```python
client.query(
    project_id,
    query="market risk",
    count=10,
    fields=["title", "created_at", "keywords"],
)
```

The response to that query looks as follows:

```text
{'count': 10,
 'items': [{'id': '7rjxIjg_gPjrfjTk3dsTTA',
            'title': 'Basel III endgame and market risk capital',
            'created_at': '2026-02-11T09:14:00',
            'keywords': {'country': ['Switzerland'], 'topic': ['Regulation']},
            'runtime_labels': {'llm_chatable': True},
            'sources': [{'id': '4fp-1YiASwS-kfNEXYus_g',
                         'title': 'News Source',
                         'photo': '<url>'}]},
           ...],
 'total': 2254,
 'total_relation': 'eq',
 'eof': False,
 'next_params': {'expected_num_results': 2254, 'start': 10},
 'now': '2026-02-18T16:53:52',
 'query_executed': {'query': 'market risk',
                    'searchbar_query': 'market risk',
                    'dashboard_filters': None,
                    'community_query': None,
                    'like': None,
                    'parsed': {'query': 'market risk', 'language': 'en'}},
 'from_cache': False,
 'time_ms': 221}
```

Two parts of that response are shortened. The `...` entry in `items` stands for the nine remaining items, each built the same way as the first. The `parsed` object holds the analyzed form of the query, and the fields it carries depend on the query processing workflow configured for the project. For more information, see the [Query Processing](../search/query-processing.md#query-processing) page.

The following fields are present on every response:

| Field | Description |
| --- | --- |
| `items` | The matching items. Their contents are shaped by the `fields` parameter. |
| `count` | Number of items in this response. This is the size of `items`, which is not necessarily the `count` value you requested. |
| `total` | Number of items in the project matching the query, not just those in this page. |
| `total_relation` | `eq` when `total` is an exact number, and `gte` when the true number is at least `total`. Very large result sets are not counted exactly, so present `total` as an approximation when this field is `gte`. |
| `eof` | `true` on the last page of results. |
| `next_params` | Values to send with the next request to continue paging. |
| `now` | Server time at which the query ran. |
| `query_executed` | The query that actually ran, as an object holding the final `query` string next to the query context it was built from. It differs from your input when query processing rewrites the query, so read this field rather than assuming your string was used verbatim. |
| `from_cache` | Whether the response was served from the query cache. |
| `time_ms` | Time the query took, in milliseconds. |

Four more fields appear only when the request asks for them:

- `aggregations`

  Returned when the request includes aggregations. For the syntax and the response format, see the [Search Aggregations](aggregations.md#api-aggregations) page.
- `spellcheck`

  Returned when the request sets `spellcheck` to `true` and the platform has corrections to suggest. For more information, see the [Spellchecking](../search/features/spellcheck.md#search-spellcheck) page.
- `timing_report`

  Returned when the request sets `timing` to `true`. Holds a breakdown of where the query spent its time.
- `search_profiler`

  Returned when the request sets `profile` to `true`. Holds the Elasticsearch profiler output for the query, which is a debugging aid rather than something to build on.

### Paging Through Results

`count` sets the maximum number of items returned and defaults to `15`. `start` sets the zero-based offset of the first item and defaults to `0`. Together they fetch one specific page, which is what a user interface with numbered pages needs:

```python
# The second page of 20 results.
response = client.query(project_id, query="market risk", start=20, count=20)
```

To walk through the pages in order, use the `next_params` object instead. Every response carries one, and passing it back as the `next_params` parameter of the following request returns the page after it:

```python
next_params = None
while True:
    response = client.query(
        project_id,
        query="market risk",
        count=50,
        next_params=next_params,
    )
    for item in response["items"]:
        process(item)
    if response["eof"]:
        break
    next_params = response["next_params"]
```

Send the query alongside `next_params` on every request, as in the example above. The two work together, because `next_params` says where to continue and the query says what to continue searching for.

`eof` is `true` on the last page that still holds items, so handle the items of that response before leaving the loop.

Pass `next_params` back unchanged rather than assembling it yourself. What it contains depends on the kind of search being paged, and for semantic and paragraph searches it holds more than an offset, so a hand-built version can return the same item twice.

To read every item of a project rather than a page of results, see [Exporting Many Items](#exporting-many-items).

## Selecting the Fields to Return

By default, every available field of an item is returned. The `fields` parameter takes an array of item field names and restricts the response to those fields:

```python
client.query(project_id, query="virus", count=1, fields=["title"])
```

Each item in the `items` array of the response then holds only the requested fields, next to the ones that are always returned:

```text
'items': [{'id': '7rjxIjg_gPjrfjTk3dsTTA',
           'title': "FDA Adviser: Vaccine To Be OK'd In Days",
           'runtime_labels': {'llm_chatable': True},
           'sources': [{'id': '4fp-1YiASwS-kfNEXYus_g',
                        'title': 'News Source',
                        'photo': '<url>'}]}]
```

Restricting the fields also reduces how much data the platform reads out of the index for each result, so `fields` is worth setting on any query that runs often or returns many items. Requesting `body` on a project of large documents is a common cause of slow, oversized responses.

> **Note**
>
> Field names that the platform does not recognize are ignored. The request succeeds and no matching key appears in the response, so a misspelled name looks like a missing value rather than an error.

### Available Field Names

The names below are accepted in `fields`. Most of them are only present in the response when the item carries a value for them, so treat a missing key as an empty value rather than an error. The boolean fields `read` and `starred` are the exception, and come back as `false` when they do not apply.

| Field | Description |
| --- | --- |
| `title` | Item title. |
| `abstract` | Shortened extract of the item body. Use the `abstract_size` option to set its length in characters. |
| `body` | Full item body, in HTML format. |
| `language` | Content language of the item. |
| `link` | Link to the item at its original location. |
| `created_at` | Item creation date. |
| `modified_at` | Date the item was last modified. |
| `external_id` | Identifier of the item in its source system. |
| `keywords` | All labels of the item, as an object of label names to value arrays. |
| `entities` | Entities extracted from the item. Also set the `child_count` parameter to the maximum number of entities you want per item, because it defaults to `0` and no entities are returned at that value. |
| `files` | Files attached to the item, such as the original PDF or Office document. |
| `score` | Relevancy score of the item. Returned for ranked search results. |
| `generated_summary` | Summary generated for the item, when one has been produced. |
| `read` | Whether the authenticated user has read the item. |
| `starred` | Whether the authenticated user has starred the item. |
| `collections` | Collections the item belongs to for the authenticated user. |
| `communities` | Communities the item is associated with. |
| `sub_items` | Sub-items of the item, such as the individual pages of a PDF document. |
| `has_sub_items` | Whether the item has sub-items. |
| `highlight_matches` | Highlighted query matches. Returned when the request asks for highlighting. |
| `webshot_url` | Main item picture, returned together with its width and height. |

`read`, `starred`, and `collections` hold values specific to the authenticated user, so the same item returns different values for different users.

For what each field means in the wider item life cycle, including the fields used when loading data into Squirro, see the [Item Format](item-format.md#data-loading-item) page.

### Returning a Single Label

Requesting `keywords` returns every label of the item. To return one specific label instead, qualify the name with `keywords.<label_name>`:

```python
client.query(project_id, query="market risk", fields=["title", "keywords.country"])
```

Several labels can be requested at once, one entry per label. As an alternative, list the label names in the `facets` option, which restricts the `keywords` object while leaving the rest of the field selection untouched:

```python
client.query(
    project_id,
    query="market risk",
    options={"facets": ["country", "author"]},
)
```

### Fields Always Returned

`id` is always present on every item, whatever `fields` contains. When the project has data sources, each item also carries a `sources` array with the identifier, title, and picture of each associated source.

Responses also carry a small number of platform-managed fields, such as `runtime_labels`, that the `fields` parameter does not control. Read the fields your integration needs by name and ignore the rest, rather than assuming that the keys of an item are limited to the ones you requested.

## Retrieving a Single Item

When the item identifier is already known, read the item directly:

```text
GET /api/topic/v0/{tenant}/projects/{project_id}/items/{item_id}
```

Here `fields` is a query parameter holding a comma-separated list rather than a JSON array, for example `?fields=title,body,keywords`. The field names are the same as for a query.

The Python SDK call is [`get_item()`](../../api/squirro_client.topic.TopicApiBaseMixin.md#squirro_client.topic.TopicApiBaseMixin.get_item), which takes the field list the same way:

```python
item = client.get_item(project_id, item_id, fields="title,body,keywords")
```

Reading one item is also the only way to retrieve the notes attached to it. Add `fetch_notes=true` to the request, because a search never returns notes.

## Exporting Many Items

Paging with `start` and `count` is meant for result pages, not for reading a whole project. To walk through every matching item, use the [`scan()`](../../api/squirro_client.topic.TopicApiBaseMixin.md#squirro_client.topic.TopicApiBaseMixin.scan) method of the Python SDK, which keeps a server-side pagination context open and accepts the same `fields` names:

```python
with client.scan(project_id, query="market risk", fields=["title", "keywords"]) as items:
    for item in items:
        process(item)
```

Always use `scan` inside a `with` block so that the pagination context is released when the iteration finishes.

`scan` takes further parameters that control the batch size and the pagination strategy. For the complete list, see the entry for the method on the [APIs by Topic](squirro_client/sc-api-topics.md#sc-api-topics) page.

If the goal is a file rather than an integration, the Squirro Toolbox ships a command line tool that exports the content of a project to CSV without any code. For more information, see the [Bulk Exporter](toolbox/bulk-exporter.md#toolbox-exporter) page.

## Discovering the Fields of a Project

The built-in item fields listed above exist on every project. The labels do not, because they come from the data loaded into that specific project. To find out which labels a project holds, read them from the project:

```text
GET /api/topic/v0/{tenant}/projects/{project_id}/facets
```

The Python SDK returns the same list through [`get_facets()`](../../api/squirro_client.topic.FacetsMixin.md#squirro_client.topic.FacetsMixin.get_facets):

```python
for label in client.get_facets(project_id):
    print(label["name"], label["data_type"])
```

Each entry describes one label. The most useful attributes are:

- `name`

  Name to use in queries, in `fields`, and in aggregations.
- `display_name`

  Name shown to users in the Squirro user interface.
- `data_type`

  One of `string`, `string_nonanalyzed`, `int`, `float`, `datetime`, `weighted`, `geo_point`, or `geo_shape`.
- `group_name`

  Group the label is listed under in the user interface.
- `visible`

  Whether the label is offered to users for filtering.
- `searchable`

  Whether the label values are searchable.
- `typeahead`

  Whether the label values are offered as typeahead suggestions.

When you already know which label you want, read that one on its own:

```text
GET /api/topic/v0/{tenant}/projects/{project_id}/facets/{facet_id_or_name}
```

The last path segment accepts either the label name or its identifier, and the name is usually the more convenient of the two. The response is a single label, with the same attributes as an entry of the list above. The Python SDK call is [`get_facet()`](../../api/squirro_client.topic.FacetsMixin.md#squirro_client.topic.FacetsMixin.get_facet):

```python
label = client.get_facet(project_id, "country")
print(label["data_type"], label["searchable"])
```

Listing the labels tells you which fields exist. To find out which _values_ a label holds and how often each occurs, run a `terms` aggregation on it, as described on the [Search Aggregations](aggregations.md#api-aggregations) page.

For guidance on which labels to model in the first place, see the [Data Modeling](data-modeling.md#data-modeling) page.

## Squirro Fields and Elasticsearch Fields

The field names in this page are Squirro field and label names. They are the names used by the search API, the query syntax, and the aggregations API, and they are not the names under which the data is held inside the index.

Read project data through the API rather than through Elasticsearch:

- The API applies project permissions and per-item access filtering to every request. A direct index query returns whatever the index holds, without those checks.
- The API returns the documented Squirro item format, which stays stable across releases. The internal index layout does not.

Direct Elasticsearch access is an administrative operation on the storage nodes, used for cluster maintenance rather than for reading project content. For those tasks, see the [Elasticsearch Management](../admin/operations/managing-elasticsearch.md#admin-managing-elasticsearch) page.

If your integration needs something from the index that the search API does not expose, visit the [Squirro Support website](https://go.squirro.com/support) and submit a technical support request describing the use case.
