Home / Product / Filters

Query surface

Filters

Twenty-plus exact filters, a radius or box on the map, event classes, numeric signal ranges, facet aggregation and pagination — combinable in one request, on every plan.

Publication

ParameterMatchesExample
sourcePublisher domainbbc.co.uk
source_typeKind of publicationweb
publisher_countryWhere the publisher is basedUS
languageSource language, ISO-639-3hin
authorBylinejane doe

Place

ParameterMatchesExample
countryCountry the article is about (FIPS 10-4)GM
cityCity mentionedBerlin
regionAdministrative region mentionedBavaria
locationAny place name mentionedRhine
location_idExact gazetteer identifier-1746443
near / radius_kmA city mentioned within that many km of a point51.9244,4.4777 / 50
bboxA city mentioned inside a box: south, west, north, east47.3,5.9,55.1,15.0

Subject and entities

ParameterMatchesExample
topicCanonical topic identifierEPU_POLICY
organizationOrganisation mentionedeuropean union
personPerson mentionedbenjamin netanyahu
nameAny proper name mentionedMinistry of Finance
entity_idEvery spelling grouped under a resolved idorganization:asml
event_typeOne of seventeen event classeslabor_strike

Statements and figures

ParameterMatchesExample
quote_verbVerb introducing a quotationwarned
amount_objectWhat a quantity refers tobarrels
amount_min / amount_maxA numeric amount mentioned within the bounds1000
mentioned_dateA date the article refers to20260801

Time and grouping

ParameterMatchesExample
from / toPublication window, to the second20260729T140000
story_idOne clustered storyst_9f2…

Signal ranges

Every article carries scored language signals across industry, business and risk groups. Filter on a range rather than an exact value, and stack constraints — all of them must match.

HTTP
# energy coverage carrying financial-stress language
GET /search?signal=energy&signal_min=0.3

# stack several: supply disruption AND financial negativity
GET /search?signals=supply_disruption:0.2,financial_negative:0.15

What the signals are and how to use them →

Facets: ask what values exist

Rather than guessing which countries, topics or outlets are worth filtering on, ask. Any filterable field can be requested as a facet, returning values with counts across the whole result set.

HTTP
GET /search?q=semiconductors&facets=country,organization,language&facet_limit=25

Responses include how much sits outside the returned buckets, so a truncated facet is visible rather than mistaken for the complete distribution. This is what the coverage figures on this site are generated from.

Pagination

Results carry offset, limit, has_more and a total. The total is marked exact for keyword and metadata queries and approximate for ranked semantic modes, so you know whether a count can be relied on or only compared.

Combining them

Filters are conjunctive: every constraint must match. That makes narrow queries cheap to express and makes it easy to over-constrain into an empty result set, so build up rather than starting from everything at once.

HTTP
# German-language coverage about France, energy sector,
# from publishers based in Germany, last 48 hours
GET /stories
  ?language=deu
  &country=FR
  &publisher_country=DE
  &signal=energy&signal_min=0.25
  &from=20260729T000000

Why an over-constrained query is the common failure

Because every constraint must match, adding one can only ever remove results, and the removal is silent — an empty response is indistinguishable from an absence of coverage. In practice teams write the query they want in one go, get nothing back, and conclude the index does not cover their subject. Build the query up instead: start with the subject and a time bound, check the count, then add one constraint at a time and watch what each costs. When the count collapses on a particular addition, that constraint is the one carrying an assumption that turned out to be wrong, and you will know which rather than guessing.

Facets are the other half of that discipline. Rather than guessing which countries, languages or organisations to constrain on, request them as facets first and read the distribution — it tells you what values actually exist in the result set and how much volume sits behind each, so the filter you add next is chosen from the data rather than from memory. The same response reports how much sits outside the returned buckets, which is what stops a truncated facet from being mistaken for the full picture.

Filters are exact matches on the values the index holds. organization=ASML Holding NV matches nothing where asml matches thousands, and the empty result looks like an absence of coverage. Ask for the spellings first: resolve_entity over MCP, GET /entities over REST, returns the values the index holds for a name, grouped, with a count per spelling and a confidence for the match. Pass a returned value verbatim as the filter.

Entity ids

Each resolved group also carries an id, such as organization:asml. Pass it as entity_id on search, stories, top headlines or aggregate and it matches every spelling grouped under it within the window you asked for, so asml and asml holding nv are one filter rather than two queries you merge by hand. Up to five ids combine, and all of them must match. The response lists the spellings each id used in resolved, and an id with no spelling in the window comes back with an empty list rather than as an empty result you could mistake for missing coverage.

HTTP
# resolve once
GET /entities?q=ASML Holding NV&type=organization&from=2026-08-01

# then filter on the group, not one spelling
GET /stories?entity_id=organization:asml&from=2026-08-01

