# RSS and Atom Feed Monitor (`plusonets/rss-feed-monitor`) Actor

Watch RSS, Atom, and JSON Feeds for new items. Keyword filters, site autodiscovery, and de-duplication across runs. Pay per feed checked and per item.

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

## Pricing

from $0.50 / 1,000 new items

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Monitor

Watch publisher feeds and keep the items you have not already saved. This Actor reads RSS, Atom, and JSON Feed documents, can find a feed from a site URL, and normalizes each entry to the same fields: feed, title, link, author, published time, summary, plain-text content, categories, and guid.

It is built for newsletter editors, research agents, and content teams who want a feed inbox rather than a one-off dump. You pay when a feed is parsed and when an item is saved. There is no start fee.

### What you can do with it

- Pull the latest items from a list of feeds into the Apify dataset, then into Sheets, Slack, or your own API.
- Point it at a site homepage and let it follow `link rel="alternate"` tags (RSS, Atom, or JSON Feed).
- Keep a keyword watch: include keywords are OR, exclude keywords drop a match.
- Schedule it with **New items only** so the next run skips guids this Actor already delivered for that state namespace.
- Cut a feed off at a date with **Published since**.

The default input reads the NASA news feed and returns a handful of items. That run does not need a key, a proxy, or a login, and it is the input the Apify daily test uses.

### How a run behaves

1. Site URLs are checked against `robots.txt`. A disallowed page is skipped and listed in the run summary.
2. Feed URLs you typed are also checked. A disallowed feed is not fetched.
3. Each feed that parses is one `feed-checked` event. A URL that fails is not billed.
4. Items are filtered, newest dated items first, then capped per feed and for the whole run.
5. Each saved item is one `new-item` event. The guid is stored in a named key-value store (`rss-feed-monitor-seen`) so a later run can skip it. Article text is not stored in that state.

Crawl-delay in `robots.txt` is honored up to 10 seconds so one host cannot use the whole run. The client sends a descriptive `MonitorActors` user agent, retries 429 and 5xx responses with backoff, and waits between requests to the same host. It does not rotate proxies or pretend to be a browser.

### Input

```json
{
  "feedUrls": ["https://www.nasa.gov/feed/"],
  "siteUrls": [],
  "includeKeywords": ["mars"],
  "excludeKeywords": ["podcast"],
  "since": "2026-10-01",
  "newItemsOnly": true,
  "stateNamespace": "space-desk",
  "maxItems": 25,
  "maxItemsPerFeed": 10
}
```

| Field | Required | What it does |
| --- | --- | --- |
| Feed URLs | No, default is the NASA feed | Direct RSS, Atom, or JSON Feed URLs |
| Site URLs | No | HTML pages to scan for feed autodiscovery links. Up to three feeds per page |
| Include keywords | No | Keep items that contain at least one keyword |
| Exclude keywords | No | Drop items that contain any keyword |
| Published since | No | ISO date or datetime. Undated items are dropped when this is set |
| New items only | No, default off | Skip guids already saved for this namespace |
| State namespace | No, default `default` | Separate monitors in one Apify account |
| Maximum items | No, default 5 | Run-wide cap |
| Maximum items per feed | No, default 20 | Per-feed cap, newest first |
| Maximum charge (USD) | No | Extra cap. The run's Apify maximum charge still applies, and the lower one wins |

Leave **New items only** off for a one-shot export. Turn it on for a schedule. A scheduled run that finds nothing new finishes successfully with an empty dataset and a status message, so a quiet hour is not a failure. The first run of a new namespace has an empty memory, so it saves whatever matches.

### Output

One dataset item per feed entry:

```json
{
  "feed": "https://www.nasa.gov/feed/",
  "feedTitle": "NASA",
  "title": "NASA example headline",
  "link": "https://www.nasa.gov/news-release/example/",
  "author": "NASA",
  "published": "2026-10-22T15:04:00Z",
  "summary": "Short plain-text excerpt.",
  "contentText": "Plain text of the entry. HTML tags are removed.",
  "contentTruncated": false,
  "categories": ["Mars"],
  "guid": "https://www.nasa.gov/?p=12345"
}
```

Bodies longer than 20,000 characters are truncated and `contentTruncated` is true. Summaries are capped at about 1,200 characters.

The key-value record `SUMMARY` reports items saved, event counts, an estimated charge, feeds checked, and per-URL errors. A run with at least one saved item succeeds even if another feed failed. A run that was not in new-items mode and saved nothing fails, so a broken feed does not look like a healthy empty export.

### Pricing

Pay per event. Do not enable the synthetic `apify-default-dataset-item` event. This Actor charges its own events with `Actor.charge` and would double-bill if that synthetic event has a price. In the Actor's monetization settings, remove it or set its price to $0, and copy the events below. `new-item` is the primary event.

| Event | Price | When it fires |
| --- | --- | --- |
| `apify-actor-start` | $0.005 | Once per run, by the platform (memory is capped at 1 GB, so once). The Actor does not call `Actor.charge` for it. |
| `feed-checked` | $0.001 | A feed URL was fetched and parsed |
| `new-item` | $0.0005 | A normalized item was saved |

