# RSS & Atom Feed Reader: Bulk, Only New Items, Auto-Discovery (`charlesmarkette/rss-atom-feed-reader-only-new-items`) Actor

Read many RSS, Atom and JSON feeds into one clean dataset. Paste feed or website URLs (feeds are found automatically), get only items that are new since the last run, filter by date and keywords. Ideal for news, blog and podcast monitoring with alerts.

- **URL**: https://apify.com/charlesmarkette/rss-atom-feed-reader-only-new-items.md
- **Developed by:** [Charles Markette](https://apify.com/charlesmarkette) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 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 & Atom Feed Reader** reads many **RSS, Atom and JSON Feed** URLs in one run and turns them into one clean dataset: one row per item, with the **title, link, publish and update dates (ISO, UTC), author, plain-text summary, categories, enclosures (podcast audio, attachments), image and comments link**. Paste a plain **website URL** and the feed is found for you. Turn on **Only new items** and each run returns just the items that appeared since the last run, which makes it the monitoring step in front of an email, Slack or Teams alert, or an export.

### Why this Actor

- **Every common format.** RSS 0.9x/1.0/2.0 and Atom are parsed with the widely used [feedparser](https://github.com/kurtmckee/feedparser) library; JSON Feed 1.0 and 1.1 are handled natively. The columns are the same whatever the format.
- **Auto-discovery.** For a website URL, it reads the page's `<link rel="alternate">` feed tags. If there are none, it tries `/feed`, `/rss`, `/rss.xml`, `/atom.xml`, `/feed.xml` and `/index.xml` and stops at the first one that is a real feed. The `discoveredFrom` column says which website a feed was found on.
- **Only new items, cheaply.** It remembers the last 5,000 item IDs per feed. It also stores each feed's `ETag` / `Last-Modified` and sends them back, so a feed that hasn't changed answers "304 Not Modified": no download, no parsing, and no item charges.
- **Clean output.** Summaries are plain text (HTML stripped, up to 2,000 characters). Optional full content HTML is sanitized (scripts and event handlers removed) with relative links made absolute.
- **One broken feed never stops the run.** A feed that fails gets one free row with the reason in `error` ("HTTP 404", "Not a feed", "Timed out"…) and the run carries on.
- **Polite and safe.** At most 2 requests at a time per website, retries with backoff on 429 and 5xx (honouring `Retry-After`), a 10 MB cap per feed and a 60-second limit per fetch. URLs that point at private, local or cloud-metadata addresses are refused. Every redirect is checked again, and the check is repeated on the address actually connected to, so a host whose DNS answer changes between the check and the connection is refused too.

### Use cases

- **News and competitor monitoring:** schedule it hourly on competitors' blogs, changelogs and press pages with *Only new items*, and send each new post to Slack or email.
- **Keyword alerts:** watch 50 industry feeds for your brand or product names with *Keywords*.
- **Podcast and video tracking:** episode audio URLs, types and sizes in `enclosures`.
- **Content aggregation:** merge many feeds into one dataset, newest first per feed, for a newsletter, a dashboard or a Google Sheet.
- **Research archives:** run daily and keep everything with dates, authors and categories in one place.

### How to use it

1. Add **Feed or website URLs**, one per line (`https://example.com/feed.xml`, or just `example.com`).
2. Optional: turn on **Only new items** for monitoring, and set **Published after**, **Keywords** or **Exclude keywords**.
3. Run it, or schedule it. Export as CSV, Excel or JSON, or chain it with an integration (see Related Actors).

**Input example**

```json
{
  "feeds": ["https://github.blog/", "https://feeds.bbci.co.uk/news/technology/rss.xml", "https://example.com/feed.json"],
  "onlyNew": true,
  "keywords": ["web scraping", "automation"],
  "since": "7 days",
  "maxItemsPerFeed": 50
}
```

**Output example**

```json
{
  "feedUrl": "https://news.example.com/blog/feed.xml",
  "feedTitle": "Example News",
  "feedLink": "https://news.example.com/",
  "itemId": "post-2",
  "title": "Second post about Python",
  "link": "https://news.example.com/posts/2",
  "publishedAt": "2026-10-13T07:30:00Z",
  "updatedAt": null,
  "author": "Ada Lovelace",
  "summary": "Hello world & friends.",
  "categories": ["Programming", "Python"],
  "enclosures": [{"url": "https://cdn.example.com/ep2.mp3", "type": "audio/mpeg", "length": 12345}],
  "imageUrl": "https://news.example.com/img/2.png",
  "commentsUrl": "https://news.example.com/posts/2#comments",
  "discoveredFrom": "https://news.example.com/",
  "error": null
}
```

With **Include full content HTML**, rows also get `contentHtml`. A feed that couldn't be read gives one row with `feedUrl` and `error` set and the other columns empty. `itemId` is the item's GUID / ID, or its link, or (if it has neither) a hash of its title and date. A run summary, with a line per feed, is saved as `OUTPUT` in the key-value store.

### How "Only new items" works

- The **first run** with *Only new items* outputs the feed's current items (up to *Max items per feed*) and remembers them. Turn on **Skip existing items on the first run** to only remember them, so that you get alerts only for items published after you start monitoring.
- Later runs output only items whose ID wasn't seen before. Items are remembered once they are output or deliberately left out by your filters or the per-feed limit, so they won't come back later. Items not output because the run hit *Max output rows* or your cost limit are **not** remembered, and come in the next run.
- The memory lives in the key-value store **`rss-feed-reader-state`** in your account: the last 5,000 item IDs per feed, plus the feed's `ETag` / `Last-Modified` and, for website URLs, the discovered feed URL (so later runs go straight to the feed).
- Each saved task has its own memory, and changing **Keywords** or **Exclude keywords** starts a fresh one. Set **Memory name** to choose it yourself: runs with the same memory name share it.

### How much does it cost?

Pay per event: **$0.00115 per output item** (one item row), plus **$0.005 per run start**. Apify platform usage is included. These are free: feed error rows, items you've already seen (*Only new items*), items left out by your filters, and feeds that answer "not modified".

**Apify Store discounts:** the price above is for Apify's Free plan. Paid plans pay less per 1,000 feed items: $1.00 on Starter, $0.78 on Scale, $0.60 on Business. The "from" price at the top of the Store page is the Business price.

**Worked examples:**

*Examples use the Free-plan rate. Where an example doesn't add it, each run also has its run-start charge, counted per GB of Actor memory (minimum one; one at the default memory); Apify Proxy (where used) is billed separately.*

- 20 feeds, 50 items each: 1,000 × $0.00115 + $0.005 = **$1.155**.
- A daily monitor of 30 feeds with *Only new items* that finds about 40 new items a day: $0.051 per day, about **$1.53 per month**.
- The same monitor run hourly: 24 run starts ($0.12) + 40 items ($0.046) a day, about **$4.98 per month**.

If you set a maximum cost per run, the Actor stops cleanly at that limit, and the items it didn't output come in the next run.

### FAQ and limits

#### Can it get the full article text?

It returns what the feed publishes: often a summary, sometimes the full content (`contentHtml`). It does not open the article pages.

#### Can I get older items than the feed shows?

No. A feed only lists the publisher's latest items (often 10 to 50). Schedule the Actor with *Only new items* to build up a history.

#### A feed gives "HTTP 403" or "HTTP 429" but works in my browser.

Some sites block automated or datacenter traffic. This Actor doesn't use proxies, log in, or send cookies, so feeds behind a login, a paywall or bot protection may not be readable.

#### Why did an old item show up as new?

The publisher changed the item's GUID or link, or the feed lists more than 5,000 items. Item IDs are taken as published.

#### What does "Published after" do with items that have no date?

They are left out when it's set, because they can't be checked. Atom entries with only an `updated` date are checked against that date.

#### Why is a URL "Blocked"?

URLs that resolve to private, local, link-local or cloud-metadata addresses (for example 127.0.0.1, 10.x.x.x or 169.254.169.254) are refused for safety, including when a redirect leads there or when the host's DNS answer changes to such an address at connection time.

#### Other limits

Feeds over 10 MB (after decompression) are skipped with an error. `contentHtml` is cut at 1,000,000 characters (the cut can fall inside a tag). Pages that build their feed links with JavaScript aren't rendered, so use the feed URL directly. Up to 3 feed links per page are tried, in page order (the first is usually the main feed).

### Related Actors

| Actor | Use it to |
|---|---|
| [Send Email Report & Slack, Discord, Teams, Telegram Alerts](https://apify.com/charlesmarkette/send-email-report-slack-discord-teams-telegram-alerts) | Get the new items as an email or chat message after each run. |
| [Dataset Delivery: Export to SFTP, FTP, Dropbox & Google Drive](https://apify.com/charlesmarkette/dataset-delivery-export-to-sftp-ftp-dropbox-google-drive) | Deliver the items as CSV, Excel or JSON files. |
| [Google Sheets Sync](https://apify.com/charlesmarkette/google-sheets-sync) | Append the items to a Google Sheet. |
| [Merge & Deduplicate Datasets](https://apify.com/charlesmarkette/merge-deduplicate-datasets-only-new-items) | Merge the output with other sources and remove duplicates. |
| [Dataset Filter & Transform](https://apify.com/charlesmarkette/dataset-filter-transform-rename-flatten-fields) | Filter items by any field, or rename and drop columns before export. |
| [Bulk URL Status Checker](https://apify.com/charlesmarkette/bulk-url-status-checker-redirects-ssl) | Check that the links in new feed items work. |

Feed parsing: [feedparser](https://github.com/kurtmckee/feedparser). JSON Feed: [jsonfeed.org](https://www.jsonfeed.org/version/1.1/).

# Actor input Schema

## `feeds` (type: `array`):

One per line. RSS, Atom or JSON Feed URLs, or plain website URLs: for a website, the feed is found from the page's <link rel="alternate"> tags, or by trying /feed, /rss, /rss.xml, /atom.xml, /feed.xml and /index.xml.

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

The newest items to output from each feed (after the filters below).

## `onlyNew` (type: `boolean`):

Output only items that weren't in the feed on an earlier run with this setting. The first run outputs the feed's current items (turn on the next option to skip them). Memory: the last 5,000 item IDs per feed, in the key-value store 'rss-feed-reader-state'.

## `skipExistingOnFirstRun` (type: `boolean`):

With 'Only new items': the first run for a feed only remembers its current items and outputs nothing, so later runs output only items published after you started monitoring.

## `stateKey` (type: `string`):

Optional. Runs with the same memory name share their 'only new' memory. By default each saved task has its own memory (and changing the keyword filters starts a fresh one). Letters, digits, '-', '\_' and '.'.

## `since` (type: `string`):

Optional. Only items published (or, if no publish date, updated) after this: a date like 2026-10-01 or 2026-10-01T08:00:00Z (UTC), or a period like '7 days', '24 hours', '2 weeks'. Items with no date at all are left out when this is set.

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

Optional. Keep only items whose title or summary contains any of these (case-insensitive, plain substring match).

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

Optional. Leave out items whose title or summary contains any of these (case-insensitive).

## `includeContentHtml` (type: `boolean`):

Add a contentHtml column with the item's full HTML content (sanitized: scripts and event handlers removed; cut at 1,000,000 characters). Rows get much larger.

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

How many feeds to read at the same time (1–20). Each website still gets at most 2 requests at a time.

## `maxResults` (type: `integer`):

Optional cap on output rows for the whole run (items plus any feed error rows).

## Actor input object example

```json
{
  "feeds": [
    "https://github.blog/",
    "https://feeds.bbci.co.uk/news/technology/rss.xml"
  ],
  "maxItemsPerFeed": 50,
  "onlyNew": false,
  "skipExistingOnFirstRun": false,
  "includeContentHtml": false,
  "concurrency": 5
}
```

# Actor output Schema

## `items` (type: `string`):

No description

## `summary` (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 = {
    "feeds": [
        "https://github.blog/",
        "https://feeds.bbci.co.uk/news/technology/rss.xml"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("charlesmarkette/rss-atom-feed-reader-only-new-items").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 = { "feeds": [
        "https://github.blog/",
        "https://feeds.bbci.co.uk/news/technology/rss.xml",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("charlesmarkette/rss-atom-feed-reader-only-new-items").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 '{
  "feeds": [
    "https://github.blog/",
    "https://feeds.bbci.co.uk/news/technology/rss.xml"
  ]
}' |
apify call charlesmarkette/rss-atom-feed-reader-only-new-items --silent --output-dataset

```

## MCP server setup

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

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/yuJ8XR2WbbhFVI29p/builds/e3g0KkhfKEGCFAr1m/openapi.json
