# RSS Feed Article Monitor (`automation-lab/rss-feed-article-monitor`) Actor

Monitor public RSS and Atom feeds for normalized new, updated, and confirmed-removed article events with keyword filters and persistent per-feed state.

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

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## RSS Feed Article Monitor

Turn public RSS and Atom feeds into stateful **new**, **updated**, and **confirmed-removed** article events. This RSS feed monitor normalizes feed entries, applies keyword rules, reuses ETag and Last-Modified validators, and remembers per-feed fingerprints across scheduled Apify Task runs.

Use it for news intelligence, editorial monitoring, research alerts, webhook workflows, or any automation that needs changes—not another full feed snapshot on every run.

### What does RSS Feed Article Monitor do?

The Actor accepts up to 50 public RSS 2.0, RSS 1.0/RDF, and Atom URLs. For each successful changed response, it:

1. Parses and normalizes each article.
2. Creates a stable identity from the feed GUID, Atom ID, article URL, or a deterministic fallback.
3. Compares the normalized fingerprint with persistent state.
4. Emits `new` or `updated` events when appropriate.
5. Counts articles missing from consecutive successful changed feed responses.
6. Emits `removed` only after the configured confirmation threshold.
7. Saves ETag and Last-Modified values for conditional requests on the next run.

An HTTP 304 response means the feed has not changed. It produces no article events and does not increase missing-item counters.

### Who is it for?

- **News and communications teams** tracking source feeds for additions, corrections, and withdrawals.
- **OSINT and research analysts** building repeatable public-news intelligence pipelines.
- **Editorial operations teams** detecting changed headlines, summaries, dates, authors, or categories.
- **Automation developers** sending feed deltas to webhooks, queues, databases, Slack, or email.
- **AI workflow builders** supplying normalized changed articles instead of repeatedly ingesting a complete feed.

This Actor is for public feed URLs. It is not an authenticated feed reader, website-to-RSS converter, full-page scraper, or notification service by itself.

### Why use stateful article events instead of a feed snapshot?

A generic feed reader returns the current entries every time. This Actor keeps state and returns transitions:

- `new`: the article identity has not been seen in this state namespace, or it reappeared after confirmed removal;
- `updated`: the identity is known, but normalized article fields changed;
- `removed`: the article remained absent for the configured number of successful changed-feed observations.

This event model reduces downstream deduplication work and makes scheduled runs useful for automations. Bounded removal confirmation avoids treating a temporary truncated feed or one unusual response as immediate removal.

### Getting started

1. Open the Actor input.
2. Add one or more public RSS or Atom URLs under **Public RSS or Atom feed URLs**.
3. Optionally add include or exclude keywords.
4. Choose how many changed-feed runs must confirm a removal.
5. Keep `stateKey` stable for the same monitoring campaign.
6. Run once to establish the baseline. Existing entries are emitted as `new`.
7. Create an Apify Task and schedule recurring runs so its default key-value store is reused.
8. Connect the default dataset to a webhook or integration.

For a one-time analysis, run the Actor once and export the baseline `new` records as JSON, CSV, Excel, or XML.

### Input parameters

| Field | Type | Default | Purpose |
|---|---|---:|---|
| `feedUrls` | array | NASA feed | 1–50 public HTTP(S) RSS or Atom URLs |
| `includeKeywords` | string array | `[]` | Emit events containing matching case-insensitive terms |
| `excludeKeywords` | string array | `[]` | Suppress events containing any excluded term |
| `keywordMode` | `any` or `all` | `any` | Require any or all include keywords |
| `removalConfirmationRuns` | integer | `2` | Consecutive successful changed fetches needed for removal |
| `maxEvents` | integer | `1000` | Maximum event records emitted in one run |
| `requestTimeoutSecs` | integer | `30` | Per-feed timeout; transient requests are retried twice |
| `stateKey` | string | `default` | Namespace for an independent monitoring campaign |

Keyword matching covers normalized title, summary, content, author, and categories. Exclude rules take priority. Tracking state is maintained for every parsed article even when a keyword rule suppresses its event, preventing a later filter change from inventing a false first observation.

