# RSS and Atom Feed Reader (bulk, with feed discovery) (`steadydata/rss-feed-reader`) Actor

Read up to 200 RSS or Atom feeds per run and get their items as clean rows: title, link, publication date in UTC, summary, categories and image. Give it a plain website instead of a feed URL and it finds the feed itself. Pay only for delivered items.

- **URL**: https://apify.com/steadydata/rss-feed-reader.md
- **Developed by:** [Steadydata Team](https://apify.com/steadydata) (community)
- **Categories:** News, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.22 / 1,000 item listeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## RSS and Atom Feed Reader (bulk, with feed discovery)

Read up to **200 feeds per run** and get their items as clean rows: title, link, publication
date in UTC, summary, categories and image. RSS and Atom come out in the same shape. Give it
a plain website instead of a feed URL and it finds the feed itself. You pay per delivered
item and nothing else.

### Why this scraper

- **A website is enough.** `github.blog` works exactly like a feed URL: the page is read,
  its `<link rel="alternate">` is followed, and if the site declares nothing the usual paths
  (`/feed`, `/rss`, `/rss.xml`, `/feed.xml`, `/atom.xml`, `/index.xml`) are tried. Measured
  on 14-09-2026: it resolved to `https://github.blog/feed/` on the first attempt and
  delivered. Nobody in your team has to go hunting for feed URLs first.
- **RSS and Atom in one row shape.** RSS keeps items in `<item>` with `<pubDate>`, Atom in
  `<entry>` with `<published>` and `<link href>`. A buyer with 200 sources should not have
  to care, so both become the same 15 columns, and `feedType` says which it was.
- **Dates you can sort on.** Every date is normalised to UTC in `publishedAt`
  (`2026-09-13T19:41:48Z`), whatever the feed wrote. In a 290-item test across eleven
  publishers, `publishedAt` was filled on 100% of the rows. `publishedText` keeps the
  original string.
- **One slow publisher does not spoil the run.** A source that does not answer is retried
  from another proxy exit and, if it still refuses, comes back as one error record while the
  other feeds deliver normally. Measured: 290 items from eleven feeds in 81 seconds, one
  error record, nothing lost.
- **Only delivered items are charged.** An unreachable site, a site with no feed, a typo in
  the list: all free.

### Who this is for

Newsroom and PR monitoring across a list of publishers. Competitor and category watching from
company blogs. Filling a newsletter or a dashboard from dozens of sources on a schedule.
Research collections that need a dated, deduplicable list of articles rather than a scrape.

### Who this is not for

**Read this before you buy.**

- **It delivers what the feed contains, not the article.** The full page is never fetched.
  `content` carries the full text only when the publisher puts it in the feed itself, which
  many do not; then you get the summary the feed offers. If you need the article body, this
  is the wrong tool.
- **A feed is a window, not an archive.** Most feeds carry between 10 and 50 recent items.
  In the 14-09-2026 test, nine of eleven publishers returned fewer than the requested 50
  because the feed simply holds fewer. There is no way to page back into history, from any
  tool, because the publisher does not serve it.
- **No author or byline.** A byline is a person, and this actor ships no person data. The
  field is not collected and cannot be switched on.
- **A few publishers refuse datacenter traffic.** `nu.nl` answers in under two seconds from
  an ordinary connection and times out from the platform, on every exit that was tried. Those
  sources come back as a free `BLOCKED` error record and this actor cannot read them. If your
  list is mostly publishers like that, test before you build on it: the free error record
  tells you within one run which of your sources are affected.
- **Discovery follows robots.txt.** Reading a *feed* is never gated, because syndication
  exists to be read by machines. Inspecting a *website page* to find its feed is an ordinary
  page fetch, so that step obeys robots.txt and a site that forbids it is skipped for free
  with `ROBOTS_DISALLOWED`.

### Input example

```json
{
    "sources": [
        "https://news.ycombinator.com/rss",
        "https://www.theverge.com/rss/index.xml",
        "github.blog",
        "techcrunch.com"
    ],
    "maxItemsPerFeed": 50,
    "sinceDays": 7,
    "includeContent": false
}
```

`maxItemsPerFeed` is your cost ceiling per source: one delivered item is one charged event.
`sinceDays` drops everything older, and items without a date are always kept.

### Output example

```json
{
    "source": "https://www.theverge.com/rss/index.xml",
    "feedUrl": "https://www.theverge.com/rss/index.xml",
    "feedTitle": "The Verge",
    "feedType": "atom",
    "position": 1,
    "title": "Trump and Mike Johnson think the AI industry is overreacting",
    "link": "https://www.theverge.com/ai-artificial-intelligence/994441/trump-mike-johnson-ai",
    "publishedAt": "2026-09-13T19:41:48Z",
    "publishedText": "2026-09-13T15:41:48-04:00",
    "summary": "Yesterday, Anthropic CEO Dario Amodei published a lengthy open letter...",
    "content": null,
    "categories": ["AI", "Anthropic", "News"],
    "imageUrl": "https://platform.theverge.com/wp-content/uploads/sites/2/2026/09/example.jpg",
    "guid": "https://www.theverge.com/ai-artificial-intelligence/994441",
    "discovered": false,
    "status": "ok"
}
```

`discovered` is `true` when you passed a website and the feed was found for you.

How often the optional fields are actually filled, measured over 290 items from eleven
publishers on 14-09-2026: title, link, `publishedAt`, `feedTitle` and `feedType` 100%,
`summary` 99%, `guid` 89%, `categories` 74%. The gaps are the publishers' own: a feed that
carries no categories cannot report any.

Error codes: `INVALID_URL`, `NO_FEED_FOUND`, `EMPTY_FEED`, `ROBOTS_DISALLOWED`,
`FETCH_FAILED`, `BLOCKED`. None of them are charged. `INPUT_TRUNCATED` appears once when your input is longer than this actor accepts.

### Related actors from steadydata

- [google-news](https://apify.com/steadydata/google-news): the same story from Google News, by query or country
- [technical-seo-audit](https://apify.com/steadydata/technical-seo-audit): the health of the pages those items point to

### Pricing

Pay per event: one `item-listed` event per delivered item. No start fee, no separate
platform-usage surcharge, and no charge for an input that fails.

**Free Apify plan:** this actor delivers up to 25 rows per run for accounts on the Apify free
plan, and then stops with a message. That limit is set by us, not by Apify. It exists so the
actor keeps paying for itself for the people who do pay. Any paid Apify plan runs it at full
size, billed per delivered row, with failed rows never charged.

**Reviews:** if this actor saves you time, a short review on this page is the one thing that
helps most. Ratings are what other buyers look at first, and we have no other way to ask.

### FAQ

**Does it work on a site without a feed?**
No, and it says so for free: `NO_FEED_FOUND` after the declared link and six usual paths have
been tried. Feeds are read, pages are not scraped.

**How do I keep a run cheap?**
`maxItemsPerFeed` is a hard ceiling per source, so 200 feeds at 10 items can never cost more
than 2,000 events. `sinceDays` cuts it further if you poll often and only want what is new.

**Can I poll every hour without paying for the same items twice?**
Every item carries `guid` and `link`, so deduplicating on your side is one key lookup.
`sinceDays: 1` combined with a small `maxItemsPerFeed` keeps a frequent poll small, but
items you already have are delivered again and are charged again.

**Why is `content` empty?**
Because the publisher does not put the article text in the feed. Switch `includeContent` on
to receive it wherever it is present; it is off by default because it multiplies dataset size
for feeds that carry the whole article.

**Is personal data collected?**
No. The author and creator fields that feeds often carry are deliberately not read, and
nothing else in a feed item identifies a person.

**What happens when a source changes?**
Feeds change from time to time; that is the nature of this work. The actor is run against
real feeds daily and fixed fast, and while it is broken you are not charged, because only
delivered results cost anything.

# Changelog

This Actor's version history is a separate document: https://apify.com/steadydata/rss-feed-reader/changelog.md

# Actor input Schema

## `sources` (type: `array`):

Feed URLs or plain website addresses, one per row, up to 200. A website is inspected for the feed it declares, so nu.nl and https://nu.nl/rss both work.

## `maxItemsPerFeed` (type: `integer`):

Cost ceiling per feed, newest first as the feed orders them. One delivered item is one charged event.

## `sinceDays` (type: `integer`):

0 keeps everything the feed contains. Items without a date are always kept.

## `includeContent` (type: `boolean`):

Feeds often carry the whole article next to the summary. On, that text is delivered as well; off keeps the dataset small.

## Actor input object example

```json
{
  "sources": [
    "https://news.ycombinator.com/rss",
    "blog.apify.com"
  ],
  "maxItemsPerFeed": 50,
  "sinceDays": 0,
  "includeContent": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sources": [
        "https://news.ycombinator.com/rss",
        "blog.apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadydata/rss-feed-reader").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "sources": [
        "https://news.ycombinator.com/rss",
        "blog.apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("steadydata/rss-feed-reader").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sources": [
    "https://news.ycombinator.com/rss",
    "blog.apify.com"
  ]
}' |
apify call steadydata/rss-feed-reader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadydata/rss-feed-reader"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/9k2hghPVfIcJBr8xH/builds/OrfpMmayas7xxYpBh/openapi.json
