PERSPECTA

News from every angle

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
200 OK
{
  "data": [
    {
      "id": "057d0b57-1ee0-4bae-ad78-beb7e385844f",
      "title": "Senate Confirms Trump's Former Lawyer Blanche as Attorney General",
      "summary": "The GOP-controlled Senate confirmed Donald Trump's former lawyer, Blanche, as the new Attorney General, a move seen as a significant win for Trump and an endorsement of a Justice Department under his influence.",
      "topic": "politics",
      "sourceCount": 62,
      "weightedSourceCount": 129.04,
      "maxOutletWeight": 1.84,
      "outlets": [
        "aktuality-sk",
        "aktualne-cz",
        "aljazeera",
        "balkan-web",
        "bbc",
        "berlingske",
        "bloomberg",
        "cnbc",
        "copenhagen-post",
        "cyprus-mail",
        "daily-sabah",
        "de-volkskrant",
        "delfi-lt",
        "der-spiegel",
        "der-standard",
        "die-presse",
        "digi24",
        "dw",
        "ekathimerini",
        "faz",
        "forbes",
        "foxnews",
        "france24",
        "guardian",
        "hindustan-times",
        "hotnews",
        "iefimerida",
        "il-sole-24-ore",
        "independent",
        "index-hr",
        "indian-express",
        "jerusalem-post",
        "jutarnji-list",
        "korea-herald",
        "la-repubblica",
        "la-vanguardia",
        "mkd-mk",
        "n1-bih",
        "naftemporiki",
        "national-post",
        "nme",
        "nos",
        "npr",
        "nytimes",
        "observador",
        "orf",
        "politiken",
        "publico",
        "rolling-stone",
        "ruv",
        "rzeczpospolita",
        "seeking-alpha",
        "svenska-dagbladet",
        "tagesschau",
        "tehran-times",
        "times-india",
        "tvn24",
        "vanguard-ng",
        "vg",
        "wapo",
        "yahoo",
        "zerohedge"
      ],
      "articleLanguages": [
        "als",
        "bos",
        "ces",
        "dan",
        "deu",
        "ell",
        "eng",
        "fra",
        "glg",
        "hrv",
        "ita",
        "lit",
        "mkd",
        "nld",
        "nno",
        "pol",
        "por",
        "ron",
        "sco",
        "slk",
        "spa",
        "src",
        "srp",
        "swe"
      ],
      "sourceSignal": "agree",
      "impactScore": 164.4,
      "velocity": 2.65,
      "publishedAt": "2026-08-08T10:19:46.000Z",
      "updatedAt": "2026-08-08T21:19:17.000Z",
      "firstSeenAt": "2026-08-08T11:52:46.210Z",
      "lastWrittenAt": "2026-08-08T21:47:35.138Z",
      "firstIngestedAt": "2026-08-08T05:48:28.809Z",
      "ingestionLagMinutes": 93,
      "mergedInto": null,
      "mergedAt": null,
      "regions": [
        "United States"
      ],
      "countries": [
        "United States"
      ],
      "entities": [
        "senate",
        "trump",
        "former",
        "lawyer",
        "blanche",
        "attorney general",
        "attorney",
        "general",
        "the gop-controlled senate",
        "gop-controlled",
        "donald trump",
        "donald",
        "justice department",
        "justice",
        "department",
        "the gop",
        "gop",
        "trump former lawyer confirmed",
        "confirmed",
        "amidst",
        "political",
        "tensions",
        "blanche confirmed",
        "influence",
        "president trump this"
      ],
      "tags": [],
      "language": null,
      "firstArticleUrl": "https://cyprus-mail.com/2026/08/08/todd-blanche-trumps-ex-lawyer-confirmed-as-us-attorney-general",
      "url": "https://perspecta.news/story/057d0b57-1ee0-4bae-ad78-beb7e385844f"
    },
    {
      "id": "eb4daee0-29c3-4b7d-8bf0-39ea2e5e7c1b",
      "title": "Iran Sets New Demands for Reopening Strait of Hormuz, Deal Remains Elusive",
      "summary": "Iran has issued new, stringent demands to the United States for the reopening of the Strait of Hormuz, indicating that a quick resolution to the waterway's closure is unlikely. Despite some reports of progress in talks, Iran insists the strait will remain closed until its conditions are met.",
      "topic": "world",
      "sourceCount": 56,
      "weightedSourceCount": 117.78,
      "maxOutletWeight": 1.8,
      "outlets": [
        "aftonbladet",
        "aktuality-sk",
        "aljazeera",
        "balkan-web",
        "berlingske",
        "bloomberg",
        "cdm-me",
        "channel-news-asia",
        "cnbc",
        "copenhagen-post",
        "cyprus-mail",
        "daily-sabah",
        "danas",
        "delfi-lt",
        "der-spiegel",
        "der-standard",
        "die-presse",
        "digi24",
        "dr-dk",
        "dw",
        "forbes",
        "ft",
        "guardian",
        "hindustan-times",
        "hotnews",
        "hvg",
        "iefimerida",
        "independent",
        "index-hr",
        "irozhlas",
        "japan-times",
        "jerusalem-post",
        "klix-ba",
        "la-repubblica",
        "le-figaro",
        "luxemburger-wort",
        "mkd-mk",
        "n1-serbia",
        "naftemporiki",
        "national-post",
        "ndtv",
        "newsbeast",
        "nos",
        "npr",
        "nytimes",
        "orf",
        "publico",
        "rte-news",
        "rzeczpospolita",
        "scmp",
        "seeking-alpha",
        "tehran-times",
        "the-journal",
        "vijesti-me",
        "yahoo",
        "zerohedge"
      ],
      "articleLanguages": [
        "als",
        "bos",
        "ces",
        "dan",
        "deu",
        "ell",
        "eng",
        "fra",
        "hrv",
        "hun",
        "ita",
        "lit",
        "mkd",
        "nld",
        "pol",
        "por",
        "ron",
        "sco",
        "slk",
        "srp",
        "swe"
      ],
      "sourceSignal": "agree",
      "impactScore": 141.4,
      "velocity": 2.52,
      "publishedAt": "2026-08-08T11:32:05.434Z",
      "updatedAt": "2026-08-08T23:03:47.000Z",
      "firstSeenAt": "2026-08-08T11:36:36.215Z",
      "lastWrittenAt": "2026-08-08T23:31:26.785Z",
      "firstIngestedAt": "2026-08-08T11:04:59.482Z",
      "ingestionLagMinutes": 5,
      "mergedInto": null,
      "mergedAt": null,
      "regions": [
        "UAE",
        "Iran"
      ],
      "countries": [
        "Iran"
      ],
      "entities": [
        "iran",
        "sets",
        "demands",
        "reopening",
        "strait",
        "hormuz",
        "deal",
        "remains",
        "elusive",
        "united states",
        "united",
        "states",
        "strait of hormuz",
        "despite",
        "iran demands us concessions",
        "us",
        "concessions",
        "reopen strait of hormuz",
        "reopen",
        "tehran",
        "washington",
        "meet",
        "conditions",
        "amidst",
        "tensions"
      ],
      "tags": [],
      "language": null,
      "firstArticleUrl": "https://cyprus-mail.com/2026/08/08/iran-deal-on-strait-of-hormuz-close-but-not-enough-to-open-vital-route",
      "url": "https://perspecta.news/story/eb4daee0-29c3-4b7d-8bf0-39ea2e5e7c1b"
    },
    {
      "id": "362a53cf-ccf8-4fd2-94ce-ee324fce70f3",
      "title": "Iran Demands US Concessions for Strait of Hormuz Reopening",
      "summary": "Iran has outlined several conditions, including compensation for war damages and an end to US military actions, for the reopening of the Strait of Hormuz. This comes as negotiations continue, with Iran stating a deal is close but not yet sufficient to open the vital waterway.",
      "topic": "world",
      "sourceCount": 45,
      "weightedSourceCount": 64.74,
      "maxOutletWeight": 1.84,
      "outlets": [
        "aktuality-sk",
        "aljazeera",
        "avgi",
        "balkan-web",
        "bangkok-post",
        "bbc",
        "berlingske",
        "bloomberg",
        "cdm-me",
        "channel-news-asia",
        "daily-nation",
        "dawn",
        "delo",
        "der-standard",
        "dh-les-sports",
        "express-tribune",
        "france24",
        "hindustan-times",
        "hotnews",
        "hvg",
        "il-sole-24-ore",
        "in-cyprus",
        "indian-express",
        "jerusalem-post",
        "klix-ba",
        "le-figaro",
        "myjoyonline",
        "ndtv",
        "newsbeast",
        "orf",
        "protothema-en",
        "punch-ng",
        "rappler",
        "rte-news",
        "ruv",
        "sbs-news",
        "scmp",
        "tagesschau",
        "telex",
        "the-journal",
        "times-india",
        "tvn24",
        "vanguard-ng",
        "vg",
        "wapo"
      ],
      "articleLanguages": [
        "als",
        "bos",
        "dan",
        "deu",
        "ell",
        "eng",
        "fra",
        "hun",
        "ita",
        "nds",
        "nld",
        "nno",
        "pol",
        "ron",
        "sco",
        "slk",
        "slv",
        "srp"
      ],
      "sourceSignal": "agree",
      "impactScore": 163.3,
      "velocity": 3.63,
      "publishedAt": "2026-08-08T21:19:00.000Z",
      "updatedAt": "2026-08-09T09:04:29.247Z",
      "firstSeenAt": "2026-08-09T00:21:39.661Z",
      "lastWrittenAt": "2026-08-09T09:31:52.139Z",
      "firstIngestedAt": "2026-08-08T19:33:01.632Z",
      "ingestionLagMinutes": 183,
      "mergedInto": null,
      "mergedAt": null,
      "regions": [
        "Oman",
        "Iran",
        "UAE",
        "United States"
      ],
      "countries": [
        "Iran"
      ],
      "entities": [
        "iran",
        "demands",
        "us",
        "concessions",
        "strait",
        "hormuz",
        "reopening",
        "strait of hormuz this",
        "irgc",
        "strait of hormuz while",
        "june mou iran",
        "june",
        "mou",
        "united states",
        "united",
        "states",
        "strait of hormuz tehran",
        "tehran",
        "sets",
        "conditions",
        "amidst",
        "tensions",
        "strait of hormuz",
        "issues",
        "de-escalation"
      ],
      "tags": [],
      "language": null,
      "firstArticleUrl": "https://en.philenews.com/international/iran-says-deal-on-strait-of-hormuz-is-close-but-not-enough-to-open-the-waterway/",
      "url": "https://perspecta.news/story/362a53cf-ccf8-4fd2-94ce-ee324fce70f3"
    }
  ],
  "meta": {
    "timestamp": "2026-08-09T09:42:57.653Z",
    "count": 3,
    "since": "2026-08-08T09:42:57.636Z",
    "orderBy": "sourceCount",
    "truncated": true,
    "nextCursor": "eyJvIjoic291cmNlQ291bnQiLCJ2Ijo0NSwiaSI6IjM2MmE1M2NmLWNjZjgtNGZkMi05NGNlLWVlMzI0ZmNlNzBmMyJ9"
  }
}

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.

