API Reference · v1
Get API Token →

Introduction

The SlashFeed API is organized around REST with predictable resource-oriented URLs, JSON bodies, and standard HTTP verbs and response codes.

Base URL: https://slashfeed.app/api/v1
Format: All request bodies and responses are application/json.
Client tools: Download the Insomnia collection from the sidebar to test every endpoint instantly.

Base URL

https://slashfeed.app/api/v1

Request

curl https://slashfeed.app/api/v1/me \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "id": "019e02ab-...",
  "email": "[email protected]",
  "confirmed": true,
  "created_at": "2025-01-01T00:00:00Z"
}

Authentication

Create and manage tokens from Settings → API Tokens. Tokens are prefixed sk_live_ and shown only once — treat them like passwords.

Pass your token in the Authorization header:

Authorization: Bearer sk_live_xxxx

Requests without a valid token return HTTP 401.

Authenticated request

curl https://slashfeed.app/api/v1/me \
-H "Authorization: Bearer sk_live_Rn8KdPwXmZq5"

401 — invalid token

{
  "error": "unauthorized",
  "message": "Invalid or missing API token"
}

Rate Limiting

Every API token is limited to 1,000 requests per hour, tracked via an in-memory ETS counter (Hammer — no Redis required).

X-RateLimit-Limit integer

Maximum requests per hour (always 1000).

X-RateLimit-Remaining integer

Requests remaining in the current window.

Retry-After integer

Seconds to wait. Present only on 429 responses.

Response headers

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987

429 — limit exceeded

{
  "error": "too_many_requests",
  "message": "Rate limit exceeded",
  "retry_after": 3600
}

Errors

BoringRSS uses standard HTTP status codes. 2xx = success, 4xx = client error, 5xx = server error.

200 OK Success.
201 Created Resource was created.
204 No Content Successful delete — no body.
401 Unauthorized No valid API token.
404 Not Found Resource does not exist.
422 Unprocessable Entity Validation failed.
429 Too Many Requests Rate limit exceeded.
500 Server Error Something went wrong.

Error object

{
  "error": "not_found",
  "message": "Resource not found"
}

422 — validation error

{
  "error": "unprocessable_entity",
  "message": "Validation failed",
  "errors": {
    "name": ["can't be blank"],
    "url":  ["must be a valid HTTP or HTTPS URL"]
  }
}

Account

Retrieve information about the API token's owner.

GET /api/v1/me

Returns the account object for the token owner.

id string

Unique user UUID v7.

email string

User email address.

confirmed boolean

Email confirmed status.

created_at datetime

ISO 8601 creation timestamp.

Request

curl https://slashfeed.app/api/v1/me \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "id": "019e02ab-89ab-7f73-a445-12c863138d21",
  "email": "[email protected]",
  "confirmed": true,
  "created_at": "2025-01-15T09:00:00Z"
}

Feeds

A Feed object represents one of your RSS/Atom subscriptions. The id is the subscription UUID, not the underlying feed's canonical UUID.

id string

Subscription UUID.

feed_id string

Underlying feed UUID (shared across subscribers).

url string

Feed URL.

title string

Feed title.

custom_title string

Your personal override title.

category_id string

Assigned category UUID, or null.

unread_count integer

Live unread entry count.

last_fetched_at datetime

Last successful fetch timestamp.

fetch_error string

Last fetch error, or null.

Feed object

{
  "id": "019e02ab-...",
  "feed_id": "019e01ff-...",
  "url": "https://example.com/feed.xml",
  "title": "Example Blog",
  "custom_title": null,
  "category_id": "019e03aa-...",
  "unread_count": 12,
  "last_fetched_at": "2025-01-15T08:30:00Z",
  "fetch_error": null
}
GET /api/v1/feeds

Returns all subscribed feeds with live unread counts.

Request

curl https://slashfeed.app/api/v1/feeds \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "feeds": [
    {
      "id": "019e02ab-...",
      "title": "Example Blog",
      "unread_count": 12
    }
  ]
}
POST /api/v1/feeds

Subscribe to a new feed by URL.

url string required

RSS or Atom feed URL.

category_id string

Category UUID to assign the subscription.

custom_title string

Override the feed title for your account.

Request

curl -X POST https://slashfeed.app/api/v1/feeds \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/feed.xml"}'

