Public API (v1)¶
/api/v1 is the stable read API for other applications. It serves published
stories only and never the extracted article text.
Contract: openapi.v1.json (also
GET /api/v1/openapi.json; browse everything at /docs).
Endpoints¶
| Endpoint | |
|---|---|
GET /api/v1/profiles, /profiles/{slug}, /profiles/{slug}/topics |
profiles, sources, topics |
GET /api/v1/profiles/{slug}/feed |
topic, limit (1-100), cursor, since (first published after), updated_since (current version published after), include=card\|full (full adds report and sources) |
GET /api/v1/stories/{id} |
brief, report (or its report_state/report_reason), sources with raw URLs plus outlet/country/kind/groups/headline_only (outlet registry labels), context[] background links (url, title, domain, note, angle), model, versions; 301 for merged ids, 404 unless published |
GET /api/v1/profiles/{slug}/feed.atom |
Atom: title = headline, content = lead + bullets, links to the story page and sources |
GET /api/v1/stream |
SSE, see below |
GET/POST /api/v1/profiles/{slug}/alert-rules, PUT/DELETE .../alert-rules/{id} |
alert rules (writes need the admin key) |
GET /api/v1/profiles/{slug}/notifications |
notification log; rule_id, status, limit |
POST /api/v1/alert-rules/{id}/preview |
matches of the last 7 days, sends nothing (admin) |
GET /api/v1/profiles/{slug}/sources.opml |
OPML export (POST imports) |
GET /api/v1/sources, /sources/discover, /sources/test, /sources/health |
source management (writes need the admin key) |
GET /api/v1/stories/{id}/ask, /stories/{id}/followups, /conversations/{id}, /chat |
Story Q&A |
GET /api/v1/admin/telemetry/summary, .../llm, .../funnel, .../stories/{id}/trace |
workflow telemetry (admin key) |
GET /api/v1/admin/enrich/status |
enrichment worker health: thread alive, last error, last enrich event, pending, provider requests since start, scheduled job recent (admin key) |
GET /api/v1/health, /api/v1/version |
curl -s 'localhost:8000/api/v1/profiles/personal-reader/feed?limit=5' | python -m json.tool
curl -s 'localhost:8000/api/v1/profiles/personal-reader/feed?include=full&cursor=<next_cursor>'
curl -s localhost:8000/api/v1/stories/42
curl -N 'localhost:8000/api/v1/stream?profile=personal-reader&include=card'
curl -N -H 'Last-Event-ID: 120' localhost:8000/api/v1/stream # resume after event 120
curl -H "Authorization: Bearer $DISCOVER_API_KEY" localhost:8000/api/v1/profiles
Invoke-RestMethod 'http://localhost:8000/api/v1/profiles/personal-reader/feed?limit=5'
$h = @{ Authorization = "Bearer $env:DISCOVER_API_KEY" }
(Invoke-RestMethod 'http://localhost:8000/api/v1/stories/42' -Headers $h).brief.lead
curl.exe -N -H "X-API-Key: $env:DISCOVER_API_KEY" 'http://localhost:8000/api/v1/stream?since_id=0'
Stream¶
GET /api/v1/stream?profile=&topic=&include=id|card|full&since_id= sends a
retry: 5000 hint, a hello event ({latest_id, resume_from}), then events
story.published, story.updated and story.retracted, each with id: <n>
(increasing) and data: {"id", "type", "story_id", "version", "profiles"[,
"story"]}, plus a heartbeat event every 15 s (heartbeat= to change).
Reconnect with the Last-Event-ID header (what EventSource does by itself)
or since_id to get every event you missed within event_retention_days;
without either the stream starts with new events. A slow client never holds up
the pipeline: its queue overflows and it catches up from the table.
Examples¶
examples/client.py
(httpx: prints the feed, follows the stream and resumes after a disconnect;
python examples/client.py --base http://nas:8000) and
examples/stream.js
(EventSource, browser or Node), plus examples/ask.py for Q&A.
Versioning¶
Within v1 changes are additive only: new endpoints, new optional parameters,
new response fields (ignore unknown fields). Removing or renaming anything, or
changing a type or meaning, ships as /api/v2. The unversioned /api/... read
routes still work for one release as deprecated aliases (Deprecation: true
and Link: <...>; rel="successor-version" headers); /api/admin/* and the
topic write routes are not versioned. make openapi (python -m app.openapi)
regenerates the snapshot; tests/test_api_v1.py fails while it is out of date,
so API changes are always deliberate.