### Example: monitor NASA science and mission news

```json
{
  "feedUrls": [
    { "url": "https://www.nasa.gov/news-release/feed/" }
  ],
  "includeKeywords": ["science", "mission"],
  "keywordMode": "any",
  "removalConfirmationRuns": 2,
  "maxEvents": 50,
  "stateKey": "nasa-science-missions"
}
```

On its first run, this input emits matching current entries as `new`. Later runs emit only matching changes observed while reusing the same key-value store.

### Output fields

Every event is written to the default dataset.

| Field | Meaning |
|---|---|
| `eventType` | `new`, `updated`, or `removed` |
| `articleId` | Feed GUID/Atom ID or deterministic fallback identity |
| `title` | Plain-text title, nullable |
| `url` | Feed-supplied article URL, nullable |
| `summary` | Plain-text description or Atom summary, nullable |
| `content` | Plain-text full feed content when supplied, nullable |
| `author` | Feed author or creator, nullable |
| `publishedAt` | Valid publication timestamp in ISO 8601 form, nullable |
| `updatedAt` | Valid update timestamp in ISO 8601 form, nullable |
| `categories` | Feed category labels |
| `feedUrl` | Source feed URL |
| `feedTitle` | Channel/feed title, nullable |
| `detectedAt` | Time this transition was detected |
| `firstSeenAt` | First observation in this state namespace |
| `lastSeenAt` | Most recent successful changed response containing the article |
| `missingRuns` | Consecutive changed responses where the item was absent |
| `changedFields` | Fields changed on an `updated` event |

### Example output

This record was produced locally from the public USGS Atom feed on 2026-08-03 (timestamps and feed contents naturally change):

```json
{
  "articleId": "urn:earthquake-usgs-gov:us:6000tgb9",
  "title": "M 6.8 - 2026 Uto, Japan Earthquake",
  "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000tgb9",
  "summary": "PAGER - RED ShakeMap - IX DYFI? - IX Time 2026-07-28 07:27:15 UTC 2026-07-28 07:27:15 UTC at epicenter Location 32.682°N 130.722°E Depth 10.00 km (6.21 mi)",
  "content": null,
  "author": null,
  "publishedAt": null,
  "updatedAt": "2026-08-01T21:29:17.417Z",
  "categories": ["Past Week", "Magnitude 6", "us"],
  "eventType": "new",
  "feedUrl": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/significant_month.atom",
  "feedTitle": "USGS Significant Earthquakes, Past Month",
  "detectedAt": "2026-08-03T06:15:34.347Z",
  "firstSeenAt": "2026-08-03T06:15:34.347Z",
  "lastSeenAt": "2026-08-03T06:15:34.347Z",
  "missingRuns": 0,
  "changedFields": []
}
```

### How updates and removals are detected

The fingerprint includes normalized identity, title, URL, summary, content, author, publication/update dates, and categories. When one of those fields changes for the same identity, the Actor emits `updated` and lists the differences in `changedFields`.

A missing item is not immediately removed. The counter advances only after a successful fetch with a changed feed response. Failed fetches and HTTP 304 responses never count against an article. Set `removalConfirmationRuns` to `3` or more for feeds that frequently rotate or truncate their visible window.

A feed removing an old entry does not necessarily mean the linked page was deleted. The event means the entry was confirmed absent from the feed under your configured rule.

### Persistent state and scheduled runs

State is stored in the run's default key-value store under a hash of `stateKey` and feed URL. Apify Tasks reuse their assigned default storages, which makes them the recommended way to schedule monitoring.

Use a different `stateKey` for independent campaigns in the same storage. Changing the key intentionally creates a fresh baseline. Deleting or purging the key-value store also resets the baseline.

Direct standalone Actor runs commonly receive fresh default storage, so they are best for one-time extraction unless you explicitly reuse storage through an Apify Task.

### Conditional HTTP fetching and retries

When feeds provide ETag or Last-Modified headers, the Actor sends `If-None-Match` and `If-Modified-Since` on the next run. This saves bandwidth and avoids unnecessary parsing.

