RSSaurus API (v1)

This API lets you read and manage items from RSSaurus.

CLI

RSSaurus also has an official command-line client.

Examples:

rssaurus auth whoami --host https://YOUR_APP_HOST
rssaurus items --host https://YOUR_APP_HOST
rssaurus mark-read --url "https://example.com/article" --host https://YOUR_APP_HOST

Webhooks (push new items)

RSSaurus can send a signed webhook when new feed items are created during a feed refresh.

Create a webhook

  • In the RSSaurus web UI, go to Settings → Webhooks
  • Add an HTTPS URL (HTTP is rejected)
  • RSSaurus will POST when new items are created during feed refresh

Webhook URLs are validated with SSRF protection (private/internal IP ranges are blocked).

Headers

Each webhook request includes:

  • Content-Type: application/json
  • X-RSSaurus-Request-Id: UUID for this delivery attempt
  • X-RSSaurus-Timestamp: unix seconds
  • X-RSSaurus-Signature: sha256=<hex> where:
hex = HMAC_SHA256(secret, "{timestamp}.{raw_body}")

Replay protection is implemented by the receiver: reject requests where the timestamp is too old (e.g. > 5 minutes) and verify the signature.

Event types

  • feed_items.created

Payload (feed_items.created)

Webhook payloads are intentionally lightweight (no full content):

{
  "event_type": "feed_items.created",
  "occurred_at": "2026-02-01T05:00:00Z",
  "feed": {
    "id": 123,
    "title": "Example Feed",
    "url": "https://example.com",
    "feed_url": "https://example.com/feed.xml"
  },
  "items": [
    {
      "id": 456,
      "guid": "https://example.com/post",
      "url": "https://example.com/post",
      "title": "An item title",
      "published_at": "2026-02-01T04:59:00Z"
    }
  ]
}

Fetch full content

Webhook payloads intentionally do not include full content. If you want item Markdown/content, fetch it via:

  • GET /api/v1/items/:id
  • GET /api/v1/items/:id.md

Authentication

Use a Bearer token in the Authorization header:

curl -H "Authorization: Bearer YOUR_TOKEN" https://YOUR_APP_HOST/api/v1/me

Tokens are managed in Settings → API Tokens (requires login).

Scopes

Tokens have scopes. Some endpoints require specific scopes.

  • read endpoints: typically feeds:read / items:read
  • write endpoints: typically items:write / saved:write

If a token is missing a scope, you’ll get:

{ "error": "forbidden", "missing_scope": "saved:write" }

Endpoints

GET /api/v1/me

Returns info about the authenticated user.

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/me

GET /api/v1/feeds

List feeds.

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/feeds

GET /api/v1/items

List items.

Query params:

  • status: unread | read | (omit for all)
  • limit: default 50, max 200
  • feed_id: filter to a feed
  • cursor: opaque pagination cursor (returned as next_cursor)
curl -H "Authorization: Bearer YOUR_TOKEN" \
  "https://YOUR_APP_HOST/api/v1/items?status=unread&limit=50" | jq

GET /api/v1/items.jsonl

List items as JSONL (NDJSON): one JSON object per line.

  • Same query params as GET /api/v1/items
  • Pagination cursor is returned in X-Next-Cursor
curl -sS -H "Authorization: Bearer YOUR_TOKEN" \
  "https://YOUR_APP_HOST/api/v1/items.jsonl?status=unread&limit=200" \
  | head -n 5

To paginate:

cursor=$(curl -sSI -H "Authorization: Bearer YOUR_TOKEN" \
  "https://YOUR_APP_HOST/api/v1/items.jsonl?status=unread&limit=200" \
  | awk -F': ' 'tolower($1)=="x-next-cursor"{print $2}' | tr -d $'\r')

curl -sS -H "Authorization: Bearer YOUR_TOKEN" \
  "https://YOUR_APP_HOST/api/v1/items.jsonl?status=unread&limit=200&cursor=${cursor}" \
  | head

GET /api/v1/items/:id

Fetch a single item.

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/items/123 | jq

GET /api/v1/items/:id.md

Fetch a single item formatted as Markdown (agent-friendly).

curl -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/items/123.md

POST /api/v1/items/:id/read

Mark a single item as read.

curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/items/123/read

POST /api/v1/items/:id/unread

Mark a single item as unread.

curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/items/123/unread

POST /api/v1/items/mark_read

Mark many items as read.

Request body options:

  • { "all": true }
  • { "ids": [1,2,3] }
  • { "url": "https://example.com/article" }
  • { "urls": ["https://a", "https://b"] }
curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids":[1,2,3]}' \
  https://YOUR_APP_HOST/api/v1/items/mark_read

POST /api/v1/saved_items

Save an item by URL.

curl -X POST \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/article"}' \
  https://YOUR_APP_HOST/api/v1/saved_items

DELETE /api/v1/saved_items/:id

Delete a saved item.

curl -X DELETE \
  -H "Authorization: Bearer YOUR_TOKEN" \
  https://YOUR_APP_HOST/api/v1/saved_items/7

Rate limiting

RSSaurus applies two layers of rate limiting:

  1. Per-token, per-endpoint limits (implemented in the API base controller). When these apply, responses include:
  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset (unix timestamp)
  1. Global throttles (Rack::Attack) that protect the app from burst abuse. These may return 429 before the per-endpoint limiter, and may not include X-RateLimit-* headers.

If rate limited, you’ll get:

  • 429 with { "error": "rate_limited" }
  • Retry-After header

Subscription access

If the user is out of trial and not subscribed, the API returns:

  • 402 with { "error": "payment_required" }

Errors

  • 401 – missing/invalid token: { "error": "unauthorized" }
  • 402 – subscription required: { "error": "payment_required" }
  • 403 – token missing scope: { "error": "forbidden", "missing_scope": "..." }
  • 429 – rate limited: { "error": "rate_limited" }