The REST API

Your own scripts and tools can read your feeds, mark articles read and follow new feeds, through the same API the Feedmesh apps use. It's JSON over HTTPS, and you sign in with a personal access token. Making an app other people sign in to? Use OAuth instead.

Get a token

  1. Go to Account > Developer (sign in first).
  2. Name the token after what will use it, choose what it may do, and when it expires: in 30 days, 90 days, a year, or never.
  3. Copy it when it's shown. Feedmesh keeps only a fingerprint of it, so it can't be shown again. Lost it? Revoke it and make another.

A token looks like fm_pat_ followed by 43 letters, digits, - and _. Keep it like a password: out of code you share, and in an environment variable instead.

export FEEDMESH_TOKEN="fm_pat_..."

An account can have up to 20 tokens. Revoke one on the same page and it stops working.

Building an app for other people? Use OAuth

A personal access token is for your own scripts: you make it, you paste it in, and it's your account. If you're making something other people sign in to (a reader app, a website, a bot), don't ask them for a token or their password. Use OAuth: each person signs in on feedmesh.com, sees what your app asks for, and can disconnect it whenever they like.

Personal access tokenOAuth
ForYour own scripts and toolsApps other people use
Signing inMake a token on Account > DeveloperThe person signs in on feedmesh.com and allows your app
The tokenLasts until it expires or you revoke itAn access token for an hour, renewed with a refresh token
Taking it backRevoke it on Account > DeveloperDisconnect it under Connected apps on the Account page

It's OAuth 2.1's authorization code flow with PKCE, the same one Claude and ChatGPT use to connect to Feedmesh. There's nothing to apply for: your app registers itself. Everything else (scopes, limits, errors) is as on the rest of this page: a connected app gets feeds.read and, if the person allows it, feeds.write, and never the account itself.

1. Find the addresses

They're in the standard metadata (RFC 8414), so an OAuth library can find them itself:

curl https://feedmesh.com/.well-known/oauth-authorization-server
RegisterPOST https://feedmesh.com/api/v2/oauth/register
AuthorizeGET https://feedmesh.com/api/v2/oauth/authorize
TokensPOST https://feedmesh.com/api/v2/oauth/token
RevokePOST https://feedmesh.com/api/v2/oauth/revoke

2. Register your app, once

Dynamic client registration (RFC 7591). The name is what people see when they allow it.

curl -X POST -H "Content-Type: application/json" https://feedmesh.com/api/v2/oauth/register \
  -d '{"client_name": "My Reader", "redirect_uris": ["https://myreader.example/callback"]}'

The answer has your client_id (it starts with fm_). Keep it; register again only if your addresses change.

3. Send the person to sign in

Make a PKCE pair for each sign-in (a random code_verifier, and its SHA-256 in base64url as the code_challenge), and a random state. Then open, in their browser:

https://feedmesh.com/api/v2/oauth/authorize?response_type=code
  &client_id=fm_...
  &redirect_uri=https%3A%2F%2Fmyreader.example%2Fcallback
  &scope=feeds.read%20feeds.write%20offline_access
  &state=...
  &code_challenge=...
  &code_challenge_method=S256

Ask only for what you need: feeds.read to read, feeds.write to change things, and offline_access for a refresh token, so people don't have to sign in again every hour. They sign in to Feedmesh as usual (email, Google or Apple), see your app's name and what it asks for, and choose Allow or Cancel. Then they come back to your redirect address with ?code=...&state=... (check the state is yours), or with ?error=access_denied.

4. Swap the code for tokens

curl -X POST https://feedmesh.com/api/v2/oauth/token \
  -d grant_type=authorization_code \
  -d client_id=fm_... \
  -d code=... \
  -d redirect_uri=https://myreader.example/callback \
  -d code_verifier=...

(A web service adds -d client_secret=....) The answer has an access_token for an hour and, with offline_access, a refresh_token. Send the access token just like a personal one: Authorization: Bearer .... Requests count against the person's own plan and limits.

5. Renew it

curl -X POST https://feedmesh.com/api/v2/oauth/token \
  -d grant_type=refresh_token \
  -d client_id=fm_... \
  -d refresh_token=...