Network timeouts, HTTP 408/429, and temporary 5xx responses are retried up to twice. Stable client errors are not retried blindly. One failed feed does not discard successful results from other feeds; the run fails only when every requested feed fails.

### How much does it cost to monitor RSS feed articles?

Pricing uses one `start` event per run plus one tiered `item` event for each emitted article event. Unchanged feeds, filtered records, failed feeds, duplicate entries, and parsed records that produce no transition have no item event charge.

The measured pricing contract starts at **$0.005 per run**. Article-event tiers are FREE **$0.001012**, BRONZE **$0.00088**, SILVER **$0.0006864**, GOLD **$0.000528**, PLATINUM **$0.000352**, and DIAMOND **$0.0002464** per emitted event. Live Store pricing remains the authority.

Examples at the BRONZE item tier:

- a scheduled run with no changes: about **$0.005**;
- a run emitting 10 article events: about **$0.0138**;
- a baseline emitting 100 article events: about **$0.093**.

Apify applies the tier associated with account usage. Platform compute is covered by PPE pricing for customers under the active Store configuration.

### Automation and integration ideas

- Schedule every 15 minutes and send only `new` events to Slack.
- Route `updated` events to an editorial audit table using `changedFields`.
- Trigger a fact-check workflow when monitored headlines or summaries change.
- Send `removed` events to a review queue rather than deleting downstream content automatically.
- Combine multiple public agency feeds into a normalized public-event intelligence dataset.
- Export a one-time baseline to CSV for content inventory analysis.

Apify integrations can connect datasets and run webhooks to Make, Zapier, Google Sheets, Slack, webhooks, and other services.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~rss-feed-article-monitor/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "feedUrls": [{"url": "https://www.nasa.gov/news-release/feed/"}],
    "maxEvents": 50,
    "stateKey": "nasa-news"
  }'
```

For persistent monitoring, call an Apify Task configured with this Actor rather than creating unrelated standalone run storages.

### JavaScript API example

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/rss-feed-article-monitor').call({
  feedUrls: [{ url: 'https://www.nasa.gov/news-release/feed/' }],
  includeKeywords: ['science', 'mission'],
  keywordMode: 'any',
  stateKey: 'nasa-science-missions',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Python API example

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/rss-feed-article-monitor').call(run_input={
    'feedUrls': [{'url': 'https://www.nasa.gov/news-release/feed/'}],
    'removalConfirmationRuns': 3,
    'stateKey': 'nasa-news-releases',
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add this Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/rss-feed-article-monitor"
```

**Claude Desktop, Cursor, and VS Code setup**