Response

{
  "feed": {
    "id": "019e02ab-...",
    "url": "https://example.com/feed.xml",
    "unread_count": 0
  }
}
GET /api/v1/feeds/:id

Retrieve a specific feed subscription.

Request

curl https://slashfeed.app/api/v1/feeds/019e02ab-... \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "id": "019e02ab-...",
  "feed_id": "019e01ff-...",
  "url": "https://example.com/feed.xml",
  "title": "Example Blog",
  "custom_title": null,
  "category_id": "019e03aa-...",
  "unread_count": 12,
  "last_fetched_at": "2025-01-15T08:30:00Z",
  "fetch_error": null
}
DELETE /api/v1/feeds/:id

Unsubscribe. Feed record and entries are preserved.

Request

curl -X DELETE https://slashfeed.app/api/v1/feeds/019e02ab-... \
-H "Authorization: Bearer sk_live_xxx"

Response

HTTP 204 No Content
POST /api/v1/feeds/:id/refresh

Enqueue a background fetch. Poll last_fetched_at to track completion.

Request

curl -X POST https://slashfeed.app/api/v1/feeds/019e02ab-.../refresh \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "message": "Feed refresh enqueued"
}

Entries

Individual articles from your subscribed feeds.

id string

Entry UUID.

feed_id string

Feed UUID this entry belongs to.

title string

Article title.

url string

Link to the original article.

summary string

Sanitised HTML excerpt.

author string

Author name, if provided.

published_at datetime

Publication timestamp.

read boolean

Whether you have read this entry.

starred boolean

Whether you have starred this entry.

saved_for_later boolean

In your Read Later list.

Entry object

{
  "id": "019e05cc-...",
  "feed_id": "019e01ff-...",
  "title": "Hello, Elixir 2.0",
  "url": "https://example.com/posts/hello",
  "author": "Jane Doe",
  "published_at": "2025-01-14T18:00:00Z",
  "read": false,
  "starred": false,
  "saved_for_later": false
}
GET /api/v1/entries

Paginated list of entries across all subscribed feeds.

unread_only boolean

Return only unread entries (default: false).

feed_id string

Filter to a specific feed subscription.

category_id string

Filter to a category.

limit integer

Page size (default 50, max 200).

offset integer

Pagination offset (default 0).

Request

curl "https://slashfeed.app/api/v1/entries?unread_only=true&limit=20" \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "total": 142,
  "entries": [
    {
      "id": "019e05cc-...",
      "title": "Hello, Elixir 2.0",
      "read": false
    }
  ]
}
GET /api/v1/entries/:id

Retrieve a single entry by its UUID.

Request

curl https://slashfeed.app/api/v1/entries/019e05cc-... \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "id": "019e05cc-...",
  "feed_id": "019e01ff-...",
  "title": "Hello, Elixir 2.0",
  "url": "https://example.com/posts/hello",
  "author": "Jane Doe",
  "published_at": "2025-01-14T18:00:00Z",
  "read": false,
  "starred": false,
  "saved_for_later": false
}
POST /api/v1/entries/:id/read

Mark a single entry as read. Idempotent.

Request

curl -X POST https://slashfeed.app/api/v1/entries/019e05cc-.../read \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "entry": { "id": "019e05cc-...", "read": true }
}
DELETE /api/v1/entries/:id/read

Mark a single entry as unread.

Request

curl -X DELETE https://slashfeed.app/api/v1/entries/019e05cc-.../read \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "entry": { "id": "019e05cc-...", "read": false }
}
POST /api/v1/entries/read

Bulk mark as read. Pass feed_id or an array of entry_ids.

feed_id string

Mark all unread entries in this feed as read.

entry_ids array

Array of entry UUIDs to mark as read.

Request

curl -X POST https://slashfeed.app/api/v1/entries/read \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"feed_id": "019e02ab-..."}'

Response

{
  "marked": 23
}

Categories

Organise subscriptions into named groups. Each subscription can belong to at most one category.

id string

Category UUID.

name string

Unique name per user.

color string

Hex colour code, e.g. #6366f1.

position integer

Display order (lower = first).

Category object

{
  "id": "019e03aa-...",
  "name": "Tech News",
  "color": "#6366f1",
  "position": 0
}
GET /api/v1/categories

