Perspectives API

Where named public voices stand, story by story. One row per position: who, the claim, the author's own words (≤25), the date, the platform, the link. No full text, ever. Read-only. Every example on this page is a real response captured 2026-09-24 17:47 UTC.

Docsnewsreel.co/perspectives/apiSpec/-/openapi.jsonBase URLhttps://newsreel-perspectives-api.onrender.comFormatJSON · add _shape=array for a plain list · drop .json for the browsable UIData45,420 positions · 517 voices · 100 story clusters · rebuilt daily

Auth & billing

HeaderAuthorization: Bearer <key>Or in the URL&key=<key> on any request, for a chatbot or browser that cannot set headers. Stripped before the query runs; never loggedOpenAPIGET /-/openapi.json, no key. Import it into a ChatGPT custom GPT Action (auth Bearer) or any OpenAPI toolClaudeMCP server at /-/mcp with the same bearer headerKeyissued per partner, tied to a Stripe customer; a key with no billing account answers 402Price$0.03 per successful call, metered, invoiced monthly by Stripe. Errors and rate-limited calls are not billedLimits300 requests/min per key · 500 rows/response · 2 s/queryHealthGET /-/versions.json, no key
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/stories_recent.json?_shape=array"

# the same call with the key in the URL
curl "https://newsreel-perspectives-api.onrender.com/positions/stories_recent.json?_shape=array&key=$KEY"
Response headers
Content-Type: application/json; charset=utf-8
access-control-allow-origin: *
Cache-Control: private, no-store
link: <https://newsreel-perspectives-api.onrender.com/positions/topics.json?_shape=array&_size=1&_next=abortion-reproductive>; rel="next"

What comes back

Every endpoint returns JSON. With _shape=array the body is a list of row objects; without it Datasette wraps rows in {rows, columns, next, …}. Numbers are numbers, dates are YYYY-MM-DD strings, nulls are null. Confidence fields are calibrated probabilities from the labeller, present for posts labelled from 19 Sep 2026 and null before; nothing is inferred to fill the gap.

Endpoints

GET /positions/stories_recent.json

The 100 newest story clusters. Take an id from here into the spread queries.

ParameterTypeMeaning
No parameters
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/stories_recent.json?_shape=array"
Response 200 · 179 ms · 100 rows (showing 2)
[
  {
    "id": "state-and-local-politics-stories-span-dozens-of-unrelated-ev",
    "headline": "State and local politics stories span dozens of unrelated events",
    "date": "2026-09-24",
    "position_count": 10,
    "voice_count": 10
  },
  {
    "id": "congress-does-little-as-mcconnell-absence-corruption-scandal",
    "headline": "Congress does little as McConnell absence, corruption scandals dominate",
    "date": "2026-09-24",
    "position_count": 8,
    "voice_count": 8
  }
]

GET /positions/spread_by_party.json

The product: what politicians in one party are saying about one story, grouped by the claim they make. Group rows by position_label.

ParameterTypeMeaning
story_idstringfrom stories_recent
party_groupstringDemocratic · Republican · Independent

Party comes from Wikidata (P102), politicians only, never inferred from text.

Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/spread_by_party.json?story_id=iran-war-drags-on-costs-mount-as-congress-fights-back&party_group=Republican&_shape=array"
Response 200 · 234 ms · 5 rows (showing 5)
[
  {
    "position_label": "Deterrence and Escalation Support",
    "voice": "Matt Gaetz",
    "party": "Republican Party",
    "quote": "Following our raging success in Iran, should the United States lead a military campaign in Yemen against the H…",
    "date": "2026-09-20",
    "platform": "x",
    "source_url": "https://x.com/mattgaetz/status/2101665459917525149",
    "stance": "lean",
    "stance_confidence": null
  },
  {
    "position_label": "Sanctions & Pressure Support",
    "voice": "Katie Britt",
    "party": "Republican Party",
    "quote": "As my colleague and dear friend Senator Lindsey Graham said in his final days, the ability to pass this bill a…",
    "date": "2026-09-17",
    "platform": "x",
    "source_url": "https://x.com/SenKatieBritt/status/2100383428579393659",
    "stance": "strong",
    "stance_confidence": null
  }
]