The grouping is deterministic and approximate: spellings are normalised and grouped by rule, not matched against a company register. Resolving the id costs a credit or two, counted in the response's credits_charged. The same ids are what relationship queries start from.

Near a place, or inside a box

near takes a point as latitude,longitude, and radius_km a distance from 0.1 to 2,000 km. bbox takes a box as south,west,north,east instead. Either one matches articles that mention a city inside the area, on search, stories and top headlines, and on the MCP tools search_news, list_stories and top_headlines. The index first narrows to the geohash cells covering the area, then checks every city an article mentions exactly against the circle or the box, so a city that shares a cell with the area but lies outside it does not match.

HTTP
# a port strike reported within 50 km of Rotterdam
GET /search?q=port strike&near=51.9244,4.4777&radius_km=50&from=2026-09-01

Each hit lists the cities that matched in matched_locations, nearest first. An excerpt of one hit, with invented values for illustration rather than a measurement of coverage:

JSON
{
  "results": [
    {
      "id": "20260909071500-4be2",
      "title": "Dockworkers extend strike at container terminals",
      "url": "https://news.example.com/logistics/dockworkers-strike",
      "source": "news.example.com",
      "published_at": "20260909071500",
      "language": "eng",
      "story_id": "20260908163000-91d4",
      "matched_locations": [
        { "name": "Rotterdam", "country": "NL", "latitude": 51.9225, "longitude": 4.4792, "distance_km": 0.2 },
        { "name": "Vlaardingen", "country": "NL", "latitude": 51.9125, "longitude": 4.3417, "distance_km": 9.4 },
        { "name": "Dordrecht", "country": "NL", "latitude": 51.8133, "longitude": 4.6901, "distance_km": 19.1 }
      ]
    }
  ],
  "credits_charged": 2,
  "partial": false,
  "coverage": "complete"
}

Matches inside a box carry no distance_km, since there is no point to measure from. Only cities count. A region's or a country's coordinates mark its centre rather than where anything happened, so a radius around a capital does not stand in for the country: filter countries with country. A box whose west edge is greater than its east crosses the antimeridian, so a box around Fiji is one box rather than two. Circles, boxes and every field they return: the geographic search reference.

Event classes

event_type filters on one of seventeen classes of event. The same list is a facet, so facets=event_type shows which classes a result set holds before you pick one, and every article carries its classes as event_types:

bankruptcy, earnings_report, ipo, cyber_attack, outage, industrial_accident, sanctions, trade_dispute, boycott, nationalization, privatization, money_laundering, executive_change, factory_closure, supply_shortage, labor_strike, antitrust_action

HTTP
# strikes reported in France since the start of the month
GET /stories?event_type=labor_strike&country=FR&from=2026-09-01

Classes are derived from the topics an article's source supplies, under version events-v1. Today one source supplies topics, so an article that arrived through another carries no class until Unzoi computes its own (how it works). Each class names the topics it needs, and each of those is checked against that source's published vocabulary. Where one theme is ambiguous, a second is required: an appointment counts as an executive_change only together with a chief-executive theme.

That makes classes precise rather than complete. An article can describe a bankruptcy without carrying the bankruptcy class, so when a missed event matters, run a query in words beside the filter rather than relying on the class alone.

Acquisitions, funding rounds, layoffs, lawsuits, contract awards, product launches and factory openings are not classes. No theme marks them reliably, so they wait for extraction from article bodies. Until then, semantic search is the way to find them, as the deal and layoff recipes show. Every class and parameter: the filter reference.

Numeric amounts

amount_min and amount_max match articles that mention a numeric amount within the bounds. One amount must satisfy every bound: amount_min=1000&amount_max=5000 needs a single figure between the two, not one figure above 1,000 and another below 5,000. With amount_object, that same amount must also describe the object.

HTTP
# a thousand workers or more, in September's reporting
GET /search?amount_object=workers&amount_min=1000&from=2026-09-01

How complete these filters are today, and what they cost

Places, event classes and amounts are derived from records every article in the index already holds, so nothing is ingested again. Each part of the index gains them when it is next rebuilt. Until a part has been rebuilt, a query using these filters answers from the parts that have, with partial: true and coverage: field_unavailable. The results that come back are correct, there may be more in the part that could not answer, and retrying before its rebuild changes nothing. Every result says when it is incomplete, so read those two fields before treating a short or empty answer as an absence.

A geographic or amount filter reads candidates in batches and checks each one. Each batch costs a credit, counted in the response's credits_charged, and when there are more candidates than a call reads, the total is estimated and marked approximate.

Reference

Every parameter, what it accepts and how it behaves against the index: the filter reference. Circles, boxes and matched_locations: the geographic search reference. What a name resolves to before you filter on it: the /entities reference.