# RSS and Atom to JSON (`comradecoding/rss-to-json`) Actor

Turn RSS, Atom and JSON Feed URLs into clean, deduplicated JSON rows, with optional full article text. $0.0005 per item.

- **URL**: https://apify.com/comradecoding/rss-to-json.md
- **Developed by:** [Comrade Coding](https://apify.com/comradecoding) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 feed items

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 and Atom to JSON

Give it a list of feed URLs. It reads RSS 2.0, RSS 1.0, Atom, and JSON Feed, and returns every item as one flat JSON row with the same field names whatever the format: title, link, author, published and updated dates in ISO 8601 UTC, a plain-text summary, categories, image, and enclosure (podcast audio and similar). Items that appear in more than one feed come back once.

Two options go further:

- **Full text.** Turn on `fetchFullText` and the actor opens each item's link and adds the main text of the page as Markdown. Useful when a feed only carries a one-line teaser.
- **Only new items.** Set `seenItemsStore` to a name, run the actor on a schedule, and each run returns only items that earlier runs with the same name have not already returned.

Typical uses: feeding news or blog posts into a database, a search index, or a language model; watching release notes from many projects in one table; turning a podcast feed into a list of episodes and audio links.

### What it does with each feed URL

1. Checks the site's `robots.txt`. If the URL is disallowed, it is skipped. If `robots.txt` sets a `Crawl-delay`, the actor waits that long between requests to that site.
2. Downloads the feed (up to 20 MB) and works out its text encoding from the byte order mark, the server's header, and the XML declaration. When a server says UTF-8 but sends Latin-1 or Windows-1252 bytes, the declaration wins, so accented letters come through intact. Feeds in KOI8-R, Windows-1251, Shift_JIS and other legacy encodings are decoded too.
3. If the URL is a web page instead of a feed, it follows the first feed the page declares in a `<link rel="alternate">` tag. So `https://github.blog/` works as well as `https://github.blog/feed/`.
4. Parses the items, fixes dates that do not follow the standard (missing time zone, zone names such as CEST, full month names), makes links absolute, and turns HTML descriptions into plain text.
5. Sorts items newest first when every item has a date. If any item has no date, the feed's own order is kept.
6. Drops items older than `publishedAfter`, items already returned in this run (same id, or same link once tracking parameters such as `utm_source` are removed), and items returned by earlier runs if `seenItemsStore` is set. Then keeps up to `maxItemsPerFeed`.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `feedUrls` | array | required, no default | Feed URLs, or web pages that link to a feed. Plain strings or `{ "url": "..." }` objects. A bare host like `example.com/feed` gets `https://`. Duplicates are dropped. |
| `maxItems` | integer | `1000` | Stop after this many items in total. Caps what a run costs you. |
| `maxItemsPerFeed` | integer | `100` | At most this many items from each feed. |
| `publishedAfter` | string | empty | Skip items older than a date (`2026-10-01`) or an age (`24 hours`, `7 days`, `2 weeks`, `3 months`). Items with no date are kept. |
| `fetchFullText` | boolean | `false` | Open each item's link and add the page text as Markdown. Charged as an extra event, see Pricing. |
| `includeContentHtml` | boolean | `false` | Add `content_html`, the item body exactly as the feed gives it. |
| `seenItemsStore` | string | empty | Name of a key-value store in your account. Items returned by earlier runs that used the same name are skipped. |

Example input:

```json
{
    "feedUrls": [
        { "url": "https://blog.rust-lang.org/feed.xml" },
        { "url": "https://www.nasa.gov/feed/" },
        { "url": "https://github.blog/" }
    ],
    "maxItems": 200,
    "maxItemsPerFeed": 20,
    "publishedAfter": "7 days",
    "fetchFullText": false,
    "seenItemsStore": "my-news-feeds"
}
```

### Output

One dataset item per feed item. This is a real result from a test run, with the summary cut short here.

```json
{
    "source_url": "https://blog.rust-lang.org/feed.xml",
    "fetched_at": "2026-10-28T06:47:35.939Z",
    "feed_url": "https://blog.rust-lang.org/feed.xml",
    "feed_type": "atom",
    "feed_title": "Rust Blog",
    "feed_link": "https://blog.rust-lang.org/",
    "item_key": "d9dbdedb3165d766f60d8f68",
    "item_id": "https://blog.rust-lang.org/2026/10/20/Rustup-1.29.2/",
    "url": "https://blog.rust-lang.org/2026/10/20/Rustup-1.29.2/",
    "title": "Announcing rustup 1.29.2",
    "author": "The Rustup Team",
    "published_at": "2026-10-20T00:00:00.000Z",
    "updated_at": "2026-10-20T00:00:00.000Z",
    "summary": "The rustup team is happy to announce the release of rustup version 1.29.2.\n\n...",
    "categories": [],
    "image_url": null,
    "enclosure_url": null,
    "enclosure_type": null,
    "enclosure_length": null,
    "comments_url": null,
    "language": "en"
}
```

With `fetchFullText` on, each row also has these fields. This example is another item from the same test run:

````json
{
    "full_text_url": "https://blog.rust-lang.org/2026/10/15/Rust-1.99.1/",
    "full_text_title": "Announcing Rust 1.99.1 | Rust Blog",
    "full_text_markdown": "The Rust team has published a new point release of Rust, 1.99.1. ...\n\n```\nrustup update stable\n```\n\n...",
    "full_text_word_count": 347,
    "full_text_error": null
}
````

Fields:

- `source_url`: the URL you gave. `feed_url`: the feed actually read, after redirects or after following a page's feed link.
- `fetched_at`: ISO 8601 time the feed was downloaded.
- `feed_type`: `rss-2.0`, `rss-0.91` and other RSS versions, `rss-1.0`, `atom`, `json-feed-1`, or `json-feed-1.1`.
- `feed_title`, `feed_link`, `language`: from the feed itself. `null` when it does not set them.
- `item_key`: a stable 24-character key for the item, the same on every run. Use it to deduplicate in your own database. It is built from the item's id (scoped to the feed's host), else its link, else its title and date.
- `item_id`: the item's `guid` (RSS) or `id` (Atom, JSON Feed) as written.
- `url`: the item's link. For RSS items without a link, a permalink `guid` is used.
- `title`, `author`: plain text. RSS authors written as `email (Name)` keep only the name. Several Atom authors are joined with commas. Falls back to the feed's author.
- `published_at`, `updated_at`: ISO 8601 UTC, or `null` when the feed gives no date or one that cannot be read. Many Atom feeds only set `updated_at`.
- `summary`: plain text of the description, or of the content when there is no description. Paragraphs are separated by a blank line, list items start with `- `. Up to 3,000 characters, cut at a word with `...`.
- `categories`: RSS categories, Atom category terms, or JSON Feed tags.
- `image_url`: from `media:thumbnail`, `media:content`, `itunes:image`, an image enclosure, the JSON Feed `image`, or the first image in the content.
- `enclosure_url`, `enclosure_type`, `enclosure_length`: the first enclosure or attachment (length in bytes, as the feed states it).
- `comments_url`: the item's comments page when the feed gives one.
- `content_html`: only when `includeContentHtml` is on. The body as the feed gives it, up to 500,000 characters.
- `full_text_*`: only when `fetchFullText` is on. `full_text_markdown` is the main text of the linked page (up to 1,000,000 characters). When the page could not be read, the feed fields are still returned and `full_text_error` says why, for example `HTTP 404`, `Disallowed by robots.txt`, or `Not a web page (server sent application/pdf)`.