GET /positions/spread_by_tag.json

Same spread for an editorial tag from the glossary.

ParameterTypeMeaning
story_idstringfrom stories_recent
tagstringone of the 16 glossary tags, e.g. anti-war, conservative, media critic
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/spread_by_tag.json?story_id=iran-war-drags-on-costs-mount-as-congress-fights-back&tag=anti-war&_shape=array"
Response 200 · 143 ms · 9 rows (showing 3)
[
  {
    "position_label": "Anti-War Opposition",
    "voice": "Jimmy Dore",
    "category": "commentator",
    "quote": "Iran Captures Unmanned U.S. Submarine!",
    "date": "2026-09-17",
    "platform": "youtube",
    "source_url": "https://www.youtube.com/watch?v=1tzYmkfbKdw",
    "stance": "lean",
    "stance_confidence": null
  },
  {
    "position_label": "Anti-War Opposition",
    "voice": "Kyle Kulinski",
    "category": "commentator",
    "quote": "‘IT’S GENOCIDE’: Tucker Carlson COMPLETELY SNAPS On Warmonger Trump &amp; Bibi | The Kyle Kulinski Show",
    "date": "2026-09-17",
    "platform": "youtube",
    "source_url": "https://www.youtube.com/watch?v=Qbli1cQlPUQ",
    "stance": "strong",
    "stance_confidence": null
  }
]

GET /positions/spread.json

Everyone on a story, grouped by claim.

ParameterTypeMeaning
story_idstringfrom stories_recent
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/spread.json?story_id=iran-war-drags-on-costs-mount-as-congress-fights-back&_shape=array"
Response 200 · 378 ms · 98 rows (showing 3)
[
  {
    "position_label": "Anti-War Opposition",
    "voice": "Quincy Institute",
    "category": "think-tank",
    "party_group": null,
    "quote": "The Houthis could tip the Iran war in Tehran's favor (by Paul R. Pillar). The conflict between the United Stat…",
    "date": "2026-09-18",
    "platform": "blog",
    "source_url": "https://responsiblestatecraft.org/iran-war-houthis/"
  },
  {
    "position_label": "Anti-War Opposition",
    "voice": "Tom Nichols",
    "category": "commentator",
    "party_group": null,
    "quote": "But he did have a military disaster in Iran",
    "date": "2026-09-18",
    "platform": "bluesky",
    "source_url": "https://bsky.app/profile/radiofreetom.bsky.social/post/3mvrb5xsbnc2f"
  }
]

GET /positions/voice.json

One voice: category, lens, party, tags.

ParameterTypeMeaning
voice_idstringe.g. ted-cruz
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/voice.json?voice_id=ted-cruz&_shape=array"
Response 200 · 295 ms · 1 rows (showing 1)
[
  {
    "id": "ted-cruz",
    "name": "Ted Cruz",
    "category": "politician",
    "approach": "argues",
    "lens": "Texas Senator and Verdict podcast host who frames issues through constitutional originalism and combative cons…",
    "party": "Republican Party",
    "party_group": "Republican",
    "wikidata_qid": "Q2036942",
    "platforms": "[\"x\", \"youtube\"]",
    "profile_url": "https://newsreel.co/perspectives/voice/ted-cruz",
    "first_seen": "2026-07-29",
    "last_seen": "2026-09-24",
    "position_count": 304,
    "tags": "conservative"
  }
]

GET /positions/voice_positions.json

Where one voice has stood, newest first.

