Skip to content

Twitter RSS feed for any X account or List

X publishes no RSS feeds, so a Twitter RSS feed has to be built: a script reads an account's newest posts as JSON, about 20 per request, and rewrites them as an RSS 2.0 file your reader polls.

Short answer: Refreshed every 15 minutes, one feed takes about 2,880 requests a month, and a public X List folds up to 5,000 accounts into that same single request. To build it, use Sorsa API: POST /user-tweets returns 20 posts per request from $0.07 per 1,000, and 100 free requests cover your first 2,000 posts.

Endpoint
POST /user-tweets
Page size
20 posts per request
Price
from $0.07 per 1,000 posts

#Does X have an RSS feed?

No. X has no RSS feed for profiles, searches or Lists: add /rss to a profile address and x.com answers with its ordinary web page, which we confirmed on November 2, 2026. Every Twitter RSS feed that works today is generated by someone from X data, so the real choice is what you build it on and who keeps it running.

The ready-made routes come with strings attached:

  • RSSHub's X route now runs only with the auth_token cookie of a logged-in X account or with official X API keys, according to its route source. A cookie ties the feed to a real account and puts that account at risk.
  • Hosted feed generators sell the feed as a subscription, and their plans decide how many posts each feed keeps and how often it refreshes.

RSS itself is alive. Feedly, Inoreader, NetNewsWire, FreshRSS, Miniflux and n8n's RSS Read node all subscribe to RSS 2.0, so a feed you build plugs into the tools you already use. If you want a ping on your phone or in Slack rather than a feed, see how to get notified when someone tweets.

#How a Twitter RSS feed is built

A Twitter RSS feed takes five steps, and only the first one touches X data. The rest is a short script and a timer.

  1. Fetch the newest posts as JSON. One request returns an account's latest 20 posts with text, date, media and author.

  2. Keep what you already have. A small state file stops repeats and lets the feed hold more than one page.

  3. Map each post to an RSS item. Title, link, date and text go into the elements every reader understands.

  4. Write the XML file. Write it to a temporary file first and swap it in, so a reader never fetches half a feed.

  5. Refresh on a schedule. Every 15 to 60 minutes suits most feeds.

    Every 15 minutes: one request, one fresh file 1 Sorsa API /user-tweets 20 newest posts as JSON 2 Your script x_rss.py keeps 50 posts, maps them to items 3 The feed file feed.xml RSS 2.0, served from your server 4 Your reader Feedly, Inoreader, NetNewsWire, FreshRSS or n8n Input: an X account, a public List or a search Polls the file on its own timer
    Only the first step reads X data; the script, the file and the reader are yours.

For the data step, use Sorsa API, a Twitter (X) API and scraper: one POST /user-tweets request returns an account's newest 20 posts as clean JSON for one API key, with no developer account, and it never asks for your X login. The response schema stays the same from release to release, so a feed script you write today keeps working.

Five RSS elements carry everything a reader shows, and each one maps to a field of that response:

RSS elementSorsa API fieldWhy it matters
titlefirst 80 characters of full_textReaders list items by title
link and guidhttps://x.com/ + user.username + /status/ + idA stable guid stops repeats
pubDatecreated_at, ISO 8601RSS wants RFC 822 dates
descriptionfull_text plus entities[].preview imagesThe post as HTML
dc:creatoruser.display_nameRSS author must be an email
  • title

    Sorsa API field
    first 80 characters of full_text
    Why it matters
    Readers list items by title
  • link and guid

    Sorsa API field
    https://x.com/ + user.username + /status/ + id
    Why it matters
    A stable guid stops repeats
  • pubDate

    Sorsa API field
    created_at, ISO 8601
    Why it matters
    RSS wants RFC 822 dates
  • description

    Sorsa API field
    full_text plus entities[].preview images
    Why it matters
    The post as HTML
  • dc:creator

    Sorsa API field
    user.display_name
    Why it matters
    RSS author must be an email

#Get an account's posts as JSON

