commafeed-api
CommaFeed REST API Skill
Interact with a self-hosted CommaFeed RSS reader instance via its REST API.
Environment Variables (REQUIRED)
Before making any API calls, ensure these environment variables are set:
| Variable | Description | Example |
|---|---|---|
COMMAFEED_HOST |
CommaFeed instance URL (with protocol, no trailing slash) | https://commafeed.example.com |
COMMAFEED_USER |
CommaFeed username | admin |
COMMAFEED_PASS |
CommaFeed password | secretpass |
export COMMAFEED_HOST="https://commafeed.example.com"
export COMMAFEED_USER="admin"
export COMMAFEED_PASS="your-password"
If these are not set, ask the user to provide them before proceeding.
Authentication
All requests use HTTP Basic Auth:
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/get" | jq .
Every request pattern below assumes:
- Base URL:
$COMMAFEED_HOST/rest - Auth:
-u "$COMMAFEED_USER:$COMMAFEED_PASS" - Content-Type:
application/json(for POST requests with JSON body)
Helper alias for examples:
CF="curl -s -u $COMMAFEED_USER:$COMMAFEED_PASS"
API Endpoints
Categories
| Method | Path | Description |
|---|---|---|
| GET | /rest/category/get |
Get root category tree (all categories + subscriptions) |
| POST | /rest/category/add |
Add a new category |
| POST | /rest/category/modify |
Rename or move a category |
| POST | /rest/category/delete |
Delete a category |
| POST | /rest/category/collapse |
Collapse/expand a category in the UI |
| GET | /rest/category/entries |
Get entries for a category |
| GET | /rest/category/entriesAsFeed |
Get category entries as RSS/Atom XML |
| POST | /rest/category/mark |
Mark all entries in a category as read |
| GET | /rest/category/unreadCount |
Get unread count per subscription |
Get category tree (all subscriptions)
Returns the full tree: root category with nested children and feed subscriptions.
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/get" | jq .
Response — Category object:
{
"id": "all",
"name": "All",
"children": [
{
"id": "10",
"parentId": "all",
"name": "Tech",
"children": [],
"feeds": [
{
"id": 42,
"name": "Hacker News",
"feedUrl": "https://news.ycombinator.com/rss",
"feedLink": "https://news.ycombinator.com",
"iconUrl": "/rest/feed/favicon/42",
"unread": 15,
"categoryId": "10",
"position": 0,
"newestItemTime": "2026-03-07T10:30:00Z",
"errorCount": 0,
"lastRefresh": "2026-03-07T10:00:00Z",
"nextRefresh": "2026-03-07T10:15:00Z"
}
],
"expanded": true,
"position": 0
}
],
"feeds": [],
"expanded": true,
"position": 0
}
Add a category
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"name": "Technology", "parentId": "all"}' \
"$COMMAFEED_HOST/rest/category/add" | jq .
Request body — AddCategoryRequest:
name(string, required, max 128 chars) — category nameparentId(string, optional) — parent category ID, omit for root level
Response: category ID (integer).
Modify a category
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": 10, "name": "Tech News", "parentId": "all", "position": 0}' \
"$COMMAFEED_HOST/rest/category/modify"
Request body — CategoryModificationRequest:
id(integer, required) — category IDname(string) — new nameparentId(string) — new parent category IDposition(integer) — position in parent
Delete a category
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": 10}' \
"$COMMAFEED_HOST/rest/category/delete"
Request body — IDRequest:
id(integer, required) — category ID
Get category entries
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=all&readType=unread&limit=20&order=desc" | jq .
Query parameters:
id(required) — category ID,"all"for all feeds, or"starred"for starred entriesreadType(required) —allorunreadlimit(integer, default 20, max 1000) — entries per pageoffset(integer, default 0) — pagination offsetorder(string, defaultdesc) —descorascnewerThan(long) — Unix timestamp in ms, only entries newer than thiskeywords(string) — search filtertag(string) — filter by tagexcludedSubscriptionIds(string) — comma-separated subscription IDs to exclude
Response — Entries object:
{
"name": "All",
"entries": [
{
"id": "feed/42:entry/abc123",
"guid": "https://example.com/article-1",
"title": "Article Title",
"content": "<p>Article HTML content...</p>",
"author": "John Doe",
"date": "2026-03-07T09:00:00Z",
"insertedDate": "2026-03-07T09:05:00Z",
"url": "https://example.com/article-1",
"feedId": "42",
"feedName": "Example Feed",
"feedUrl": "https://example.com/rss",
"feedLink": "https://example.com",
"iconUrl": "/rest/feed/favicon/42",
"read": false,
"starred": false,
"markable": true,
"tags": [],
"categories": "tech, news",
"rtl": false,
"enclosureUrl": null,
"enclosureType": null,
"mediaThumbnailUrl": null
}
],
"timestamp": 1741339200000,
"hasMore": true,
"offset": 0,
"limit": 20,
"errorCount": 0,
"ignoredReadStatus": false
}
Get unread counts
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/unreadCount" | jq .
Response — array of UnreadCount:
[
{"feedId": 42, "unreadCount": 15},
{"feedId": 55, "unreadCount": 3}
]
Mark category as read
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": "all", "olderThan": 1741339200000, "read": true}' \
"$COMMAFEED_HOST/rest/category/mark"
Request body — MarkRequest:
id(string, required) — category ID or"all"read(boolean) — true to mark read, false for unreadolderThan(long) — timestamp, mark only entries older than thisinsertedBefore(long) — mark only entries inserted before thiskeywords(string) — filter by keywords
Collapse/expand category
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": 10, "collapse": true}' \
"$COMMAFEED_HOST/rest/category/collapse"
Feed Subscriptions
| Method | Path | Description |
|---|---|---|
| POST | /rest/feed/subscribe |
Subscribe to a feed |
| POST | /rest/feed/unsubscribe |
Unsubscribe from a feed |
| POST | /rest/feed/modify |
Modify subscription (rename, move, filter) |
| GET | /rest/feed/get/{id} |
Get subscription details |
| POST | /rest/feed/fetch |
Fetch feed info by URL (preview before subscribing) |
| GET | /rest/feed/refreshAll |
Queue all feeds for refresh |
| GET | /rest/feed/entries |
Get entries for a specific feed |
| GET | /rest/feed/entriesAsFeed |
Get feed entries as RSS/Atom XML |
| POST | /rest/feed/mark |
Mark feed entries as read |
| GET | /rest/feed/favicon/{id} |
Get feed favicon |
| GET | /rest/feed/export |
Export all subscriptions as OPML |
| POST | /rest/feed/import |
Import subscriptions from OPML file |
Subscribe to a feed
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/rss",
"title": "Example Blog",
"categoryId": 10
}' \
"$COMMAFEED_HOST/rest/feed/subscribe" | jq .
Request body — SubscribeRequest:
url(string, required) — feed URLtitle(string, required) — display namecategoryId(integer) — target category ID
Response: subscription ID (integer).
Unsubscribe from a feed
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": 42}' \
"$COMMAFEED_HOST/rest/feed/unsubscribe"
Modify a subscription
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"id": 42,
"name": "New Feed Name",
"categoryId": 10,
"position": 0,
"filter": "title.contains(\"important\")",
"pushNotificationsEnabled": false,
"autoMarkAsReadAfterDays": 30
}' \
"$COMMAFEED_HOST/rest/feed/modify"
Request body — FeedModificationRequest:
id(integer, required) — subscription IDname(string) — new display namecategoryId(integer) — move to a different categoryposition(integer) — position in categoryfilter(string) — CEL expression to auto-mark entries as readpushNotificationsEnabled(boolean) — enable push notificationsautoMarkAsReadAfterDays(integer) — auto-mark read after N days (null to disable)
Get subscription details
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/feed/get/42" | jq .
Response — Subscription object:
{
"id": 42,
"name": "Hacker News",
"message": null,
"errorCount": 0,
"lastRefresh": "2026-03-07T10:00:00Z",
"nextRefresh": "2026-03-07T10:15:00Z",
"feedUrl": "https://news.ycombinator.com/rss",
"feedLink": "https://news.ycombinator.com",
"iconUrl": "/rest/feed/favicon/42",
"unread": 15,
"categoryId": "10",
"position": 0,
"newestItemTime": "2026-03-07T10:30:00Z",
"filter": null,
"pushNotificationsEnabled": false,
"autoMarkAsReadAfterDays": null
}
Fetch feed info (preview)
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/rss"}' \
"$COMMAFEED_HOST/rest/feed/fetch" | jq .
Request body — FeedInfoRequest:
url(string, required, 1–4096 chars) — feed URL to probe
Response — FeedInfo: title, url, link for the discovered feed.
Refresh all feeds
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/feed/refreshAll"
Note: Returns 429 Too Many Requests if called too frequently.
Get feed entries
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/feed/entries?id=42&readType=unread&limit=50&order=desc" | jq .
Query parameters (same pattern as category entries):
id(required) — subscription IDreadType(required) —allorunreadlimit(integer, default 20, max 1000)offset(integer, default 0)order—descorascnewerThan(long) — Unix timestamp in mskeywords(string) — search filter
Response: Entries object (same structure as category entries).
Mark feed as read
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": "42", "read": true}' \
"$COMMAFEED_HOST/rest/feed/mark"
OPML Export
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/feed/export" -o subscriptions.opml
Response: OPML XML file.
OPML Import
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-F "file=@subscriptions.opml" \
"$COMMAFEED_HOST/rest/feed/import"
Note: Uses
multipart/form-data, not JSON.
Entries (individual entry operations)
| Method | Path | Description |
|---|---|---|
| POST | /rest/entry/mark |
Mark a single entry as read/unread |
| POST | /rest/entry/markMultiple |
Mark multiple entries at once |
| POST | /rest/entry/star |
Star/unstar an entry |
| POST | /rest/entry/tag |
Set tags on an entry |
| GET | /rest/entry/tags |
Get all user tags |
Mark entry as read/unread
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": "feed/42:entry/abc123", "read": true}' \
"$COMMAFEED_HOST/rest/entry/mark"
Request body — MarkRequest:
id(string, required) — entry IDread(boolean) — true = read, false = unread
Mark multiple entries
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"requests": [
{"id": "feed/42:entry/abc123", "read": true},
{"id": "feed/42:entry/def456", "read": true}
]
}' \
"$COMMAFEED_HOST/rest/entry/markMultiple"
Star/unstar an entry
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"id": "feed/42:entry/abc123",
"feedId": 42,
"starred": true
}' \
"$COMMAFEED_HOST/rest/entry/star"
Request body — StarRequest:
id(string, required) — entry IDfeedId(integer, required) — feed subscription IDstarred(boolean, required) — true to star, false to unstar
Tag an entry
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"entryId": 12345,
"tags": ["important", "read-later"]
}' \
"$COMMAFEED_HOST/rest/entry/tag"
Request body — TagRequest:
entryId(integer, required) — entry IDtags(array of strings, required) — tags to assign (replaces existing)
Get all user tags
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/entry/tags" | jq .
Response: array of tag strings.
User Profile & Settings
| Method | Path | Description |
|---|---|---|
| GET | /rest/user/profile |
Get user profile |
| POST | /rest/user/profile |
Update user profile |
| POST | /rest/user/profile/deleteAccount |
Delete own account |
| GET | /rest/user/settings |
Get user settings |
| POST | /rest/user/settings |
Save user settings |
Get profile
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/user/profile" | jq .
Response — UserModel:
{
"id": 1,
"name": "admin",
"email": "admin@example.com",
"apiKey": "abc-123-def-456",
"enabled": true,
"admin": true
}
Update profile
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"currentPassword": "old-pass",
"newPassword": "new-pass",
"newEmail": "new@example.com"
}' \
"$COMMAFEED_HOST/rest/user/profile"
Request body — ProfileModificationRequest:
currentPassword(string, required) — current password for verificationnewPassword(string) — new passwordnewEmail(string) — new emailnewApiKey(boolean) — true to regenerate API key
Get settings
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/user/settings" | jq .
Response — Settings:
{
"language": "en",
"readingMode": "unread",
"readingOrder": "desc",
"showRead": true,
"scrollMarks": true,
"scrollSpeed": 400,
"scrollMode": "if_needed",
"customCss": "",
"customJs": "",
"entriesToKeepOnTopWhenScrolling": 1,
"starIconDisplayMode": "always",
"externalLinkIconDisplayMode": "always",
"markAllAsReadConfirmation": true,
"markAllAsReadNavigateToNextUnread": false,
"customContextMenu": true,
"mobileFooter": false,
"unreadCountTitle": true,
"unreadCountFavicon": true,
"disablePullToRefresh": false,
"primaryColor": null,
"sharingSettings": {
"email": false,
"gmail": false,
"facebook": false,
"twitter": false,
"tumblr": false,
"pocket": false,
"instapaper": false,
"buffer": false
}
}
Settings enums:
readingMode:all,unreadreadingOrder:asc,descscrollMode:always,never,if_neededstarIconDisplayMode/externalLinkIconDisplayMode:always,never,on_desktop,on_mobile
Save settings
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"language": "en",
"readingMode": "unread",
"readingOrder": "desc",
"showRead": false,
"scrollMarks": true
}' \
"$COMMAFEED_HOST/rest/user/settings"
Server Info
| Method | Path | Description |
|---|---|---|
| GET | /rest/server/get |
Get server information (no auth required) |
| GET | /rest/server/proxy |
Proxy an image through the server |
Get server info
curl -s "$COMMAFEED_HOST/rest/server/get" | jq .
Response — ServerInfo:
{
"announcement": "",
"version": "5.x.x",
"gitCommit": "abc1234",
"loginPageTitle": "CommaFeed",
"allowRegistrations": false,
"googleAnalyticsTrackingCode": null,
"smtpEnabled": false,
"demoAccountEnabled": false,
"websocketEnabled": true,
"websocketPingInterval": 15000,
"treeMode": "unread"
}
Proxy image
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/server/proxy?u=https://example.com/image.png" \
-o proxied-image.png
Admin Endpoints
Require
ADMINrole.
| Method | Path | Description |
|---|---|---|
| GET | /rest/admin/metrics |
Get server metrics |
| GET | /rest/admin/user/getAll |
List all users |
| GET | /rest/admin/user/get/{id} |
Get user by ID |
| POST | /rest/admin/user/save |
Create or update a user |
| POST | /rest/admin/user/delete |
Delete a user |
Get server metrics
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/admin/metrics" | jq .
List all users
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/admin/user/getAll" | jq .
Response: array of UserModel.
Get user by ID
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/admin/user/get/1" | jq .
Create / update user
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"name": "newuser",
"password": "securepass123",
"email": "user@example.com",
"enabled": true,
"admin": false
}' \
"$COMMAFEED_HOST/rest/admin/user/save"
Request body — AdminSaveUserRequest:
name(string, required) — usernamepassword(string) — password (required for new users)email(string) — email addressenabled(boolean, required) — account activeadmin(boolean, required) — admin role
Delete user
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"id": 5}' \
"$COMMAFEED_HOST/rest/admin/user/delete"
Registration & Password Reset
| Method | Path | Description |
|---|---|---|
| POST | /rest/user/register |
Register new account (if allowed) |
| POST | /rest/user/initialSetup |
Create initial admin (first-run only) |
| POST | /rest/user/passwordReset |
Request password reset email |
| POST | /rest/user/passwordResetCallback |
Confirm password reset with token |
Register
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{
"name": "newuser",
"password": "mypassword",
"email": "user@example.com"
}' \
"$COMMAFEED_HOST/rest/user/register"
Request: name (3–32 chars), password, email (all required).
Common Patterns
List all feeds with unread counts
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/get" | \
jq '[.. | .feeds? // empty | .[] | {id, name, unread, feedUrl}]'
Get all unread entries across all feeds
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=all&readType=unread&limit=100" | \
jq '.entries[] | {title, url, feedName, date}'
Get starred entries
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=starred&readType=all&limit=50" | \
jq '.entries[] | {title, url, starred, date}'
Search entries by keyword
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&keywords=kubernetes&limit=50" | \
jq '.entries[] | {title, url, feedName}'
Mark all entries as read
# Get current timestamp
TS=$(date +%s)000
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d "{\"id\": \"all\", \"read\": true, \"olderThan\": $TS}" \
"$COMMAFEED_HOST/rest/category/mark"
Subscribe to a feed and put it in a category
# 1. Fetch feed info to verify URL
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{"url": "https://blog.example.com/feed"}' \
"$COMMAFEED_HOST/rest/feed/fetch" | jq .
# 2. Subscribe
curl -s -X POST \
-u "$COMMAFEED_USER:$COMMAFEED_PASS" \
-H "Content-Type: application/json" \
-d '{
"url": "https://blog.example.com/feed",
"title": "Example Blog",
"categoryId": 10
}' \
"$COMMAFEED_HOST/rest/feed/subscribe" | jq .
Export OPML backup
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/feed/export" -o "commafeed-backup-$(date +%Y%m%d).opml"
Paginate through all entries
OFFSET=0
LIMIT=100
while true; do
RESPONSE=$(curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&limit=$LIMIT&offset=$OFFSET")
COUNT=$(echo "$RESPONSE" | jq '.entries | length')
HAS_MORE=$(echo "$RESPONSE" | jq '.hasMore')
echo "$RESPONSE" | jq '.entries[] | {title, date, feedName}'
if [ "$HAS_MORE" = "false" ] || [ "$COUNT" -eq 0 ]; then
break
fi
OFFSET=$((OFFSET + LIMIT))
done
Get entries by tag
curl -s -u "$COMMAFEED_USER:$COMMAFEED_PASS" \
"$COMMAFEED_HOST/rest/category/entries?id=all&readType=all&tag=important&limit=50" | \
jq '.entries[] | {title, url, tags}'
Node.js Example
const COMMAFEED_HOST = process.env.COMMAFEED_HOST;
const COMMAFEED_USER = process.env.COMMAFEED_USER;
const COMMAFEED_PASS = process.env.COMMAFEED_PASS;
const AUTH = 'Basic ' + Buffer.from(`${COMMAFEED_USER}:${COMMAFEED_PASS}`).toString('base64');
async function cfApi(method, path, body = null) {
const url = `${COMMAFEED_HOST}/rest${path}`;
const options = {
method,
headers: {
'Authorization': AUTH,
'Content-Type': 'application/json',
},
};
if (body) options.body = JSON.stringify(body);
const res = await fetch(url, options);
if (!res.ok) throw new Error(`CommaFeed API ${res.status}: ${await res.text()}`);
const text = await res.text();
return text ? JSON.parse(text) : null;
}
// Get all subscriptions
const tree = await cfApi('GET', '/category/get');
console.log(tree);
// Get unread entries
const entries = await cfApi('GET', '/category/entries?id=all&readType=unread&limit=20');
for (const e of entries.entries) {
console.log(`[${e.feedName}] ${e.title} — ${e.url}`);
}
// Subscribe to a feed
const subId = await cfApi('POST', '/feed/subscribe', {
url: 'https://example.com/rss',
title: 'Example Feed',
});
console.log('Subscribed, ID:', subId);
// Star an entry
await cfApi('POST', '/entry/star', {
id: entries.entries[0].id,
feedId: parseInt(entries.entries[0].feedId),
starred: true,
});
Python Example
import os
import requests
COMMAFEED_HOST = os.environ["COMMAFEED_HOST"]
COMMAFEED_USER = os.environ["COMMAFEED_USER"]
COMMAFEED_PASS = os.environ["COMMAFEED_PASS"]
AUTH = (COMMAFEED_USER, COMMAFEED_PASS)
HEADERS = {"Content-Type": "application/json"}
def cf_api(method, path, json=None):
url = f"{COMMAFEED_HOST}/rest{path}"
resp = requests.request(method, url, auth=AUTH, headers=HEADERS, json=json)
resp.raise_for_status()
return resp.json() if resp.text else None
# Get category tree
tree = cf_api("GET", "/category/get")
for cat in tree["children"]:
print(f"Category: {cat['name']}")
for feed in cat["feeds"]:
print(f" {feed['name']} — unread: {feed['unread']}")
# Get unread entries
entries = cf_api("GET", "/category/entries?id=all&readType=unread&limit=20")
for e in entries["entries"]:
print(f"[{e['feedName']}] {e['title']}")
# Subscribe to a feed
sub_id = cf_api("POST", "/feed/subscribe", {
"url": "https://example.com/rss",
"title": "Example Feed",
})
print(f"Subscribed, ID: {sub_id}")
# Export OPML
resp = requests.get(f"{COMMAFEED_HOST}/rest/feed/export", auth=AUTH)
with open("backup.opml", "w") as f:
f.write(resp.text)
Error Handling
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (missing/invalid fields) |
| 401 | Not authorized (wrong credentials) |
| 403 | Forbidden (insufficient role — need ADMIN) |
| 404 | Resource not found (wrong feed/entry/category ID) |
| 429 | Too many requests (refreshAll rate limit) |
| 500 | Server error |
Implementation Notes
- Always read
COMMAFEED_HOST,COMMAFEED_USER,COMMAFEED_PASSfrom environment variables — never hardcode - Authentication is HTTP Basic Auth, not API key headers
- The
/rest/server/getendpoint does not require authentication - Entry IDs follow the format
feed/{feedId}:entry/{guid}— preserve them as strings - Timestamps are in milliseconds (Unix epoch) for
newerThan/olderThanparameters - OPML import uses
multipart/form-data, all other POST endpoints useapplication/json - The
readTypeparameter is required for entry listing endpoints - Maximum
limitis 1000 entries per request — useoffset+hasMorefor pagination id=allmeans all feeds,id=starredmeans starred entries in category endpoints- Tags replace the full tag list on an entry — send the complete desired set
- CEL filter expressions on subscriptions auto-mark non-matching entries as read
More from biggora/claude-plugins-registry
captcha
>
32tailwindcss-best-practices
Tailwind CSS v4.x utility-first CSS framework best practices. Use when styling web applications with utility classes, building responsive layouts, customizing design systems with @theme variables, migrating from v3 to v4, configuring dark mode, creating custom utilities with @utility, or working with any Tailwind CSS v4 features. This skill covers the full v4.x line through v4.2 including text shadows, masks, logical properties, and source detection. Use this skill even for simple Tailwind questions — v4 changed many class names and configuration patterns that trip people up.
18gemini-cli
>
12vite-best-practices
Vite build tool configuration, plugin API, SSR, library mode, and Vite 8 Rolldown/Oxc migration. Use when working with Vite projects, vite.config.ts, Vite plugins, building libraries or SSR apps with Vite, migrating from older Vite versions, or configuring Rolldown/Oxc options. Also use when the user mentions HMR, import.meta.glob, virtual modules, or Vite environment variables.
12test-mobile-app
>
11typescript-expert
TypeScript language expertise covering the type system, generics, utility types, advanced type patterns, and project configuration. Use this skill whenever writing, reviewing, or refactoring TypeScript code, designing type-safe APIs, working with complex generics, debugging type errors, configuring tsconfig.json, migrating JavaScript to TypeScript, or leveraging TypeScript 5.x features like satisfies, const type parameters, decorators, and the using keyword. Also use when the user asks about type narrowing, conditional types, mapped types, template literal types, branded types, discriminated unions, or any TypeScript type system question — even seemingly simple ones, because TypeScript's type system has subtle gotchas that catch experienced developers.
11