ParameterTypeMeaning
voice_idstring
topicstringslug, or empty string
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/voice_positions.json?voice_id=ted-cruz&topic=&_shape=array"
Response 200 · 158 ms · 200 rows (showing 2)
[
  {
    "topic": "civil-rights-race",
    "stance": "strong",
    "summary": "Criticizes Nikole Hannah-Jones and leftist failures",
    "quote": "Cruz on Nikole Hannah-Jones’s astonishing and sad admissions highlighting the failures of the Left.",
    "date": "2026-09-24",
    "platform": "youtube",
    "source_url": "https://www.youtube.com/shorts/gwIEnpSEtJc",
    "story_id": null,
    "position_label": null,
    "stance_confidence": null
  },
  {
    "topic": "foreign-policy-diplomacy",
    "stance": "strong",
    "summary": "Claims China funds both sides",
    "quote": "The Chinese are funding both.",
    "date": "2026-09-24",
    "platform": "x",
    "source_url": "https://x.com/tedcruz/status/2103108500175925714",
    "story_id": null,
    "position_label": null,
    "stance_confidence": null
  }
]

GET /positions/voices_by_group.json

The voices in a party or with a tag. Pass the other as an empty string.

ParameterTypeMeaning
party_groupstring
tagstring
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/voices_by_group.json?party_group=&tag=media+critic&_shape=array"
Response 200 · 137 ms · 12 rows (showing 3)
[
  {
    "id": "glenn-greenwald",
    "name": "Glenn Greenwald",
    "category": "journalist",
    "party_group": null,
    "lens": "Journalist who broke the Edward Snowden NSA surveillance story. Once a leading voice on the progressive left, …",
    "position_count": 237
  },
  {
    "id": "tim-pool",
    "name": "Tim Pool",
    "category": "commentator",
    "party_group": null,
    "lens": "Independent journalist turned right-populist commentator. Timcast reaches millions daily. Anti-establishment, …",
    "position_count": 209
  }
]

GET /positions/glossary.json

What every tag, category and stance label means, and the rule about what is never tagged.

ParameterTypeMeaning
No parameters
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/glossary.json?_shape=array"
Response 200 · 123 ms · 31 rows (showing 3)
[
  {
    "kind": "category",
    "key": "activist",
    "definition": "advocacy-focused public figure",
    "tier": null
  },
  {
    "kind": "category",
    "key": "commentator",
    "definition": "opinion-focused media personality",
    "tier": null
  }
]

GET /positions/positions.json

Any table, filtered by any column with Datasette operators.

ParameterTypeMeaning
<column>exact match
<column>__gte / __lte / __containscomparisons
_sizeintrows per page, max 500
_nexttokenpagination cursor from the previous response

Tables: positions · voices · voice_tags · stories · story_positions · topics · glossary · meta.

Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions/positions.json?_shape=array&topic=iran-conflict&date__gte=2026-09-20&_size=2"
Response 200 · 131 ms · 2 rows (showing 2)
[
  {
    "id": "5c6ad76ad3d269ba",
    "voice_id": "robert-reich",
    "voice_name": "Robert Reich",
    "topic": "iran-conflict",
    "topics": "[\"iran-conflict\", \"healthcare\"]",
    "stance": "strong",
    "summary": "Opposes Iran war spending, supports ACA subsidy extension",
    "quote": "Cost of extending ACA subsidies: $30 billion/year Cost of Trump's Iran War: $43 billion in roughly seven month…",
    "date": "2026-09-24",
    "platform": "bluesky",
    "source_url": "https://bsky.app/profile/rbreich.bsky.social/post/3mwa2l5vjtz23",
    "story_id": "iran-war-drags-on-costs-mount-as-congress-fights-back",
    "position_label": "This war is a costly failure Trump owns",
    "stance_p": null,
    "stance_confidence": null,
    "topic_p": null,
    "topic_confidence": null,
    "relevance_confidence": null,
    "label_model": null,
    "attribution": "Robert Reich, bluesky, 2026-09-24, via Newsreel Perspectives"
  },
  {
    "id": "ca2c24ce10e12107",
    "voice_id": "randi-weingarten",
    "voice_name": "Randi Weingarten",
    "topic": "iran-conflict",
    "topics": "[\"iran-conflict\"]",
    "stance": "strong",
    "summary": "Opposes Trump's Iran rhetoric as incitement",
    "quote": "Trump’s threat “annihilate” Iran is an incitement to mass slaughter. It invokes the worst crimes in history & …",
    "date": "2026-09-24",
    "platform": "bluesky",
    "source_url": "https://bsky.app/profile/rweingarten.bsky.social/post/3mwafjnplhs2n",
    "story_id": "iran-war-drags-on-costs-mount-as-congress-fights-back",
    "position_label": "Trump's annihilation threat is a war crime",
    "stance_p": null,
    "stance_confidence": null,
    "topic_p": null,
    "topic_confidence": null,
    "relevance_confidence": null,
    "label_model": null,
    "attribution": "Randi Weingarten, bluesky, 2026-09-24, via Newsreel Perspectives"
  }
]

