# RSS & Atom Feed to JSON with New-Item Detection (`zendren/feed-snapshot-delta`) Actor

Fetch a public RSS 2.0 or Atom feed by URL (or paste XML) and get clean JSON with stable IDs, dedup and new-item flags vs IDs you've seen. $0.003 per feed.

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

## Pricing

from $3.00 / 1,000 processed documents

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 to JSON with New-Item Detection

Turn an RSS 2.0 or Atom feed — from a **public URL** or pasted XML — into normalized, agent-ready JSON — stable `id`, `title`, `url`, `publishedAt` — and flag which items are **new** compared to the IDs you already processed.

### Why use it

- **"What's new?" in one call** — pass `knownIds` from your last run (top-level for URL feeds, or per pasted document); each entry gets `isNew`, plus `newCount` and `duplicateCount`.
- **One schema for RSS and Atom** — no more per-feed parsing quirks in your Make/n8n/agent workflow.
- **Deduplicated & deterministic** — duplicate GUIDs/links collapsed, SHA-256 per snapshot.
- **Cheap** — flat $0.003 per feed, ideal for frequent schedules.

### Input

| Field | Description |
|---|---|
| `publicDataOnly` | Must be `true`. |
| `feedUrls[]` | Public `http(s)` feed URLs to fetch (see *Fetching from URLs*). |
| `knownIds[]` | Optional, up to 5,000 previously seen entry IDs; applies to every feed in `feedUrls`. |
| `documents[]` | Optional pasted feeds: objects with `xml` (required, feed XML) and optional per-document `knownIds`. |

Supply at least one of `feedUrls` or `documents`; **1–5 items in total per run** (URLs + pasted documents).

RSS 2.0 and Atom only, 1 MiB and 500 entries per feed; each entry needs an absolute http(s) link. No RSS 1.0/RDF, DTDs or article fetching (only the feed document itself is downloaded). Full descriptions and author fields are intentionally omitted. JSON input under 8 MB, output under 1 MB per document. Fixed 256 MB memory. Each document is parsed in an isolated child process (no Apify credentials, 160 MiB memory, 15 s CPU / 20 s wall-time limits). Nothing in the input is executed.

Example input (the public Python releases feed) is prefilled in Console and in `INPUT.example.json`:

```json
{"publicDataOnly": true, "feedUrls": ["https://www.python.org/downloads/feed.rss"]}
```

#### Fetching from URLs: safety and limits

The Actor downloads only what you list, from the main process; the parser still runs in an isolated, network-free child process.

- Public `http`/`https` URLs only, ports 80 and 443 only, no credentials in the URL (`user:pass@`).
- The hostname is resolved and **every** resulting address must be publicly routable. Localhost, private (10/8, 172.16/12, 192.168/16), link-local/cloud-metadata (169.254/16), CGNAT (100.64/10), multicast, reserved and IPv6 loopback/ULA/site-local addresses are refused (`URL_BLOCKED`). The connection is pinned to the vetted address, so DNS rebinding cannot redirect it.
- Up to 3 redirects, each hop re-validated the same way. 10 s connect timeout, 20 s total per URL. No cookies, no authentication headers, no proxies.
- Responses are capped at 1 MiB per feed (gzip transfer encoding accepted; the decompressed size is capped too, and the download is aborted once the cap is exceeded).
- Failed fetches (`URL_BLOCKED`, `FETCH_FAILED`, `FETCH_TIMEOUT`, `TOO_LARGE`, `WRONG_CONTENT`) are listed in `OUTPUT.errors` with `documentIndex`, the error code and the URL, and are **never charged**. Only successfully processed items are charged.
- `documentIndex` counts pasted `documents` first, then URL entries. Every output item has `sourceUrl` (`null` for pasted documents) and `fetchedUrls`; `OUTPUT.fetchedUrls` is the number of successful downloads in the run.
- Requests identify as `ZendrenApifyActor/0.2 (+https://apify.com/zendren)`. Only fetch content you are permitted to process.

### Output

