# RSS Feed Monitor - Only New Items, Atom & JSON Feed, Podcasts (`bruco3/feed-monitor`) Actor

Read hundreds of RSS, Atom and JSON feeds in one run and get clean, deduplicated items. Turn on 'only new items' and schedule it: every run delivers exactly what was published since the last one. Feed autodiscovery from any site URL. Pay per feed check and per item.

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

## Pricing

from $0.50 / 1,000 item delivereds

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 Feed Monitor — only new items, from hundreds of feeds, in one run

Give it a list of RSS, Atom or JSON feeds — or just the websites, and the feeds are found for you — and get every item as clean, uniform JSON: title, link, date in UTC, author, plain-text summary, categories, images and podcast enclosures. Whatever dialect the publisher uses, the rows look the same.

Then turn on **Only new items** and schedule it. Each feed remembers what it already delivered, so every run returns exactly the items published since the previous run. No duplicates, no "did I see this already?", no database on your side.

What sets it apart from other feed readers on Apify Store:

- **Only new items, per feed, remembered for you** — the monitoring case handled natively, not with a "dedupe by URL" afterthought. Seed a feed silently on the first run if you only want future items.
- **Every dialect** — RSS 2.0, RSS 1.0 (RDF), Atom, **JSON Feed**, podcast feeds with enclosures and durations, `media:` images, `content:encoded` full text.
- **Feed autodiscovery** — paste a blog URL; `<link rel="alternate">` tags and common paths (`/feed`, `/rss.xml`, `/atom.xml`, `/feed.json`, Blogger, WordPress) are checked for you.
- **Hundreds of feeds per run**, fetched in parallel, each one isolated: a dead feed is reported in the summary and never stops the others.

### What you get

One row per item:

```json
{
  "title": "Python 3.15.0 beta 1 released",
  "link": "https://blog.python.org/2026/09/python-3150b1.html",
  "published": "2026-09-16T14:02:00+00:00",
  "updated": null,
  "author": "Python Insider",
  "summary": "The first beta of Python 3.15 is out. Beta means no new features…",
  "categories": ["release"],
  "enclosures": [],
  "image": null,
  "itemId": "tag:blogger.com,1999:blog-123.post-456",
  "feedUrl": "https://blog.python.org/feeds/posts/default?alt=rss",
  "feedTitle": "Python Insider",
  "feedType": "rss",
  "siteUrl": "https://blog.python.org/",
  "fetchedAt": "2026-09-18T13:05:00+00:00",
  "isNew": true
}
```

| Field | Notes |
|---|---|
| `published`, `updated` | Normalised to ISO 8601 UTC from RFC 822, ISO 8601 or date-only values. `null` when the feed gives none. |
| `summary` | HTML stripped, whitespace collapsed, up to 600 characters. Turn on **Include full item content** for `contentHtml`. |
| `enclosures` | `{url, type, length, duration}` — podcast episodes, videos, files. |
| `itemId` | The feed's own `guid` / `id`, or the link, or a stable hash. What "only new" is keyed on. |
| `isNew` | Only when **Only new items** is on. |

A per-feed summary (items in feed, new, delivered, first run, errors) is saved as the `SUMMARY` record of the run's key-value store.

### Input

| Option | Default | What it does |
|---|---|---|
| **Feed URLs** | — | One per line. RSS / Atom / JSON Feed / podcast feeds. |
| **Or: websites to discover feeds from** | — | Blog or news URLs; their feeds are discovered. |
| **Only new items since the last run** | on | Per-feed memory in a named key-value store. |
| **On the first run of a feed** | deliver everything | Or *seed only*: remember the current items and deliver nothing, so the next run is already "new only". |
| **Only items published since** | — | ISO date or `24h`, `7d`, `2w`. |
| **Only items whose title or summary matches** / **Skip items matching** | — | Case-insensitive regular expressions, e.g. `python|rust`, `sponsored`. |
| **Include full item content (HTML)** | off | Adds `contentHtml` when the feed provides the body. |
| **Deduplicate the same link across feeds** | on | One row when several feeds carry the same article. |
| **Maximum items per feed** | `0` | |
| **Snapshot store name** | `feed-monitor-snapshots` | Change it to track the same feeds independently (e.g. per project). |

### How "only new" stays correct

- Memory is **per feed**, keyed on the feed's own item ids, with a rolling window of the last 5,000 ids — old items that drop off a feed and come back later are not reported as new.
- If a run stops at your maximum charge, items that could not be delivered are **not** remembered: they come back on the next run instead of being lost.
- Changing filters does not touch the memory: an item filtered out today is still "new" tomorrow if the filter changes.

### Pricing

Pay per event, no subscription:

- **one event per feed checked** (a few cents per thousand checks — it covers the cost of runs that find nothing new);
- **one event per item delivered**.

A scheduled monitor over 50 feeds that finds 12 new items pays for 50 checks and 12 items. Set a maximum charge on the run to cap the cost; the Actor stops cleanly when it is reached.

