# RSS & News Feed Monitor (`munin/feed-monitor`) Actor

Monitor RSS, Atom and JSON feeds — or any site that has one — and get back only the items published since your last run. Optional full article text, keyword and date filters. You pay per delivered item: nothing new means nothing charged.

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

## Pricing

$2.00 / 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 & News Feed Monitor

Watch any list of RSS, Atom or JSON feeds — or plain site URLs — and get back **only the items published since your last run**. Optionally with the full article text, not just the 150-character summary the feed carries.

Built for scheduled runs: point it at 50 blogs, run it every hour, and each run delivers the handful of posts that are actually new. You are charged per delivered item, so an hour with nothing new costs you nothing.

### What it does

- **Reads every common feed format** — RSS 2.0, RSS 1.0 (RDF), Atom and JSON Feed. Measured on 53 live sources: 51 worked (96%).
- **Finds the feed for you.** Give it `https://blog.cloudflare.com` or `https://stripe.com/blog` and it looks for the feed the page declares, then for the usual paths (`/feed`, `/rss.xml`, `/atom.xml`, `/index.xml`, `/feed.json`, …).
- **Remembers what it already sent.** Item IDs are kept in a named key-value store, so a scheduled run returns new items only — no duplicates, no double charges. A run with nothing new delivers 0 items and costs 0.
- **A per-feed limit throttles, it does not discard.** Items over `maxItemsPerFeed` are delivered by the following run instead of being dropped.
- **Skips unchanged feeds for free.** It sends `If-None-Match` / `If-Modified-Since`; a `304 Not Modified` costs you nothing at all. About 60% of publishers support this (32 of 51 feeds in our measurement).
- **Fetches the real article** when you ask for it. Feeds usually carry 100–200 characters; the extracted body is around 10× longer (median 686 → 6,700 characters in our measurement).
- **Filters before charging.** Keyword, exclude-keyword, author and date filters are applied first. Items you filter out are not billed.

### Input

| Field | Type | What it does |
|---|---|---|
| `sources` | array | Feed URLs or site URLs. Required. |
| `keywords` | array | Keep items matching any keyword (case-insensitive). |
| `excludeKeywords` | array | Drop items matching any of these. |
| `matchIn` | array | Fields the keyword filters look at. Default: title + summary. |
| `authors` | array | Keep only these authors. |
| `publishedWithinHours` | integer | Drop items older than this. 0 = no limit. |
| `onlyNewItems` | boolean | Default on. Deliver only what appeared since the last run. |
| `stateStoreName` | string | Named store for the memory, so separate watch lists don't mix. |
| `includeFullText` | boolean | Open each article and extract the body. Default off. |
| `fullTextMinChars` | integer | Shorter text counts as "not found". Default 500. |
| `includeContentHtml` | boolean | Also return HTML, not only plain text. |
| `maxItemsPerFeed`, `maxItems` | integer | Limits. Counted on delivered items. |
| `maxFeedsPerSite` | integer | Take more than one feed when a site declares several. |
| `maxConcurrency` | integer | Feeds read in parallel. Default 5. |
| `useApifyProxy` | boolean | For the few publishers that block datacenter traffic. |

#### Example input

```json
{
  "sources": ["https://techcrunch.com/feed/", "https://blog.cloudflare.com", "https://www.theverge.com/rss/index.xml"],
  "keywords": ["ai", "acquisition"],
  "publishedWithinHours": 24,
  "onlyNewItems": true,
  "includeFullText": true
}
```

### Output

One dataset item per feed entry:

```json
{
  "title": "Cloudflare acquires Human Native",
  "url": "https://blog.cloudflare.com/cloudflare-acquires-human-native/",
  "id": "https://blog.cloudflare.com/cloudflare-acquires-human-native/",
  "author": "Matthew Prince",
  "publishedAt": "2026-09-17T13:00:00.000Z",
  "updatedAt": null,
  "summary": "The web has always run on a bargain…",
  "contentText": "The web has always run on a bargain between…",
  "categories": ["AI", "Acquisitions"],
  "imageUrl": "https://cf-assets.www.cloudflare.com/…/image.png",
  "enclosures": [],
  "commentsUrl": null,
  "feedTitle": "The Cloudflare Blog",
  "feedUrl": "https://blog.cloudflare.com/rss/",
  "feedType": "rss",
  "fullText": "The web has always run on a bargain… (6,700 characters)",
  "wordCount": 1104,
  "success": true,
  "scrapedAt": "2026-09-19T04:12:07.881Z"
}
```

A source whose feed could not be found or read produces one row instead of failing the run, and that row is **not charged**:

```json
{
  "input": "https://www.anthropic.com/news",
  "success": false,
  "itemsDelivered": 0,
  "error": "No RSS, Atom or JSON feed found for this URL. If the site has one, pass its feed URL directly."
}
```

### What it cannot do