Returns all categories ordered by position.

Request

curl https://slashfeed.app/api/v1/categories \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "categories": [
    { "id": "019e03aa-...", "name": "Tech News", "color": "#6366f1" }
  ]
}
POST /api/v1/categories

Create a new category.

name string required

Category name — unique per account.

color string

Hex colour code, e.g. #6366f1.

Request

curl -X POST https://slashfeed.app/api/v1/categories \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Tech News", "color": "#6366f1"}'

Response

{
  "category": { "id": "019e03aa-...", "name": "Tech News", "color": "#6366f1" }
}
PUT /api/v1/categories/:id

Update a category. Only fields you send are changed.

name string

New name.

color string

New hex colour.

Request

curl -X PUT https://slashfeed.app/api/v1/categories/019e03aa-... \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "Technology"}'

Response

{
  "category": { "id": "019e03aa-...", "name": "Technology" }
}
DELETE /api/v1/categories/:id

Delete a category. Subscriptions have their category_id set to null.

Request

curl -X DELETE https://slashfeed.app/api/v1/categories/019e03aa-... \
-H "Authorization: Bearer sk_live_xxx"

Response

HTTP 204 No Content

Tags

Flexible labels for entries and subscriptions. Unlike categories, a resource can have multiple tags.

id string

Tag UUID.

name string

Unique tag name per user.

color string

Optional hex colour for the tag chip.

Tag object

{
  "id": "019e07bb-...",
  "name": "must-read",
  "color": "#10b981"
}
GET /api/v1/tags

Returns all tags sorted alphabetically.

Request

curl https://slashfeed.app/api/v1/tags \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "tags": [
    { "id": "019e07bb-...", "name": "must-read", "color": "#10b981" }
  ]
}
POST /api/v1/tags

Create a new tag.

name string required

Tag name — unique per account.

color string

Hex colour code.

Request

curl -X POST https://slashfeed.app/api/v1/tags \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "must-read"}'

Response

{
  "tag": { "id": "019e07bb-...", "name": "must-read" }
}
DELETE /api/v1/tags/:id

Delete a tag and remove it from all entries and subscriptions.

Request

curl -X DELETE https://slashfeed.app/api/v1/tags/019e07bb-... \
-H "Authorization: Bearer sk_live_xxx"

Response

HTTP 204 No Content

Statistics

Reading statistics for the authenticated user over the last 30 days.

GET /api/v1/stats

Returns total reads, reading streaks, and most-read feeds.

Request

curl https://slashfeed.app/api/v1/stats \
-H "Authorization: Bearer sk_live_xxx"

Response

{
  "total_reads_30d": 214,
  "reading_streak": { "current": 7, "longest": 23 },
  "most_read_feeds": [
    { "feed_id": "019e01ff-...", "title": "Hacker News", "count": 88 }
  ]
}

Miniflux API

BoringRSS implements a subset of the Miniflux v1 API at /v1/ — connect any Miniflux-compatible client without code changes.

Base URL: https://yourserver.com/v1
Auth: Pass your BoringRSS token as the X-Auth-Token header, or as the password in HTTP Basic Auth (any username).
Method Path Description
GET /v1/me Current user
GET /v1/feeds List feeds
POST /v1/feeds Subscribe to feed
GET /v1/feeds/:id Get feed
DELETE /v1/feeds/:id Delete feed
PUT /v1/feeds/:id/refresh Refresh feed
POST /v1/feeds/refresh Refresh all feeds
GET /v1/feeds/:id/entries Get feed entries
GET /v1/entries List entries
GET /v1/entries/:id Get entry
PUT /v1/entries Update entries
GET /v1/categories List categories
POST /v1/categories Create category
DELETE /v1/categories/:id Delete category

X-Auth-Token header

curl https://yourserver.com/v1/me \
-H "X-Auth-Token: sk_live_xxx"

HTTP Basic Auth (alternative)

# Any username, token as password
curl https://yourserver.com/v1/feeds \
-u "user:sk_live_xxx"

User object (Miniflux format)

{
  "id": 1234567,
  "username": "[email protected]",
  "is_admin": false,
  "language": "en_US",
  "timezone": "UTC",
  "theme": "light",
  "entry_sorting_direction": "desc"
}

Fever API

