Relationships
Four endpoints answer "who is reported alongside whom" from the index, at the moment you ask. Each starts from entity ids, counts the articles that mention entities together, and returns the newest of those articles as evidence.
| Operation | MCP tool | What it does |
|---|---|---|
GET /graph/related | related_entities | Entities co-mentioned with one entity. |
GET /graph/path | connection_path | How two entities connect. |
GET /graph/network | entity_network | One entity's neighbours, and what changed. |
GET /graph/exposures | shared_exposures | What several entities have in common. |
Co-mention, not relationship
Every number on this page counts articles. The link between two entities is their co-mentions: the articles in your window that mention both. No record of who owns, supplies, funds or employs whom sits behind these endpoints. They count what journalists wrote about together.
Entities are the ids /entities returns, such as
organization:asml. An id stands for every spelling in its alias group, looked up within the request
window, and the response lists the spellings it used as aliases_used. An empty
aliases_used means the index holds no spelling of that id in the window, so nothing can be connected to
it there. It does not mean the entity has no connections.
How an answer is built
Nothing is precomputed. A call resolves each id to its spellings. It then takes the articles that mention the
starting entity and counts which other entities they mention, one facet per entity type. Spellings that share a
normalised key count as one neighbour, with the id /entities would give it. Evidence is a bounded search,
newest first, over the articles that mention both ends of a link. Nothing is cached between calls, so an answer is
as current as the index.
Reading an edge
Each neighbour, each hop of a path and each change is described by one edge:
| Field | Type | Description |
|---|---|---|
| aliases | string[] | Every spelling grouped under the id. |
| approximate | boolean | Whether the counts came from a capped facet, from spellings one article can repeat, or were summed across indexes. |
| co_mentions | integer | Articles mentioning both. Exact when the edge carries evidence; otherwise a facet count (see approximate). |
| entity_id | string | The associated entity; pass it back as an entity_id. |
| evidence | CompactArticle[] | The newest articles mentioning both. Empty when evidence was not requested, the edge ranked below the evidence cut, or the call's query budget was spent. |
| first_seen | string | The earliest article mentioning both, when evidence was gathered. |
| label | string | Its most frequent spelling. |
| last_seen | string | The newest article mentioning both, when evidence was gathered. |
| share | number | co_mentions over the articles mentioning the entity the edge is seen from, 0-1: the starting entity for related_entities, entity_network, and a path's direct edge and first hop; the end entity on a path's second hop. Compare shares, not counts, between a large entity and a small one. |
| sources | integer | Distinct publishers among the co-mentions, when evidence was gathered. More sources is broader reporting, not necessarily independent reporting. |
| stories | integer | Distinct clustered stories among the co-mentions, when evidence was gathered. |
| type | `organization` · `person` · `location` · `city` · `region` · `topic` · `name` | Its entity field. |
-
co_mentionscounts articles, not events. Forty outlets running one wire story that names both entities are forty co-mentions. -
shareisco_mentionsdivided by the articles that mention the entity the edge is seen from, rounded to three decimals. On/graph/relatedand/graph/networkthat is the starting entity (seed.articles). On a path, the direct edge and the first hop are seen fromfrom_entity, and the second hop fromto_entity. A heavily covered entity has large co-mention counts with almost everything. Share says how much of its coverage the neighbour appears in, so compare shares, not counts. - Evidence goes to the strongest edges only: the first
evidenceof them (default 3), each with up toevidencearticles. Gathering it recountsco_mentionsexactly. It also addssourcesandstories, the distinct publishers and clustered stories among those articles, andfirst_seenandlast_seen. Edges below the cut carry counts and an emptyevidence.evidence=0asks for counts only. -
sourcesagainststories. Many sources and few stories is one or two events carried widely. Many stories spread betweenfirst_seenandlast_seenis a pattern that recurs. More sources means broader reporting, not necessarily independent reporting. -
approximate: truemeans a count came from a capped facet or was summed across indexes. The flag is also set when a neighbour's spellings were counted separately, which can count one article twice. Read an approximate count as an upper bound. Evidence replaces it with an exact count.
Freshness, cost and bounds
Every graph response carries the same fields about the answer itself:
note: the association caveat, verbatim, so it travels with the data.-
indexed_through: the newest article the answering index holds in your window. It says how current the associations are. The newest shared article is an edge'slast_seen. -
entity_resolution_version: the normalisation the ids were computed with,er-v1. Ids are stable within a version, so store the version next to any id you keep. -
credits_charged: a credit per index query the call issued, which is what it is charged. Also sent as thex-credits-chargedheader. -
partialandcoverage: whether the index fully answered, as on every search.
A call issues one query to find indexed_through, one or two to resolve each id, one to count
neighbours, and two for each edge that gets evidence. /graph/related with the default three evidence
edges is usually nine or ten credits. An edge the budget cannot afford keeps its counts and goes without evidence; the
call is not refused. Across indexes, a call is charged the most any one index needed, not the sum. A network
comparison adds a flat 3 credits. Credits and overage lists what
other calls cost.
| Bound | Limit |
|---|---|
| Hops in a path | 2: the direct connection, or through one shared neighbour |
| Neighbours returned | 50 on /graph/related; 20 per type on /graph/network |
| Paths returned | 5 |
| Evidence | 10 articles per edge, on at most the 10 strongest edges |
Entities in one /graph/exposures call | 5 |
| Index queries | 16 per index, per call |
The window and the corpus
Every graph call takes from and to, plus four filters that narrow which articles are
counted: language, publisher_country, source_type and country.
They mean what they mean on /search. Ids are resolved over the same window;
the four filters narrow the counts, not the lookup.
Pass a window. With a key, an omitted from is your plan's archive boundary, and a neighbourhood over a
year answers a different question from one over a week. from_clamped says when your from
was moved forward.
Related entities
Entities co-mentioned with one entity.
The organizations, people, places and topics most often mentioned alongside one entity in a window, strongest first, with evidence for the strongest. Answered at request time from the index: co-mention counts come from facets over the articles mentioning an entity, evidence from bounded searches. Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. Bounded: two hops, fifty neighbours, ten evidence articles and sixteen index queries per index, reported and charged as `credits_charged`. Equivalent to the `related_entities` MCP tool. Credits: 2 up to 4 + 2 per edge that gets evidence, at most 16; evidence=0 keeps it to 4 or less. Pass estimate=true to get the exact range without running the call, or max_credits to refuse anything costlier.
| Name | Type | Description |
|---|---|---|
| entity_id required | string | The entity to start from: one id from resolve_entity, e.g. organization:asml. |
| types | string | Neighbour types to return, comma separated: organization, person, location, city, region, topic, name (default organization,person,location,topic). |
| limit | integer | Neighbours to return, 1-50 (default 20). |
| evidence | integer | How many of the strongest connections get evidence, and how many articles each carries (default 3; 0 for counts only). Evidence costs index queries, reported as credits_charged. |
| estimate | boolean | Return this call's price instead of running it: the credit range, a breakdown, your remaining credits and whether the call would run. A preview costs 0 credits. |
| max_credits | integer | Refuse the call, at no cost, when its worst case would cost more than this many credits. The refusal carries the estimate. |
- Time range
-
fromto - Corpus scope
-
source_typepublisher_countrylanguagecountry
One id in entity_id. Several are refused; /graph/exposures takes
up to five. types defaults to organizations, people, locations and topics. Neighbours of every
requested type are ranked together by co_mentions, strongest first, and cut at limit
(default 20). The starting entity is never its own neighbour.
Response
| Field | Type | Description |
|---|---|---|
| attribution | string | Required source attribution for any use of these results. |
| coverage | `complete` · `timed_out` · `range_too_wide` · `recent_unavailable` · `field_unavailable` | Why the answer is or is not complete, named for what you can do about it. `complete`: everything in range was read (or provably could not have changed the page). `timed_out`: the query ran out of budget -- retry, or narrow the range. `range_too_wide`: the range spans more of the corpus than one request may read -- narrow it; retrying unchanged will not help. `recent_unavailable`: the most recent data in range could not be read -- retry shortly; widening the range will not help. `field_unavailable`: a filter needs a field part of the index was built without (geographic, event-class and amount fields arrive with each shard's rebuild) -- the answer covers only the rebuilt part; retrying before the rebuild will not help. |
| credits_charged | integer | Index queries this answer took, and what the call is charged. At most 16 per index; a network comparison across indexes is two passes. |
| entity_resolution_version | string | The normalisation entity ids were computed with; ids are stable within one version. |
| from_clamped | boolean | Whether the window was moved forward to the plan's boundary. |
| history_days | integer | How far back the caller's plan may query; null = the full archive. |
| indexed_through | string | The newest article the answering index holds in the window, 14-digit YYYYMMDDHHMMSS: how fresh these associations are. |
| note | string | Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. |
| partial | boolean | Whether this answer is incomplete. `true` means part of the index could not be read, so an empty or short result set is NOT evidence of absence -- retry rather than caching it as a negative. |
| related | GraphEdge[] | Neighbours, strongest first; the strongest carry evidence. |
| seed | GraphSeed | The entity the query started from. |
GraphSeed
| Field | Type | Description |
|---|---|---|
| aliases_used | string[] | The spellings it matched in the window. Empty means none: nothing can be connected to it there. |
| articles | integer | Articles mentioning it in the window, when the call counted them. |
| entity_id | string | The id as passed. |
| type | `organization` · `person` · `location` · `city` · `region` · `name` · `topic` · `author` · `source` | The entity field. |
curl -s -G "https://api.unzoi.com/graph/related" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "entity_id=organization:asml" \
--data-urlencode "types=organization,person" \
--data-urlencode "from=2026-08-01" \
--data-urlencode "to=2026-08-31" \
--data-urlencode "limit=5" \
--data-urlencode "evidence=1" {
"seed": {
"entity_id": "organization:asml",
"type": "organization",
"aliases_used": ["asml", "asml holding nv"],
"articles": 1271
},
"related": [
{
"entity_id": "organization:tsmc",
"type": "organization",
"label": "tsmc",
"aliases": ["tsmc", "tsmc ltd"],
"co_mentions": 212,
"share": 0.167,
"stories": 64,
"sources": 118,
"first_seen": "20260801031500",
"last_seen": "20260830214500",
"evidence": [
{
"id": "20260830214500-a1b2c3",
"title": "Chip equipment orders climb as foundries add capacity",
"url": "https://example.com/chip-orders",
"source": "example.com",
"published_at": "20260830214500",
"language": "eng",
"story_id": "s-4e7a"
}
],
"approximate": false
},
{
"entity_id": "organization:nvidia",
"type": "organization",
"label": "nvidia",
"aliases": ["nvidia"],
"co_mentions": 97,
"share": 0.076,
"evidence": [],
"approximate": true
}
],
"indexed_through": "20260831234500",
"entity_resolution_version": "er-v1",
"credits_charged": 5,
"note": "Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. Read the evidence before stating a relationship.",
"partial": false,
"coverage": "complete",
"history_days": 365,
"from_clamped": false,
"attribution": "Data derived from the GDELT Project (https://www.gdeltproject.org/)."
}
With evidence=1, only the strongest edge is recounted and carries an article. The second keeps its facet
count, marked approximate. Five units: the freshness check, one lookup for the id, one neighbour count, and two for
the evidence.
Connection path
How two entities connect.
The articles mentioning both, and up to five paths through one shared neighbour. Answered at request time from the index: co-mention counts come from facets over the articles mentioning an entity, evidence from bounded searches. Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. Bounded: two hops, fifty neighbours, ten evidence articles and sixteen index queries per index, reported and charged as `credits_charged`. Equivalent to the `connection_path` MCP tool. Credits: 3-8 with max_hops=1, 3-9 with evidence=0, up to 16 otherwise. Pass estimate=true to get the exact range without running the call, or max_credits to refuse anything costlier.
| Name | Type | Description |
|---|---|---|
| from_entity required | string | Where the path starts: an id from resolve_entity. |
| to_entity required | string | Where the path ends: an id from resolve_entity. |
| max_hops | integer | 1 for the direct connection only; 2 (default) also looks through one shared neighbour. Paths are bounded to two hops by design. |
| evidence | integer | How many of the strongest connections get evidence, and how many articles each carries (default 3; 0 for counts only). Evidence costs index queries, reported as credits_charged. |
| estimate | boolean | Return this call's price instead of running it: the credit range, a breakdown, your remaining credits and whether the call would run. A preview costs 0 credits. |
| max_credits | integer | Refuse the call, at no cost, when its worst case would cost more than this many credits. The refusal carries the estimate. |
- Time range
-
fromto - Corpus scope
-
source_typepublisher_countrylanguagecountry
direct is always present: an edge reaching to_entity, counted exactly over the articles
that mention both, with up to evidence of them. co_mentions: 0 means no article in the
window names both.
With max_hops=2, the default, the call also lists up to 50 neighbours of each type for each end and
keeps those the two share. A path runs from the start, through one shared neighbour, to the end. Paths are ranked by
their weaker hop, and at most five come back. strength is that weaker hop's co-mentions: a path is only
as well reported as its thinner link. Each hop gets evidence while evidence is above zero and the call
can afford it.
A path chains two co-mentions from different articles: the start reported with the neighbour, and the neighbour
reported with the end. It never shows the two ends in one article; that is direct. An empty
paths means no shared neighbour among each end's strongest, not that no connection exists. Compare hops
by co_mentions. The first hop's share is over from.articles and the second
hop's over to.articles, so the two are fractions of different coverage. With max_hops=1
the call counts the starting entity's articles, one more index query: from.articles is set,
to.articles is absent, and direct.share is measured over from.articles as it
is at two hops.
Response
| Field | Type | Description |
|---|---|---|
| attribution | string | Required source attribution for any use of these results. |
| coverage | `complete` · `timed_out` · `range_too_wide` · `recent_unavailable` · `field_unavailable` | Why the answer is or is not complete, named for what you can do about it. `complete`: everything in range was read (or provably could not have changed the page). `timed_out`: the query ran out of budget -- retry, or narrow the range. `range_too_wide`: the range spans more of the corpus than one request may read -- narrow it; retrying unchanged will not help. `recent_unavailable`: the most recent data in range could not be read -- retry shortly; widening the range will not help. `field_unavailable`: a filter needs a field part of the index was built without (geographic, event-class and amount fields arrive with each shard's rebuild) -- the answer covers only the rebuilt part; retrying before the rebuild will not help. |
| credits_charged | integer | Index queries this answer took, and what the call is charged. At most 16 per index; a network comparison across indexes is two passes. |
| direct | GraphEdge | The articles mentioning both, as an edge reaching `to`. |
| entity_resolution_version | string | The normalisation entity ids were computed with; ids are stable within one version. |
| from | GraphSeed | Where the path starts. |
| from_clamped | boolean | Whether the window was moved forward to the plan's boundary. |
| history_days | integer | How far back the caller's plan may query; null = the full archive. |
| indexed_through | string | The newest article the answering index holds in the window, 14-digit YYYYMMDDHHMMSS: how fresh these associations are. |
| note | string | Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. |
| partial | boolean | Whether this answer is incomplete. `true` means part of the index could not be read, so an empty or short result set is NOT evidence of absence -- retry rather than caching it as a negative. |
| paths | GraphPath[] | Up to five paths through one shared neighbour, strongest first; empty with max_hops=1. |
| to | GraphSeed | Where the path ends. |
GraphPath
| Field | Type | Description |
|---|---|---|
| edges | GraphEdge[] | One edge per hop, each describing the entity it reaches. |
| hops | string[] | Entity ids from the start, through the neighbour, to the end. |
| strength | integer | The weaker hop's co-mentions. |
curl -s -G "https://api.unzoi.com/graph/path" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "from_entity=organization:asml" \
--data-urlencode "to_entity=organization:rapidus" \
--data-urlencode "from=2026-08-01" \
--data-urlencode "to=2026-08-31" \
--data-urlencode "evidence=1" \
| jq '{direct: (.direct | {co_mentions, stories, sources}), paths: [.paths[] | {hops, strength}], credits_charged}' {
"direct": { "co_mentions": 14, "stories": 6, "sources": 12 },
"paths": [
{ "hops": ["organization:asml", "organization:tokyo electron", "organization:rapidus"], "strength": 9 },
{ "hops": ["organization:asml", "location:hokkaido japan", "organization:rapidus"], "strength": 7 }
],
"credits_charged": 15
} Ids can contain spaces, so pass them URL-encoded. Here the two ends appear together in six stories, and both are also reported alongside Tokyo Electron and Hokkaido. Neither path says anything the articles do not.
Entity network
One entity's neighbours, and what changed.
Neighbours by type for a window; with `compare_from` or `compare_to`, new and gone neighbours and ties that strengthened or weakened. Answered at request time from the index: co-mention counts come from facets over the articles mentioning an entity, evidence from bounded searches. Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. Bounded: two hops, fifty neighbours, ten evidence articles and sixteen index queries per index, reported and charged as `credits_charged`. Equivalent to the `entity_network` MCP tool. Credits: 2 up to 4 + 2 per edge that gets evidence, at most 16, plus 3 for a comparison window. Pass estimate=true to get the exact range without running the call, or max_credits to refuse anything costlier.
| Name | Type | Description |
|---|---|---|
| entity_id required | string | The entity to start from: one id from resolve_entity, e.g. organization:asml. |
| types | string | Neighbour types to return, comma separated: organization, person, location, city, region, topic, name (default organization,person,location,topic). |
| limit | integer | Neighbours per type, 1-20 (default 10). |
| evidence | integer | How many of the strongest connections get evidence, and how many articles each carries (default 3; 0 for counts only). Evidence costs index queries, reported as credits_charged. |
| compare_from | string | Start of a second window to compare the network against, in the same formats as `from`. With compare_from or compare_to the response carries `changes`. |
| compare_to | string | End of the comparison window, in the same formats as `to`. |
| estimate | boolean | Return this call's price instead of running it: the credit range, a breakdown, your remaining credits and whether the call would run. A preview costs 0 credits. |
| max_credits | integer | Refuse the call, at no cost, when its worst case would cost more than this many credits. The refusal carries the estimate. |
- Time range
-
fromto - Corpus scope
-
source_typepublisher_countrylanguagecountry
One entity's neighbourhood grouped by type. groups is keyed by entity type, and each list is strongest
first and cut at limit per type (default 10, at most 20). Evidence goes to the strongest
evidence edges across all the groups.
What changed: compare_from and compare_to
Pass either, and the same neighbourhood is computed over a second window and compared with the first. An end you
omit is open. The comparison window is bound by your plan's archive depth, as from is.
new: shown now, and not among the comparison window's neighbours.gone: shown in the comparison window, and not among this window's neighbours.-
strengthened: a share at least one and a half times what it was, with at least two co-mentions now. weakened: a share down by a third or more.
Before comparing, each window's neighbours are read to three times limit per type. So
gone means a neighbour fell well out of the other window's list, not just below the cut. Shares are
over each window's own article count for the entity, so a month with twice the coverage does not strengthen every
tie. The comparison is a second pass, charged in full.
A change is a change in the reporting. A neighbour is new when journalists started writing about the two
together, and one large story is enough to cause it. Check the edge's stories and evidence before
calling it a development.
Response
| Field | Type | Description |
|---|---|---|
| attribution | string | Required source attribution for any use of these results. |
| changes | GraphNetworkChanges | Present with compare_from or compare_to, unless the call's index-query budget ran out before the comparison: lower `evidence` to make room. |
| coverage | `complete` · `timed_out` · `range_too_wide` · `recent_unavailable` · `field_unavailable` | Why the answer is or is not complete, named for what you can do about it. `complete`: everything in range was read (or provably could not have changed the page). `timed_out`: the query ran out of budget -- retry, or narrow the range. `range_too_wide`: the range spans more of the corpus than one request may read -- narrow it; retrying unchanged will not help. `recent_unavailable`: the most recent data in range could not be read -- retry shortly; widening the range will not help. `field_unavailable`: a filter needs a field part of the index was built without (geographic, event-class and amount fields arrive with each shard's rebuild) -- the answer covers only the rebuilt part; retrying before the rebuild will not help. |
| credits_charged | integer | Index queries this answer took, and what the call is charged. At most 16 per index; a network comparison across indexes is two passes. |
| entity_resolution_version | string | The normalisation entity ids were computed with; ids are stable within one version. |
| from_clamped | boolean | Whether the window was moved forward to the plan's boundary. |
| groups | object | Neighbours by entity type, strongest first. |
| history_days | integer | How far back the caller's plan may query; null = the full archive. |
| indexed_through | string | The newest article the answering index holds in the window, 14-digit YYYYMMDDHHMMSS: how fresh these associations are. |
| note | string | Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. |
| partial | boolean | Whether this answer is incomplete. `true` means part of the index could not be read, so an empty or short result set is NOT evidence of absence -- retry rather than caching it as a negative. |
| seed | GraphSeed | The entity the query started from. |
GraphNetworkChanges
| Field | Type | Description |
|---|---|---|
| gone | GraphEdge[] | Neighbours shown then that are not neighbours now. |
| new | GraphEdge[] | Neighbours shown now that were not neighbours then. |
| strengthened | GraphChange[] | Ties whose share grew by half or more, with at least two co-mentions now. |
| weakened | GraphChange[] | Ties whose share fell by a third or more. |
GraphChange
| Field | Type | Description |
|---|---|---|
| after | integer | Co-mentions in the requested window. |
| after_share | number | Share in the requested window. |
| before | integer | Co-mentions in the comparison window. |
| before_share | number | Share in the comparison window. |
| entity_id | string | The neighbour. |
| label | string | Its most frequent spelling. |
# August against July, counts only: evidence for the changes worth reporting comes after.
curl -s -G "https://api.unzoi.com/graph/network" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "entity_id=organization:asml" \
--data-urlencode "types=organization,person" \
--data-urlencode "from=2026-08-01" \
--data-urlencode "to=2026-08-31" \
--data-urlencode "compare_from=2026-07-01" \
--data-urlencode "compare_to=2026-07-31" \
--data-urlencode "evidence=0" \
| jq '.changes | {new: [.new[].label], gone: [.gone[].label], strengthened, weakened}' {
"new": ["rapidus"],
"gone": ["imec"],
"strengthened": [
{ "entity_id": "organization:tsmc", "label": "tsmc", "before": 118, "after": 212, "before_share": 0.102, "after_share": 0.167 }
],
"weakened": [
{ "entity_id": "organization:intel", "label": "intel", "before": 105, "after": 61, "before_share": 0.091, "after_share": 0.048 }
]
} Shared exposures
What several entities have in common.
The articles mentioning up to five entities together, with their facets, signal intensities and evidence. Answered at request time from the index: co-mention counts come from facets over the articles mentioning an entity, evidence from bounded searches. Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. Bounded: two hops, fifty neighbours, ten evidence articles and sixteen index queries per index, reported and charged as `credits_charged`. Equivalent to the `shared_exposures` MCP tool. Credits: 3, plus 1-2 per entity id, plus 1 with signal_stats. Pass estimate=true to get the exact range without running the call, or max_credits to refuse anything costlier.
| Name | Type | Description |
|---|---|---|
| entity_id required | string | Up to five entity ids from resolve_entity, comma separated (over REST the parameter may also repeat). Each matches any spelling in its alias group in the requested window; several ids must all match. The spellings used are reported in `resolved`. |
| facets | string | Comma-separated fields to count over the full match set, up to 8. |
| facet_limit | integer | Values per facet, 1-100 (default 10). |
| signal_stats | string | Signal names to summarise over the shared articles, comma separated, e.g. finance,supply_disruption. |
| evidence | integer | How many of the strongest connections get evidence, and how many articles each carries (default 3; 0 for counts only). Evidence costs index queries, reported as credits_charged. |
| estimate | boolean | Return this call's price instead of running it: the credit range, a breakdown, your remaining credits and whether the call would run. A preview costs 0 credits. |
| max_credits | integer | Refuse the call, at no cost, when its worst case would cost more than this many credits. The refusal carries the estimate. |
- Time range
-
fromto - Corpus scope
-
source_typepublisher_countrylanguagecountry
Up to five ids, comma separated, or with entity_id repeated. An article counts when it mentions every
one of them. The name describes the question a watchlist asks ("what reporting do these share?"). It is not a claim
that any of them is exposed to anything.
-
articlesis exact.storiesandsourcesare distinct counts over those articles;approximate: truesays one of them was capped. -
facetscounts the fields you name over the shared articles: which countries, topics or outlets the shared reporting is about. Comma separated over REST, an array over MCP. -
signal_statsnames signals, andsignalsreports each one's mean and 50th and 95th percentile intensity over the shared articles, the same statistics/aggregatereports. evidenceis the newest shared articles, up toevidenceof them.
An id with an empty aliases_used in entities makes the intersection empty, so check
entities before reading articles: 0 as nothing shared. Five ids ask for articles that name
all five, which is usually very few. For overlap across a watchlist, call once per pair.
Response
| Field | Type | Description |
|---|---|---|
| approximate | boolean | Whether the distinct story or source counts were capped or summed across indexes. |
| articles | integer | Articles mentioning every entity. |
| attribution | string | Required source attribution for any use of these results. |
| coverage | `complete` · `timed_out` · `range_too_wide` · `recent_unavailable` · `field_unavailable` | Why the answer is or is not complete, named for what you can do about it. `complete`: everything in range was read (or provably could not have changed the page). `timed_out`: the query ran out of budget -- retry, or narrow the range. `range_too_wide`: the range spans more of the corpus than one request may read -- narrow it; retrying unchanged will not help. `recent_unavailable`: the most recent data in range could not be read -- retry shortly; widening the range will not help. `field_unavailable`: a filter needs a field part of the index was built without (geographic, event-class and amount fields arrive with each shard's rebuild) -- the answer covers only the rebuilt part; retrying before the rebuild will not help. |
| credits_charged | integer | Index queries this answer took, and what the call is charged. At most 16 per index; a network comparison across indexes is two passes. |
| entities | GraphSeed[] | The entities, as the index spells them. |
| entity_resolution_version | string | The normalisation entity ids were computed with; ids are stable within one version. |
| evidence | CompactArticle[] | The newest shared articles. |
| facets | object | The facets requested, counted over the shared articles. |
| from_clamped | boolean | Whether the window was moved forward to the plan's boundary. |
| history_days | integer | How far back the caller's plan may query; null = the full archive. |
| indexed_through | string | The newest article the answering index holds in the window, 14-digit YYYYMMDDHHMMSS: how fresh these associations are. |
| note | string | Co-mention in reporting is evidence of association, not of ownership, partnership, causation or exposure. |
| partial | boolean | Whether this answer is incomplete. `true` means part of the index could not be read, so an empty or short result set is NOT evidence of absence -- retry rather than caching it as a negative. |
| signals | object | Per requested signal: mean and percentile intensity over the shared articles. |
| sources | integer | Distinct publishers among them. |
| stories | integer | Distinct clustered stories among them. |
curl -s -G "https://api.unzoi.com/graph/exposures" -H "x-api-key: $UNZOI_KEY" \
--data-urlencode "entity_id=organization:asml,organization:tsmc" \
--data-urlencode "facets=country,topic" \
--data-urlencode "signal_stats=supply_disruption,trade" \
--data-urlencode "from=2026-08-01" \
--data-urlencode "to=2026-08-31" \
--data-urlencode "evidence=3" \
| jq '{articles, stories, sources, countries: .facets.country[:3], signals: (.signals | map_values({mean, p95: .percentiles["95"]})), credits_charged}' {
"articles": 212,
"stories": 64,
"sources": 118,
"countries": [
{ "value": "TW", "count": 131 },
{ "value": "NL", "count": 97 },
{ "value": "US", "count": 88 }
],
"signals": {
"supply_disruption": { "mean": 0.84, "p95": 2.61 },
"trade": { "mean": 1.12, "p95": 2.95 }
},
"credits_charged": 6
} Errors
The four operations return the same statuses.
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Invalid request, charged nothing. Bounds, enums, dates and offsets are validated BEFORE any index work, so this never means a partially-served query. `credit_budget_exceeded`: the call's worst case is more than `max_credits`. |
| 401 | Missing or invalid API key. `code` is `unauthorized`. |
| 402 | Account suspended (subscription canceled or payment failed). `code` is `suspended`. |
| 429 | Refused before running, and charged nothing. `credit_rate_limited`: this minute's credits are spent. `message_rate_limited`: too many free calls or refused requests this minute. `insufficient_credits`: the key stops at its monthly allocation and does not have this call's worst case left. `retry-after` says how long to wait. |
| 503 | The index could not be read for this request. This is NOT an empty result set and NOT a statement that nothing matched — retry rather than caching it as a negative. `coverage` says which part was unreadable. |
400 with code: invalid_parameter covers the refusals particular to these endpoints: a
missing entity_id, from_entity or to_entity; more than one id on
/graph/related or /graph/network, or more than five on /graph/exposures; an
entity_id that is not <type>:<name>; and a limit,
evidence or max_hops out of range. A parameter an endpoint does not take is
unknown_parameter, with the name you probably meant.
Over MCP
The same four requests are the related_entities,
connection_path,
entity_network and
shared_exposures tools, with the same arguments. Their
descriptions tell a model to read the evidence and present co-mentions as a pattern in the reporting.
Reading relationships goes from a name to an answer you can defend.
{
"name": "related_entities",
"arguments": {
"entity_id": "organization:asml",
"from": "2026-08-01",
"to": "2026-08-31",
"types": "organization,person",
"evidence": 3
}
}