For AI agents: this page is also available as Markdown at /docs/searching.md. Every docs page is: add .md to its URL. llms.txt lists them all.
Searching
A search names a query language, a query, and optionally filters, sorting, paging and the fields to return. WarpSearch runs it over one consistent view of the collection and returns ranked hits, plus any facets and metrics you asked for, in one response. This page covers the request and response. The languages themselves are in Query languages, analytics in Facets and aggregations, and vectors in Vector search and Hybrid search.
#A first search
curl -X POST https://api.warpsearch.io/v1/collections/books/search \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"language": "lucene",
"query": "title:dune",
"filters": ["year:[1960 TO 1980]", "genre:scifi"],
"sort": ["year desc"],
"fields": ["title", "year"],
"limit": 10
}'
{
"took_ms": 3,
"collection": "books",
"total": 2,
"offset": 0,
"hits": [
{"id": "b2", "fields": {"title": "Dune Messiah", "year": 1969}},
{"id": "b1", "fields": {"title": "Dune", "year": 1965}}
]
}
The request needs a viewer role or higher. The collection can be in the path
(/v1/collections/{c}/search) or in the body (POST /v1/search, where SQL takes it from FROM).
#Request fields
| Field | Meaning |
|---|---|
language |
Required. sql, es, lucene, solr, logql, grafana or luxir (the native syntax). See Query languages. |
query |
The query. A string for most languages, a JSON object for es and grafana. Omitted means match everything. |
collection |
The collection, when you use POST /v1/search. |
filters |
An array of non-scoring restrictions, written in the request's language. They narrow the matches without changing scores. |
sort |
An array such as ["year desc", "_score"]. |
offset, limit |
Paging. Both count hits. |
fields |
Which fields to return for each hit. |
facets, metrics |
Analytics over all matches. See Facets and aggregations. |
vars |
Values for $name placeholders in native queries. Never parsed as syntax. |
time_range |
{"from": ..., "to": ..., "field": ...}: restrict to a time window. Handy with logs and grafana. |
time_zone |
Zone for date literals, date math and calendar buckets, for example America/Denver. |
default_operator |
OR (default) or AND for bare words in a Lucene-style query. |
track_total |
Default true. Set false to skip counting every match. |
scores |
Default true. Set false to skip returning scores. |
include_translation |
Adds the native form of your query to the response. |
Unknown fields in the request are rejected, so a typo is an error, not a silently ignored option.
For quick checks there is also GET /v1/collections/{c}/search?q=...&limit=...&offset=...&sort=year:desc&fields=title,year,
which runs q as a Lucene query.
#Filters versus query
Put what should rank documents in query and what should only include or exclude them in
filters. A filter does not affect scores and is cheaper. Typical filters are tenant, category,
status and date range.
{
"language": "es",
"query": {"match": {"title": "dune"}},
"filters": [{"term": {"genre": "scifi"}}, {"range": {"year": {"gte": 1960}}}]
}
#Sorting
Sort by a field, by relevance, or by several keys in order: ["genre", "year desc", "_score"]. A
sort without a direction is ascending for fields and descending for _score. Documents missing the
sort value come last in either direction.
Sort on keyword, number and date values (the es language can also sort by geo distance). A text field cannot be sorted by its
whole value, so give it a keyword sub-field; see
Schemas and field types.
#Paging
Paging is offset based: limit is the page size and offset the number of hits to skip.
{"language": "lucene", "query": "title:dune", "limit": 20, "offset": 40}
offset + limit cannot exceed your plan's result window, and limit cannot exceed the plan's
maximum page size; either answers 422 limit_exceeded. Deep pages cost more than shallow ones
because the engine keeps offset + limit candidates. Show a few pages and let people narrow with
filters rather than offering page 500.
Not supported: cursors,
search_after, scroll and point in time. Use filters (for example on an increasing id or date) to walk through a large result set.
#Counting
total is the exact number of matches by default, which means evaluating every matching
document. When you do not display a total, set "track_total": false and the query can skip
documents that cannot reach the top results. That makes broad queries on large collections much
faster and cheaper. To count only, use the count endpoint:
curl -X POST https://api.warpsearch.io/v1/collections/books/count \
-H "Authorization: Bearer $WARPSEARCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"language": "lucene", "query": "genre:scifi"}'
# {"count": 1204, "took_ms": 2}
Coming soon: a bounded total hit count (
10000+) that is cheaper than an exact one.
#Choosing fields
fields limits what comes back, which keeps responses small and cheap. Without it you get every
retrievable field, except vectors and geo points. Name a vector field to get it back. Hits that
lack a requested field simply omit that key.
Coming soon: a stored
_sourcecopy of each document, and highlighting.
Not supported: highlighting and snippets today, suggesters and autocomplete, and more-like-this.
#What each query type does
| You write | It does |
|---|---|
A word (dune) on a text field |
Matches the word after analysis (case folded, stemmed for English fields) and scores with BM25. |
A phrase ("way of kings") |
Matches the words in order. A slop ("way kings"~2) allows words in between. |
A value on a keyword field |
Matches the whole value exactly. |
| A prefix, wildcard, regex or fuzzy term | Matches indexed terms. Wildcard and regex depend on your plan. Fuzzy allows up to 2 edits. |
A range (year:[1960 TO 1980]) |
Compares numbers, dates or terms. Dates accept date math such as now-30d. |
A knn clause |
Finds nearest vectors; see Vector search. |
| Geo distance or box | Matches points within a radius or rectangle, through the Elasticsearch and Solr languages. |
Geo queries are available in the es language (geo_distance, geo_bounding_box, and sorting by
_geo_distance) and in solr ({!geofilt}, {!bbox}, geodist()).
Coming soon: polygon geo queries.
#Dates and time zones
Dates in queries are interpreted in UTC unless the request sets time_zone. A partial date
matches its whole window: 2026-06 is all of June. With a zone set, now/d starts at local
midnight and calendar buckets follow local days across daylight-saving changes. Documents are
always ingested in UTC, so write an explicit Z or offset when the exact instant matters.
#Warnings
A request can succeed with warnings, an array of strings. They tell you when WarpSearch
approximated something instead of rejecting it, for example a score approximation in an
Elasticsearch multi_match or an analyzer that fell back to standard. Read them while you are
building. Compatible endpoints also send them in a Warning: 299 response header.
#Result shape
| Field | Meaning |
|---|---|
took_ms |
Time spent on the search. |
total |
Number of matches (exact unless track_total is false). |
hits |
The page: each hit has id, score and fields. |
facets, metrics |
Present when requested. |
translation |
Present with include_translation. |
warnings |
Approximations and adjustments. |
Pass ?format=es on POST /v1/collections/{c}/search to get an Elasticsearch-shaped response
instead.
#Errors
| Status | Code | Meaning |
|---|---|---|
400 |
query_error |
The query could not be parsed. The detail says where. |
400 |
unsupported_feature |
The query uses something WarpSearch does not offer. |
400 |
invalid_field |
A field name does not exist or is not allowed. |
403 |
forbidden |
Your key lacks the role on this collection. |
404 |
not_found |
No such collection, or you have no role on it. |
422 |
limit_exceeded |
A plan limit such as page size, result window, clauses or facets. |
429 |
rate_limited |
Slow down; honor Retry-After. |
503 |
warming_up |
The collection is loading. Retry after Retry-After. |
504 |
timeout |
The request ran past your plan's time limit. Narrow it with filters. |
Full list and handling advice in Limits and errors.
#Tips
- Search-box input should go through a language that tolerates punctuation. In
luxir, bind the text to a variable ("vars": {"q": "..."}) so a stray quote orANDcannot break the query. - Narrow with filters before you widen with
limit. - Return only the fields you show, and keep page sizes small. Response bytes are billed beyond your allowance; see Pricing.
- Use
track_total: falsewhenever you do not show a count.