Docs
Early Access hello@polariapi.com
Intelligence Layer · echo.api.polariapi.com

What the public
says about the story.

Echo enriches Polari story clusters with public discourse signal — controversy scores, dominant themes, counter-narratives, and sentiment from Reddit and Bluesky. It tells you what editorial coverage missed.

Reddit Bluesky · X / Twitter — coming soon
Overview

Echo sits above the pipeline

The four pipeline layers process editorial content — articles, their entities, their clusters, their relationships. Echo is different. It listens to what happens in public discourse after a story is established, and attaches that signal back to the cluster.

L0 L1 L2 L3
Echo public reaction to what L3 already knows
Reddit comments and Bluesky posts are not editorial — they don't feed the pipeline from the bottom. They are processed separately and their derived signal is attached to the clusters they respond to.

This means Echo enrichment is always cluster-scoped. You need a cluster_id from Layer 2 to query Echo. The enrichment you get back tells you how the public received that story — which entities they introduced that editorial coverage didn't mention, whether they're pushing back on the dominant narrative, and how much controversy the story generated across platforms.

contested A new lifecycle state, driven by Echo

Polari story clusters move through lifecycle states as editorial coverage evolves: breakingdevelopingmatureconcluded. Echo introduces a fifth state: contested.

A story becomes contested when editorial coverage has gone quiet — the story is concluded from a journalism standpoint — but public discourse on Reddit or Bluesky is still active and generating high controversy or counter-narrative signal. The story isn't over. The public just hasn't caught up, or disagrees with how it was covered.

contested surfaces automatically on Layer 2 story responses when Echo enrichment is present. No additional API calls needed — if a cluster is contested, the lifecycle_state field on GET /v1/story/{id} will say so.


API Reference

Endpoints Professional+

Base URL: https://echo.api.polariapi.com · All endpoints require Authorization: Bearer {key} except /health. Starter tier keys receive 403 tier_restricted.

GET /v1/echo/story/{story_id}

The primary Echo endpoint. Returns the full public discourse enrichment for a story cluster — sentiment, controversy, dominant themes, novel entities, and counter-narrative analysis, broken down by platform.