Request
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:

Response
{
  "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.

GET/api/v1/signalsRanked market signals

Parameters

sincestringTime window: a duration (30m, 6h, 7d) or an ISO 8601 timestamp (default: 24h)
sectorstringFilter by sector: geopolitics, policy, markets, tech, energy, healthcare, entertainment
countrystringFilter by country name (case-insensitive)
tagstringFilter by story hashtag
limitnumberMax 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.

Example
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.

GET/api/v1/risk/countriesAll countries ranked by risk
GET/api/v1/risk/countries/:codeSingle country detail

Parameters

hoursnumberLookback window in hours, 1-168 (default: 24)
limitnumberMax countries, 1-200 (default: 50)
Example
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.

GET/api/v1/eventsList events with filters
GET/api/v1/events/:idEvent detail with all source articles
GET/api/v1/events/:id/perspectivesArticles grouped by outlet
GET/api/v1/mergesStories folded into other stories
GET/api/v1/entitiesEnumerate the entity vocabulary
GET/api/v1/entities/:name/timelineEvent timeline for a person, place, or org

Parameters — /events

sincestringPublisher-clock window: a duration (30m, 6h, 7d) or an ISO 8601 timestamp (default: 24h)
changedSincestringWall-clock window — stories Perspecta wrote at or after this point. Duration or ISO 8601. Use this for incremental sweeps, not since
orderBystringsourceCount (default), publishedAt, lastWritten, or firstIngested. lastWritten is stable under concurrent writes; firstIngested is the archive ordering
orderstringdesc (default) or asc. Ascending is how you walk into the past
untilstringUpper bound on the publisher clock, mirroring since
ingestedSincestringLower bound on the ingestion clock — when we first received content in the cluster
ingestedUntilstringUpper bound on the ingestion clock
cursorstringOpaque pagination token from meta.nextCursor. Must match the orderBy it was issued for
includestringSet to articles to inline each event's top constituent articles
articlesPerEventnumberArticles per event when include=articles, 1-10 (default: 3). Also accepted as articleLimit
includeMergedbooleanInclude stories that were folded into another story (default: false)
topicstringFilter by topic: world, politics, business, technology, science, health, entertainment, environment
countrystringFilter by country name
tagstringFilter by hashtag
entitystringFilter by entity name (person, org, place)
minSourcesnumberMinimum source count (default: 1)
minVelocitynumberMinimum sources per hour, using the same definition as the velocity field. Combine with ingestedSince to find young, fast-moving stories
limitnumberRows 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.

Incremental sweep — everything that changed since the last poll
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

Reading the archive

firstIngestedAt is when we first received content in a cluster, derived from the earliest fetch among its articles. This is the field to bound a point-in-time replay on — not firstSeenAt, which only became honest on 2026-07-31 and was backfilled from the publisher clock before that.

It is monotonically non-increasing: it never moves forward, and moves earlier only when a cluster absorbs an article fetched before anything it already held — which happens when stories merge. A replay bounded at a given instant can therefore gain a story it previously excluded, but can never lose one. Direct writes cannot change it at all.

meta.archiveStartsAt on every events response gives the earliest ingestion time we hold. Do not infer the archive depth from publishedAt — a few dozen stories carry publisher dates going back years, which makes the corpus look far deeper than it is.

Ordering by firstIngested drops the 24-hour since default, since a publisher-clock window would truncate an archive read to nothing. Archive reads may also take up to 1000 rows a page, except when inlining articles.

Walk a fixed historical range, oldest first
curl -H "X-API-Key: pk_your_key"   "https://www.perspecta.news/api/v1/events?orderBy=firstIngested&order=asc&ingestedSince=2026-03-01T00:00:00Z&ingestedUntil=2026-04-01T00:00:00Z&limit=1000"

# follow meta.nextCursor until null; the cursor carries its own direction,
# so an ascending cursor cannot be replayed through a descending walk.

Parameters — /merges

sincestringWindow on mergedAt: duration or ISO 8601 (default: 24h)
limitnumberRows per page, 1-200 (default: 50)
cursorstringOpaque 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.

sincestringOnly entities last seen at or after this point
minStoriesnumberMinimum stories in the 30-day window (default: 1)
typestringcompany, person, country, organization, location, product, event, other. Null on names not yet classified
minConfidencenumber0-1. Filters on typeConfidence. Use with type=company for instrument resolution — the extractor emits common nouns, and ambiguous names are scored low rather than guessed
limitnumberRows per page, max 2000 (default: 500)
offsetnumberRow offset — this endpoint pages by offset, not cursor

Parameters — /entities/:name/timeline

daysnumberLookback in days, 1-30 (default: 7)
limitnumberMax events, 1-200 (default: 50)
Events by country + tag
curl -H "X-API-Key: pk_your_key" \
  "https://www.perspecta.news/api/v1/events?country=iran&tag=IranWar&minSources=10&limit=5"
Event detail with articles
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"
      },
      ...
    ]
  }
}
Perspectives — same event, different outlets
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": [ ... ]
      }
    ]
  }
}
Entity timeline
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

MCP / Tool Use Schema
{
  "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

Python — autonomous geopolitical risk agent
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 minutes

Example: RAG Pipeline

Grounding an LLM with real-time news context
# 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. publishedAt and updatedAt are publisher clocks: they come from the outlets, and updatedAt can move backwards in real time when a story gains a late-arriving article that was published hours ago. firstSeenAt and lastWrittenAt are our wall clock and only ever move forwards. Poll on lastWrittenAt; never on updatedAt. Both wall-clock fields are null on stories that predate 2026-07-31.
  • sourceSignal is "agree", "diverge" or null. 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 returns 200 with { id, mergedInto, mergedAt } rather than the stale row, and /api/v1/merges lists them in bulk.
  • Entity names are lowercased and carry no legal suffixes — the vocabulary contains ubs, not UBS Group AG. Match case-insensitively and do not expect S.A. or AG.

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 access

Alpha means the endpoints above are stable enough to build on and we will tell you before anything breaks — not that they are frozen.