- **It cannot invent a feed that doesn't exist.** Sites that publish no RSS/Atom/JSON feed (for example `anthropic.com/news`) return a failure row. It does not scrape article lists out of HTML.
- **Full article text is not guaranteed.** Measured at 84–88% of articles. It fails on pages rendered entirely by JavaScript, on paywalled articles (NYTimes, The Economist), and on pages that aren't articles at all (a landing page, a forum post). Those rows carry `fullText: null` and a reason; the item is still delivered and charged, because the feed entry itself is the product.
- **Short posts fall below the length floor.** Link-blog entries of 200–400 characters are treated as "no body" at the default `fullTextMinChars: 500`. Lower it if you watch link blogs.
- **Some publishers block datacenter IPs outright.** AP News returns 403 to any datacenter request. Turn on Apify Proxy for those.
- **No login, no anti-bot bypass.** Feeds behind a subscriber login are out of scope.
- **Feeds only carry what the publisher puts in them.** Missing authors, missing dates and truncated summaries are the publisher's choice, not a bug here. Dates are normalised to ISO 8601 when present.
- **It is not a full-site archive.** A feed typically holds the latest 10–50 items; the actor cannot reach back further than the feed goes.

### Pricing

Pay per event: **$2.00 per 1,000 delivered items**. Platform usage (compute, proxy, storage) is included — you pay for delivered items and nothing else.

Not charged: filtered-out items, items already delivered in an earlier run, unchanged feeds (`304`), and sources that failed.

### Tips

- Schedule it hourly with `onlyNewItems: true` and `publishedWithinHours: 48`. The date filter guards against a feed that republishes old items with new IDs.
- Use `stateStoreName` to separate watch lists — `"competitors"` and `"industry-news"` won't hide each other's items.
- Leave `includeFullText` off for alerting and turn it on for summarising. Off, a 50-feed run finishes in a few seconds.
- Chain it into an LLM step with the `text` output link: it returns title, URL, date and body only.

# Actor input Schema

## `sources` (type: `array`):

Feed URLs, or plain site URLs. For a site, the actor looks for a declared <link rel="alternate"> feed and then for the usual paths (/feed, /rss.xml, /atom.xml, …). Works with RSS 2.0, RSS 1.0, Atom and JSON Feed.

## `keywords` (type: `array`):

Keep only items matching at least one keyword. Case-insensitive substring match. Leave empty to get everything.

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

Drop items matching any of these.

## `matchIn` (type: `array`):

Which fields the keyword filters look at. Add "content" to search the text the feed itself carries. Full article text is not searched, because it is fetched after filtering.

## `authors` (type: `array`):

Keep only items whose author matches one of these. Many feeds leave the author empty; those items are dropped when this is set.

## `publishedWithinHours` (type: `integer`):

Drop items older than this. 0 = no date limit. Items with no date in the feed are always kept.

## `onlyNewItems` (type: `boolean`):

On for scheduled monitoring: the actor remembers what it already delivered and sends only what appeared since. Turn off to get the whole current feed every run.

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

Named key-value store holding the delivered item IDs. Use different names to keep separate watch lists from interfering. Leave empty to use the shared store "feed-monitor-state".

## `includeFullText` (type: `boolean`):

Most feeds carry only a 100-200 character summary. With this on, the actor opens each article page and extracts the body. Adds about 0.2-1 second per item and does not always succeed (pages rendered by JavaScript or behind a paywall come back empty with a reason).

## `fullTextMinChars` (type: `integer`):

Text shorter than this is treated as not found rather than returned as a fragment.

## `includeContentHtml` (type: `boolean`):

Also return the content as HTML, not only as plain text. Makes the dataset noticeably larger.

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

0 = no limit. Counts delivered items, not items removed by filters.

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

Hard stop for the whole run. 0 = no limit.

## `maxFeedsPerSite` (type: `integer`):

Some sites declare several feeds (all posts, one per category). Raise this to take more than the first.

## `maxConcurrency` (type: `integer`):

How many feeds to read at once.

## `useApifyProxy` (type: `boolean`):

Not needed for most feeds. Turn on if a publisher blocks datacenter traffic.

## Actor input object example

```json
{
  "sources": [
    "https://techcrunch.com/feed/",
    "https://www.theverge.com/rss/index.xml",
    "https://blog.cloudflare.com",
    "https://news.ycombinator.com/rss"
  ],
  "matchIn": [
    "title",
    "summary"
  ],
  "publishedWithinHours": 0,
  "onlyNewItems": true,
  "includeFullText": false,
  "fullTextMinChars": 500,
  "includeContentHtml": false,
  "maxItemsPerFeed": 0,
  "maxItems": 100,
  "maxFeedsPerSite": 1,
  "maxConcurrency": 5,
  "useApifyProxy": false
}
```

# Actor output Schema

## `items` (type: `string`):

Every delivered entry with title, link, author, publication date, summary and tags.

## `itemsCsv` (type: `string`):

Title, source, author, date and link. Ready for a spreadsheet.

## `text` (type: `string`):

Title, link and article body only, for summarising or LLM prompts.

# 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 = {
    "sources": [
        "https://techcrunch.com/feed/",
        "https://www.theverge.com/rss/index.xml",
        "https://blog.cloudflare.com",
        "https://news.ycombinator.com/rss"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("munin/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 = {
    "sources": [
        "https://techcrunch.com/feed/",
        "https://www.theverge.com/rss/index.xml",
        "https://blog.cloudflare.com",
        "https://news.ycombinator.com/rss",
    ],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("munin/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 '{
  "sources": [
    "https://techcrunch.com/feed/",
    "https://www.theverge.com/rss/index.xml",
    "https://blog.cloudflare.com",
    "https://news.ycombinator.com/rss"
  ],
  "maxItems": 100
}' |
apify call munin/feed-monitor --silent --output-dataset

```

## MCP server setup

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