Skip to content
unzoi docs

Pagination and facets

Paging

Offset-based: offset and limit. limit is 1–100 and defaults to 10; offset goes up to 100,000.

curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=inflation" \
  --data-urlencode "offset=20" --data-urlencode "limit=20"

Stop on has_more: false rather than by comparing offset + limit to total. The two agree for keyword queries and can disagree for ranked ones, for the reason below.

Deep paging

The offset ceiling is not arbitrary: a large offset makes the server rank and discard everything before it. If you genuinely need to walk a large result set, narrow by time instead and page within each window — a month at a time is both faster and cheaper than an offset in the tens of thousands. There is no cursor API.

Totals

total_relation tells you how much to trust total.

ValueMeansWhen
exact total is the true number of matching articles. Keyword and metadata-only queries, which the inverted index can count exhaustively.
approximate total is how many candidates were considered, not how many exist. Semantic, hybrid and similarity queries, which rank a bounded candidate set rather than scoring the whole corpus.

So do not render "About 4,132 results" for a hybrid query — it is a count of what the ANN pass looked at. If you need a true count, run the same filters as a keyword query and read that total instead.

Facets

Facets count over the whole match set, not over the page you were handed. That is what makes them worth a request: they tell you what exists before you filter on it.

curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=lithium supply" \
  --data-urlencode "facets=source,country,topic" \
  --data-urlencode "facet_limit=10" \
  --data-urlencode "limit=5"
"facets": {
  "country": {
    "values": [
      { "value": "AU", "count": 214 },
      { "value": "CL", "count": 168 }
    ],
    "sum_other_doc_count": 91,
    "doc_count_error_upper_bound": 0,
    "approximate": false
  }
}

Up to 8 facet fields per request, each returning up to 100 values. Any filter field can be faceted.

Reading the error bounds

  • sum_other_doc_count — how many matches fell outside the returned values. A large number means facet_limit is cutting off a long tail.
  • doc_count_error_upper_bound — the most any returned count could be understated. 0 means the counts are exact.
  • approximatetrue when that upper bound is non-zero. Counts are exact for keyword queries and can be approximate for ranked ones, for the same reason totals are.

The practical use is to discover rather than to report: facet by source and country, see who is actually covering something, then re-query filtered to one of them. Presenting a facet count to a user as a precise figure is only safe when approximate is false. Evaluating coverage works through it.

Stories page too

/stories takes the same offset and limit and reports the same total, total_relation and has_more — where total counts clusters, not articles. It does not take facets, because facet counts are over articles and belong on the article endpoints.