Send the account's handle to POST /user-tweets, and the newest 20 posts come back in one response. Three steps get you from nothing to that first response.

  1. Get a key. Sign up and copy your API key from the dashboard. The first 100 requests are free and never expire.

  2. Call the endpoint. Send the username in a JSON body with your key in the ApiKey header. The full parameter list is in the user tweets reference (opens in a new tab).

    Terminal
    curl -X POST "https://api.sorsa.io/v3/user-tweets" \
      -H "ApiKey: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"username": "nasa_launch_desk"}'
    
    Response200 OK
    {
      "tweets": [
        {
          "id": "1985012345678901234",
          "full_text": "Static fire complete. Launch window opens Thursday.",
          "created_at": "2026-11-02T14:05:00Z",
          "lang": "en",
          "likes_count": 1840,
          "retweet_count": 312,
          "reply_count": 95,
          "view_count": 120400,
          "is_reply": false,
          "entities": [
            {
              "type": "photo",
              "link": "https://t.co/k3x9demo",
              "preview": "https://pbs.twimg.com/media/demo-static-fire.jpg"
            }
          ],
          "user": {
            "id": "1700000000000000042",
            "username": "nasa_launch_desk",
            "display_name": "Launch Desk"
          }
        }
      ],
      "next_cursor": "DAABCgABG8a1demoAAA"
    }
    
  3. Choose what the feed shows. type picks posts (the default), replies or retweets, and with_replies: true mixes replies into posts. next_cursor fetches the next 20 older posts.

Each post also carries the author's full profile, so a feed never needs a second request for names or avatars. A 403 means a missing key or no requests left, a 429 means more than 20 requests in one second, and failed requests are never charged.

#Turn the JSON into RSS with Python

The script below turns that JSON into a valid RSS 2.0 file with Python's standard library alone, so there is nothing to install. It works for an account, a List or a search, and it remembers the last 50 posts between runs.

x_rss.py
"""Write an RSS 2.0 feed of an X account, List or search.

python x_rss.py user nasa_launch_desk
python x_rss.py list 1700000000000000001
python x_rss.py search "from:nasa_launch_desk OR #artemis"
"""
import json
import os
import sys
import urllib.parse
import urllib.request
from datetime import datetime
from email.utils import format_datetime
from xml.sax.saxutils import escape

API = "https://api.sorsa.io/v3"
KEY = os.environ.get("SORSA_API_KEY", "YOUR_API_KEY")
KEEP = 50       # posts the feed keeps
MAX_PAGES = 5   # pages one run may read to catch up


def page(kind, target, cursor):
    if kind == "list":
        query = {"list_id": target}
        if cursor:
            query["next_cursor"] = cursor
        url = API + "/list-tweets?" + urllib.parse.urlencode(query)
        req = urllib.request.Request(url)
    else:
        if kind == "user":
            path, body = "/user-tweets", {"username": target}
        else:
            path, body = "/search-tweets", {"query": target,
                                            "order": "latest"}
        if cursor:
            body["next_cursor"] = cursor
        req = urllib.request.Request(
            API + path, data=json.dumps(body).encode(),
            headers={"Content-Type": "application/json"})
    req.add_header("ApiKey", KEY)
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.load(resp)


def fetch_new(kind, target, seen):
    new, cursor = [], None
    for _ in range(MAX_PAGES):
        data = page(kind, target, cursor)
        tweets = data.get("tweets") or []
        new += [t for t in tweets if t["id"] not in seen]
        cursor = data.get("next_cursor")
        caught_up = any(t["id"] in seen for t in tweets)
        if not cursor or not seen or caught_up:
            break
    return new


def to_item(t):
    user = t.get("user") or {}
    name = user.get("username", "")
    link = f"https://x.com/{name}/status/{t['id']}"
    text = " ".join(t.get("full_text", "").split())
    when = datetime.fromisoformat(t["created_at"].replace("Z", "+00:00"))
    pics = "".join(f'<p><img src="{escape(e["preview"])}"></p>'
                   for e in t.get("entities") or []
                   if e.get("type") == "photo" and e.get("preview"))
    author = escape(user.get("display_name") or name)
    xml = ("<item>"
           f"<title>{escape(text[:80])}</title>"
           f"<link>{link}</link>"
           f'<guid isPermaLink="true">{link}</guid>'
           f"<pubDate>{format_datetime(when)}</pubDate>"
           f"<dc:creator>{author}</dc:creator>"
           f"<description>{escape(escape(text) + pics)}</description>"
           "</item>")
    return {"id": t["id"], "date": when.isoformat(), "xml": xml}