Implements the Fever RSS protocol at POST /fever — for Reeder 5, NetNewsWire, ReadKit, and more.

Setup

  1. Go to Settings → Fever API and set a Fever password.
  2. Enter https://yourserver.com/fever as the server URL.
  3. Use your BoringRSS email + the Fever password as credentials.
Auth: Client sends api_key=MD5(email:fever_password) as a POST parameter.
?feeds All subscribed feeds
?groups Categories as Fever groups
?items Entries (paginated: since_id, max_id, with_ids)
?unread_item_ids Comma-separated unread entry IDs
?saved_item_ids Comma-separated starred entry IDs
?mark=item… Mark entry read/unread/saved/unsaved
?mark=feed… Mark all feed entries as read
?mark=group… Mark all category entries as read

Fever request

curl -X POST "https://yourserver.com/fever?feeds" \
-d "api_key=5f4dcc3b5aa765d61d8327deb882cf99"

Successful response

{
  "api_version": 3,
  "auth": 1,
  "last_refreshed_on_time": "1736928000",
  "feeds": [
    {
      "id": 12345678,
      "title": "Example Blog",
      "url": "https://example.com/feed.xml",
      "is_spark": 0
    }
  ]
}

Model Context Protocol

Connect Claude Desktop, Cursor, Zed, or any MCP-compatible client to your personal RSS reading data. The MCP endpoint gives your AI assistant real-time access to your subscribed feeds, unread entries, reading stats, and more.

Endpoint: https://slashfeed.app/api/mcp
Auth modes: Two modes: OAuth 2.0 + PKCE (recommended for any interactive client — no manual token copying) and Personal Bearer Token (copy-paste once, for headless scripts).
Tool Description
list_my_feeds All subscribed feeds with category, fetch status, and error state
list_my_entries Recent unread entries, filterable by feed or category
search_my_entries Full-text search across your subscribed feeds
subscribe_to_feed Add a new RSS/Atom feed to your subscriptions
mark_entry_read Mark a single entry as read
save_for_later Save an entry to your Read Later list
get_my_stats Reading streak, total reads, and top feeds for a date window

Example tool call

POST /api/mcp
Authorization: Bearer <token>
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_my_feeds",
    "arguments": {}
  }
}

Response

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "[019e…] Hacker News [Tech]\n  url: https://news.ycombinator.com/rss | last_fetched: 2026-05-21T08:00:00Z\n\n[019f…] The Register\n  url: https://www.theregister.com/headlines.atom | last_fetched: 2026-05-21T07:45:00Z"
      }
    ]
  }
}

OAuth 2.0 PKCE

The preferred authentication method. Your LLM client opens a browser, you sign in with your existing BoringRSS account, and the client stores/refreshes tokens automatically — no credential management on your end.

  1. Register a public OAuth client (run the command on the right or use the admin UI at /admin/oauth/clients).
  2. Configure your MCP client with the client_id and the endpoint URL (see Client Config section).
  3. Your client auto-discovers auth endpoints from /.well-known/openid-configuration .
  4. On first connection, a browser window opens for sign-in. After that, tokens refresh silently.
Discovery: Clients that support OIDC auto-discovery only need the URL https://slashfeed.app/.well-known/openid-configuration .

Access tokens expire after 1 hour; refresh tokens last 30 days.

Register a client (run once)

mix boring_rss.oauth.create_client \
  --name "Claude Desktop" \
  --redirect-uri "http://localhost:8888/callback" \
  --grants "authorization_code,refresh_token" \
  --scopes "feeds:read,entries:read,entries:write,categories:read,stats:read" \
  --pkce \
  --access-ttl 3600 \
  --refresh-ttl 2592000

# → client_id: 019f1a2b-...  (no secret — PKCE public client)

PKCE authorization flow

# 1. Generate PKCE pair (your MCP client does this automatically)
code_verifier  = random 32-byte hex string
code_challenge = BASE64URL(SHA-256(code_verifier))

# 2. Open in browser
GET /oauth/authorize
  ?client_id=019f1a2b-...
  &redirect_uri=http://localhost:8888/callback
  &response_type=code
  &scope=feeds:read+entries:read+stats:read
  &code_challenge=<challenge>
  &code_challenge_method=S256

# 3. User logs in → redirected to callback with ?code=…

