GET /stories
Search, collapsed into deduplicated stories.
One real event across many outlets, with an article count, an outlet count and a representative headline. Use this whenever results go into a context window. Equivalent to the `list_stories` MCP tool.
How clustering works
Articles are clustered offline, not at query time, so the same event always carries the same
story_id whichever query surfaced it. Clustering runs in two passes: URL canonicalisation and
near-duplicate detection catch syndication and light rewrites, then a second pass over the entities and topics in
the text catches independent coverage of the same event that shares no wording.
That means the counts are meaningful. count is how many articles are in the cluster;
outlets is how many distinct publishers. A story with 40 articles across 3 outlets is one wire story
republished; 40 articles across 35 outlets is a story the whole world is covering. Those are very different facts
and it is worth reading them separately.
Parameters
The same query, filter, signal and time parameters as /search — clustering
changes how results are grouped, not what matches. Facets are the one exception: they count over articles, so they
belong on the article endpoints.
| Name | Type | Description |
|---|---|---|
| q | string | Query text. Omit for a recency browse. |
| mode | `keyword` · `semantic` · `hybrid` | Ranking mode. `hybrid` is usually right when a model wrote the query, because models paraphrase. |
| source | string | Source domain, e.g. bbc.co.uk. |
| source_type | string | web | citation | academic_archive | defense_archive | journal_archive | non_textual | other. |
| publisher_country | string | The publisher's canonical two-character country identifier. |
| language | string | Source language, ISO-639-3, e.g. eng, fra, ara, zho. |
| story_id | string | A cluster id from /stories, to expand one story into its articles. |
| topic | string | An exact canonical topic identifier returned by the API. |
| organization | string | An exact organization entity. |
| person | string | An exact person entity. |
| country | string | A mentioned country's canonical two-character identifier. |
| author | string | An exact article author. |
| location | string | An exact mentioned location. |
| location_id | string | An exact canonical location identifier. |
| city | string | An exact mentioned city. |
| region | string | An exact mentioned region. |
| name | string | An exact proper name. |
| mentioned_date | string | An exact date mentioned in the article text. |
| quote_verb | string | An exact verb introducing a quotation. |
| amount_object | string | An exact object described by a numeric amount. |
| signal | string | A normalized business signal to range-filter on, e.g. finance, energy, conflict. |
| signal_min | number | Inclusive lower bound for `signal`. |
| signal_max | number | Inclusive upper bound for `signal`. |
| signals | string | Several signal ranges at once, ANDed: `name[:min[:max]]`, comma separated, e.g. `finance:1.5:,energy::2`. |
| from | string | Lower time bound, e.g. 2026-07-09 or 20260709120000. Clamped forward to what the caller's plan may reach; see `history_days` and `from_clamped` in the response. |
| to | string | Upper time bound, same formats as `from`. |
| offset | integer | Zero-based result offset, up to 100000. |
| limit | integer | Results per page, 1-100 (default 10). |
Response
| Field | Type | Description |
|---|---|---|
| attribution | string | |
| from_clamped | boolean | |
| has_more | boolean | |
| history_days | integer | |
| limit | integer | |
| offset | integer | |
| stories | Story[] | |
| total | integer | |
| total_relation | string |
Story
| Field | Type | Description |
|---|---|---|
| count | integer | Articles in the cluster. |
| outlets | integer | Distinct outlets in the cluster. |
| sources | string[] | |
| story_id | string | |
| title | string | |
| url | string |
title and url are a representative article from the cluster, not a synthesised summary —
the API never generates text.
Expanding a story
Pass a story_id back to /search as a filter to get every article
in that cluster:
# One event, then every article in it.
curl -s -G "https://api.unzoi.com/stories" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "q=grid outage" --data-urlencode "limit=5"
curl -s -G "https://api.unzoi.com/search" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "story_id=s-9f2c" --data-urlencode "limit=50" That two-step is the usual shape for an agent: cluster to decide what happened, expand to compare how it was covered. The guide works through it.
When not to use this
When coverage is the question. "How many outlets ran this?", "did the framing differ by country?", "find me
every mention of this company" — those want articles, and collapsing them throws away the answer. Use
/search with facets instead.