# RSS Feed Change Monitor — Only New & Updated Items (`eliai/rss-feed-change-monitor`) Actor

Watch RSS and Atom feeds and get only what changed since the last run. Keeps a baseline between scheduled runs, separates new items from edited ones, ignores tracking-parameter churn, and never floods you on run one. $0.005 per feed checked; feeds that fail to fetch or parse are never charged.

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

## Pricing

$5.00 / 1,000 checked feeds

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?

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 Change Monitor

Watch RSS and Atom feeds and get **only what changed** since the last run. Schedule it, point a
webhook at it, and stop re-processing the same items every time.

Charged per feed successfully checked. A feed that fails to fetch or isn't a feed is reported with
the error and **never charged**.

***

### Why not just parse the feed?

Parsing RSS is a library call. What you actually want scheduled is *"tell me what's new"* — and
that needs state carried between runs, which no parser gives you. This Actor keeps a fingerprint of
every feed in a named key-value store, so consecutive runs can diff against it.

That state is the whole product, and getting the diff right is harder than it looks.

#### The four things naive change-detection gets wrong

**1. Item identity.** Many feeds re-stamp `<link>` with `utm_*` tracking parameters on every fetch,
and some republish items with a fresh `<guid>`. Diff on the link, or on a hash of the whole item,
and you'll get change alerts for items that never changed. We resolve identity in order:
`guid` → `id` → link with tracking parameters stripped → `title` + date.

**2. New versus edited.** A corrected article isn't a new one. We keep a separate content
fingerprint per item, so edits come back as `updatedItems` and genuinely new items as `newItems`.
You decide which matters.

**3. Items disappearing is not deletion.** Feeds carry only the latest N entries, so items scroll
off the bottom constantly. Reporting those as "removed" would be a lie by construction, so we don't
report removals at all.

**4. The first run.** There's no previous state, so nothing has changed. Tools that report every
existing item as new on run one flood your webhook the moment you schedule them. Our first run
records a baseline, reports zero changes, and says so explicitly in the output.

**Both formats.** RSS (`<item>`, `<pubDate>`, `<link>`) and Atom (`<entry>`, `<updated>`,
`<link href>`) are both handled. A monitor that only reads RSS silently misses half the web.

***

### Input

```json
{
  "feedUrls": [
    "https://news.ycombinator.com/rss",
    "https://example.com/blog/atom.xml"
  ],
  "stateStoreName": "my-watchlist"
}
```

| Field | Type | Default | Notes |
|---|---|---|---|
| `feedUrls` | array | — | Feeds to watch |
| `feedUrl` | string | — | Watch a single feed |
| `stateStoreName` | string | `rss-monitor-state` | Where the baseline lives. Different names keep independent watchlists apart. |
| `resetBaseline` | boolean | `false` | Forget history and start fresh. That run reports no changes. |
| `maxNewItemsPerFeed` | integer | 50 | Caps output size on busy feeds. Counts stay exact. |
| `maxFeeds` | integer | 25 | Cap on feeds, and therefore on spend |

### Output

One record per feed, plus a `SUMMARY`:

```json
{
  "feedUrl": "https://example.com/feed.xml",
  "ok": true,
  "feedTitle": "Example Blog",
  "format": "rss",
  "status": "changed",
  "isFirstRun": false,
  "itemsInFeed": 30,
  "newCount": 2,
  "updatedCount": 1,
  "newItems": [
    {
      "id": "https://example.com/post-42",
      "title": "The post that just went up",
      "link": "https://example.com/post-42",
      "published": "Sun, 27 Jul 2026 18:04:00 GMT",
      "author": "Jane Doe",
      "summary": "First 600 characters of the description…"
    }
  ],
  "updatedItems": [],
  "checkedAt": "2026-07-27T20:31:00.000Z"
}
```

`status` is `baseline` on the first run for a feed, then `changed` or `unchanged`.

### Scheduling

Set an Apify schedule (hourly, daily, whatever suits) and attach a webhook on run success. Every
run after the first tells you exactly what appeared or changed. Keep the same `stateStoreName`
across runs — that's what makes the diff possible.

### For agents and automation

- **Capability:** detect new and updated items across RSS/Atom feeds between scheduled runs
- **Required input:** `feedUrl` or `feedUrls`
- **Returns:** one record per feed plus a `SUMMARY`; `newItems` / `updatedItems` are the payload
- **Stateful by design:** the named key-value store persists between runs. Same store name = same
  watchlist.
- **Bounded:** `maxFeeds` caps the run and the spend
- **Side effects:** reads feeds, writes fingerprints to your own key-value store. Nothing external.
- **Failure:** a bad feed is returned as a record with `ok: false` and an error; the run continues
  and that feed is not charged.

### Pricing

Pay per feed successfully checked. Failed fetches are free.

### FAQ

**How do I get notified when an RSS feed has new items?** Schedule this actor (hourly or daily), attach a webhook on run success, and read `newItems` — every run after the first reports only what appeared since the previous one.

**Why did the first run report zero changes?** By design. With no previous state nothing has "changed"; the first run records a baseline and says so (`status: "baseline"`) instead of flooding your webhook with every existing item — the classic failure of naive monitors.

**How does it tell a new item from an edited one?** Identity is resolved `guid` → `id` → tracking-stripped link → title+date, and a separate content fingerprint per item catches edits — so corrections come back in `updatedItems` and genuinely new posts in `newItems`.

**Can I watch several unrelated feed lists?** Yes — give each watchlist its own `stateStoreName`. Different store names keep fully independent baselines.

**Does it work with Atom feeds?** Yes — RSS (`<item>`) and Atom (`<entry>`) are both parsed; a monitor that only reads RSS silently misses half the web.

**How do I reset the baseline?** Run once with `resetBaseline: true` — history is forgotten, that run reports no changes, and monitoring continues fresh from there.

# Actor input Schema

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

RSS or Atom feed URLs. Schedule this Actor and each run reports only what changed since the last one.

## `feedUrl` (type: `string`):

Watch one feed.

## `stateStoreName` (type: `string`):

Where the baseline is kept between runs. Use different names to run independent watchlists side by side.

## `resetBaseline` (type: `boolean`):

Forget what was seen before and start fresh. This run will report no changes.

## `maxNewItemsPerFeed` (type: `integer`):

Caps output size on very busy feeds. Counts are always exact.

## `maxFeeds` (type: `integer`):

Safety cap. You are charged per feed successfully checked, so this is also your budget cap.

## Actor input object example

```json
{
  "feedUrls": [
    "https://news.ycombinator.com/rss"
  ],
  "feedUrl": "https://news.ycombinator.com/rss",
  "stateStoreName": "rss-feed-monitor-state",
  "resetBaseline": false,
  "maxNewItemsPerFeed": 50,
  "maxFeeds": 25
}
```

# Actor output Schema

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

Every item this run produced, as JSON.

## `resultsCsv` (type: `string`):

The same items as a spreadsheet-ready CSV.

# 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://news.ycombinator.com/rss"
    ],
    "feedUrl": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/rss-feed-change-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://news.ycombinator.com/rss"],
    "feedUrl": "",
}

# Run the Actor and wait for it to finish
run = client.actor("eliai/rss-feed-change-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://news.ycombinator.com/rss"
  ],
  "feedUrl": ""
}' |
apify call eliai/rss-feed-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eliai/rss-feed-change-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/c9Vcii4YeqjAGnCHb/builds/6CJC7Q4sRtrkUN4YW/openapi.json
