# RSS Change Monitor (`eliai/rss-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. Charged per feed checked; failures are free.

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

## Pricing

from $1.00 / 1,000 feed checkeds

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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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 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.

# 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"
  ],
  "stateStoreName": "rss-monitor-state",
  "resetBaseline": false,
  "maxNewItemsPerFeed": 50,
  "maxFeeds": 25
}
```

# 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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/rss-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"] }

# Run the Actor and wait for it to finish
run = client.actor("eliai/rss-change-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
  ]
}' |
apify call eliai/rss-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=eliai/rss-change-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "RSS Change Monitor",
        "description": "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. Charged per feed checked; failures are free.",
        "version": "0.1",
        "x-build-id": "gJYOyR42DimIecFqA"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/eliai~rss-change-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-eliai-rss-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/eliai~rss-change-monitor/runs": {
            "post": {
                "operationId": "runs-sync-eliai-rss-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/eliai~rss-change-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-eliai-rss-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "feedUrls": {
                        "title": "Feeds to watch",
                        "type": "array",
                        "description": "RSS or Atom feed URLs. Schedule this Actor and each run reports only what changed since the last one.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "feedUrl": {
                        "title": "Single feed",
                        "type": "string",
                        "description": "Watch one feed."
                    },
                    "stateStoreName": {
                        "title": "State store name",
                        "type": "string",
                        "description": "Where the baseline is kept between runs. Use different names to run independent watchlists side by side.",
                        "default": "rss-monitor-state"
                    },
                    "resetBaseline": {
                        "title": "Reset baseline",
                        "type": "boolean",
                        "description": "Forget what was seen before and start fresh. This run will report no changes.",
                        "default": false
                    },
                    "maxNewItemsPerFeed": {
                        "title": "Max items reported per feed",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Caps output size on very busy feeds. Counts are always exact.",
                        "default": 50
                    },
                    "maxFeeds": {
                        "title": "Max feeds",
                        "minimum": 1,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Safety cap. You are charged per feed successfully checked, so this is also your budget cap.",
                        "default": 25
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
