# RSS & Atom Feed Reader with Auto-Discovery and Incremental Mode (`obligate_castle/rss-atom-feed-monitor`) Actor

Turn any RSS feed, Atom feed or JSON Feed into a clean, flat dataset of articles. Paste a feed URL, or just a website address: the Actor finds the feed the site advertises, reads it and returns one normalized row per article, with ISO dates, plain-text summaries, categories, images and podcast...

- **URL**: https://apify.com/obligate\_castle/rss-atom-feed-monitor.md
- **Developed by:** [Yoann TUQUET](https://apify.com/obligate_castle) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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 & Atom Feed Reader – Auto-Discovery and New-Articles-Only Mode

Turn any **RSS feed**, **Atom feed** or **JSON Feed** into a clean, flat dataset of articles. Paste a feed URL, or just a **website address**: the Actor finds the feed the site advertises, reads it and returns one normalized row per article, with ISO dates, plain-text summaries, categories, images and podcast attachments.

Switch on **"Only new articles since the last run"** and schedule the Actor: every run returns only what was published since the previous one. No start fee: you pay for the articles you receive, nothing else.

Everything is read from the feeds that publishers put online on purpose. No scraping, no login, no browser.

### Why use it

- ✅ **Feed auto-discovery**: give `https://go.dev/blog/` instead of hunting for the feed URL. The Actor reads the `<link rel="alternate">` tags the page itself publishes, article feeds first, comment feeds last.
- ✅ **Every common format**: RSS 0.9x / 2.0, RSS 1.0 (RDF), Atom 1.0 and JSON Feed, including podcasts (enclosures) and media thumbnails.
- ✅ **Incremental monitoring**: a persistent memory per feed returns only the new articles, and uses `ETag` / `Last-Modified` so unchanged feeds cost almost nothing.
- ✅ **Normalized output**: dates in ISO 8601 UTC, absolute links, HTML stripped, duplicates removed, stable article IDs across runs.
- ✅ **Polite and safe**: respects `robots.txt`, at most 2 requests per second per host, refuses private network addresses.
- ✅ **No start fee, pay per article**: failed feeds and duplicates are never charged.

### Use cases

1. **News and competitor monitoring**: follow the blogs, changelogs and press pages of dozens of companies and get only what is new, on a schedule.
2. **Content pipelines**: feed fresh articles to a newsletter, a Slack channel, a Notion database or an AI summarizer through webhooks.
3. **Security and release watching**: track vendor advisories, product release notes and status feeds published as RSS or Atom.
4. **Podcast and video tracking**: collect new episodes with their audio URL, MIME type and size.
5. **SEO and PR**: monitor mentions published in feeds, and archive articles with their categories and dates.

### How to use it

1. Click **Try for free** and paste feed URLs or website addresses in **Feed or website URLs**.
2. Choose how many articles per feed you want (**Max articles per feed**), and optionally enable **Only new articles since the last run**.
3. Click **Start**. Export the dataset as **JSON, CSV, Excel or HTML**, or fetch it through the Apify API.

Tip: create a **Schedule** (for example hourly or daily) together with **Only new articles since the last run**, and connect a **webhook** or an integration to be notified of new articles.

### What is returned

| Field | Description |
|---|---|
| `id`, `guid` | Stable article identifier, and the identifier given by the feed itself |
| `title`, `link` | Plain-text title and absolute article URL |
| `publishedAt`, `updatedAt` | ISO 8601 UTC dates (RFC 822, Atom and other formats are normalized) |
| `summary`, `content` | Summary (500 characters max) and, if enabled, full text without HTML (20,000 characters max) |
| `categories` | Categories or tags (20 max) |
| `imageUrl` | Thumbnail from `media:thumbnail`, `media:content`, an image enclosure or JSON Feed |
| `enclosureUrl`, `enclosureType`, `enclosureLength` | Attached file such as podcast audio, video or PDF |
| `feedUrl`, `feedTitle`, `feedType`, `siteUrl`, `language` | The feed the article comes from |
| `discoveredFrom` | The web page you gave, when the feed was auto-discovered |
| `fetchedAt` | Download time |

Author names are deliberately not collected, so the dataset contains no personal profile data.

### Input

```json
{
  "items": ["https://go.dev/blog/", "https://github.blog/feed/"],
  "maxItemsPerFeed": 20,
  "onlyNewSinceLastRun": true,
  "stateNamespace": "engineering-blogs",
  "maxAgeDays": 30,
  "includeContent": false
}
```

| Field | Description | Default |
|---|---|---|
| `items` | Feed URLs (RSS, Atom, JSON Feed) or website addresses to discover feeds from | required |
| `maxItemsPerFeed` | Most recent articles returned per feed, 1 to 500 | `20` |
| `onlyNewSinceLastRun` | Return only articles not seen in a previous run with the same state name | `false` |
| `stateNamespace` | Name of the incremental memory; use different names for independent monitors | `default` |
| `maxAgeDays` | Ignore articles older than this many days (`0` = no limit; undated articles are kept) | `0` |
| `discoverFeeds` | Read the feeds advertised by web pages | `true` |
| `maxFeedsPerSite` | Feeds read per web page, 1 to 5 | `1` |
| `includeContent` | Add the full article text as plain text | `false` |
| `timeoutSecs` | Network timeout per download | `20` |

The first incremental run returns the latest articles (up to `maxItemsPerFeed`); the following runs return only the new ones. If a feed publishes more new articles than `maxItemsPerFeed` between two runs, raise the limit or run more often.

### Output

One dataset item per article. Real example, from the Go blog (abridged):

```json
{
  "id": "5fe29c284785b3a6",
  "guid": "tag:blog.golang.org,2013:blog.golang.org/size-specialized-allocations",
  "title": "Size-Specialized Memory Allocation",
  "link": "https://go.dev/blog/size-specialized-allocations",
  "publishedAt": "2026-09-16T00:00:00.000Z",
  "updatedAt": "2026-09-16T00:00:00.000Z",
  "summary": "Go 1.27 improves performance of small allocations using size-specialized allocation functions.",
  "categories": [],
  "imageUrl": null,
  "enclosureUrl": null,
  "enclosureType": null,
  "enclosureLength": null,
  "feedUrl": "https://go.dev/blog/feed.atom",
  "feedTitle": "The Go Blog",
  "feedType": "atom",
  "siteUrl": null,
  "language": null,
  "discoveredFrom": "https://go.dev/blog/",
  "fetchedAt": "2026-09-18T17:38:16.923Z"
}
```

A **run summary** is also saved under the `SUMMARY` key of the default key-value store: for every feed, its type, the number of articles found and published, and the error when a feed could not be read.

### Pricing

Pay per event: **$0.002 per article returned**, which is **$2 per 1,000 articles**. There is **no start fee**. Feeds that fail, invalid URLs, duplicates and unchanged feeds (in incremental mode) are not charged. You can cap your spending with the maximum charge setting of each run.

### Integrations

- **API**: start runs and download results from any language with the Apify API or clients.
- **Schedules and webhooks**: run hourly or daily with "Only new articles" and trigger a webhook when the run finishes.
- **Make, Zapier, n8n**: post new articles to Slack, email, Google Sheets, Notion or your CMS.
- **AI agents**: callable as a tool through the Apify MCP server.

### FAQ

**Does it work with any website?**
Only with sites that publish a feed. The Actor reads the feeds a page advertises in its HTML head; it does not guess URLs such as `/feed` or crawl the site. If a page advertises no feed, the run reports it and nothing is charged.

**How does "Only new articles" know what is new?**
It stores a compact list of the article IDs it has already returned, per feed and per state name, in a named key-value store of your account. The same article is never returned twice, even if the feed reorders it. An article that is edited later keeps its ID and is not returned again.

**What if a site forbids robots?**
`robots.txt` is respected. A feed disallowed for automated readers is reported as an error and not requested.

**Why is a date missing?**
Some feeds give no date or an unreadable one. `publishedAt` is then `null`, and the article is kept unless it is filtered by other options. Atom entries without a publication date fall back to their update date.

**Are authors included?**
No. Author fields are never read, to keep the output free of personal data.

### Limitations

- Only public feeds are read: no login, no paywalled or password-protected feeds.
- Summaries and full text are converted to plain text; images and formatting inside articles are not returned.
- A feed larger than 10 MB is rejected.
- Feeds show only their most recent articles (often 10 to 50); older articles are not available through this Actor.
- Feed autodiscovery relies on the `<link rel="alternate">` tags of the page you provide; pages rendered only by JavaScript may not expose them.

# Actor input Schema

## `items` (type: `array`):

RSS, Atom or JSON Feed URLs, or website addresses (a domain or a page URL) whose advertised feed is discovered automatically.

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

Most recent articles returned for each feed (after the other filters). Each article returned is one charged result.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only articles not seen in a previous run with the same state name. The first run returns the latest articles. Ideal for schedules.

## `stateNamespace` (type: `string`):

Name of the incremental memory. Use different names to run independent monitors on the same feeds. Only used when 'Only new articles' is enabled.

## `maxAgeDays` (type: `integer`):

Ignore articles published more than this many days ago. 0 keeps everything. Articles without a date are kept.

## `discoverFeeds` (type: `boolean`):

When a URL is a web page, read the feeds it advertises with <link rel="alternate"> tags. No other path is tried.

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

How many discovered feeds to read for each web page (article feeds first, comment feeds last).

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

Add a 'content' field with the article body as plain text (up to 20,000 characters) when the feed provides it.

## `timeoutSecs` (type: `integer`):

Network timeout for each download.

## Actor input object example

```json
{
  "items": [
    "https://go.dev/blog/",
    "https://github.blog/feed/",
    "https://www.nasa.gov/feed/"
  ],
  "maxItemsPerFeed": 20,
  "onlyNewSinceLastRun": false,
  "stateNamespace": "default",
  "maxAgeDays": 0,
  "discoverFeeds": true,
  "maxFeedsPerSite": 1,
  "includeContent": false,
  "timeoutSecs": 20
}
```

# Actor output Schema

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

Latest articles with date, title, link, feed and summary.

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

Complete article items including categories, media and feed metadata.

## `summary` (type: `string`):

Per-feed report: feed type, articles found and published, errors, budget status.

# 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 = {
    "items": [
        "https://go.dev/blog/",
        "https://github.blog/feed/",
        "https://www.nasa.gov/feed/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("obligate_castle/rss-atom-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 = { "items": [
        "https://go.dev/blog/",
        "https://github.blog/feed/",
        "https://www.nasa.gov/feed/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("obligate_castle/rss-atom-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 '{
  "items": [
    "https://go.dev/blog/",
    "https://github.blog/feed/",
    "https://www.nasa.gov/feed/"
  ]
}' |
apify call obligate_castle/rss-atom-feed-monitor --silent --output-dataset

```

## MCP server setup

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