# RSS Feed Reader: Bulk Feeds from a CSV, Google Sheet or Dataset (`nerolabs/rss-feed-reader`) Actor

Returns the newest items (title, link, date, author, summary, image, podcast media) from RSS, Atom and JSON feeds or websites, read from a list, CSV, Google Sheet or dataset, columns kept. Only-new mode. $0.001 per item; failed and repeat items free. Agent-ready via x402 and MCP.

- **URL**: https://apify.com/nerolabs/rss-feed-reader.md
- **Developed by:** [Adam Pearce](https://apify.com/nerolabs) (community)
- **Categories:** News, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 feed items

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 Feed Reader: Bulk Feeds from a CSV, Google Sheet or Dataset

**Watching dozens of blogs, news sites, podcasts or competitors?** Give this Actor your list (typed in, a CSV, a Google Sheet or another Actor's dataset) and get every new post back as a clean row: title, link, date, author, summary, tags, image and podcast audio, **next to your own columns**.

Paste a normal website address and it finds the site's feed for you. Turn on **Only new items** and each run returns only what has appeared since the last one, so a daily schedule or an AI agent never sees (or pays for) the same story twice.

### What you can do with it

- **Competitor and industry monitoring:** a Sheet of 50 competitor blogs with a `client` column, run every morning, only new posts, straight into Slack via the webhook.
- **News alerts on a topic:** many news feeds plus `Must mention: ["acquisition", "funding round"]`, so only matching stories come back.
- **Podcast tracking:** new episodes with the audio file link, size and duration, ready for a transcription step.
- **Content pipelines and agents:** an n8n flow, Make scenario or AI agent asks "what is new on these sites?" and gets JSON rows back, paying a fraction of a cent per item.
- **Lead and account research:** a lead list with a `website` column comes back with each company's latest posts, so outreach can mention something they just published.

### Example output

One row per item. Your own columns (here `client` and `topic`) are copied onto every item from that feed:

```json
{
  "client": "Acme",
  "topic": "security",
  "rowNumber": 1,
  "feedUrl": "https://blog.cloudflare.com",
  "resolvedFeedUrl": "https://blog.cloudflare.com/rss/",
  "feedDiscovered": true,
  "feedStatus": "ok",
  "feedTitle": "Cloudflare Blog",
  "feedType": "rss",
  "position": 1,
  "itemId": "01M5E1HXVZGTKAKEYZ0KMZQ65Q",
  "title": "Introducing full transaction detection for Cloudflare WAF",
  "link": "https://blog.cloudflare.com/full-transaction-waf/",
  "author": "Daniele Molteni",
  "publishedAt": "2026-10-21T14:10:42.000Z",
  "summary": "Cloudflare's web application firewall can now correlate an HTTP request with its response. ...",
  "categories": ["Managed Rules", "Security", "Security Analytics", "Vulnerabilities", "WAF"],
  "imageUrl": "https://blog.cloudflare.com/_emdash/api/media/file/01M5E1HKVR7WJX7KGB4B8MSK75.png",
  "enclosureUrl": "https://blog.cloudflare.com/_emdash/api/media/file/01M5E1HKVR7WJX7KGB4B8MSK75.png",
  "durationSeconds": null,
  "fetchedAt": "2026-10-22T12:58:41.000Z"
}
```

A feed that cannot be read gives one free row instead, so you can see which entries need fixing:

| `feedStatus` | Meaning | Charged? |
|---|---|---|
| `ok` | The feed was read; its items are the other rows | Per item delivered |
| `no_feed_found` | The website declares no feed and none was at the usual addresses | No |
| `not_a_feed` | The link returned something that is not a feed | No |
| `blocked` | The site's bot protection refused every request | No |
| `http_error`, `unreachable`, `timeout`, `too_large`, `invalid_url`, `no_url` | The link could not be read | No |

### Inputs

| Input | What it does |
|---|---|
| **Feeds or websites** | A plain list of feed links or website addresses |
| **Dataset** / **File or Google Sheet URL** | Read the list from an Apify dataset, CSV, TSV, Excel, JSON or a Google Sheet. The feed column is found automatically (`feedUrl`, `feed`, `rss`, then `url` or `website`) |
| **Items per feed** | Newest items to take from each feed (default 20) |
| **Only new items** | Remember what was returned and give only new items next time. The first run returns the current items |
| **Published after** | A date (`2026-10-01`) or a period (`24 hours`, `7 days`) |
| **Must mention / Must not mention** | Whole-word keyword filters on title, summary, content and tags |
| **Include full content** | Adds the full post text the feed carries, as plain text or HTML |
| **Remove duplicate stories** | The same link in two feeds counts once (on by default) |
| **Export files, Webhook, Append to named dataset** | CSV or Excel download, a POST with the summary and newest 50 items (sent only when there are new items), a growing archive |

Every format is read: RSS 2.0, RSS 1.0 (RDF), Atom and JSON Feed, including podcast (iTunes), Media RSS and YouTube channel feeds. Dates are converted to ISO 8601 in UTC; a missing date stays empty and is never guessed.

### Pricing

Pay per event, no subscription:

| Event | Price |
|---|---|
| Feed item delivered | **$0.001** (cheaper on higher Apify plans) |
| CSV or Excel file | $0.01 per file |
| Webhook delivered | $0.005 |
| Residential proxy (only if you turn it on) | $0.003 per feed that returns items through it |

**What it costs in practice:** watching 50 blogs every day with Only new items on, at about 3 new posts per blog a day, is 150 items a day, about **$4.50 a month**. Failed feeds, items filtered out by date or keyword, repeats and duplicates are never charged.

### How it works

1. Each link is downloaded with plain HTTP (no browser), identifying itself as a feed reader, with a browser-style retry if a site refuses. Never more than two requests at a time go to one site.
2. If the link is a web page, the feed the page declares in its `<link rel="alternate">` tag is used; failing that, the usual addresses (`/feed`, `/rss`, `/feed.xml`, `/rss.xml`, `/atom.xml`, `/index.xml`) are tried.
3. Items are sorted newest first, filtered, checked against the memory (Only new items) and written, then charged for exactly what was written.

### For AI agents and automations

Call it through the Apify API, the Apify MCP server or x402 pay-per-call with no account. Minimal input: `{"feedUrls": ["https://example.com"], "onlyNewItems": true, "stateName": "my-monitor"}`. The run summary (`RUN_SUMMARY` in the key-value store) lists feeds read and failed and items skipped by reason.

### FAQ

**Can I monitor a site that has no RSS feed?** Only if the site publishes a feed somewhere. Most blogs, news sites, podcasts, YouTube channels, Substack, Medium and WordPress sites do; the Actor finds it from the homepage. If `no_feed_found` comes back, the site has none.

**How does Only new items know what is new?** It keeps the ID of every item it returned under the memory name you choose, in your own Apify account. Use a different memory name for each separate monitor.

**Can I get the full article when the feed only has a summary?** Send the `link` column to [Bulk Article Extractor](https://apify.com/nerolabs/bulk-article-extractor), which keeps all these columns and adds the full text.

**Is it legal to read feeds?** Feeds are published so that readers and apps can fetch them. This Actor reads only the feed itself, at a polite rate, and returns what the publisher chose to put in it. Respect each publisher's terms for how you reuse the content.

If this saved you checking feeds by hand, a review on the Apify Store helps a lot.

# Actor input Schema

## `feedUrls` (type: `array`):

RSS, Atom or JSON Feed links, or plain website addresses (the site's own feed is found automatically). One per line. Use the Dataset or File inputs below instead to read a list and keep your own columns next to every item.

## `datasetId` (type: `string`):

An Apify dataset with one row per feed or website, for example a list of competitor blogs or a lead list with a website column. Every original column is kept on each item row. Use the picker rather than typing an ID.

## `fileUrl` (type: `string`):

A public link to a CSV, TSV, Excel, JSON or JSON Lines file with one row per feed or website. A normal Google Sheets link works: share it as 'Anyone with the link can view'.

## `fileFormat` (type: `string`):

Leave on 'Detect automatically' unless the link has no file extension and the server reports the wrong content type.

## `sheetName` (type: `string`):

Which sheet to read from an Excel workbook. Defaults to the first sheet.

## `data` (type: `array`):

Rows as inline JSON, an alternative to a dataset or file. Each object needs a field holding a feed or website link.

## `feedField` (type: `string`):

The column holding the feed or website link. Left empty it is detected automatically, preferring columns named feedUrl, feed or rss, then url or website.

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

The newest items to take from each feed. Feeds usually list 10 to 50; podcasts can list hundreds.

## `onlyNewItems` (type: `boolean`):

Remember what was already returned and give only items that are new since the last run. Ideal on a schedule or for an agent checking for news: repeats are never returned or charged. The first run returns the current items.

## `stateName` (type: `string`):

Separate monitors keep separate memories. Use one name per project, for example 'competitor-blogs'. Letters, numbers and dashes.

## `publishedAfter` (type: `string`):

Only items published after this date (2026-10-01) or within this period ("24 hours", "7 days", "2 weeks"). Items with no date in the feed are kept.

## `keywords` (type: `array`):

Keep only items whose title, summary, content or tags mention at least one of these words or phrases. Whole words, not case sensitive: 'AI' matches 'AI' but not 'said'.

## `excludeKeywords` (type: `array`):

Drop items that mention any of these words or phrases. Whole words, not case sensitive.

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

Add a 'content' column with the full post text the feed carries (many feeds include whole articles). The summary is always included. For full article text from feeds that only carry a summary, send the links to Bulk Article Extractor.

## `contentFormat` (type: `string`):

Plain text with paragraph breaks, or the feed's own HTML.

## `maxCharsPerItem` (type: `integer`):

Summary and content longer than this are cut, so one huge post cannot bloat the dataset.

## `dedupeAcrossFeeds` (type: `boolean`):

When the same link appears in two feeds in one run (a story in both 'World' and 'Top stories'), return it once and charge it once.

## `discoverFeeds` (type: `boolean`):

When a link is a normal web page, use the feed the page declares, or try the usual addresses (/feed, /rss, /feed.xml and others). Turn off to treat only real feed links as valid.

## `keep` (type: `string`):

'All rows' also writes one free row per feed that failed (blocked, no feed found, unreachable), so you can see which ones need fixing.

## `keepOriginalFields` (type: `boolean`):

Copy every column from the input row onto each item from that feed, so items line up with your own data (client, topic, owner). Turn off for feed fields only.

## `exportFormats` (type: `array`):

Also write the items as a downloadable CSV or Excel file, linked from the run's output. $0.01 per file, charged only when the run delivered at least one item.

## `outputDatasetName` (type: `string`):

Also append every row to a named dataset that persists across runs, building one growing archive of items. Not charged again.

## `webhookUrl` (type: `string`):

POST the run summary and the first 50 new items to this URL when the run finishes, for Slack, Zapier, Make, n8n or your own API. Sent only when the run delivered at least one item; charged only on a confirmed 2xx response.

## `proxyConfiguration` (type: `object`):

Off by default and rarely needed for feeds. Apify residential proxy can help with feeds blocked by address or country and adds $0.003 per feed that returns items through it.

## `concurrency` (type: `integer`):

How many feeds to read in parallel. Never more than two at a time go to any one site.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one feed before giving up. A timed-out feed is never charged.

## `maxFeedMb` (type: `integer`):

Feeds larger than this are skipped (free). Big podcast feeds with hundreds of episodes can reach 20 to 30 MB.

## `maxItems` (type: `integer`):

A hard ceiling on items delivered (and charged) across all feeds in one run.

## Actor input object example

```json
{
  "feedUrls": [
    "https://feeds.bbci.co.uk/news/rss.xml",
    "https://techcrunch.com",
    "https://www.theverge.com/rss/index.xml",
    "https://www.nasa.gov/news-release/feed/"
  ],
  "fileFormat": "auto",
  "maxItemsPerFeed": 10,
  "onlyNewItems": false,
  "stateName": "default",
  "includeContent": false,
  "contentFormat": "text",
  "maxCharsPerItem": 20000,
  "dedupeAcrossFeeds": true,
  "discoverFeeds": true,
  "keep": "all",
  "keepOriginalFields": true,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "concurrency": 10,
  "requestTimeoutSecs": 30,
  "maxFeedMb": 30
}
```

# Actor output Schema

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

One row per feed item with title, link, date, author, summary, tags, image and media, plus your original columns.

## `runSummary` (type: `string`):

Feeds read, failed and found from websites, items delivered and skipped by reason, export links and warnings.

# 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 = {
    "feedUrls": [
        "https://feeds.bbci.co.uk/news/rss.xml",
        "https://techcrunch.com",
        "https://www.theverge.com/rss/index.xml",
        "https://www.nasa.gov/news-release/feed/"
    ],
    "maxItemsPerFeed": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("nerolabs/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 = {
    "feedUrls": [
        "https://feeds.bbci.co.uk/news/rss.xml",
        "https://techcrunch.com",
        "https://www.theverge.com/rss/index.xml",
        "https://www.nasa.gov/news-release/feed/",
    ],
    "maxItemsPerFeed": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("nerolabs/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 '{
  "feedUrls": [
    "https://feeds.bbci.co.uk/news/rss.xml",
    "https://techcrunch.com",
    "https://www.theverge.com/rss/index.xml",
    "https://www.nasa.gov/news-release/feed/"
  ],
  "maxItemsPerFeed": 10
}' |
apify call nerolabs/rss-feed-reader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nerolabs/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/gc0lC2S5YKtBcFb45/builds/ScXa1NgMyLLKJl9Td/openapi.json
