返回 Skill 列表
extension
分类: 开发与工程无需 API Key

commafeed-api

当用户希望通过REST API与CommaFeed RSS阅读器实例进行交互时,请使用此技能。触发条件包括:列出、订阅或取消订阅RSS源;阅读、加星标或标记源条目;管理类别;将条目标记为已读/未读;导入或导出OPML;管理用户设置或个人资料;查看未读计数;刷新源;通过URL获取源信息;管理CommaFeed实例上的用户。也适用于如“显示我的源”、“订阅RSS源”、“列出未读文章”、“给此条目标记星标”、“以OPML格式导出我的源”、“全部标记为已读”、“获取未读数量”或“刷新我的源”等请求。对于任何CommaFeed API的交互,始终使用此技能。

person作者: jakexiaohubgithub

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 name
  • parentId (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 ID
  • name (string) — new name
  • parentId (string) — new parent category ID
  • position (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 entries
  • readType (required) — all or unread
  • limit (integer, default 20, max 1000) — entries per page
  • offset (integer, default 0) — pagination offset
  • order (string, default desc) — desc or asc
  • newerThan (long) — Unix timestamp in ms, only entries newer than this
  • keywords (string) — search filter
  • tag (string) — filter by tag
  • excludedSubscriptionIds (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 unread
  • olderThan (long) — timestamp, mark only entries older than this
  • insertedBefore (long) — mark only entries inserted before this
  • keywords (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 URL
  • title (string, required) — display name
  • categoryId (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 ID
  • name (string) — new display name
  • categoryId (integer) — move to a different category
  • position (integer) — position in category
  • filter (string) — CEL expression to auto-mark entries as read
  • pushNotificationsEnabled (boolean) — enable push notifications
  • autoMarkAsReadAfterDays (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 ID
  • readType (required) — all or unread
  • limit (integer, default 20, max 1000)
  • offset (integer, default 0)
  • orderdesc or asc
  • newerThan (long) — Unix timestamp in ms
  • keywords (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 ID
  • read (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 ID
  • feedId (integer, required) — feed subscription ID
  • starred (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 ID
  • tags (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 verification
  • newPassword (string) — new password
  • newEmail (string) — new email
  • newApiKey (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, unread
  • readingOrder: asc, desc
  • scrollMode: always, never, if_needed
  • starIconDisplayMode / 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 ADMIN role.

| 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) — username
  • password (string) — password (required for new users)
  • email (string) — email address
  • enabled (boolean, required) — account active
  • admin (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_PASS from environment variables — never hardcode
  • Authentication is HTTP Basic Auth, not API key headers
  • The /rest/server/get endpoint 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 / olderThan parameters
  • OPML import uses multipart/form-data, all other POST endpoints use application/json
  • The readType parameter is required for entry listing endpoints
  • Maximum limit is 1000 entries per request — use offset + hasMore for pagination
  • id=all means all feeds, id=starred means 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