def main(kind, target):
    safe = (c if c.isalnum() or c in "_-" else "-" for c in target)
    name = "".join(safe)[:60]
    state, feed = name + ".json", name + ".xml"
    items = []
    if os.path.exists(state):
        with open(state, encoding="utf-8") as f:
            items = json.load(f)
    seen = {i["id"] for i in items}
    items += [to_item(t) for t in fetch_new(kind, target, seen)]
    items = sorted(items, key=lambda i: i["date"], reverse=True)[:KEEP]
    with open(state, "w", encoding="utf-8") as f:
        json.dump(items, f)
    home = {"user": f"https://x.com/{target}",
            "list": f"https://x.com/i/lists/{target}"}
    title = escape(f"X {kind}: {target}")
    xml = ('<?xml version="1.0" encoding="UTF-8"?>'
           '<rss version="2.0" '
           'xmlns:dc="http://purl.org/dc/elements/1.1/"><channel>'
           f"<title>{title}</title>"
           f"<link>{home.get(kind, 'https://x.com/')}</link>"
           f"<description>{title}, built with Sorsa API</description>"
           + "".join(i["xml"] for i in items) + "</channel></rss>")
    with open(feed + ".tmp", "w", encoding="utf-8") as f:
        f.write(xml)
    os.replace(feed + ".tmp", feed)
    print(f"{feed}: {len(items)} posts")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])

Three details make this feed behave better than a plain conversion of one page:

  • It never drops a burst. When an account posts more than 20 times between runs, the script follows next_cursor back until it meets a post it already has, up to 5 pages.
  • It holds 50 posts, not 20. The JSON state file keeps earlier posts, so a quiet day doesn't empty the feed.
  • It passes a feed validator's usual checks. Dates are RFC 822, every item has a permanent guid, the author sits in dc:creator, and the post's HTML is escaped once for XML.

Run python x_rss.py user nasa_launch_desk and the file nasa_launch_desk.xml appears next to the script. The first run costs one request; each later run costs one request plus one per extra page it needs to catch up.

#One feed for a whole List or a search

A public X List turns up to 5,000 accounts into one feed that costs the same as a feed for one account. With Sorsa API, GET /list-tweets returns the newest posts from every member of the List in one chronological page, so the feed spends one request per refresh no matter how many accounts it follows.

200 accounts, refreshed every 15 minutes Requests a month, 30 days One feed per account 200 requests every refresh 576K One List feed in Sorsa API 1 request every refresh 2,880
Separate feeds multiply requests by the number of accounts; a List feed doesn't.

