For AI agents: this page is also available as Markdown at /docs/facets-and-aggregations.md. Every docs page is: add .md to its URL. llms.txt lists them all.

Facets and aggregations

Facets group the documents a query matches into buckets and count them: "Science fiction (31)", "Under $20 (18)", "May (240)". Metrics reduce them to numbers: average price, highest latency. Both see every matching document, not just the page of hits you asked for, and both come back in the same response, from the same consistent view of the collection. Counts are exact.

You can describe them in the native request shown here, or in the language you already use: Elasticsearch aggs, Solr facets, SQL GROUP BY, or LogQL metric queries. They all produce the same kinds of buckets. See Query languages.

Facets in a request

Add a facets object to any search request. Each key is a name you choose, and it comes back unchanged in the response.

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",
    "limit": 5,
    "facets": {
      "by_genre": {"type": "terms", "field": "genre", "size": 10,
                   "metrics": {"avg_price": "avg(price)"},
                   "order": {"by": "avg_price", "desc": true}},
      "per_decade": {"type": "range", "field": "year", "start": 1900, "end": 2030, "interval": 10},
      "price_tiers": {"type": "query",
                      "buckets": {"cheap": "price:<10", "pricey": "price:>=10"}}
    },
    "metrics": {"max_price": "max(price)"}
  }'
{
  "total": 42,
  "hits": [],
  "facets": {
    "by_genre": {
      "type": "terms",
      "field": "genre",
      "missing": 2,
      "buckets": [
        {"value": "scifi", "count": 31, "metrics": {"avg_price": 11.7}},
        {"value": "classic", "count": 9, "metrics": {"avg_price": 8.2}}
      ]
    }
  },
  "metrics": {"max_price": 24.5}
}

Hits arrive as usual, so a results page and its sidebar need one request. Set limit to 0 when you only want the analytics.

Facet types

type Groups by Main options
terms Each distinct value of a field. size (number of buckets), min_count, missing (also count documents with no value), order
range Fixed-width numeric buckets. field, start, end, interval
date_histogram Time buckets. interval (a number, a duration such as 5m, or auto) or calendar_interval (day, week, month, quarter, year), time_zone, format
query Named buckets that you define with queries. buckets: a map of bucket name to query

Every facet also accepts metrics and facets, which run once per bucket. That is how you ask for "the average price and the top publishers of every category" in one request: a terms facet with a metrics object and a nested facets object.

"by_category": {
  "type": "terms", "field": "category", "size": 10,
  "metrics": {"avg_price": "avg(price)"},
  "facets": {"publishers": {"type": "terms", "field": "publisher", "size": 5}}
}

Notes on terms:

Each bucket in the response has a value and a count, plus metrics and facets when you asked for them. A range or date_histogram bucket also has from and to (the lower bound is included, the upper one is not), and a date bucket has key_as_string, the formatted lower bound.

Metrics

A metric is "function(field)" or an object. They sit at the top of the request (one number over all matches) or inside a facet (one number per bucket).

Function Result Notes
avg, sum, min, max A number Missing values are skipped. An empty set returns null.
count A number Documents with a value.
stats {count, min, max, avg, sum}
cardinality A number ({value} in Elasticsearch shape) Approximate distinct count. Exact up to a threshold, then about 1.6% error. Not on text fields.
percentiles A map keyed by percent, such as "99.0" Approximate, from a t-digest. Numeric and date fields.
percentile_ranks A map keyed by value Approximate. Needs values.

The string form uses the defaults. The three approximate functions take parameters in the object form:

"metrics": {
  "p": {"func": "percentiles", "field": "latency_ms", "percents": [50, 99], "keyed": false},
  "users": {"func": "cardinality", "field": "user_id", "precision_threshold": 1000},
  "slow": {"func": "percentile_ranks", "field": "latency_ms", "values": [500]}
}

precision_threshold is capped at 40000. percents default to 1, 5, 25, 50, 75, 95 and 99. compression (20 to 1000) tunes accuracy. Metric names are free-form strings and come back unchanged.

Status by API: cardinality, percentiles and percentile_ranks are available through the /v1 metrics object and through Elasticsearch and OpenSearch aggregations. stats and the basic functions work everywhere.

Date histograms

"facets": {
  "per_day": {"type": "date_histogram", "field": "published",
              "calendar_interval": "day", "time_zone": "America/Denver"}
}

Calendar intervals follow the civil calendar: months vary in length and a daylight-saving day has 23 or 25 hours. Fixed intervals such as 5m or 1h are exact durations. By default the histogram runs from the first non-empty bucket to the last, with empty buckets between. To widen it, set extended_start and extended_end, and trim_empty to drop empty edge buckets outside them. Giving the histogram explicit bounds, for example through a date range in the query or time_range, saves the engine a pass to find them.

Limits: a histogram returns at most 5000 buckets, and at most 1024 when it has metrics or nested facets. A wider span fails with limit_exceeded, and the fix is a bigger interval or a narrower range.

Faceted navigation

For a sidebar where users tick several values and still see their alternatives, count each facet without its own selection:

Other languages

You write You get
Elasticsearch aggs: terms, histogram, date_histogram, range, date_range, filters, filter, global, top_hits, and the metrics avg, sum, min, max, value_count, stats, extended_stats, cardinality, percentiles, percentile_ranks The same buckets, in Elasticsearch shape.
Solr facet.field, facet.range, facet.query, facet.pivot, stats, and the JSON Facet API Solr-shaped facet results.
SQL GROUP BY a, b, COUNT(*), AVG, SUM, MIN, MAX, HAVING COUNT(*) >= n, SELECT DISTINCT col Nested terms facets.
LogQL count_over_time, sum by (...), avg_over_time A date histogram per series.

Terms ordering by a cardinality or percentiles sub-aggregation, scripts, terms include and exclude, and hdr percentiles are not supported. Unsupported pieces answer 400 unsupported_feature, and approximations come back as warnings; see Limits and errors.

Plan limits and cost

Your plan caps how many facets a request may carry and how many buckets each returns, in addition to the fixed histogram limits above. A size of -1 is capped at your plan's limit rather than unbounded. Facets over a high-cardinality field with nested facets are the most expensive shape, so filter first and keep size small. Bound date histograms with a time range; see Pricing.

Gotchas

View this page as Markdown