Each refresh gives a new refresh token too: keep the new one, as the old one stops working. A refresh token lasts 90 days, so an app that's used keeps working. A 401 on a request means the access token ran out: refresh and try again. If the refresh fails, the person disconnected your app (or didn't use it for 90 days): send them to sign in again.

To sign someone out of your app, send their refresh token to POST https://feedmesh.com/api/v2/oauth/revoke with your client_id.

Making requests

The base address https://feedmesh.com/api/v2

Send the token (a personal one, or an OAuth access token) in the Authorization header of every request:

Authorization: Bearer fm_pat_...

For example, the feeds you follow:

curl -H "Authorization: Bearer $FEEDMESH_TOKEN" https://feedmesh.com/api/v2/subscriptions

HTTPS only. Every endpoint, with its parameters and answers, is in the API reference.

Read and change

When you make a token, you choose what it may do:

AccessScopeWhat it can do
Read onlyfeeds.readEvery GET: your categories and feeds, their articles, full text, saved and recently read articles, article search, Discover, notification settings and Local News
Read and changefeeds.read feeds.writeAlso everything else: mark read and unread, save, follow and unfollow, rename and move feeds, categories, notifications, Local News follows

A request the token can't make gets 403 (see Errors). No token can change your account itself: email, password, sign-in, or deleting it.

Plans and limits

The API is part of Feedmesh Plus, for personal tokens and connected apps alike. Reading in the Feedmesh apps and on this website stays free. An account without Plus gets 403 with the code plus_required (only GET /me/plan answers it, so a script can tell why).

With Feedmesh Plus
Reading and changesYes
Requests60 a minute
Searches (feeds and articles)30 a minute
Full-text articles300 a day, shared with MCP
Feeds followed20 a day

The API is for your own use: see the rules.

JSON and conventions

Paging

