Patriot Debrief
FeedDebriefAPI Docs

Neutralized by AI.

API Reference

Programmatic access to stories, feeds, and the headline debrief tool. All endpoints return JSON and are served at ....

Quick Start

The POST /api/debrief endpoint requires an API key. Pass it via the X-API-Key header or the api_key field in the request body:

1
Send a debrief request. Pass your key via header or in the request body:
# Option A — X-API-Key header
curl -X POST /api/debrief \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pd_your_key_here" \
  -d '{"headline": "You Won't BELIEVE What Happened!"}'

# Option B — api_key in JSON body
curl -X POST /api/debrief \
  -H "Content-Type: application/json" \
  -d '{"headline": "You Won't BELIEVE What Happened!", "api_key": "pd_your_key_here"}'
Save the raw key immediately — it's returned only once at creation. After that, only the prefix (e.g. pd_aBcDeFg...) is shown.

Request an API Key

Submit a request for an API key. Requests are reviewed manually — you'll receive your key once approved.

Authentication & Rate Limits

The debrief endpoint requires an API key. All other read endpoints are public.

Auth TypeMethodUsed ByRate Limited
API KeyX-API-Key: pd_...or body: {"api_key":"pd_..."}POST /api/debrief200 req/min
None—Stories, Feeds, HealthNo
Rate limiting: Each API key allows 200 requests per minute using a sliding 60-second window. Exceeding the limit returns 429 Too Many Requests. The window slides continuously — no need to wait a full minute for it to reset.

Debrief

GET/api/debrief/prompt

Get the default system prompt used for headline analysis. Useful for reference before overriding with a custom prompt.

Response (200)

{
  "system_prompt": "You are a media literacy analyst. Identify every manipulative tactic used, could be a varying number. Generate exactly 5 alternative neutral headlines (different factual angles, 5-15 words each). Output JSON: {\"variations\": [...], \"clickbait_tactics_found\": [...]}"
}

cURL example

curl "/api/debrief/prompt"
POST/api/debrief

Submit a clickbait headline and receive neutralized, factual alternatives plus an analysis of the manipulation tactics used.

Authentication required: Pass your API key via the X-API-Key header or the api_key field in the JSON body. Rate limited to 200 req/min per key. Header takes priority if both are provided.

Parameters

NameTypeRequiredDescription
headlinestringYesThe clickbait headline to analyze (5-500 characters)
system_promptstringNoCustom system prompt (max 5000 chars). Overrides default; bypasses cache.
num_variationsintNoNumber of neutral variations to generate, 1-5 (default: 5)
api_keystringNoAPI key (alternative to X-API-Key header). Useful when headers can't be set.

Request body (JSON)

{
  "headline": "You Won't BELIEVE What This Senator Just Did!",
  "system_prompt": "(optional) Custom system prompt override",
  "num_variations": 5,
  "api_key": "(optional) pd_your_key_here — alternative to X-API-Key header"
}

Response (200)

{
  "original": "You Won't BELIEVE What This Senator Just Did!",
  "variations": [
    "Senator [Name] Introduces New Policy on [Topic]",
    "Senate Committee Reviews Proposed Legislation",
    "Senator Takes Action on [Policy Area]",
    "New Senate Proposal Addresses [Issue]",
    "Senator Announces Legislative Initiative"
  ],
  "analysis": {
    "clickbait_tactics_found": [
      "Curiosity gap / vague cliffhanger",
      "ALL CAPS for emotional emphasis",
      "Second-person address to create urgency"
    ]
  }
}

cURL examples

# With X-API-Key header
curl -X POST /api/debrief \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pd_your_key_here" \
  -d '{"headline": "You Won't BELIEVE What This Senator Just Did!"}'

# With api_key in request body (no header needed)
curl -X POST /api/debrief \
  -H "Content-Type: application/json" \
  -d '{"headline": "You Won't BELIEVE What This Senator Just Did!", "api_key": "pd_your_key_here"}'