Feeds that fail are not added to the dataset and are not charged. They are listed in the `FAILED_URLS` record in the run's key-value store with a reason, for example `HTTP 404`, `Got an HTML page that does not link to a feed`, `Disallowed by robots.txt`, or `Not a feed: root element is <note>`. A `SUMMARY` record in the same store gives the counts for the run, including how many items were skipped as too old, duplicates, seen before, or over the per-feed cap, and why the run stopped.

### Limits

- Public feeds only. It does not log in or send cookies, so private or members-only feeds fail.
- It reads what the feed contains at the moment of the run. Most feeds hold only the last 10 to 50 items; older items are not fetched from the site's archive.
- Feeds over 20 MB and pages over 5 MB are skipped.
- XML that is badly broken (cut off in the middle, for example) fails with a reason. Common mistakes such as HTML entities like `&nbsp;` in XML, dates without a time zone, or a wrong charset header are handled.
- Full text uses the same main-content extraction as a reader view. It works well on articles and blog posts. Pages that are mostly images, or built entirely with JavaScript, return little useful text. Full text is not fetched from sites whose `robots.txt` asks for more than 10 seconds between requests; those rows get a `full_text_error` and are not charged the full-text event.
- Requests go out one at a time, with a 300 to 800 ms pause between requests to the same site, or the site's `Crawl-delay` if longer. Sites that ask for more than 120 seconds are skipped. With full text on, expect about one second per item, more on sites with a crawl delay.
- Redirects are followed up to 5 times, and `robots.txt` is checked for each target, including a different site, before it is requested.
- Feeds that declare their own XML entities (a `DOCTYPE` with `ENTITY` lines) fail with a reason and are not charged. Ordinary feeds do not use them.
- If a site's `robots.txt` cannot be reached (server error or timeout), that site is skipped rather than guessed at.
- `seenItemsStore` remembers the most recent 100,000 item keys per store name. The store lives in your Apify account until you delete it. Items cut off by `maxItems` are not marked as seen, so the next run picks them up.
- Output keeps whatever the feed and pages contain, including author names and any personal details the publisher printed. Check that you may use the content you collect.

