GuidesFEEDS
A free rss2json alternative
You can replace rss2json with proxy.cors.dev plus a 30-line DOMParser function that returns the same status, feed and items JSON for RSS 2.0 and Atom feeds. The free proxy needs no signup or key, allows GET and HEAD at about 600 requests a minute per IP, and returns every item in the feed up to 6 MiB, while rss2json's Free plan returns 10 items unless you pass a key and allows 10,000 requests a day. On 28 October 2026 every request we sent to api.rss2json.com got a Cloudflare challenge page with HTTP 403.
What rss2json offers
rss2json converts an XML feed into JSON at https://api.rss2json.com/v1/api.json?rss_url=<URL-encoded feed URL>. The api_key parameter is optional, though the count parameter (which defaults to 10 items), order_by and order_dir only work if you pass an api_key. You can also pass a callback parameter to get JSONP instead of standard JSON.
When a request succeeds, the response returns status set to "ok", a feed object (url, title, link, author, description, image) and an items array (title, pubDate, link, guid, author, thumbnail, description, content, enclosure, categories).
rss2json.com/plans, as archived on 18 October 2026, lists three plans. Paid plans can be billed for 1, 3, 6 or 12 months at 0, 3, 5 or 10 percent off.
| Plan | Price | Requests a day | Feeds | Feed refresh |
|---|---|---|---|---|
| rss2json's Free plan | $0 | 10,000 | 25 | every 60 minutes |
| rss2json's Basic plan | $4 a month | 1,000,000 | 1,500 | every 15 minutes |
| rss2json's Pro plan | $8 a month | 2,000,000 | 4,500 | every 5 minutes |
rss2json from the browser on 28 October 2026
During a probe on 28 October 2026, every request to api.rss2json.com, along with the rss2json.com home, /plans and /docs pages, returned a Cloudflare "Just a moment..." challenge page with HTTP 403. That happened from a server, through cors.dev, from 3 residential IP addresses sending Chrome request headers, and inside a real headless Firefox browser.
Because the Cloudflare challenge page doesn't include an Access-Control-Allow-Origin header, the browser blocks the response before your code can inspect it. Your fetch() call rejects with a TypeError instead of returning JSON or a readable 403 status. A fetch() cannot complete a Cloudflare challenge, so while rss2json serves one, browser calls fail too.
Convert RSS to JSON in the browser
You can replace rss2json by fetching the raw XML feed through https://proxy.cors.dev/<feed URL> and parsing it in the browser with DOMParser. The feedToJson() function below takes 30 lines of JavaScript, returns the same status, feed and items shape as rss2json for RSS 2.0, RSS 1.0 (RDF) and Atom feeds, and throws "Not an RSS or Atom feed" for anything else.
const PROXY = 'https://proxy.cors.dev/';
async function feedToJson(feedUrl) {
const res = await fetch(PROXY + feedUrl);
if (!res.ok) throw new Error(`Feed answered ${res.status} ${res.headers.get('X-Cors-Error') ?? ''}`);
const root = new DOMParser().parseFromString(await res.text(), 'text/xml').documentElement;
if (!['rss', 'feed', 'RDF'].includes(root.localName)) throw new Error('Not an RSS or Atom feed');
const atom = root.localName === 'feed';
const text = (el, ...tags) => tags.map((t) => el.getElementsByTagName(t)[0]?.textContent.trim()).find(Boolean) ?? '';
const href = (el, rel) => el.querySelector(`link[rel="${rel}"]`)?.getAttribute('href') ?? '';
const link = (el) => (atom ? href(el, 'alternate') || el.querySelector('link')?.getAttribute('href') || '' : text(el, 'link'));
const channel = atom ? root : root.querySelector('channel');
return {
status: 'ok',
feed: { url: feedUrl, title: text(channel, 'title'), link: link(channel), description: text(channel, 'description', 'subtitle') },
items: [...root.querySelectorAll(atom ? 'entry' : 'item')].map((item) => ({
title: text(item, 'title'),
pubDate: text(item, 'pubDate', 'published', 'updated', 'dc:date'),
link: link(item),
guid: text(item, 'guid', 'id'),
author: text(item, 'dc:creator', 'name', 'author'),
description: text(item, 'description', 'summary'),
content: text(item, 'content:encoded', 'content'),
enclosure: item.querySelector('enclosure')?.getAttribute('url') ?? href(item, 'enclosure'),
categories: [...item.getElementsByTagName('category')].map((c) => c.getAttribute('term') ?? c.textContent.trim()),
})),
};
}
console.log(await feedToJson('https://github.blog/feed/'));To put this in place:
- Check if the feed needs a proxy
If a feed already sends
Access-Control-Allow-Origin, drop the proxy prefix and fetch it directly. NASA's WordPress feed sendsAccess-Control-Allow-Origin: *, for example, whereas BBC News andhnrss.orgsend none. - Fetch and parse the XML
Pass the target URL to
feedToJson(), which prependshttps://proxy.cors.dev/(such ashttps://proxy.cors.dev/https://example.com/feed) and runs the response throughDOMParser. - Render the items safely
The
descriptionandcontentfields contain HTML written by a third party. Sanitize them before inserting them into your page, or render them as plain text; our guide to fetching and parsing RSS feeds covers safe rendering.
On 28 October 2026, we tested feedToJson() against several live feeds:
- WordPress News in RSS 2.0 (10 items) and Atom (10 entries)
- The GitHub Blog (10 items) and GitHub releases in Atom (10 entries)
- A YouTube channel's Atom feed (15 entries)
hnrss.org(20 items) and a Substack newsletter (20 items)- BBC News (32 items)
- Slashdot (15 items) and Debian News (6 items) in RSS 1.0
- A plain HTML page, which threw
"Not an RSS or Atom feed"as intended
How RSS and Atom fields map to JSON
RSS and Atom name several elements differently. feedToJson() normalizes them into the object properties that rss2json returns.
| JSON field | RSS 2.0 source | Atom source |
|---|---|---|
| title | item title | entry title |
| pubDate | pubDate (dc:date in RSS 1.0) | published, else updated |
| link | link element text | href of link rel="alternate" |
| guid | item guid | entry id |
| author | dc:creator or author | author name |
| description | item description | entry summary |
| content | content:encoded | entry content |
| enclosure | enclosure url attribute | href of link rel="enclosure" |
| categories | category text | category term attribute |
rss2json also returns an item thumbnail field along with the feed author and image. feedToJson() leaves those out, but you can add them with one line each if your layout uses them. RSS 1.0 items have no guid, so that field comes back empty for them.
For pubDate, feedToJson() keeps the feed's own string format: RFC 822 in RSS ("Wed, 28 Oct 2026 17:13:02 +0000") and ISO 8601 in Atom ("2026-10-28T17:13:02Z"). JavaScript's new Date() parses both formats without extra code.
Free limits side by side
| Limit | rss2json Free plan | cors.dev free tier |
|---|---|---|
| Items per feed | 10 without a key | Every item in the feed up to 6 MiB |
| Requests | 10,000 a day | About 600 a minute per IP, no daily quota |
| Feeds | 25 | Any |
| Freshness | Cached, refreshed every 60 minutes | Fetched live on every request |
| API key | Optional, needed for count and ordering | None |
| Output | JSON or JSONP | Feed XML converted by your own code |
The cors.dev free tier requires no signup and no key. It supports GET and HEAD requests at up to about 600 requests a minute per IP, answering 429 with a Retry-After header past that limit, and has no daily or monthly quota. Each upstream request has a 30-second deadline and can return up to 6 MiB (6,291,456 bytes); larger responses fail with HTTP 502 and the header X-Cors-Error: response_too_large.
Because the free tier doesn't cache responses, every page view fetches the feed live. cors.dev Pro ($5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card) can cache a GET for up to 300 seconds with the X-Cors-Cache header when the upstream response allows it. On 28 October 2026 the GitHub Blog feed (max-age=300) qualified for the full 300 seconds, BBC News (max-age=2) capped it at 2 seconds, and Substack feeds (Set-Cookie, no-cache) are never cached. Cache hits still count as requests.
Good questions.
Why is rss2json throwing a CORS TypeError in my browser?
On 28 October 2026, requests to api.rss2json.com returned an HTTP 403 Cloudflare challenge page instead of JSON. Because that challenge response doesn't include an Access-Control-Allow-Origin header, the browser blocks your script from reading it and fetch() rejects with a TypeError.
How do I get more than 10 items from an RSS feed without an API key?
rss2json's Free plan defaults to 10 items per feed unless you pass an api_key with the count parameter. When you fetch the XML through https://proxy.cors.dev/<feed URL> and run it through feedToJson(), you get every item published in the feed file up to the 6 MiB response cap. In our 28 October 2026 tests, that returned 15 entries from YouTube, 20 items from Substack and hnrss.org, and 32 items from BBC News.
How do I convert an Atom or RSS 1.0 feed to JSON in JavaScript?
Use the same feedToJson() function: it detects RSS 2.0, RSS 1.0 (RDF) and Atom from the root element and maps format-specific elements such as RSS 1.0's dc:date and Atom's published, summary and link rel="alternate" to rss2json's property names. On 28 October 2026 it returned 15 items from Slashdot's RSS 1.0 feed and 10 entries from the WordPress News Atom feed. Anything else throws "Not an RSS or Atom feed".
Can I cache feed responses with cors.dev so my site does not fetch the XML on every page view?
The free tier fetches the feed live on every request and caches nothing. On cors.dev Pro, X-Cors-Cache: 300 caches a GET for up to 300 seconds when the upstream allows it: status 200, no Set-Cookie, no private, no-store or no-cache, no key or token in the query string, and a TTL capped by the feed's own max-age. X-Cors-Cache-Status and X-Cors-Cache-Reason tell your code whether a response was stored, and cache hits still count toward your 500,000 monthly requests.
What else is included in cors.dev Pro if I upgrade?
For $5 a month, cors.dev Pro gives you 500,000 requests, a throughput of 3,000 requests a minute per account with 100 in flight, and only answers your exact allowed origins. It also adds POST, PUT, PATCH and DELETE methods, request bodies up to 1 MiB, and forwards Authorization and custom headers, while keeping the same 6 MiB response cap and 30-second deadline as the free tier. Note that cors.dev Pro does not hide an API key: anything your browser code sends is visible to anyone using the page.