# 4. Exchange code for tokens
POST /oauth/token
  grant_type=authorization_code
  &code=<code>
  &code_verifier=<verifier>
  &client_id=019f1a2b-...

# → {"access_token": "…", "expires_in": 3600, "refresh_token": "…"}

Refresh an expired token

POST /oauth/token
  grant_type=refresh_token
  &refresh_token=<refresh_token>
  &client_id=019f1a2b-...

Personal Bearer Token

A simpler alternative for headless scripts, one-person self-hosting setups, or any client that can't complete an OAuth browser flow. Generate a permanent 64-character token in Settings and paste it once.

  1. Open Settings → MCP Access Token.
  2. Click "Generate Token" — the raw token is shown once; copy it now.
  3. Paste it as an Authorization: Bearer <token> header in your MCP client config.
Security: The token is stored only as a SHA-256 hash in the database. Revoke it any time from Settings. For clients that can't set headers, append ?token=<value> to the URL — but only over HTTPS.

Rotating (re-generating) a token immediately invalidates the previous one.

Generate & use

# In Settings → MCP Access Token, click "Generate Token"
# Copy the 64-character token shown once:
# a3f9e2b1c4d5...  (64 hex chars)

# Use it as a Bearer token in any HTTP client:
curl -N https://slashfeed.app/api/mcp \
  -H "Authorization: Bearer a3f9e2b1c4d5..."

Client Configuration

Complete configuration examples for the three most popular MCP-compatible AI clients. Replace 019f1a2b-... with the client_id printed by mix boring_rss.oauth.create_client .

Claude Desktop

Add the boring-rss block to your claude_desktop_config.json file.

Cursor

Add to ~/.cursor/mcp.json under the mcpServers key.

Zed

Add to ~/.config/zed/settings.json inside the assistant key.

Personal Token (all clients)

For clients that don't support OAuth, use the bearer header approach — pass the token directly in the headers map instead.

For self-hosted instances, replace slashfeed.app with your own domain.

Claude Desktop

// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "slashfeed": {
      "type": "http",
      "url": "https://slashfeed.app/api/mcp",
      "oauth": {
        "client_id": "019f1a2b-...",
        "authorization_url": "https://slashfeed.app/oauth/authorize",
        "token_url": "https://slashfeed.app/oauth/token",
        "scope": "feeds:read entries:read entries:write categories:read stats:read"
      }
    }
  }
}

Cursor

// ~/.cursor/mcp.json
{
  "mcpServers": {
    "slashfeed": {
      "type": "http",
      "url": "https://slashfeed.app/api/mcp",
      "oauth": {
        "client_id": "019f1a2b-...",
        "authorization_url": "https://slashfeed.app/oauth/authorize",
        "token_url": "https://slashfeed.app/oauth/token",
        "scope": "feeds:read entries:read entries:write categories:read stats:read"
      }
    }
  }
}

Zed

// ~/.config/zed/settings.json  (inside "assistant" key)
{
  "assistant": {
    "version": "2",
    "mcp_servers": {
      "slashfeed": {
        "type": "http",
        "url": "https://slashfeed.app/api/mcp",
        "oauth": {
          "client_id": "019f1a2b-...",
          "discovery_url": "https://slashfeed.app/.well-known/openid-configuration",
          "scope": "feeds:read entries:read entries:write categories:read stats:read"
        }
      }
    }
  }
}

Personal token (any client)

// Any client — paste your personal token directly:
{
  "mcpServers": {
    "slashfeed": {
      "type": "http",
      "url": "https://slashfeed.app/api/mcp",
      "headers": {
        "Authorization": "Bearer a3f9e2b1c4d5..."
      }
    }
  }
}

SDKs & Tools

Download a pre-built collection for Insomnia or Hoppscotch to test every endpoint instantly. After importing, set the base_url and api_token environment variables shown on the right. Covers all three APIs — REST v1, Miniflux, and Fever.

Insomnia — environment variables

{
  "base_url": "https://yourserver.com",
  "api_token": "sk_live_your_token_here"
}

Hoppscotch — environment variables

[
  { "key": "base_url",   "value": "https://yourserver.com",   "secret": false },
  { "key": "api_token",  "value": "sk_live_your_token_here",  "secret": true  }
]
BoringRSS API — Elixir & Phoenix