```json
{"documentIndex": 0, "kind": "feed", "type": "rss2", "entryCount": 10, "newCount": 7, "duplicateCount": 0,
 "sourceUrl": "https://www.python.org/downloads/feed.rss", "fetchedUrls": 1,
 "entries": [{"id": "https://www.python.org/downloads/release/python-3150/", "title": "Python 3.15.0",
   "url": "https://www.python.org/downloads/release/python-3150/", "publishedAt": "Fri, 09 Oct 2026 13:54:59 +0000", "isNew": true}],
 "sha256": "483235f5…"}
```

The key-value store record `OUTPUT` summarises the run: `processed` and `fetchedUrls` counts, an `errors` list (with `documentIndex`, error code, message and, for URL items, the `url`) and `stoppedForSpendingLimit`. Invalid documents are reported there, are **not** written to the dataset and are **not** charged; valid siblings still process. Check `OUTPUT` even when the run status is *Succeeded*.

### Pricing

Pay per event, no subscription, no platform-usage pass-through:

| Event | Price | When |
|---|---|---|
| `processed-document` | **$0.003** | One feed successfully processed and saved to the dataset |
| `apify-actor-start` | $0.00005 | Once per run (Apify synthetic start event) |

Example: 5 documents in one run = $0.01505. Invalid documents are free; the start event still applies to runs whose input is rejected. Set **Maximum cost per run** in Console or `?maxTotalChargeUsd=` in the API — the Actor stops before parsing a document it cannot pay for.

### Use it from the API, Make, n8n or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/zendren~feed-snapshot-delta/run-sync-get-dataset-items?maxTotalChargeUsd=0.05" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d @INPUT.example.json
```

Send the token in the `Authorization` header, never in the URL. The Actor is also usable as an MCP tool through Apify's MCP server (`mcp.apify.com`).

### Responsible use

Supply only public data you are permitted to process, with no personal data. `publicDataOnly` is a policy acknowledgement, not an automated privacy detector. The Actor only downloads the exact public URLs you list (no crawling, no logins, no proxies) and never calls external AI services.

### Support

Found a bug or need a format supported? Open an issue on the Actor's **Issues** tab. Fixture attributions: see `NOTICE.md` in the source.

# Actor input Schema

## `publicDataOnly` (type: `boolean`):

Keep checked to confirm the documents are public, permitted to process, and contain no personal data. Runs with this unchecked are rejected (not charged). This is a policy acknowledgement, not automated PII detection.

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

Public RSS 2.0 or Atom feed URLs (up to 5 in total together with pasted documents). Each feed is fetched by the Actor: at most 1 MiB (gzip accepted, decompressed size also capped), ports 80/443 only, no credentials in the URL, redirects limited to 3, 20-second timeout; URLs resolving to private/internal addresses are refused. Failed fetches are listed in the run summary and are not charged.

## `knownIds` (type: `array`):

Entry IDs you have already seen (up to 5,000). Entries with these IDs get isNew=false in every fetched feed. Pasted documents carry their own knownIds.

## `documents` (type: `array`):

Optional. Content you paste directly. Counts toward the 1-5 items per run together with the URL list. 1 MiB XML, 500 input entries, knownIds up to 5,000 strings. RSS 2.0/Atom only. Each entry requires a valid absolute HTTP(S) link. No RDF/RSS 1.0, relative-link resolution, XML entities or DTDs. No article fetching, author data, full descriptions, emails or contacts. Titles and publication dates stay as provided, not inferred. Pricing: $0.003 per successful document + $0.00005/run start. XML entries/PDF chunks are nested, not separately charged. Total input under 8 MB.

## Actor input object example

```json
{
  "publicDataOnly": true,
  "feedUrls": [
    "https://www.python.org/downloads/feed.rss"
  ]
}
```

# Actor output Schema

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

Processed documents (default dataset, overview view).

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

OUTPUT record: processed count, indexed input errors (not charged) and stoppedForSpendingLimit.

# 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://www.python.org/downloads/feed.rss"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("zendren/feed-snapshot-delta").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://www.python.org/downloads/feed.rss"] }

# Run the Actor and wait for it to finish
run = client.actor("zendren/feed-snapshot-delta").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://www.python.org/downloads/feed.rss"
  ]
}' |
apify call zendren/feed-snapshot-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zendren/feed-snapshot-delta"
        }
    }
}
```

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/zD5kPUl4ZQp57cDcx/builds/F81nCpn8hTx29LqmO/openapi.json
