News Reader API
Three endpoints, served by every News Reader installation – JSON for Collector and articles, an encrypted binary blob for sync. No API key. Errors come back as {"error":{"code","message"}}.
Collector
Any website or feed URL → normalized entries. Works for sites without an RSS feed, too.
GET /api/collect?url=https://example.com/blog[&limit=1..100][&refresh=1]
{
"source": { "url": "…", "type": "rss", "strategy": "discovered", "feedUrl": "…", "title": "…", "siteUrl": "…", "icon": "…" },
"entries": [ { "id": "…", "url": "…", "title": "…", "publishedAt": "…", "updatedAt": "…", "summary": "…", "image": "…", "author": "…" } ],
"meta": { "count": 20, "cached": true, "nextRefreshAfter": "…" }
}
limit defaults to 100. refresh=1 bypasses the cache once it is older than 15 minutes. The cache is shared per source: every address that leads to the same feed (or page) uses one copy. The scheme defaults to https://.
If the source turns the server away (UPSTREAM_HTTP_ERROR with upstreamStatus 401/403), the error lists clientTry: addresses a browser may fetch itself (the site often still allows it via CORS, e.g. the WordPress REST API). Post the body you got to have it parsed – for you only, nothing is cached:
POST /api/collect?url=https://example.com/&from=<one of clientTry> (body: the raw response, max. 1 MB)
Article
A readable copy of one article, e.g. for offline reading. With feed and id, a full text the feed already carries is served without fetching the page.
GET /api/article?url=https://example.com/blog/post[&feed=<feed url>&id=<entry id>]
{ "article": { "url": "…", "title": "…", "byline": "…", "content": "<p>…</p>", "words": 812, "partial": false } }
Status codes
400invalid or forbidden URL (INVALID_URL,UNSUPPORTED_URL,FORBIDDEN_HOST,DNS_FAILED)422the source failed or has no posts (NO_FEED_DETECTED,PLATFORM_NO_FEED,UPSTREAM_*,TOO_MANY_REDIRECTS,NOT_AN_ARTICLE,NO_CONTENT)429rate limited ·503busy – both withRetry-After; only fetches that reach the source count against the limit, cache hits and busy answers are free
Sync
Stores one end-to-end encrypted blob per vault (one group of paired devices). The server never sees the key.
Authorization: Bearer <token>
GET /api/sync → blob + ETag (304 with If-None-Match, 404 if none)
PUT /api/sync If-Match: "<etag>" or If-None-Match: * (412 conflict, 410 forwarded)
DELETE /api/sync → 204
Full details: README on GitHub.