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": "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.

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, or lastWritten. Only lastWritten is stable under concurrent writes
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
articleLimitnumberArticles per event when include=articles, 1-10 (default: 3)
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)
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

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