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 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 page, or the Label entry on the Squirro Glossary page.
Before You Start#
Every request needs an access token in the Authorization header. To learn how to obtain one, see the 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 page.
The examples on this page use the Python SDK. For how to install it and create an authenticated client, see the SquirroClient Tutorial page.
Querying Items#
Items are returned by the following endpoint:
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
tenantfield, for examplesquirro_demo. For more information, see the Authentication page.{project_id}The identifier of the project to query. To find it, list the projects the authenticated user can access:
GET /api/topic/v0/{tenant}/projectsThe response is an array of the matching projects, each carrying its identifier in the
idfield:[{'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():for project in client.get_projects(): print(project["id"], project["title"])
The request body is JSON. Using the Python SDK (SquirroClient), the same call is the query() method. The SDK stores the tenant when you authenticate and adds it to every request, so you only pass the project identifier:
client.query(
project_id,
query="market risk",
count=10,
fields=["title", "created_at", "keywords"],
)
The equivalent HTTP request:
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 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:
client.query(
project_id,
query="market risk",
count=10,
fields=["title", "created_at", "keywords"],
)
The response to that query looks as follows:
{'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 page.
The following fields are present on every response:
Field |
Description |
|---|---|
|
The matching items. Their contents are shaped by the |
|
Number of items in this response. This is the size of |
|
Number of items in the project matching the query, not just those in this page. |
|
|
|
|
|
Values to send with the next request to continue paging. |
|
Server time at which the query ran. |
|
The query that actually ran, as an object holding the final |
|
Whether the response was served from the query cache. |
|
Time the query took, in milliseconds. |
Four more fields appear only when the request asks for them:
aggregationsReturned when the request includes aggregations. For the syntax and the response format, see the Search Aggregations page.
spellcheckReturned when the request sets
spellchecktotrueand the platform has corrections to suggest. For more information, see the Spellchecking page.timing_reportReturned when the request sets
timingtotrue. Holds a breakdown of where the query spent its time.search_profilerReturned when the request sets
profiletotrue. 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:
# 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:
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.
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:
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:
'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 |
|---|---|
|
Item title. |
|
Shortened extract of the item body. Use the |
|
Full item body, in HTML format. |
|
Content language of the item. |
|
Link to the item at its original location. |
|
Item creation date. |
|
Date the item was last modified. |
|
Identifier of the item in its source system. |
|
All labels of the item, as an object of label names to value arrays. |
|
Entities extracted from the item. Also set the |
|
Files attached to the item, such as the original PDF or Office document. |
|
Relevancy score of the item. Returned for ranked search results. |
|
Summary generated for the item, when one has been produced. |
|
Whether the authenticated user has read the item. |
|
Whether the authenticated user has starred the item. |
|
Collections the item belongs to for the authenticated user. |
|
Communities the item is associated with. |
|
Sub-items of the item, such as the individual pages of a PDF document. |
|
Whether the item has sub-items. |
|
Highlighted query matches. Returned when the request asks for highlighting. |
|
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 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>:
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:
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:
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(), which takes the field list the same way:
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() method of the Python SDK, which keeps a server-side pagination context open and accepts the same fields names:
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 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 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:
GET /api/topic/v0/{tenant}/projects/{project_id}/facets
The Python SDK returns the same list through get_facets():
for label in client.get_facets(project_id):
print(label["name"], label["data_type"])
Each entry describes one label. The most useful attributes are:
nameName to use in queries, in
fields, and in aggregations.display_nameName shown to users in the Squirro user interface.
data_typeOne of
string,string_nonanalyzed,int,float,datetime,weighted,geo_point, orgeo_shape.group_nameGroup the label is listed under in the user interface.
visibleWhether the label is offered to users for filtering.
searchableWhether the label values are searchable.
typeaheadWhether the label values are offered as typeahead suggestions.
When you already know which label you want, read that one on its own:
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():
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 page.
For guidance on which labels to model in the first place, see the 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 page.
If your integration needs something from the index that the search API does not expose, visit the Squirro Support website and submit a technical support request describing the use case.