The SlashFeed API is organized around REST with predictable resource-oriented URLs, JSON bodies, and standard HTTP verbs and response codes.
https://slashfeed.app/api/v1
application/json.
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"
}
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"
}
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
}
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"]
}
}
Retrieve information about the API token's owner.
/api/v1/me
Returns the account object for the token owner.
id
string
Unique user UUID v7.
email
string
User email address.
name
string
Display name, or null.
confirmed
boolean
Email confirmed status.
created_at
datetime
ISO 8601 creation timestamp.
default_view_mode
string
"list" or "reader".
reader_font_size
string
Preferred font size.
ai_chat_enabled
boolean
Whether AI chat is enabled for this account.
Request
curl https://slashfeed.app/api/v1/me \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"id": "019e02ab-89ab-7f73-a445-12c863138d21",
"email": "[email protected]",
"name": "Jane Doe",
"confirmed": true,
"created_at": "2025-01-15T09:00:00Z",
"default_view_mode": "list",
"reader_font_size": "base",
"ai_chat_enabled": false
}
/api/v1/me
Update reading preferences. Only provided fields are changed.
display_name
string
Your display name or handle.
default_view_mode
string
"list" or "reader".
reader_font_size
string
Font size preference.
Request
curl -X PUT https://slashfeed.app/api/v1/me \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"default_view_mode": "reader"}'
Response
{
"id": "019e02ab-89ab-7f73-a445-12c863138d21",
"email": "[email protected]",
"name": "Jane Doe",
"confirmed": true,
"created_at": "2025-01-15T09:00:00Z",
"default_view_mode": "list",
"reader_font_size": "base",
"ai_chat_enabled": false
}
/api/v1/me/counts
Returns live badge counts for the sidebar — unread entries, saved, starred, and total feeds.
Request
curl https://slashfeed.app/api/v1/me/counts \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"unread_entries": 47,
"saved_entries": 12,
"starred_entries": 5,
"feeds": 23
}
/api/v1/me/opml
Download all subscriptions as an OPML file, preserving category grouping. Returns text/xml with a Content-Disposition attachment header.
Request
curl https://slashfeed.app/api/v1/me/opml \ -H "Authorization: Bearer sk_live_xxx" \ -o subscriptions.opml
Response
HTTP 200 text/xml — OPML file download
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
}
/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
}
]
}
/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
}
}
/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
}
/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
/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"
}
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
}
/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
}
]
}
/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
}
/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 }
}
/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 }
}
/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
}
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
}
/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" }
]
}
/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" }
}
/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" }
}
/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
/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" }
]
}
/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" }
}
/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
Reading statistics for the authenticated user over the last 30 days.
/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 }
]
}
Bookmark entries to read later. Saved entries appear in the Read Later queue in the app and are preserved even after an entry is marked as read.
id
string
Entry UUID.
title
string
Article title.
url
string
Article URL.
summary
string
Short preview text.
author
string
Author name.
published_at
datetime
Original publication timestamp.
saved_at
datetime
When the entry was saved.
Saved entry object
{
"object": "list",
"data": [
{
"id": "019e05cc-...",
"title": "Hello, Elixir 2.0",
"url": "https://example.com/posts/hello",
"summary": "A short preview…",
"author": "Jane Doe",
"published_at": "2025-01-14T18:00:00Z",
"saved_at": "2025-01-15T09:01:00Z"
}
]
}
/api/v1/save_later
Returns the paginated list of saved entries, newest first.
limit
integer
Items per page (default 20, max 100).
offset
integer
Zero-based page offset.
Request
curl https://slashfeed.app/api/v1/save_later \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019e05cc-...",
"title": "Hello, Elixir 2.0",
"url": "https://example.com/posts/hello",
"summary": "A short preview…",
"author": "Jane Doe",
"published_at": "2025-01-14T18:00:00Z",
"saved_at": "2025-01-15T09:01:00Z"
}
]
}
/api/v1/save_later
Save an entry for later.
entry_id
string
required
UUID of the entry to save.
Request
curl -X POST https://slashfeed.app/api/v1/save_later \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"entry_id": "019e05cc-..."}'
Response (201 Created)
{
"id": "019f00aa-...",
"saved_at": "2025-01-15T09:01:00Z"
}
/api/v1/save_later/:entry_id
Remove an entry from the Read Later list.
Request
curl -X DELETE https://slashfeed.app/api/v1/save_later/019e05cc-... \ -H "Authorization: Bearer sk_live_xxx"
Response
HTTP 204 No Content
Star your favourite entries. Stars are a permanent personal favourite — they never expire and appear in the Starred view in the app.
id
string
Entry UUID.
title
string
Article title.
url
string
Article URL.
author
string
Author name.
published_at
datetime
Original publication timestamp.
starred_at
datetime
When the entry was starred.
Starred entry object
{
"object": "list",
"data": [
{
"id": "019e05cc-...",
"title": "Hello, Elixir 2.0",
"url": "https://example.com/posts/hello",
"author": "Jane Doe",
"published_at": "2025-01-14T18:00:00Z",
"starred_at": "2025-01-15T10:00:00Z"
}
]
}
/api/v1/starred
Returns the paginated list of starred entries, newest first.
limit
integer
Items per page (default 20, max 100).
offset
integer
Zero-based page offset.
Request
curl https://slashfeed.app/api/v1/starred \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019e05cc-...",
"title": "Hello, Elixir 2.0",
"url": "https://example.com/posts/hello",
"author": "Jane Doe",
"published_at": "2025-01-14T18:00:00Z",
"starred_at": "2025-01-15T10:00:00Z"
}
]
}
/api/v1/starred
Star an entry.
entry_id
string
required
UUID of the entry to star.
Request
curl -X POST https://slashfeed.app/api/v1/starred \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"entry_id": "019e05cc-..."}'
Response (201 Created)
{
"id": "019f01bb-...",
"starred_at": "2025-01-15T10:00:00Z"
}
/api/v1/starred/:entry_id
Unstar an entry.
Request
curl -X DELETE https://slashfeed.app/api/v1/starred/019e05cc-... \ -H "Authorization: Bearer sk_live_xxx"
Response
HTTP 204 No Content
Highlight text in an entry and attach an optional note. Annotations
are private to the authenticated user. Each highlight has a colour: yellow, green, blue, or pink.
id
string
Annotation UUID.
entry_id
string
Parent entry UUID.
highlighted_text
string
The selected passage.
note
string
Optional personal note, or null.
color
string
yellow | green | blue | pink.
created_at
datetime
Creation timestamp.
Annotation object
{
"id": "019f02cc-...",
"entry_id": "019e05cc-...",
"highlighted_text": "The quick brown fox",
"note": "Important observation",
"color": "yellow",
"created_at": "2025-01-15T11:00:00Z"
}
/api/v1/entries/:entry_id/annotations
List all annotations for a specific entry.
Request
curl https://slashfeed.app/api/v1/entries/019e05cc-.../annotations \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019f02cc-...",
"highlighted_text": "The quick brown fox",
"color": "yellow",
"created_at": "2025-01-15T11:00:00Z"
}
]
}
/api/v1/entries/:entry_id/annotations
Create a new annotation on an entry.
highlighted_text
string
required
The selected text passage.
color
string
Highlight colour (default: yellow).
note
string
Optional personal note.
Request
curl -X POST https://slashfeed.app/api/v1/entries/019e05cc-.../annotations \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"highlighted_text": "The quick brown fox", "color": "yellow", "note": "Important"}'
Response (201 Created)
{
"id": "019f02cc-...",
"entry_id": "019e05cc-...",
"highlighted_text": "The quick brown fox",
"note": "Important observation",
"color": "yellow",
"created_at": "2025-01-15T11:00:00Z"
}
/api/v1/annotations/:id
Update the note or colour of an existing annotation.
note
string
Updated note text.
color
string
New colour (yellow | green | blue | pink).
Request
curl -X PUT https://slashfeed.app/api/v1/annotations/019f02cc-... \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"note": "Updated note", "color": "blue"}'
Response
{
"id": "019f02cc-...",
"entry_id": "019e05cc-...",
"highlighted_text": "The quick brown fox",
"note": "Important observation",
"color": "yellow",
"created_at": "2025-01-15T11:00:00Z"
}
/api/v1/annotations/:id
Delete an annotation.
Request
curl -X DELETE https://slashfeed.app/api/v1/annotations/019f02cc-... \ -H "Authorization: Bearer sk_live_xxx"
Response
HTTP 204 No Content
/api/v1/newsletters
List all your newsletter inboxes.
Request
curl https://slashfeed.app/api/v1/newsletters \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019f03dd-...",
"email": "[email protected]",
"feed_id": "019f03de-..."
}
]
}
/api/v1/newsletters
Create a new inbox. Returns the unique email address to use as a subscription address.
title
string
required
Label for the inbox (used as the feed title).
Request
curl -X POST https://slashfeed.app/api/v1/newsletters \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"title": "My Newsletter Inbox"}'
Response (201 Created)
{
"inbox": {
"id": "019f03dd-...",
"email": "[email protected]",
"feed_id": "019f03de-...",
"inserted_at": "2025-01-15T12:00:00Z"
}
}
/api/v1/newsletters/:id
Delete an inbox and its associated feed and all entries.
Request
curl -X DELETE https://slashfeed.app/api/v1/newsletters/019f03dd-... \ -H "Authorization: Bearer sk_live_xxx"
Response
HTTP 204 No Content
Receive real-time HTTP POST callbacks when new entries arrive in your
subscribed feeds. Each delivery is signed with
HMAC-SHA256
so you can verify it came from SlashFeed. Failed deliveries are
retried up to 3 times (30 s → 5 min back-off).
id
string
Webhook UUID.
url
string
Destination HTTPS URL.
label
string
Friendly name, or null.
events
array
Event types: ["new_entries"].
enabled
boolean
Whether the webhook is active.
last_attempted_at
datetime
Last delivery attempt.
last_succeeded_at
datetime
Last successful delivery.
inserted_at
datetime
Creation timestamp.
Webhook object
{
"id": "019f04ee-...",
"url": "https://your-server.com/hooks/slashfeed",
"label": "My App",
"events": ["new_entries"],
"enabled": true,
"last_attempted_at": "2025-01-15T13:00:00Z",
"last_succeeded_at": "2025-01-15T13:00:01Z",
"inserted_at": "2025-01-15T09:00:00Z"
}
/api/v1/webhooks
List all webhooks for the account.
Request
curl https://slashfeed.app/api/v1/webhooks \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019f04ee-...",
"url": "https://your-server.com/hooks/slashfeed",
"events": ["new_entries"],
"enabled": true
}
]
}
/api/v1/webhooks
Create a new webhook.
url
string
required
The HTTPS URL to POST events to.
label
string
Friendly label for the webhook.
events
array
Event types (default: ["new_entries"]).
enabled
boolean
Active state (default: true).
Request
curl -X POST https://slashfeed.app/api/v1/webhooks \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-server.com/hooks/slashfeed", "label": "My App", "events": ["new_entries"]}'
Response (201 Created)
{
"id": "019f04ee-...",
"url": "https://your-server.com/hooks/slashfeed",
"label": "My App",
"events": ["new_entries"],
"enabled": true,
"last_attempted_at": "2025-01-15T13:00:00Z",
"last_succeeded_at": "2025-01-15T13:00:01Z",
"inserted_at": "2025-01-15T09:00:00Z"
}
/api/v1/webhooks/:id
Retrieve a specific webhook.
Request
curl https://slashfeed.app/api/v1/webhooks/019f04ee-... \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"id": "019f04ee-...",
"url": "https://your-server.com/hooks/slashfeed",
"label": "My App",
"events": ["new_entries"],
"enabled": true,
"last_attempted_at": "2025-01-15T13:00:00Z",
"last_succeeded_at": "2025-01-15T13:00:01Z",
"inserted_at": "2025-01-15T09:00:00Z"
}
/api/v1/webhooks/:id
Update a webhook. Only provided fields are changed.
url
string
New destination URL.
label
string
Updated label.
events
array
New event type list.
enabled
boolean
Enable or disable.
Request
curl -X PUT https://slashfeed.app/api/v1/webhooks/019f04ee-... \
-H "Authorization: Bearer sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
Response
{
"id": "019f04ee-...",
"url": "https://your-server.com/hooks/slashfeed",
"label": "My App",
"events": ["new_entries"],
"enabled": true,
"last_attempted_at": "2025-01-15T13:00:00Z",
"last_succeeded_at": "2025-01-15T13:00:01Z",
"inserted_at": "2025-01-15T09:00:00Z"
}
/api/v1/webhooks/:id
Delete a webhook and all its delivery logs.
Request
curl -X DELETE https://slashfeed.app/api/v1/webhooks/019f04ee-... \ -H "Authorization: Bearer sk_live_xxx"
Response
HTTP 204 No Content
/api/v1/webhooks/:id/deliveries
List the last 20 delivery attempts for a webhook (newest first).
Request
curl https://slashfeed.app/api/v1/webhooks/019f04ee-.../deliveries \ -H "Authorization: Bearer sk_live_xxx"
Response
{
"object": "list",
"data": [
{
"id": "019f05ff-...",
"status": "success",
"status_code": 200,
"attempt": 1,
"duration_ms": 143,
"inserted_at": "2025-01-15T13:00:00Z"
}
]
}
/api/v1/webhooks/:id/test
Send a test delivery to verify the endpoint is reachable.
Request
curl -X POST https://slashfeed.app/api/v1/webhooks/019f04ee-.../test \ -H "Authorization: Bearer sk_live_xxx"
Response
{"status": "sent"}
SlashFeed signs every delivery with
HMAC-SHA256
using a per-webhook secret. The secret is returned once in the
create response and never again — store it securely.
Three headers are sent with every delivery:
X-SlashFeed-Signature
string
sha256=<hex> HMAC of the raw body.
X-SlashFeed-Event
string
Event type, e.g. new_entries.
X-SlashFeed-Delivery
string
Delivery UUID — use for idempotency / deduplication.
Payload received by your server
POST https://your-server.com/hooks/slashfeed
X-SlashFeed-Event: new_entries
X-SlashFeed-Signature: sha256=3a9f4b…
X-SlashFeed-Delivery: 019f05ff-…
Content-Type: application/json
{
"event": "new_entries",
"timestamp": "2025-01-15T13:00:00Z",
"user_id": "019e02ab-...",
"data": {
"entries": [
{
"id": "019e05cc-...",
"title": "Hello, Elixir 2.0",
"url": "https://example.com/posts/hello",
"summary": "A short preview…",
"author": "Jane Doe",
"published_at": "2025-01-14T18:00:00Z",
"read": false,
"feed": {
"id": "019e01ff-...",
"title": "Example Blog",
"url": "https://example.com/feed.xml"
}
}
]
}
}
Signature verification (Python)
# Python example import hmac, hashlib secret = "your-webhook-secret" body = request.body.read() sig = request.headers["X-SlashFeed-Signature"] expected = "sha256=" + hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() assert hmac.compare_digest(expected, sig)
BoringRSS implements a subset of the
Miniflux v1 API
at /v1/
—
connect any Miniflux-compatible client without code changes.
https://yourserver.com/v1
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"
}
Implements the surviving subset of the classic Google Reader
protocol at
/reader/api/0/
— for FeedMe, Readrops, NetNewsWire, FocusReader, legacy Reeder, and every FreshRSS/Miniflux-aware client.
https://yourserver.com/reader/api/0
POST /accounts/ClientLogin
with Email
+ Passwd
returns an Auth
token; send it as Authorization: GoogleLogin auth=<token>.
| Method | Path | Description |
|---|---|---|
| POST | /accounts/ClientLogin | Mint the session Auth token |
| GET | /reader/api/0/user-info | Current user identity |
| GET | /reader/api/0/token | Write token |
| GET | /reader/api/0/subscription/list | Subscriptions + folders |
| POST | /reader/api/0/subscription/quickadd | Subscribe by URL |
| POST | /reader/api/0/subscription/edit | Subscribe/unsubscribe/rename/move |
| GET | /reader/api/0/tag/list | Folders + starred state |
| GET | /reader/api/0/unread-count | Unread counts per stream |
| GET | /reader/api/0/stream/contents/:id | Entries for a stream |
| GET | /reader/api/0/stream/items/ids | Item IDs for a stream |
| POST | /reader/api/0/stream/items/contents | Items for posted IDs |
| POST | /reader/api/0/edit-tag | Mark read/unread/starred |
| POST | /reader/api/0/mark-all-as-read | Mark a stream read |
Stream IDs follow the Google Reader vocabulary: user/-/state/com.google/reading-list, …/read, …/starred, feed/<url>, and user/-/label/<folder>. Use
xt=
to exclude a stream (e.g. only unread).
ClientLogin + a request
# 1) Mint a session token curl "https://yourserver.com/accounts/ClientLogin" \ -d "[email protected]" -d "Passwd=your-password" # → SID=sk_live_xxx # LSID=sk_live_xxx # Auth=sk_live_xxx # 2) Use it on every request curl "https://yourserver.com/reader/api/0/subscription/list?output=json" \ -H "Authorization: GoogleLogin auth=sk_live_xxx"
stream/contents (Google Reader format)
{
"id": "user/-/state/com.google/reading-list",
"items": [
{
"id": "tag:google.com,2005:reader/item/00000000075bcd15",
"title": "Example headline",
"published": 1736928000,
"canonical": [{ "href": "https://example.com/post" }],
"categories": ["user/-/state/com.google/reading-list"],
"summary": { "content": "<p>…</p>" },
"origin": {
"streamId": "feed/https://example.com/feed.xml",
"title": "Example Blog"
}
}
]
}
Implements the
Fever RSS protocol
at POST /fever
—
for Reeder 5, NetNewsWire, ReadKit, and more.
Setup
https://yourserver.com/fever
as the server URL.
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
}
]
}
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.
https://slashfeed.app/api/mcp
| 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"
}
]
}
}
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.
/admin/oauth/clients).
client_id
and the endpoint URL (see Client Config section).
/.well-known/openid-configuration
.
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-...
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.
Authorization: Bearer <token>
header in your MCP client config.
?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..."
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..."
}
}
}
}
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 four APIs — REST v1, Miniflux, Google Reader, 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 }
]
Every release that changes the public API surface — REST v1, the native OAuth endpoints, the Fever and Miniflux compatibility layers, or the MCP server — is recorded here, newest first. Additive changes (new endpoints, new response fields) land without notice; anything breaking is called out explicitly.
Entry format (maintainers)
%{
date: "2026-07-15",
title: "Email MFA for the native login flow",
items: [
"POST /oauth/native/password now returns mfa_methods",
"New endpoint: POST /oauth/native/mfa/send-code"
]
}
Entries live in ApiDocsLive's @changelog
and are mirrored in docs/api.md
§ Changelog — both
must be updated in the same release (see AGENTS.md).