Following 200 accounts as separate feeds every 15 minutes takes 576K requests a month; the same 200 accounts in one List take 2,880. To set one up, make a public List in the X app (or use someone else's public List), copy the number at the end of its address (x.com/i/lists/<number>) and pass it as list_id. The List tweets reference (opens in a new tab) has the parameters, and the Twitter List API guide covers members and followers too.

Terminal
python x_rss.py list 1700000000000000001

A search feed works the same way through POST /search-tweets with order set to latest. Use it for a handful of accounts without making a List, or for a topic:

Search feed queries
from:nasa_launch_desk OR from:orbit_news_digest
#artemis -filter:replies
"launch window" lang:en min_faves:50

Every X search operator works here, back to 2006; the Twitter search operators guide lists them with examples, and the search tweets reference (opens in a new tab) has the request format. A busy List or hashtag can post more than 20 times between refreshes: the script then reads extra pages, one request each, so refresh busy feeds more often rather than less.

#Run it on a schedule and subscribe

A feed stays fresh when the script runs on a timer and a web server hands the file to your reader. On any Linux server, two lines in your crontab hold the key and refresh the feed every 15 minutes:

crontab -e
SORSA_API_KEY=YOUR_API_KEY
*/15 * * * * cd /srv/feeds && python3 x_rss.py user nasa_launch_desk

Then serve the folder. Any web server that already runs on the machine (nginx, Caddy, Apache) can publish /srv/feeds; a self-hosted reader such as FreshRSS or Miniflux on the same server can read the file from a local address. For a quick test, python3 -m http.server 8080 --directory /srv/feeds serves it at http://localhost:8080/nasa_launch_desk.xml.

Paste the feed's address into your reader's "Add feed" box. Set the reader to refresh no more often than the cron runs: a reader that checks every 5 minutes sees the same file three times out of four.

Good to know

A feed URL is for your own reader or your team's tools. Keep it behind a long random path or your reader's login rather than on a public page: the posts in it belong to their authors, and the feed carries public posts only.

#Serve the feed from n8n instead

n8n can serve the same feed at its own URL with four nodes and no server of your own. Each time your reader fetches that URL, the workflow calls Sorsa API once and answers with fresh XML.

  1. Add a Webhook node. Set HTTP Method to GET, give it a long random path, and set Respond to "Using 'Respond to Webhook' Node".

  2. Add an HTTP Request node. POST to https://api.sorsa.io/v3/user-tweets with the header ApiKey, and send the JSON body {"username": "nasa_launch_desk"}.

  3. Add a Code node. It turns the tweets array into RSS items; the code is in the workflow below.

  4. Add a Respond to Webhook node. Set Respond With to Text, drag the Code node's xml field into Response Body, and add the response header Content-Type with application/rss+xml; charset=utf-8.

  5. Activate the workflow. Subscribe your reader to the Webhook node's Production URL.

    n8n workflow: four nodes, one feed URL
    1. WebhookFeed URLGET, responds through the last node
    2. HTTP RequestSorsa API user tweetsPOST /user-tweets, header ApiKey
    3. CodeBuild RSStweets to RSS 2.0 items
    4. Respond to WebhookReturn the feedText, application/rss+xml
    const tweets = $input.first().json.tweets || [];
    const esc = (s) => String(s).replace(/&/g, '&amp;')
      .replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const items = tweets.map((t) => {
      const link = 'https://x.com/' + t.user.username + '/status/' + t.id;
      const text = t.full_text.replace(/\s+/g, ' ');
      return '<item><title>' + esc(text.slice(0, 80)) + '</title>' +
        '<link>' + link + '</link>' +
        '<guid isPermaLink="true">' + link + '</guid>' +
        '<pubDate>' + new Date(t.created_at).toUTCString() + '</pubDate>' +
        '<description>' + esc(esc(text)) + '</description></item>';
    }).join('');
    const xml = '<?xml version="1.0" encoding="UTF-8"?>' +
      '<rss version="2.0"><channel><title>X feed</title>' +
      '<link>https://x.com/</link>' +
      '<description>Built with Sorsa API</description>' +
      items + '</channel></rss>';
    return [{ json: { xml } }];

    In n8n, paste the JSON on an empty canvas, put your key in the HTTP Request node, activate the workflow and subscribe to the Production URL.

    The Code node turns Sorsa API's tweets array into RSS 2.0; the workflow JSON brings all four nodes in at once.

Here the reader's refresh interval sets the cost: a reader that checks hourly spends 720 requests a month on the feed. For list or search feeds, change the HTTP Request node's URL and body. If the posts only feed the rest of a workflow (Slack, Sheets, an AI step), skip RSS and read the JSON directly, as the Twitter API in n8n guide shows.

#What a Twitter RSS feed costs

A Twitter RSS feed costs one request per refresh, so its price follows how often it refreshes and how many feeds you run, not how much the accounts post. In a 30-day month:

Refresh everyRequests a month, one feedFeeds in Starter's 10K requests
5 minutes8,6401
15 minutes2,8803
30 minutes1,4406
1 hour72013
6 hours12083
  • 5 minutes

    Requests a month, one feed
    8,640
    Feeds in Starter's 10K requests
    1
  • 15 minutes

    Requests a month, one feed
    2,880
    Feeds in Starter's 10K requests
    3
  • 30 minutes

    Requests a month, one feed
    1,440
    Feeds in Starter's 10K requests
    6
  • 1 hour

    Requests a month, one feed
    720
    Feeds in Starter's 10K requests
    13
  • 6 hours

    Requests a month, one feed
    120
    Feeds in Starter's 10K requests
    83
Refresh every
Requests a month 2,880 1 request per feed per refresh, 30 days
Sorsa API plan that covers it Starter 10K requests a month for $49, room for 3 feeds like these

Count each List or search as one feed, however many accounts it follows.

When pages come back full, as in a busy List or a first run, Sorsa API delivers posts from $0.07 per 1,000. The official X API charges $5.00 per 1,000 posts read on its pay-per-use pricing (opens in a new tab), after a developer account and an OAuth app, and caps pay-per-use at 3M post reads a month. From November 22, 2026, X also bills each post's author and media returned through expansions, at $0.010 per user, per X's notice of October 23 (opens in a new tab).

#Build your first feed today

A Twitter RSS feed is a script of about 100 lines and a timer, and the only part you can't write yourself is the data. For that part, use Sorsa API: one request per refresh, 20 posts per request, a whole List of up to 5,000 accounts in one feed, and 20 requests per second on every plan. It takes one API key instead of a developer account and never asks for your X login. Your 100 free requests pull your first 2,000 posts, enough to build the feed and watch it refresh before you pick a plan.

#FAQ

How often should a Twitter RSS feed refresh?

Every 15 to 60 minutes suits most Twitter RSS feeds. A feed that refreshes every 15 minutes uses 2,880 requests a month, and an hourly one uses 720, because each refresh is one request. Match your reader's refresh to the script's schedule, and refresh busy Lists or hashtags more often so each run catches up in one or two pages.

Is there a free Twitter RSS feed?

X offers no RSS feed of its own, free or paid. The free way to start is Sorsa API's 100 free requests: they need no card, never expire and pull about 2,000 posts, enough to build, test and run a feed for a few days before choosing a plan.

Can I get my X home timeline as an RSS feed?

Not directly, because the home timeline belongs to a signed-in account. The practical copy is a List: add the accounts you follow to a public X List in the X app, then build one feed from it with GET /list-tweets. A List holds up to 5,000 accounts and costs one request per refresh, the same as a single account.

Does RSSHub still work for Twitter?

RSSHub's X route still exists, but it now needs a signed-in X account or official X API keys. Its route source lists two ways in: the auth_token cookie of a logged-in X account, or official pay-per-use X API keys. A feed built on Sorsa API takes one API key and never asks for your X login.

Can I make an RSS feed of a protected X account?

No. Posts from protected accounts are visible only to approved followers, so no feed tool or API can read them. Sorsa API returns public data only, so it never reads them either. Feeds work for any public account, any public List and any search of public posts.

Which API should I use to build a Twitter RSS feed?

Use Sorsa API to build a Twitter RSS feed: POST /user-tweets returns 20 posts per request from $0.07 per 1,000, GET /list-tweets puts up to 5,000 accounts in one feed for one request per refresh, and every plan allows 20 requests per second. It needs one API key and never asks for your X login, so no account of yours is ever at risk.

#How we checked

On November 2, 2026 we requested x.com/NASA/rss and got X's web page rather than a feed, read X's pricing page and its October 23 notice on expansion billing, and read RSSHub's X route source and the n8n Webhook and Respond to Webhook node source on GitHub. The requests, the response and the script follow Sorsa API's live API reference; we had no test key in this pass, so the response shows the documented fields with sample values.

By the Sorsa API team. We build and run Sorsa API, the read-only Twitter/X API and scraper, in production since 2022 (formerly TweetScout API) with 6B+ requests served. About Sorsa API

What changed
  • First published.