Lists of articles (a feed's, saved, recently read, search) come a page at a time:

params = {"unreadOnly": "true", "limit": 100}
while True:
    page = feedmesh.get(f"{API}/subscriptions/{sub_id}/items", params=params).json()
    for item in page["items"]:
        print(item["title"])
    if not page["nextCursor"]:
        break
    params["cursor"] = page["nextCursor"]

Errors

Errors are problem details with a code to check in your code (the title is for people, and its words may change):

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "type": "https://feedmesh.com/api/v2/errors/already-subscribed",
  "title": "Already subscribed to that feed.",
  "status": 409,
  "code": "already_subscribed",
  "subscriptionId": "6f1d6c3e-2b0a-4c47-9d0e-8a5b7e2f4c19"
}
StatusWhat it means
400Something in the request isn't right; code says what, like invalid_cursor or missing_subscription
401No token, or it's expired or revoked (an OAuth access token lasts an hour: refresh it)
403insufficient_scope: the token is read only. plus_required: the API is part of Feedmesh Plus, and the account doesn't have it (any more)
404Not found, or not yours: another account's feeds and articles are simply not there
409A conflict: already_subscribed, similar_feed (Feedmesh has a feed at a very similar address; the answer's feed is it), category_limit (10 categories at most)
410feed_ended: the feed stopped for good; note says why
429Too many requests this minute: wait the seconds in the Retry-After header. Or allowance_used: the day's full texts or follows are used up, until resetsAt

Examples

In curl and in Python with requests. The Python examples follow on from the first one's feedmesh session.

1. List the feeds you follow

curl -H "Authorization: Bearer $FEEDMESH_TOKEN" https://feedmesh.com/api/v2/subscriptions
import os
import requests

API = "https://feedmesh.com/api/v2"
feedmesh = requests.Session()
feedmesh.headers["Authorization"] = "Bearer " + os.environ["FEEDMESH_TOKEN"]

subscriptions = feedmesh.get(f"{API}/subscriptions").json()
for subscription in subscriptions:
    print(subscription["id"], subscription["title"], subscription["status"])

Each one has its id, title, categoryId, feeds and status (ok, new, or broken with a statusNote saying why). Add ?category= with an ID from GET /categories, or none for the ones in no category.

2. Get a feed's unread articles

curl -H "Authorization: Bearer $FEEDMESH_TOKEN" "https://feedmesh.com/api/v2/subscriptions/$SUB_ID/items?unreadOnly=true&limit=10"
sub_id = subscriptions[0]["id"]
page = feedmesh.get(f"{API}/subscriptions/{sub_id}/items", params={"unreadOnly": "true", "limit": 10}).json()
for item in page["items"]:
    print(item["datePublished"], item["feedTitle"], item["title"])

The answer, shortened:

{
  "items": [
    {
      "id": "3c1d2a8e-5b7f-4f0e-9a43-2a1f6e0b9c11",
      "title": "The best laptops of the year",
      "author": "Jane Doe",
      "datePublished": "2026-11-02T13:05:00Z",
      "wordCount": 812,
      "isRead": false,
      "feedTitle": "The Verge"
    }
  ],
  "nextCursor": "cDIuMTA",
  "status": "ok"
}

The article itself, with its link and summary, is GET /items/{id}?subscription={id}. Add &fullText=true for the full text (it counts toward the day's full texts):

item_id = page["items"][0]["id"]
article = feedmesh.get(f"{API}/items/{item_id}", params={"subscription": sub_id}).json()
print(article["title"], article["link"])
# content is the summary as blocks: paragraphs and headings have spans of text (and links)
for block in article["content"] or []:
    print("".join(span["text"] for span in block.get("spans", [])))

3. Mark an article read

Needs a token that can change things. The answer is 204 No Content, and sending it twice is fine.

curl -X PUT -H "Authorization: Bearer $FEEDMESH_TOKEN" "https://feedmesh.com/api/v2/items/$ITEM_ID/read?subscription=$SUB_ID"
r = feedmesh.put(f"{API}/items/{item_id}/read", params={"subscription": sub_id})
r.raise_for_status()

DELETE on the same address marks it unread. To mark a whole feed read, POST /subscriptions/{id}/read with {} (everything up to now) or {"readTo": "2026-11-02T12:00:00Z"}.

4. Find a feed and follow it

Needs a token that can change things, and counts toward the day's follows.

curl -H "Authorization: Bearer $FEEDMESH_TOKEN" "https://feedmesh.com/api/v2/discover/search?q=the+verge"

curl -X POST -H "Authorization: Bearer $FEEDMESH_TOKEN" -H "Content-Type: application/json" \
  -d '{"url": "https://www.theverge.com/rss/index.xml"}' https://feedmesh.com/api/v2/subscriptions
found = feedmesh.get(f"{API}/discover/search", params={"q": "the verge"}).json()
feed = found[0]
# Feeds Feedmesh already has come with a feedId; ones found on the web, with their url
body = {"feedId": feed["feedId"]} if feed["feedId"] else {"url": feed["url"]}
r = feedmesh.post(f"{API}/subscriptions", json=body)
if r.status_code == 409 and r.json()["code"] == "already_subscribed":
    print("Already following it:", r.json()["subscriptionId"])
else:
    r.raise_for_status()
    print("Following", r.json()["title"])

A new follow answers 201 Created with the subscription. Add "categoryId" to put it in one of your categories.

Be kind to the limits

import time

def get(path, **params):
    while True:
        r = feedmesh.get(f"{API}{path}", params=params)
        if r.status_code == 429 and "Retry-After" in r.headers:
            time.sleep(int(r.headers["Retry-After"]))
            continue
        r.raise_for_status()
        return r.json()

Questions

Something missing, or not working as written? Email [email protected]. Your tokens are under Account > Developer.

Before you build

The rules

The API, the MCP server and RSS app sync are for you: your own reading, your own AI assistant, and automating your own workflow, through your own account.

  • Lists, search and RSS app sync give titles, links and the feed's short summaries. The full text comes one article at a time, when you (or your agent, for you) ask for it, like a browser's Reader View.
  • Full texts are 10 a day on Free and 300 a day on Feedmesh Plus: one count, shared by MCP and the REST API. Account > Usage shows today's.
  • Don't use any of it to bulk-export articles, build datasets (to sell or otherwise), republish or redistribute publishers' articles, or train, fine-tune or ground AI models, or feed an AI product, search index or RAG system that serves anyone but you.
  • One account per person. Don't use extra accounts or tokens to get around the daily amounts.
  • Articles belong to their publishers. Read them for yourself, and link to them rather than copying them.

The legal version is section 5 of the Terms of Service. Feedmesh may limit or stop access that breaks these rules. Not sure whether something fits? Ask at [email protected].