Error responses

StatusMeaning
401Missing, invalid, or revoked API key
422Invalid request body (headline too short/long, etc.)
429Rate limit exceeded (200 req/min)
502AI service unavailable — try again

Stories

GET/api/stories

Get paginated story groups with nested articles. Each story group clusters related articles from multiple sources.

Parameters

NameTypeRequiredDescription
pageintNoPage number (default: 1)
per_pageintNoItems per page, 1-100 (default: 30)
sortstringNo"latest" (by date) or "sources" (by article count)
searchstringNoFull-text search across headlines and summaries (max 200 chars)
sourcestringNoFilter by single source slug (e.g. bbc)
sourcesstringNoComma-separated source slugs (e.g. bbc,nyt,fox)
categorystringNoFilter by source category (e.g. "us_news", "international", "business", "tech_science", "original")
title_formatstringNo"both" (default), "neutral" (only AI-neutralized titles), or "original" (only source titles with bias)

Response (200)

{
  "stories": [
    {
      "id": 42,
      "canonical_title": "Senate Passes Infrastructure Bill",
      "canonical_summary": "The Senate voted 68-29 to pass...",
      "article_count": 5,
      "latest_published_at": "2025-01-15T14:30:00Z",
      "articles": [
        {
          "id": 101,
          "source": { "name": "BBC News", "slug": "bbc", "bias_label": "center" },
          "original_title": "US Senate approves major infrastructure package",
          "neutral_title": "Senate Passes Infrastructure Spending Bill",
          "original_summary": "The US Senate has approved...",
          "link": "https://bbc.com/news/...",
          "published_at": "2025-01-15T14:30:00Z",
          "image_url": "https://..."
        }
      ]
    }
  ],
  "total": 150,
  "page": 1,
  "per_page": 30,
  "has_more": true
}

cURL examples

curl "/api/stories?page=1&per_page=10&sort=latest"

# Search
curl "/api/stories?search=infrastructure"

# Filter by sources
curl "/api/stories?sources=bbc,nyt,fox"

# Filter by category
curl "/api/stories?category=us_news"

# Only neutralized titles (hides original biased titles)
curl "/api/stories?title_format=neutral"

# Only original titles (shows source bias)
curl "/api/stories?title_format=original"
GET/api/stories/{story_group_id}

Get a single story group with all its articles. Supports the same title_format parameter.

cURL example

curl "/api/stories/42"

# With original titles only
curl "/api/stories/42?title_format=original"

Feeds

GET/api/feeds

List all configured RSS feed sources with their name, slug, URL, bias label, and category.

Response (200)

{
  "feeds": [
    {
      "id": 1,
      "name": "BBC News",
      "slug": "bbc",
      "rss_url": "https://feeds.bbci.co.uk/news/world/rss.xml",
      "bias_label": "center",
      "category": "original",
      "is_active": true
    }
  ]
}

cURL example

curl "/api/feeds"
POST/api/feeds/refresh

Trigger a background RSS feed ingestion. Returns immediately — articles are fetched, grouped, and neutralized asynchronously.

Response (202)

{
  "status": "ingestion_started",
  "message": "RSS feed refresh initiated"
}

cURL example

curl -X POST "/api/feeds/refresh"

Health

GET/api/health

Check backend status and database connectivity. Used by Docker healthchecks.

Response (200)

{
  "status": "healthy",
  "db": "connected",
  "timestamp": "2025-01-15T14:30:00Z"
}

cURL example

curl "/api/health"

Error Codes

All errors return JSON with a detail field describing what went wrong.

CodeMeaning
200Success
202Accepted — feed refresh queued in background
400Bad request — validation failed
401Missing, invalid, or revoked API key
404Resource not found
422Unprocessable entity — invalid field values
429Rate limit exceeded — 200 requests/min per API key
502Backend or AI service unavailable
504Request timed out

Example error response

{
  "detail": "Rate limit exceeded (200 requests/min). Try again shortly."
}