GET /positions.json?sql=…

Read-only SQL over the whole file. Anything the tables can answer.

ParameterTypeMeaning
sqlstringa SELECT; writes are refused
_shapearray
Request
curl -H "Authorization: Bearer $KEY"  \
  "https://newsreel-perspectives-api.onrender.com/positions.json?sql=select%20party_group%2C%20count%28%2A%29%20as%20voices%20from%20voices%20where%20category%3D%27politician%27%20group%20by%201&_shape=array"
Response 200 · 317 ms · 3 rows (showing 3)
[
  {
    "party_group": "Democratic",
    "voices": 29
  },
  {
    "party_group": "Independent",
    "voices": 2
  }
]

Errors

StatusWhenBody
403no key, or a key we don't knowHTML "Forbidden" page (Datasette's default)
402a known key with no billing account{"error": {"code": "payment_required", "message": "This key is not attached to a billing account. Email jack@newsreel.co…
429over 300/min on this key{"error": {"code": "rate_limited", …}} with Retry-After: 60
400bad parameter or SQLDatasette error JSON

Position row

FieldTypeMeaning
idstringsha1(voice_id|source_url)[:16], stable across rebuilds
voice_id, voice_namestring
topic, topicsslug, JSON listfrom the 40-topic taxonomy
stancestrong · leanneutral posts are not positions and are not in the file
summarystring ≤120model-written from the linked post
quotestring ≤25 wordsthe author's own words; ellipsis marks truncation
date, platform, source_urldate only, no time of day; the link is the record
story_id, position_labelstring | nullset when the post was published under a story cluster
stance_p, stance_confidence, topic_p, topic_confidence, relevance_confidence0–1 | nullcalibrated; null before 19 Sep 2026
label_modelstring | nulle.g. jev-1.13.0
attributionstring"Name, platform, date, via Newsreel Perspectives"

Voice row

FieldMeaning
id, name
categoryone of 11: commentator, politician, journalist, creator, think-tank, economist, activist, medical, legal, satirist, entrepreneur
lensone line on the angle they argue from
party, party_groupWikidata's label, and Democratic / Republican / Independent; politicians only
wikidata_qidthe entity key
platforms, first_seen, last_seen, position_count
tagsin voice_tags; from a fixed 16-term glossary, or they do not exist. Never race, religion, gender or demographic identity

Never served: post text, timestamps with time of day, handles, feeds, photos, follower counts, email. The file the API reads is built by naming its columns; a test that builds from a fixture containing all of those and asserts none survive runs in every deploy.

Retention

No query is logged, stored or cached. Requests are answered from a read-only SQLite file in memory; no database, model or third party is in the request path; the HTTP access log is off. The only retained numbers are per-key call counts for billing. Removal requests and corrections: jack@newsreel.co.