Skip to content
unzoi docs

Entity and signal filtering

Two ways to narrow a query that do not make ranking worse: exact filters on extracted entities, and ranges over derived signals.

Filters beat more words

Every word you add to q dilutes ranking — the engine now has to satisfy more terms, and articles that match your real intent but use one word differently fall away. A filter does not compete with ranking at all: it removes non-matching documents before ranking runs.

# Diluted.
q=asml export licence netherlands august 2026

# Same intent, ranked cleanly.
q=export licence
&organization=asml
&publisher_country=NL
&from=2026-08-01

Entity filters are exact

organization, person, country, location, topic and the rest match canonical extracted values, not free text. organization=asml works; organization=ASML Holding NV probably does not.

The reliable way to learn the vocabulary is to ask for it:

curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=semiconductor supply" \
  --data-urlencode "facets=organization,person,country,topic" \
  --data-urlencode "facet_limit=15" \
  --data-urlencode "limit=1" | jq '.facets | map_values(.values)'

Facets count over the whole match set, so this tells you what actually exists in the coverage before you filter on it. Then filter to one of the values you got back.

Locations: name or identifier

location, city and region match the extracted string ("Austin, Texas, United States"), which is readable and brittle. location_id matches a canonical numeric identifier, which is neither — take it from an article's location_details via /doc/{id} and use it for anything long-lived.

Signals

Every article carries derived intensities across three families — industry, business context, risk context. They are not sentiment and not a classification: they measure how strongly an article's language sits in a given register.

That makes them the tool for a question about character rather than about subject: "supply-chain news that reads as disruption", "earnings coverage that reads as uncertainty". Those are hard to write as keywords and easy to write as a range.

# Shipping coverage that reads as disruption, not as routine logistics.
curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
  --data-urlencode "q=shipping" \
  --data-urlencode "signals=supply_disruption:1.5:,transportation:0.5:"

Several constraints are ANDed. Over MCP the same thing is an array, which a model emits more reliably:

{
  "name": "search_news",
  "arguments": {
    "q": "shipping",
    "signals": [
      { "name": "supply_disruption", "min": 1.5 },
      { "name": "transportation", "min": 0.5 }
    ]
  }
}

The full signal list is here.

Finding a threshold instead of guessing one

There is no absolute scale to memorise — intensities are relative, and the useful cut depends on the slice you are looking at. Find it empirically in about a minute:

for min in 0.5 1.0 1.5 2.0 3.0; do
  total=$(curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
    --data-urlencode "q=quarterly results" \
    --data-urlencode "from=2026-08-01" \
    --data-urlencode "signal=financial_uncertainty" \
    --data-urlencode "signal_min=$min" \
    --data-urlencode "limit=1" | jq -r '.total')
  echo "min=$min  $total articles"
done

Pick the point where the count starts dropping sharply, then read the top few titles at that threshold to check they are what you meant. Six requests, and you have a number you can defend.

Combining them

The three narrowing tools are independent, and stacking them is usually better than deepening any one of them:

q=port congestion            # what it is about
&mode=hybrid                  # how to rank it
&publisher_country=SG         # who published it
&language=eng                 # what language
&from=2026-08-01              # when
&signals=supply_disruption:1.5:   # what register

Every one of those except q and mode is free, in the sense that it costs no relevance — and the whole thing is still one billed request.