Collections Feeds API
March 1, 2026 ยท View on GitHub
Collections Feeds is a thin API over Watchlists sources and jobs that ingests RSS/Atom into Collections items. Each subscription is stored as a Watchlists source and polled by a Watchlists job. Items are written to the Collections DB with origin="feed".
This API is ingest-only. It does not publish RSS/Atom or render UI. See Docs/Product/Collections_Feeds_Ingestion.md for the roadmap and Reference implementation below for newsletter-to-Atom details.
Endpoints
POST /api/v1/collections/feeds- create a feed subscriptionGET /api/v1/collections/feeds- list feed subscriptionsGET /api/v1/collections/feeds/{feed_id}- get a feed subscriptionPATCH /api/v1/collections/feeds/{feed_id}- update a feed subscriptionDELETE /api/v1/collections/feeds/{feed_id}- delete a feed subscription
Core object: CollectionsFeed
Key fields:
id: watchlists source idname,url,source_type(rss)origin: alwaysfeedtags: list of tag stringsactive: source/job active flagsettings: optional source settings (sanitized)last_scraped_at,etag,last_modified,defer_until,status,consec_not_modified: polling statecreated_at,updated_at: source timestampsjob_id: watchlists job idschedule_expr,timezone,job_active,next_run_at,wf_schedule_id: job schedule and scheduler metadata
Create
POST /api/v1/collections/feeds
Request:
{
"url": "https://example.com/feed.xml",
"name": "Example Feed",
"tags": ["news"],
"active": true,
"timezone": "UTC",
"settings": {
"history": {"strategy": "auto", "max_pages": 3}
}
}
Response:
{
"id": 42,
"name": "Example Feed",
"url": "https://example.com/feed.xml",
"source_type": "rss",
"origin": "feed",
"tags": ["news"],
"active": true,
"settings": {
"history": {"strategy": "auto", "max_pages": 3}
},
"job_id": 101,
"schedule_expr": "0 * * * *",
"timezone": "UTC",
"job_active": true,
"next_run_at": "2025-01-01T01:00:00+00:00",
"wf_schedule_id": "sched_abc123"
}
Notes:
- If
nameis omitted, it defaults to the URL hostname. - If
schedule_expris omitted, the subscription defaults to hourly polling (0 * * * *) and auto-promotes to daily after 24 hours (see Scheduling). timezoneaccepts IANA names (e.g.America/New_York) orUTC+/-offsets.
List
GET /api/v1/collections/feeds
Query params: q, page (default 1), size (default 20, max 200).
Response:
{
"items": [
{
"id": 42,
"name": "Example Feed",
"url": "https://example.com/feed.xml",
"source_type": "rss",
"origin": "feed",
"tags": ["news"],
"active": true,
"settings": null,
"job_id": 101,
"schedule_expr": "0 * * * *",
"timezone": "UTC",
"job_active": true,
"next_run_at": "2025-01-01T01:00:00+00:00",
"wf_schedule_id": "sched_abc123"
}
],
"total": 1
}
Get
GET /api/v1/collections/feeds/{feed_id}
Returns a CollectionsFeed object.
Update
PATCH /api/v1/collections/feeds/{feed_id}
Request:
{
"name": "Example Feed (Daily)",
"tags": ["news", "daily"],
"schedule_expr": "0 0 * * *",
"timezone": "UTC",
"active": true,
"settings": {
"history": {"strategy": "none"}
}
}
Response: updated CollectionsFeed.
Notes:
- When you set
schedule_exprvia PATCH, the job switches to manual scheduling and stops auto-promoting. settingsis merged into existing settings; reserved keys are overwritten (see Settings).
Delete
DELETE /api/v1/collections/feeds/{feed_id}
Response:
{ "success": true }
Scheduling behavior
Default behavior:
- If
schedule_expris omitted on create, the job starts hourly (0 * * * *). - After 24 hours, the watchlists pipeline promotes the schedule to daily (
0 0 * * *) and marks the job as promoted.
Manual behavior:
- If you set
schedule_expron create or update, the job is treated as manual and will not auto-promote. timezoneis normalized to UTC when absent.
Settings pass-through
settings is stored in the Watchlists source settings_json and is passed into the RSS fetchers.
Reserved keys are enforced by the server and will be removed or overwritten:
collections_origincollections_feed_job_id,collections_job_idcollections.origin,collections.job_id
For supported settings fields (history/backfill options, limits), see Docs/API-related/Watchlists_API.md and tldw_Server_API/app/core/Watchlists/fetchers.py.
Collections ingestion behavior
- RSS/Atom sources are fetched via the Watchlists pipeline (ETag/Last-Modified, RFC5005 history, dedupe by guid/url/title).
- Items are upserted into Collections with:
origin = "feed"origin_type = source_typeorigin_id = source id
- Embeddings metadata includes
originfor traceability.
Reference implementation: Kill the Newsletter (MIT)
The Kill the Newsletter script is a useful reference for email ingestion, Atom rendering, and UI flows. These features are not implemented in tldw_server yet, but can guide future work.
Data model (SQLite)
Tables and key columns:
feeds:id,publicId,title,icon,emailIconfeedEntries:id,publicId,feed,createdAt,author,title,contentfeedEntryEnclosures:id,publicId,type,length,namefeedEntryEnclosureLinks:feedEntry,feedEntryEnclosurefeedVisualizations:feed,createdAt(rate limiting)feedWebSubSubscriptions:feed,createdAt,callback,secret
SMTP/email ingestion and attachments
- SMTP server with TLS, AUTH disabled, and a size limit (
2 ** 19). - Validates
mailFromand recipient addresses, blocks known relay domains. - Parses email with mailparser; chooses HTML or text-as-HTML for content.
- Creates
feedEntryEnclosuresfor attachments and writes files underdata/files/<publicId>/<name>. - Links enclosures to entries; feed rendering exposes them as Atom enclosures.
- Caps feed size by total title + content length (
2 ** 19) and deletes oldest entries and links when exceeded.
Atom feed rendering
- Feed id:
urn:kill-the-newsletter:<feed publicId>. selflink:/feeds/<publicId>.xml,hublink:/feeds/<publicId>/websub.- Optional
<icon>fromiconoremailIcon. updateduses most recent entrycreatedAt.- Entries include
linkto HTML entry view,published/updated,author,title, and<content type="html">. - Enclosures are emitted as
<link rel="enclosure" type="..." length="..." href="...">. - Footer link in entry content points back to feed settings page.
HTML entry views
/feeds/<feedPublicId>/entries/<entryPublicId>.htmlreturns raw HTML body.- CSP:
default-src 'self'; img-src *; style-src 'self' 'unsafe-inline'; frame-src 'none'; object-src 'none'; form-action 'self'; frame-ancestors 'none'.
UI and settings pages
- Root page creates a feed, then shows the generated email address and Atom URL.
- Feed settings page includes copy-to-clipboard for email/feed URLs, update title/icon, and delete with title confirmation.
- Uses flash notifications, simple CSS layout, and form-based flows.
WebSub push and rate limiting
POST /feeds/<publicId>/websubvalidates callback URL, blocks localhost, and caps new callbacks per day.- Background verification job calls the hub with
hub.challenge. - Dispatch job POSTs the Atom body to each callback, with
Linkheaders and optionalX-Hub-SignatureHMAC. feedVisualizationslimits XML feed requests to 10 per hour and is cleaned up hourly.
Cleanup jobs
- Removes orphaned attachment files and DB rows.
- Deletes stale
feedVisualizations(> 1 hour) andfeedWebSubSubscriptions(> 24 hours).