### Pricing

Pay per event:

- **$0.0005 per item** ($0.50 per 1,000) for each item saved to the dataset.
- **$0.0015 extra per item with full text** ($1.50 per 1,000), only when `fetchFullText` is on and the page was read. So an item with full text costs $0.002 in total.

Failed feeds, skipped items, and failed full-text fetches cost nothing extra.

What a run costs:

| Run | Feed items only | With full text |
| --- | --- | --- |
| 10 feeds, 20 items each (200 items) | $0.10 | $0.40 |
| 1,000 items (the default `maxItems`) | $0.50 | $2.00 |
| 10,000 items | $5.00 | $20.00 |

A schedule that checks 20 feeds every hour with `seenItemsStore` set, and finds about 5 new items each hour, saves about 3,600 items a month: $1.80 a month without full text.

Set `maxItems` to cap the cost of a run. You can also set a maximum cost per run in Apify, and the actor stops when it is reached.

This actor is not affiliated with any of the sites used in the examples.

### Use it from an AI agent

Add this actor as a tool through Apify's MCP server: `https://mcp.apify.com?tools=comradecoding/rss-to-json`. The agent gets one clean, deduplicated JSON row per feed item, with optional full article text.

### Integrations

Anything that can call the Apify API can run this actor: the Apify API and client libraries, and Apify's integrations for Make, n8n, Zapier and LangChain. Results come back as a dataset you can download as JSON, CSV or Excel.

### FAQ

**Which feed formats work?**

RSS, Atom and JSON Feed.

**Can it fetch older items than the feed shows?**

No. Most feeds hold the last 10 to 50 items, and it reads what the feed contains at run time.

**How do I get only new items on a schedule?**

Set `seenItemsStore` to a name. Each run skips items it has already returned.

**Am I charged for full text that fails?**

No. Rows where full text could not be fetched get a `full_text_error` and are not charged the full-text event.

### Related actors by ComradeCoding

- [URL to Markdown Converter](https://apify.com/comradecoding/url-to-markdown)
- [Sitemap URL Extractor](https://apify.com/comradecoding/sitemap-url-extractor)

# Actor input Schema

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

Required. RSS, Atom, or JSON Feed URLs. Plain strings or objects with a url field. A web page URL also works if the page declares its feed in a link tag. Duplicates are dropped. Feeds that fail are listed in the FAILED_URLS record and are not charged.

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

Stop after this many items have been saved, across all feeds. You pay per item, so this caps the cost of a run.

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

Save at most this many items from each feed, newest first when every item has a date, otherwise in feed order.

## `publishedAfter` (type: `string`):

Skip items older than this. A date such as 2026-10-01, or an age such as 24 hours, 7 days, 2 weeks, 3 months. Items with no date are kept. Leave empty to keep everything.

## `fetchFullText` (type: `boolean`):

Open each item's link and add the main text of the page as Markdown. Slower, and items with full text are charged an extra event. Pages that fail still return the feed data with a full_text_error.

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

Add a content_html field with the item body exactly as the feed gives it (up to 500,000 characters). The plain-text summary is always included.

## `seenItemsStore` (type: `string`):

Name of a key-value store in your account, such as my-news-feeds. Items saved by earlier runs that used the same name are skipped, so scheduled runs return only new items. Leave empty to return every item each run.

## Actor input object example

```json
{
  "feedUrls": [
    {
      "url": "https://www.nasa.gov/feed/"
    },
    {
      "url": "https://github.blog/feed/"
    }
  ],
  "maxItems": 200,
  "maxItemsPerFeed": 20,
  "publishedAfter": "7 days",
  "fetchFullText": false,
  "includeContentHtml": true,
  "seenItemsStore": "my-news-feeds"
}
```

# Actor output Schema

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

No description

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

No description

## `failures` (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 = {
    "feedUrls": [
        {
            "url": "https://www.nasa.gov/feed/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("comradecoding/rss-to-json").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": [{ "url": "https://www.nasa.gov/feed/" }] }

# Run the Actor and wait for it to finish
run = client.actor("comradecoding/rss-to-json").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": [
    {
      "url": "https://www.nasa.gov/feed/"
    }
  ]
}' |
apify call comradecoding/rss-to-json --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,comradecoding/rss-to-json"
        }
    }
}
```

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/fOq9nqI8VAHERetAk/builds/RvdQYHmDYIM9jP4tW/openapi.json
