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 http://localhost:8001.

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 http://localhost:8001/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 http://localhost:8001/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.

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 "http://localhost:8001/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 http://localhost:8001/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 http://localhost:8001/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)
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 "http://localhost:8001/api/stories?page=1&per_page=10&sort=latest"

# Search
curl "http://localhost:8001/api/stories?search=infrastructure"

# Filter by sources
curl "http://localhost:8001/api/stories?sources=bbc,nyt,fox"

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

# Only original titles (shows source bias)
curl "http://localhost:8001/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 "http://localhost:8001/api/stories/42"

# With original titles only
curl "http://localhost:8001/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 "http://localhost:8001/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 "http://localhost:8001/api/feeds/refresh"

Health

GET/api/health

Check backend status, database connectivity, article/story counts, and last ingestion time. Used by Docker healthchecks.

Response (200)

{
  "status": "healthy",
  "db": "connected",
  "last_ingestion": "2025-01-15T14:00:00Z",
  "article_count": 2847,
  "story_group_count": 483,
  "timestamp": "2025-01-15T14:30:00Z"
}

cURL example

curl "http://localhost:8001/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."
}