Twenty feeds and 100 new items cost `$0.005 + 20 × $0.001 + 100 × $0.0005 = $0.075`. Platform usage is not billed to you; the events above are the whole price. Set a maximum charge on the run. When the next event would pass that cap, the Actor stops, keeps what it already saved, and records `stoppedReason: max_charge`.

Failed feed URLs are not charged. Items filtered out by keywords, dates, or the new-item memory are not charged.

### FAQ

**Does it only return new items?**
Only when you turn on **New items only**. The memory is per Apify account and per state namespace, in the named store `rss-feed-monitor-seen`. Deleting that store, or changing the namespace, makes the next run treat items as new.

**What if I raise the item cap later?**
Only guids that were actually saved are remembered. Items left behind by the cap can show up on a later run. Newest dated items are saved first.

**Will it scrape the article page?**
No. It reads the feed document the publisher already offers and returns the text inside that document. It does not open each article URL.

**What about `robots.txt`?**
Disallow rules are followed for site pages and for feed URLs. If the feed you care about is disallowed, the summary says so and that URL is skipped.

**JSON Feed?**
Yes, when the response is JSON and has a `jsonfeed.org` version with an `items` array, or a JSON content type with an `items` array.

**Can I run several monitors?**
Yes. Give each schedule its own state namespace and its own keyword list. They can share the Actor.

### Limits

- This is a feed reader, not a site crawler. Paywalled or truncated feed bodies stay truncated.
- Autodiscovery reads `link rel="alternate"` tags only. It does not guess `/feed` paths.
- At most three discovered feeds are taken from one site page.
- Very large feeds over 8 MB are rejected.
- Seen-id lists keep the newest 10,000 guids per feed namespace.
- Platform usage (memory and time) is not billed to you; you pay only the events above.

### Run it locally

From this directory, with `monitor_core` vendored beside the Actor package (see the repository README):

```bash
mkdir -p storage/key_value_stores/default
printf '%s\n' '{"feedUrls":["https://www.nasa.gov/feed/"],"maxItems":3}' > storage/key_value_stores/default/INPUT.json
python -m rss_feed_monitor
```

Dataset items are written under `storage/datasets/default/`. The summary is `storage/key_value_stores/default/SUMMARY.json`.

### Publish notes

Keep this Actor on **limited permissions**. It only reads its input and writes its own default storages plus the named seen-id store it creates. Copy `.actor/pay_per_event.json` into the pay-per-event monetization form, and do not put a price on `apify-default-dataset-item`.

# Actor input Schema

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

RSS, Atom, or JSON Feed URLs to read. The default is the NASA news feed, which is public and returns items without a login.

## `siteUrls` (type: `array`):

HTML pages to scan for link rel=alternate feed tags. Up to three feeds are taken from each page. robots.txt Disallow rules are honored.

## `includeKeywords` (type: `array`):

If set, keep an item only when its title, summary, content, or categories contain at least one keyword. Matching is case-insensitive.

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

Drop an item when its title, summary, content, or categories contain any of these keywords.

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

Keep items published on or after this ISO date or datetime, for example 2026-10-01 or 2026-10-01T00:00:00Z. Items with no date are dropped when this is set.

## `newItemsOnly` (type: `boolean`):

Skip items whose guid was already saved in a previous run for this state namespace. Leave this off for the daily test and for one-shot exports. A scheduled monitor can turn it on; a quiet run with nothing new still succeeds.

## `stateNamespace` (type: `string`):

Separate saved-id lists when you run more than one monitor in the same Apify account. Use a short name such as climate-desk.

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

Stop after this many items have been saved in the run.

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

Cap items taken from each feed before the run-wide maximum is applied. Newest dated items are kept first.

## `maxChargeUsd` (type: `number`):

Optional extra cap for this run. The Actor also stops at ACTOR_MAX_TOTAL_CHARGE_USD, which Apify sets from the run's maximum charge. The lower of the two wins.

## Actor input object example

```json
{
  "feedUrls": [
    "https://www.nasa.gov/feed/"
  ],
  "newItemsOnly": false,
  "stateNamespace": "default",
  "maxItems": 5,
  "maxItemsPerFeed": 20
}
```

# Actor output Schema

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

Normalized feed items in the default dataset.

## `summary` (type: `string`):

Counts, estimated charge, and per-feed errors.

# 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://www.nasa.gov/feed/"
    ],
    "newItemsOnly": false,
    "stateNamespace": "default",
    "maxItems": 5,
    "maxItemsPerFeed": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("plusonets/rss-feed-monitor").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://www.nasa.gov/feed/"],
    "newItemsOnly": False,
    "stateNamespace": "default",
    "maxItems": 5,
    "maxItemsPerFeed": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("plusonets/rss-feed-monitor").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://www.nasa.gov/feed/"
  ],
  "newItemsOnly": false,
  "stateNamespace": "default",
  "maxItems": 5,
  "maxItemsPerFeed": 20
}' |
apify call plusonets/rss-feed-monitor --silent --output-dataset

```

## MCP server setup

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

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/AzJouS7n46JtrcBEg/builds/iM6ozA8E0p0PFQlHG/openapi.json
