API Documentation
Access real-time news intelligence, market signals, and geopolitical risk data.
In alpha and free while it is. Email us for a key.
Try It
Real responses from the live API, no key required. Pick a query.
The stories with the broadest coverage right now, ordered by source count.
GET https://www.perspecta.news/api/v1/events?limit=3&since=24h{
"data": [
{
"id": "f91dd04e-80d6-4932-aec2-ce07a0cb5e8f",
"title": "Hamas Agrees to Disarm Under Trump's Peace Plan, Awaiting Israeli Approval",
"summary": "Hamas has reportedly agreed to disarm as part of a peace plan proposed by former President Trump's 'Board of Peace,' which would also involve an Israeli withdrawal from Gaza. However, a Hamas official stated the group would reject the deal if Israel does not approve it, and reports suggest IRGC officials urged Hamas not to sign.",
"topic": "politics",
"sourceCount": 94,
"weightedSourceCount": 233.02,
"maxOutletWeight": 1.84,
"outlets": [
"20-minuten",
"24ur",
"aftonbladet",
"aktuality-sk",
"aktualne-cz",
"aljazeera",
"balkan-web",
"bbc",
"berlingske",
"bloomberg",
"capital-bg",
"cbc",
"channel-news-asia",
"cnbc",
"cyprus-mail",
"dagbladet",
"daily-sabah",
"danas",
"dawn",
"de-volkskrant",
"delfi-lt",
"delo",
"dennik-n",
"der-standard",
"dh-les-sports",
"die-presse",
"digi24",
"dnevnik-bg",
"dw",
"el-mundo",
"elpais",
"express-tribune",
"faz",
"forbes",
"foxnews",
"france24",
"ft",
"guardian",
"helsingin-sanomat",
"hindustan-times",
"hotnews",
"hvg",
"iefimerida",
"il-sole-24-ore",
"independent",
"index-hr",
"indian-express",
"irish-independent",
"irozhlas",
"japan-times",
"jerusalem-post",
"jutarnji-list",
"klix-ba",
"korea-herald",
"la-repubblica",
"la-vanguardia",
"le-figaro",
"le-monde",
"lsm-lv",
"meta-mk",
"mkd-mk",
"morgunbladid",
"myjoyonline",
"n1-bih",
"n1-serbia",
"naftemporiki",
"national-post",
"ndtv",
"newsbeast",
"nhk",
"nos",
"npr",
"nrk",
"nytimes",
"nzz",
"observador",
"orf",
"protothema-en",
"publico",
"rte-news",
"ruv",
"rzeczpospolita",
"sbs-news",
"scmp",
"seeking-alpha",
"svenska-dagbladet",
"tagesschau",
"telex",
"the-journal",
"times-india",
"vg",
"vijesti-me",
"yahoo",
"yle-uutiset"
],
"articleLanguages": [
"als",
"bos",
"bul",
"ces",
"dan",
"deu",
"ekk",
"ell",
"eng",
"fin",
"fra",
"hrv",
"hun",
"ita",
"jpn",
"lit",
"lvs",
"mkd",
"nld",
"nno",
"nob",
"pol",
"por",
"ron",
"sco",
"slk",
"slv",
"spa",
"srp",
"tpi",
"zlm"
],
"sourceSignal": "agree",
"impactScore": 386.6,
"velocity": 4.11,
"publishedAt": "2026-07-30T12:47:57.000Z",
"updatedAt": "2026-07-31T08:18:43.000Z",
"firstSeenAt": "2026-07-30T22:35:12.000Z",
"lastWrittenAt": "2026-07-31T09:03:43.331Z",
"ingestionLagMinutes": 587,
"mergedInto": null,
"mergedAt": null,
"regions": [
"Israel",
"Palestine",
"United States",
"Egypt"
],
"countries": [
"Israel",
"United States"
],
"entities": [
"hamas agrees",
"hamas",
"agrees",
"disarm",
"trump",
"peace",
"plan",
"awaiting",
"israeli",
"approval",
"israel",
"board",
"gaza however",
"gaza",
"however",
"irgc",
"disarmament",
"deal",
"pending",
"board of peace",
"iranian",
"iran",
"withdrawal",
"former president donald trump",
"former"
],
"tags": [
"Trump",
"Hamas",
"Gaza",
"MiddleEastPeace",
"Disarmament"
],
"language": null,
"firstArticleUrl": "http://www3.nhk.or.jp/news/html/20260731/k10015191691000.html",
"url": "https://perspecta.news/story/f91dd04e-80d6-4932-aec2-ce07a0cb5e8f"
},
{
"id": "f031130a-1dec-4a03-a17b-10163140cae0",
"title": "Thousands of Migrants Storm Spanish Enclave of Ceuta from Morocco",
"summary": "Thousands of migrants, estimated at around 49,000, crossed into the Spanish enclave of Ceuta from Morocco, leading to a crisis that Spanish Prime Minister Pedro Sánchez called an \"attack on Spain's integrity.\" The influx resulted in at least 18 migrant deaths and prompted calls from some European nations for Spain's suspension from Schengen.",
"topic": "world",
"sourceCount": 81,
"weightedSourceCount": 266.14,
"maxOutletWeight": 1.84,
"outlets": [
"20-minuten",
"24ur",
"aftonbladet",
"aktuality-sk",
"aktualne-cz",
"aljazeera",
"ansa",
"balkan-web",
"bbc",
"berlingske",
"bloomberg",
"cbc",
"cyprus-mail",
"dagbladet",
"daily-sabah",
"danas",
"dawn",
"delfi-lt",
"delo",
"dh-les-sports",
"die-presse",
"digi24",
"dnevnik-bg",
"el-mundo",
"elpais",
"faz",
"foxnews",
"france24",
"ft",
"guardian",
"helsingin-sanomat",
"helsinki-times",
"hindustan-times",
"hotnews",
"iefimerida",
"independent",
"index-hr",
"indian-express",
"irish-independent",
"irozhlas",
"japan-times",
"jutarnji-list",
"klix-ba",
"la-repubblica",
"la-vanguardia",
"le-figaro",
"le-monde",
"lsm-lv",
"luxemburger-wort",
"meta-mk",
"mkd-mk",
"myjoyonline",
"n1-bih",
"n1-serbia",
"naftemporiki",
"newsbeast",
"nl-times",
"nos",
"npr",
"nytimes",
"nzz",
"observador",
"orf",
"politiken",
"protothema-en",
"publico",
"punch-ng",
"rte-news",
"ruv",
"rzeczpospolita",
"sbs-news",
"svenska-dagbladet",
"tagesschau",
"telex",
"the-journal",
"times-india",
"tvn24",
"vanguard-ng",
"vijesti-me",
"yahoo",
"yle-uutiset"
],
"articleLanguages": [
"als",
"bos",
"bul",
"cat",
"ces",
"dan",
"deu",
"ell",
"eng",
"fin",
"fra",
"glg",
"hrv",
"hun",
"ita",
"lit",
"lvs",
"mkd",
"nld",
"nno",
"nob",
"pol",
"por",
"ron",
"sco",
"slk",
"slv",
"spa",
"src",
"srp",
"swe"
],
"sourceSignal": "agree",
"impactScore": 643,
"velocity": 7.94,
"publishedAt": "2026-07-31T01:26:56.000Z",
"updatedAt": "2026-07-31T11:34:46.951Z",
"firstSeenAt": "2026-07-31T01:26:56.000Z",
"lastWrittenAt": "2026-07-31T11:38:46.777Z",
"ingestionLagMinutes": 0,
"mergedInto": null,
"mergedAt": null,
"regions": [
"Morocco",
"Spain",
"Italy"
],
"countries": [
"Spain"
],
"entities": [
"migrants",
"storm",
"spanish",
"enclave",
"ceuta",
"morocco",
"spain",
"prime",
"pedro",
"sánchez",
"european",
"schengen",
"tens",
"enter",
"nearly",
"spain and italy",
"italy",
"mass",
"migration",
"crisis",
"cross",
"the spanish",
"france at",
"france",
"thousands of migrants cross"
],
"tags": [
"Ceuta",
"MigrationCrisis",
"Spain",
"Schengen"
],
"language": null,
"firstArticleUrl": "https://cyprus-mail.com/2026/07/31/migrant-deaths-rise-to-18-in-massive-crossing-into-spains-ceuta-from-morocco",
"url": "https://perspecta.news/story/f031130a-1dec-4a03-a17b-10163140cae0"
},
{
"id": "e29b5805-165d-484a-acc1-98493a4ee893",
"title": "Italy Calls for Spain's Schengen Suspension Amidst Ceuta Migrant Crisis",
"summary": "Italy has called for Spain's suspension from the Schengen area following a massive influx of migrants into the Spanish enclave of Ceuta from Morocco. The crisis led to Spain deploying troops and a diplomatic rift with Italy over its proposed response.",
"topic": "world",
"sourceCount": 79,
"weightedSourceCount": 202.48,
"maxOutletWeight": 1.84,
"outlets": [
"20-minuten",
"24ur",
"aftonbladet",
"aktuality-sk",
"aktualne-cz",
"aljazeera",
"ansa",
"balkan-web",
"bbc",
"berlingske",
"cbc",
"channel-news-asia",
"cyprus-mail",
"dagbladet",
"daily-sabah",
"danas",
"de-volkskrant",
"delfi-lt",
"delo",
"der-standard",
"dh-les-sports",
"die-presse",
"digi24",
"dnevnik-bg",
"dw",
"el-mundo",
"elpais",
"faz",
"forbes",
"foxnews",
"france24",
"ft",
"guardian",
"helsingin-sanomat",
"hindustan-times",
"hotnews",
"hvg",
"iefimerida",
"il-sole-24-ore",
"independent",
"index-hr",
"indian-express",
"irozhlas",
"jerusalem-post",
"jutarnji-list",
"klix-ba",
"la-repubblica",
"la-vanguardia",
"le-figaro",
"le-monde",
"lsm-lv",
"mkd-mk",
"morgunbladid",
"myjoyonline",
"n1-serbia",
"naftemporiki",
"national-post",
"ndtv",
"newsbeast",
"nos",
"nytimes",
"nzz",
"observador",
"orf",
"politiken",
"protothema-en",
"publico",
"rte-news",
"rzeczpospolita",
"scmp",
"svenska-dagbladet",
"tagesschau",
"telex",
"the-journal",
"times-india",
"tvn24",
"vg",
"yahoo",
"yle-uutiset"
],
"articleLanguages": [
"als",
"bos",
"bul",
"ces",
"dan",
"deu",
"ell",
"eng",
"fin",
"fra",
"glg",
"hrv",
"hun",
"ita",
"lit",
"lvs",
"mkd",
"nld",
"nob",
"pol",
"por",
"ron",
"slv",
"spa",
"srp",
"swe"
],
"sourceSignal": null,
"impactScore": 274.3,
"velocity": 3.47,
"publishedAt": "2026-07-30T12:54:03.000Z",
"updatedAt": "2026-07-31T00:12:42.000Z",
"firstSeenAt": "2026-07-30T12:54:03.000Z",
"lastWrittenAt": "2026-07-31T00:12:42.000Z",
"ingestionLagMinutes": 0,
"mergedInto": null,
"mergedAt": null,
"regions": [
"Spain",
"Morocco"
],
"countries": [
"Spain"
],
"entities": [
"italy",
"calls",
"spain",
"schengen",
"suspension",
"amidst",
"ceuta",
"migrant",
"crisis",
"spanish",
"morocco the",
"morocco",
"spain deploys military",
"deploys",
"migrants",
"cross",
"north african",
"north",
"african",
"spain ceuta",
"breach",
"spain ceuta enclave",
"enclave",
"spain north african",
"morocco spain deploys military"
],
"tags": [
"Migration",
"Ceuta",
"Spain"
],
"language": null,
"firstArticleUrl": "http://www.delo.si/novice/svet/migranti-premagali-policiste-in-vdrli-v-spansko-ceuto",
"url": "https://perspecta.news/story/e29b5805-165d-484a-acc1-98493a4ee893"
}
],
"meta": {
"timestamp": "2026-07-31T11:39:11.632Z",
"count": 3,
"since": "2026-07-30T11:39:11.557Z",
"orderBy": "sourceCount",
"truncated": true,
"nextCursor": "eyJvIjoic291cmNlQ291bnQiLCJ2Ijo3OSwiaSI6ImUyOWI1ODA1LTE2NWQtNDg0YS1hY2MxLTk4NDkzYTRlZTg5MyJ9"
}
}Live data from the same code path the API serves — not a saved sample. These previews return a couple of rows and are cached for a minute; a real key has neither limit.
Authentication
All API requests require an API key. Pass it via the X-API-Key header or as a Bearer token in the Authorization header.
curl -H "X-API-Key: pk_your_key_here" \ "https://www.perspecta.news/api/v1/events?since=24h"
Response Format
All endpoints return JSON with a consistent structure:
{
"data": [ ... ],
"meta": {
"timestamp": "2026-03-17T08:00:00.000Z",
"count": 20,
"since": "2026-03-16T08:00:00.000Z",
"truncated": true,
"nextCursor": "eyJvIjoibGFzdFdyaXR0ZW4iLCJ2IjoiMjAy..."
}
}Market Signals
Stories scored by impact using source velocity, coverage breadth, and narrative divergence.
/api/v1/signals— Ranked market signalsParameters
since | string | Time window: a duration (30m, 6h, 7d) or an ISO 8601 timestamp (default: 24h) |
sector | string | Filter by sector: geopolitics, policy, markets, tech, energy, healthcare, entertainment |
country | string | Filter by country name (case-insensitive) |
tag | string | Filter by story hashtag |
limit | number | Max results, 1-100 (default: 20) |
Impact Score
Each signal includes an impactScore and velocity. Velocity measures how fast a story accumulated sources (sources/hour). Stories where sources diverge on framing get a 1.5× multiplier, reflecting higher uncertainty.
curl -H "X-API-Key: pk_your_key" \
"https://www.perspecta.news/api/v1/signals?sector=energy&since=6h&limit=5"
# Response:
{
"data": [
{
"id": "3830025b-...",
"title": "Europe Rules Out Joining Trump's Hormuz Armada",
"summary": "Europe has officially ruled out...",
"topic": "world",
"sector": "geopolitics",
"sourceCount": 31,
"sourceSignal": "diverge",
"impactScore": 142.5,
"velocity": 6.2,
"publishedAt": "2026-03-17T02:00:00Z",
"regions": ["europe", "middle-east"],
"countries": ["iran", "united kingdom"],
"entities": ["trump", "hormuz"],
"hashtags": ["IranWar", "Hormuz"]
}
]
}Geopolitical Risk
Country-level risk scores derived from aggregated story impact. Normalized 0–100 with contributing stories.
/api/v1/risk/countries— All countries ranked by risk/api/v1/risk/countries/:code— Single country detailParameters
hours | number | Lookback window in hours, 1-168 (default: 24) |
limit | number | Max countries, 1-200 (default: 50) |
curl -H "X-API-Key: pk_your_key" \
"https://www.perspecta.news/api/v1/risk/countries?hours=24&limit=10"
# Response:
{
"data": [
{
"country": "iran",
"riskScore": 100.0,
"storyCount": 45,
"topStories": [
{
"id": "bcd4f911-...",
"title": "Analysts Say Iran's Attacks Have Collapsed",
"impactScore": 98.3,
"sourceCount": 24
}
]
},
{
"country": "israel",
"riskScore": 82.4,
"storyCount": 31,
"topStories": [ ... ]
}
]
}News Events
Structured, deduplicated events with source articles, perspectives, and entity timelines.
/api/v1/events— List events with filters/api/v1/events/:id— Event detail with all source articles/api/v1/events/:id/perspectives— Articles grouped by outlet/api/v1/merges— Stories folded into other stories/api/v1/entities— Enumerate the entity vocabulary/api/v1/entities/:name/timeline— Event timeline for a person, place, or orgParameters — /events
since | string | Publisher-clock window: a duration (30m, 6h, 7d) or an ISO 8601 timestamp (default: 24h) |
changedSince | string | Wall-clock window — stories Perspecta wrote at or after this point. Duration or ISO 8601. Use this for incremental sweeps, not since |
orderBy | string | sourceCount (default), publishedAt, or lastWritten. Only lastWritten is stable under concurrent writes |
cursor | string | Opaque pagination token from meta.nextCursor. Must match the orderBy it was issued for |
include | string | Set to articles to inline each event's top constituent articles |
articleLimit | number | Articles per event when include=articles, 1-10 (default: 3) |
includeMerged | boolean | Include stories that were folded into another story (default: false) |
topic | string | Filter by topic: world, politics, business, technology, science, health, entertainment, environment |
country | string | Filter by country name |
tag | string | Filter by hashtag |
entity | string | Filter by entity name (person, org, place) |
minSources | number | Minimum source count (default: 1) |
limit | number | Rows per page, 1-200 (default: 50). Not a cap on total rows — page with cursor |
Pagination
Every list endpoint returns meta.truncated and meta.nextCursor. When truncated is true, more rows matched than were returned — pass nextCursor back to continue. Paging is by keyset, not offset, so it neither repeats nor skips rows while the clustering job is writing. A cursor is only valid for the orderBy it was issued for; mixing them is rejected rather than silently returning the wrong rows.
For an incremental sweep, order by lastWritten and pass the previous sweep's highest lastWrittenAt as changedSince. Under orderBy=sourceCount or publishedAt a concurrent write can move a row across a page boundary; lastWritten cannot.
curl -H "X-API-Key: pk_your_key" \ "https://www.perspecta.news/api/v1/events?orderBy=lastWritten\ &changedSince=2026-07-31T08:00:00.000Z&limit=200&include=articles" # then follow meta.nextCursor until it comes back null
Parameters — /merges
since | string | Window on mergedAt: duration or ISO 8601 (default: 24h) |
limit | number | Rows per page, 1-200 (default: 50) |
cursor | string | Opaque token from meta.nextCursor |
Parameters — /entities
A bulk endpoint, served from a snapshot refreshed every 30 minutes. storyCount and totalSources are always over a fixed 30-day window whatever since says; since filters on lastSeenAt. meta.computedAt gives the snapshot's age. Names appearing in fewer than three stories in the window are not tracked.
since | string | Only entities last seen at or after this point |
minStories | number | Minimum stories in the 30-day window (default: 1) |
type | string | company, person, country, organization, location, product, event, other. Null on names not yet classified |
limit | number | Rows per page, max 2000 (default: 500) |
offset | number | Row offset — this endpoint pages by offset, not cursor |
Parameters — /entities/:name/timeline
days | number | Lookback in days, 1-30 (default: 7) |
limit | number | Max events, 1-200 (default: 50) |
curl -H "X-API-Key: pk_your_key" \ "https://www.perspecta.news/api/v1/events?country=iran&tag=IranWar&minSources=10&limit=5"
curl -H "X-API-Key: pk_your_key" \
"https://www.perspecta.news/api/v1/events/3830025b-8759-44cd-8bd9-20a9b33a16a7"
# Returns the event with all source articles:
{
"data": {
"id": "3830025b-...",
"title": "Europe Rules Out Joining Trump's Hormuz Armada",
"sourceCount": 31,
"sourceSignal": "diverge",
"articles": [
{
"id": "a1b2c3...",
"title": "Europe refuses to join US naval mission",
"outlet": "reuters",
"url": "https://...",
"publishedAt": "2026-03-17T01:30:00Z"
},
...
]
}
}curl -H "X-API-Key: pk_your_key" \
"https://www.perspecta.news/api/v1/events/3830025b-.../perspectives"
# Articles grouped by outlet:
{
"data": {
"eventTitle": "Europe Rules Out Joining Trump's Hormuz Armada",
"sourceSignal": "diverge",
"outletCount": 18,
"perspectives": [
{
"outlet": "reuters",
"articleCount": 3,
"articles": [ ... ]
},
{
"outlet": "al-jazeera",
"articleCount": 2,
"articles": [ ... ]
}
]
}
}curl -H "X-API-Key: pk_your_key" \
"https://www.perspecta.news/api/v1/entities/trump/timeline?days=7"
# All events mentioning "trump" in the last 7 days:
{
"data": {
"entity": "trump",
"storyCount": 42,
"timeline": [
{
"id": "...",
"title": "Trump Criticizes Allies for Rejecting Hormuz Request",
"sourceCount": 28,
"impactScore": 120.4,
"publishedAt": "2026-03-17T03:00:00Z"
},
...
]
}
}AI & Agentic Workflows
Perspecta's structured, multi-source event data is purpose-built for AI agents, RAG pipelines, and LLM-powered applications.
Why Perspecta for AI?
Raw news feeds are noisy — duplicate articles, single-source claims, no event structure. Perspecta solves this by clustering articles into deduplicated events with source counts, consensus signals, entity extraction, and multi-perspective coverage. This means your AI gets clean, structured, verified-by-breadth data instead of raw article soup.
RAG & Grounding
Use /events as a real-time knowledge source for LLMs. Each event includes a summary, source count, and direct article URLs — giving your model grounded, citable, multi-source answers instead of hallucinated ones.
Tool Use & Function Calling
Every endpoint returns structured JSON that maps directly to LLM tool schemas. Define Perspecta as a tool in Claude, GPT, or Gemini and let the model query events, risk scores, and entity timelines autonomously.
Agentic Research
Build agents that monitor geopolitical risk, track entities across events, and surface narrative shifts. The /entities/:name/timeline endpoint gives agents a structured event history for any person, org, or place.
Signal Detection
The impactScore and sourceSignal fields let agents distinguish signal from noise. High velocity + source divergence = something significant is happening and outlets disagree on what it means.
Example: Claude Tool Definition
{
"name": "get_news_events",
"description": "Get recent news events from Perspecta, filtered by topic, country, entity, or tag. Returns structured events with source counts, impact scores, and multi-perspective coverage.",
"input_schema": {
"type": "object",
"properties": {
"topic": {
"type": "string",
"description": "Filter by topic: world, politics, business, technology, science, health"
},
"country": {
"type": "string",
"description": "Filter by country name"
},
"entity": {
"type": "string",
"description": "Filter by entity (person, org, place)"
},
"tag": {
"type": "string",
"description": "Filter by hashtag"
},
"since": {
"type": "string",
"description": "Time window: 1h, 6h, 24h, 7d",
"default": "24h"
},
"minSources": {
"type": "integer",
"description": "Minimum source count for filtering noise",
"default": 3
}
}
}
}Example: Agentic Risk Monitor
import requests, time
API_KEY = "pk_your_key"
HEADERS = {"X-API-Key": API_KEY}
BASE = "https://www.perspecta.news/api/v1"
def check_risk():
"""Agent loop: monitor country risk and alert on spikes."""
r = requests.get(f"{BASE}/risk/countries?hours=6", headers=HEADERS)
countries = r.json()["data"]
for c in countries:
if c["riskScore"] > 80:
# High risk — get details and perspectives
top_story = c["topStories"][0]
detail = requests.get(
f"{BASE}/events/{top_story['id']}/perspectives",
headers=HEADERS
).json()["data"]
print(f"⚠️ {c['country'].upper()} risk={c['riskScore']}")
print(f" {top_story['title']}")
print(f" {detail['outletCount']} outlets, signal: {detail['sourceSignal']}")
# → Feed to LLM for analysis, send Slack alert, update dashboard
while True:
check_risk()
time.sleep(300) # Check every 5 minutesExample: RAG Pipeline
# Fetch high-signal events to inject into LLM context
events = requests.get(
f"{BASE}/events?minSources=5&since=12h&limit=10",
headers=HEADERS
).json()["data"]
context = "\n".join([
f"- {e['title']} ({e['sourceCount']} sources, "
f"impact: {e['impactScore']}, signal: {e['sourceSignal'] or 'neutral'})"
for e in events
])
# Use as system context for any LLM
response = claude.messages.create(
model="claude-sonnet-4-20250514",
system=f"You have access to today's verified news events:\n{context}",
messages=[{"role": "user", "content": "What's happening in the Middle East?"}]
)Rate Limits
There are no tiers. Your limit is set on your key when we issue it, sized to what you told us you were building — including unmetered, which is what partners polling on a short interval get. If your limit turns out to be wrong, email us and we will change the number.
Limits are enforced per key, per calendar minute. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). Exceeding the limit returns 429 with Retry-After in seconds. An unmetered key returns no rate-limit headers at all — their absence is how you know you have no limit.
Response Contract
- Timestamps are ISO 8601 strings, not numbers or date objects. Parse them before comparing — an ISO string flowing into a date comparison compares lexically and produces wrong answers silently.
- Two clocks, and they mean different things.
publishedAtandupdatedAtare publisher clocks: they come from the outlets, andupdatedAtcan move backwards in real time when a story gains a late-arriving article that was published hours ago.firstSeenAtandlastWrittenAtare our wall clock and only ever move forwards. Poll onlastWrittenAt; never onupdatedAt. Both wall-clock fields are null on stories that predate 2026-07-31. sourceSignalis"agree","diverge"ornull. Null means not yet analysed — it does not mean neutral. Analysis is generated asynchronously, so a fresh story is normally null and gains a signal later.- Merged stories are excluded by default. When two clusters turn out to describe the same event, one is folded into the other and carries a non-null
mergedInto. Its source count did not collapse — its coverage moved. Fetching a merged story by id returns200with{ id, mergedInto, mergedAt }rather than the stale row, and/api/v1/mergeslists them in bulk. - Entity names are lowercased and carry no legal suffixes — the vocabulary contains
ubs, notUBS Group AG. Match case-insensitively and do not expectS.A.orAG.
Get Access
The API is in alpha. There is no self-serve signup and nothing to pay — keys are issued by hand, one conversation at a time, so we can size the rate limit to what you are actually building.
Email hello@perspecta.news with a sentence or two about what you want to pull and how often. That is the whole process.
Request alpha accessAlpha means the endpoints above are stable enough to build on and we will tell you before anything breaks — not that they are frozen.