### Use cases

- **Newsroom / competitor watch** — every morning, what the sites you follow published, deduplicated, in one dataset.
- **Content pipelines and AI agents** — feed new articles into summarisation, translation or classification; an agent can call this Actor and pay per event.
- **Podcast tracking** — new episodes across many shows, with audio URLs and durations.
- **Job and release feeds** — python.org jobs, GitHub releases, changelogs, status pages: anything with a feed.
- **Alerts** — pipe new items to Slack, email or a sheet with Apify integrations and schedules.

### Run it from code

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("bruco3/feed-monitor").call(run_input={
    "feeds": ["https://hnrss.org/frontpage", "https://blog.apify.com/rss/"],
    "onlyNew": True,
    "firstRun": "seed",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["published"], item["title"], item["link"])
```

### FAQ

**A feed returns an HTML page.** You passed a web page, not a feed. Put it under *websites to discover feeds from* instead and the feed will be found, or open an issue if a site's feed goes undiscovered.

**Dates look wrong.** They are converted to UTC. `2026-09-01T10:00:00+02:00` becomes `2026-09-01T08:00:00+00:00`.

**Is it legal?** Feeds are published by site owners specifically for automated readers. The Actor identifies itself with a clear `User-Agent` and fetches only feed files.

### Related

- **[Sitemap URL Extractor](https://apify.com/bruco3/sitemap-url-extractor)** — every URL of a site, with added/removed pages between runs.
- **[Broken Link Checker](https://apify.com/bruco3/broken-link-checker)** — 404s, redirect chains and slow pages across a whole site.

### Feedback

A feed dialect this Actor gets wrong, or a field you need? Open an issue on the **Issues** tab. If it saved you time, a review helps others find it.

### Changelog

- **0.1** — initial release: RSS 2.0 / 1.0, Atom, JSON Feed, podcast enclosures, autodiscovery, only-new memory per feed with seed mode, filters, cross-feed dedupe, pay per check and per item.

# Actor input Schema

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

RSS 2.0, RSS 1.0, Atom or JSON Feed URLs, one per line (or upload a text file). Podcast feeds work too. Hundreds of feeds in one run are fine.

## `startUrls` (type: `array`):

Paste a blog or news site and its feed is found for you through <code>\<link rel="alternate"></code> tags and common paths (<code>/feed</code>, <code>/rss.xml</code>, <code>/atom.xml</code>, …).

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

Each feed remembers which items it has already delivered (in a named key-value store). Turn this on and schedule the Actor: every run returns exactly what was published since the previous run.

## `firstRun` (type: `string`):

<b>Deliver everything</b> returns all current items the first time a feed is seen. <b>Seed only</b> remembers them without returning anything, so the very next run already delivers only new items.

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

ISO date (<code>2026-01-31</code>) or a window like <code>24h</code>, <code>7d</code>, <code>2w</code>. Items without a date are dropped when this is set.

## `includeRegex` (type: `string`):

Case-insensitive regular expression, e.g. <code>python|rust</code>.

## `excludeRegex` (type: `string`):

Case-insensitive regular expression, e.g. <code>sponsored|webinar</code>.

## `includeContent` (type: `boolean`):

Adds <code>contentHtml</code> with the full article body when the feed provides it. Off by default to keep rows small.

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

<code>0</code> = no limit.

## `dedupeAcrossFeeds` (type: `boolean`):

When several feeds carry the same article, deliver it once.

## `snapshotStoreName` (type: `string`):

Named key-value store that remembers delivered items, one snapshot per feed. Use different names to track the same feeds independently.

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

How many feeds to fetch at once.

## `userAgent` (type: `string`):

Sent with every request.

## `proxyConfiguration` (type: `object`):

Not needed for public feeds. Enable only if a publisher blocks datacenter traffic.

## Actor input object example

```json
{
  "feeds": [
    "https://hnrss.org/frontpage",
    "https://blog.apify.com/rss/"
  ],
  "onlyNew": true,
  "firstRun": "emit",
  "includeContent": false,
  "maxItemsPerFeed": 0,
  "dedupeAcrossFeeds": true,
  "snapshotStoreName": "feed-monitor-snapshots",
  "concurrency": 10,
  "userAgent": "FeedMonitor/1.0 (+https://apify.com/bruco3/feed-monitor)",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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://hnrss.org/frontpage",
        "https://blog.apify.com/rss/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bruco3/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 = { "feeds": [
        "https://hnrss.org/frontpage",
        "https://blog.apify.com/rss/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("bruco3/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 '{
  "feeds": [
    "https://hnrss.org/frontpage",
    "https://blog.apify.com/rss/"
  ]
}' |
apify call bruco3/feed-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,bruco3/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/JsIxyD6v4araOkhK5/builds/7pc3Lp77yAdn7vc9R/openapi.json