Add the same HTTP server configuration to Claude Desktop, Cursor, or VS Code:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/rss-feed-article-monitor"
    }
  }
}
```

Example prompts:

- “Run the RSS Feed Article Monitor for NASA news and return only science or mission events.”
- “Schedule this feed-monitoring Task hourly and send new article dataset items to my webhook.”
- “Summarize updated headlines and show which fields changed.”

### Limits and expected behavior

- Supports RSS 2.0, RSS 1.0/RDF, and Atom XML—not JSON Feed.
- Accepts up to 50 feeds and responses up to 10 MB each.
- Emits up to 10,000 events per run.
- Does not fetch full linked web pages; fields are limited to what the feed publishes.
- Does not bypass authentication, CAPTCHAs, geo-restrictions, or paywalls.
- Private/local network targets and URLs containing credentials are rejected.
- A publisher may recycle GUIDs incorrectly; identity quality ultimately depends on feed data.
- Feed-window disappearance means absence from that feed, not confirmed deletion of the article page.
- Reordering entries alone does not create an update.

### Troubleshooting

**The second run emits all articles as new again.**

Use an Apify Task and do not purge its default key-value store. Keep `stateKey` unchanged. Independent standalone runs may use independent storage.

**I expected a removed event but received none.**

The item must be absent from the configured number of consecutive successful changed responses. HTTP 304 and failed requests do not advance the counter.

**A feed returns “unsupported RSS or Atom feed.”**

Open the exact URL and confirm it returns RSS/Atom XML rather than an HTML page, JavaScript shell, JSON Feed, login page, or challenge response.

**Some articles do not match my keyword.**

Matching uses the text the feed supplies. The Actor does not visit the article page to discover additional body text. Check `keywordMode` and remember that exclusions take priority.

### Responsible use and legality

Process only public feeds you are authorized to access. Follow publisher terms, applicable robots guidance, copyright rules, privacy law, and reasonable request frequencies. Feed content may be copyrighted; an accessible feed is not a grant to republish it without restriction.

Do not use this Actor to target internal services, authenticated/private feeds, or personal data unlawfully. Store only the fields needed for your legitimate workflow and apply appropriate retention controls downstream.

### Related scrapers

RSS Feed Article Monitor is intentionally focused on turning already-known public feed URLs into stateful article events. These Automation Lab Actors support adjacent workflows:

- [RSS Feed Reader](https://apify.com/automation-lab/rss-feed-reader) returns the current entries from RSS and Atom feeds when you need a snapshot rather than change events.
- [News Article Extractor](https://apify.com/automation-lab/news-article-extractor) extracts article-page content after a feed event supplies a URL.
- [Google News Scraper](https://apify.com/automation-lab/google-news-scraper) discovers news by search query when you do not already have feed URLs.

Pair this Actor's dataset and webhooks with Apify schedules and integrations for notification or storage workflows. It does not claim feed discovery, website conversion, full-page extraction, or delivery as built-in features.

### FAQ

**Does the first run show only future articles?**\
No. The first run establishes state and emits current matching entries as `new`.

**Can it monitor more than one feed?**\
Yes, up to 50 in one run. State is isolated by normalized feed URL and `stateKey`.

**Does it detect article body edits?**\
Only when the feed itself updates its supplied summary/content or another normalized field. Linked pages are not fetched.

**Can I receive notifications?**\
Connect the dataset or run webhook to an Apify integration. The Actor emits records but does not send email or chat notifications directly.

**What does a removed event prove?**\
It proves that an identity was absent from the configured number of successful changed feed snapshots. It does not prove the article URL is offline.

**Are duplicate feed entries charged twice?**\
No. Duplicate identities in the same parsed feed are collapsed, and unchanged identities do not emit item events.

# Actor input Schema

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

One to 50 public HTTP(S) RSS 2.0, RSS 1.0/RDF, or Atom URLs. Private, local, and authenticated URLs are rejected.

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

Emit only events whose title, summary, content, author, or categories contain these case-insensitive terms. Leave empty to include every article.

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

Suppress events containing any of these case-insensitive terms. Exclusions take priority over inclusions.

## `keywordMode` (type: `string`):

Require any include keyword or require all include keywords.

## `removalConfirmationRuns` (type: `integer`):

Mark an article removed only after it is absent from this many consecutive successful changed feed fetches. HTTP 304 responses do not count as missing observations.

## `maxEvents` (type: `integer`):

Maximum new, updated, and removed records saved in this run. Feed state is still updated after the cap is reached.

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

Per-feed HTTP request timeout in seconds. Transient failures are retried twice.

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

Optional namespace for independent monitoring campaigns sharing a key-value store. Keep it stable across scheduled runs.

## Actor input object example

```json
{
  "feedUrls": [
    {
      "url": "https://www.nasa.gov/news-release/feed/"
    }
  ],
  "includeKeywords": [],
  "excludeKeywords": [],
  "keywordMode": "any",
  "removalConfirmationRuns": 2,
  "maxEvents": 1000,
  "requestTimeoutSecs": 30,
  "stateKey": "default"
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset's article-event overview table or retrieve the same records through the dataset API.

# 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": [
        {
            "url": "https://www.nasa.gov/news-release/feed/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/rss-feed-article-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": [{ "url": "https://www.nasa.gov/news-release/feed/" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/rss-feed-article-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": [
    {
      "url": "https://www.nasa.gov/news-release/feed/"
    }
  ]
}' |
apify call automation-lab/rss-feed-article-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/rss-feed-article-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/kerJ0oUKyF9fQETz1/builds/U0fFzZjTXGa0b2UCT/openapi.json