GET https://echo.api.polariapi.com/v1/echo/story/{story_id}
BASH
curl https://echo.api.polariapi.com/v1/echo/story/clus_9x3k2m8f \ -H "Authorization: Bearer pk_live_your_key"
RESPONSE
{ "story_id": "clus_9x3k2m8f", "enrichments": [ { "platform": "reddit", "fetched_at": "2026-06-04T17:18:44Z", "comment_count_analyzed": 10, "sentiment_score": -0.35, // –1 negative → +1 positive "sentiment_label": "negative", "controversy_score": 0.80, // 0–1; above 0.6 can trigger `contested` "dominant_themes": [ "Israel-Lebanon ceasefire", "AI disinformation", "Iran conflict" ], "editorial_entities": ["Iran", "Israel", "Benjamin Netanyahu"], "novel_entities": ["Tucker Carlson", "Marge Greene"], // ← not in editorial coverage "counter_narrative": { "detected": true, "summary": "Public discourse introduced AI disinformation framing and domestic US political figures absent from editorial coverage, suggesting the story is being received through a different lens than reported.", "confidence": 0.85 // above 0.7 can trigger `contested` } }, { "platform": "bluesky", "fetched_at": "2026-06-04T14:09:00Z", "comment_count_analyzed": 8, "sentiment_score": -0.41, "sentiment_label": "negative", "controversy_score": 0.72, "dominant_themes": ["Iran nuclear", "US foreign policy"], "editorial_entities": ["Iran", "Israel"], "novel_entities": ["AIPAC", "Axios"], "counter_narrative": { "detected": false, "summary": null, "confidence": 0.0 } } ], "contested": true // convenience flag — true if any platform triggers }
Field Type Description
platform string reddit or bluesky. One object per platform with enrichment data.
sentiment_score float Aggregate sentiment across analyzed posts/comments. –1 (negative) to +1 (positive).
controversy_score float 0–1. Scores ≥ 0.6 contribute to contested lifecycle state.
dominant_themes string[] LLM-synthesized themes from public discourse — often diverge from editorial framing.
editorial_entities string[] Named entities that appear in both editorial coverage and public posts.
novel_entities string[] Named entities that appear in public discourse but not in editorial coverage. The signal that something is being said that journalism isn't covering.
counter_narrative.detected bool Whether a counter-narrative was identified. Confidence ≥ 0.7 contributes to contested state.
counter_narrative.summary string Natural language description of the counter-narrative, synthesized by LLM from public posts.
contested bool Convenience flag. true if any platform has controversy ≥ 0.6 or counter-narrative confidence ≥ 0.7, and enrichment is within 48 hours.
GET /v1/echo/stories

Batch retrieval of Echo enrichments for up to 50 story clusters in a single request.

GET https://echo.api.polariapi.com/v1/echo/stories?ids=clus_aaa,clus_bbb,clus_ccc
BASH
curl "https://echo.api.polariapi.com/v1/echo/stories?ids=clus_9x3k2m8f,clus_4k2m9x7p" \ -H "Authorization: Bearer pk_live_your_key"
Parameter Type Description
ids required string Comma-separated cluster IDs. Max 50.

Response is an object keyed by story ID. Clusters with no Echo enrichment are omitted from the response rather than returning null entries.

GET /v1/echo/trending

Stories with the highest public controversy or active counter-narratives right now — ordered by controversy score descending. Useful for surfacing what the editorial layer hasn't caught up with yet.

GET https://echo.api.polariapi.com/v1/echo/trending
BASH
curl "https://echo.api.polariapi.com/v1/echo/trending?min_controversy=0.6&counter_narrative_only=false&limit=10" \ -H "Authorization: Bearer pk_live_your_key"
Parameter Type Description
min_controversy float Minimum controversy score threshold. Default 0.6.
counter_narrative_only bool If true, only returns stories where a counter-narrative was detected. Default false.
platform string Filter by source platform: reddit or bluesky. Omit for all platforms.
limit integer Max results. Default 20, max 50.

SDK

Using Echo from the Python SDK

Echo enrichment is accessed directly via HTTP — the SDK does not yet have a typed client.echo layer. Use httpx or any HTTP client alongside the SDK for pipeline calls.

PYTHON
import asyncio, httpx from polari import PolariClient, ArticleInput ECHO_BASE = "https://echo.api.polariapi.com" async def main(): async with PolariClient.from_env() as client: api_key = client.api_key # Run an article through the pipeline article = ArticleInput( title="US evacuates personnel from Beirut", content="...", url="https://reuters.com/beirut-evacuation-2026", source="Reuters", ) l0 = await client.layer0.analyze(article) l1 = await client.layer1.process(l0.article_id, article.title, article.content) l2 = await client.layer2.cluster(l0.article_id) # Fetch Echo enrichment for the cluster async with httpx.AsyncClient() as http: resp = await http.get( f"{ECHO_BASE}/v1/echo/story/{l2.cluster_id}", headers={"Authorization": f"Bearer {api_key}"}, ) echo = resp.json() print(f"Contested: {echo['contested']}") for e in echo["enrichments"]: print(f"[{e['platform']}] controversy={e['controversy_score']:.2f} sentiment={e['sentiment_label']}") if e["counter_narrative"]["detected"]: print(f" ↳ counter-narrative: {e['counter_narrative']['summary']}") if e["novel_entities"]: print(f" ↳ novel entities: {e['novel_entities']}") asyncio.run(main())
SDK support coming. A typed client.echo layer with the same patterns as the pipeline SDK — get_story(), get_stories(), get_trending() — is on the roadmap. Until then, the HTTP interface above is the integration path.

Data model

Freshness & coverage

Echo enrichment is written per platform per story — one row for Reddit, one for Bluesky. Each is updated in place as new activity is detected. The fetched_at timestamp on each enrichment tells you when the latest signal was collected.

Platform Update cadence Signal type Status
Reddit Inline with article fetch Comment threads on editorial posts Live
Bluesky Every 30 minutes Targeted post search driven by story entities Live
X / Twitter Keyword search on high-velocity clusters Coming soon

Stories with no Echo enrichment return a 404 from the single-story endpoint and are omitted from batch responses. Not all clusters will have enrichment — Echo targets active, high-signal stories.