# RSS Feed Checker: Dead Episodes & Stale Feeds (`aiqlabs/rss-feed-checker`) Actor

Directories point listeners at a podcast feed forever and never recheck it. Reports episodes whose audio answers 404, feeds that died or moved, and shows whose lastBuildDate is a year fresher than their newest episode - 14.2% of 300 sampled shows, all looking active to anything reading that date.

- **URL**: https://apify.com/aiqlabs/rss-feed-checker.md
- **Developed by:** [Ai-Q Labs](https://apify.com/aiqlabs) (community)
- **Categories:** News, Social media, Automation
- **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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Podcast Audit: Dead Episodes & Feeds That Look Fresh

A podcast directory records a feed URL once and points listeners at it forever. Nobody goes back to ask whether that feed still answers, whether its episodes still play, or whether the show has published anything since.

This checks. Give it Apple Podcasts show ids, a chart, or a random sample of the directory, and it reports what the feed actually delivers today against what the listing implies.

**Pay per event: $0.00005 per run plus $0.002 per feed checked** (in effect since 4 August 2026). No key, no login, and nothing is downloaded that a podcast app would not fetch.

### What it looks for

**Episode audio that is gone.** The enclosure URL is still in the feed, the episode is still listed, and the file answers 404. Measured across 588 episodes: 0.5%.

**Shows that only look active.** `<lastBuildDate>` is written by the hosting platform, not by the publisher, so a show that stopped three years ago keeps a build date from this morning. **14.2% of a 300-show sample carried a build date more than a year ahead of their newest episode.** Anything reading that field — which is most things — calls those shows active.

**Feeds that died or moved.** A feed URL that answers 404, that has moved to another host, or that declares a new home in `<itunes:new-feed-url>` the directory never picked up. Episodes published to the new address never reach the old subscribers.

**Owner addresses that cannot receive.** The `<itunes:owner>` email is where Apple Podcasts and Spotify send ownership verification. This reports whether that domain is registered at all, and whether it is shaped to receive mail. In a 300-show sample not one was unregistered, which is the honest answer: this is rare, and it is reported because when it is true it matters, not because it is common.

**The show's own website**, followed to wherever it ends up.

### What makes this different

Fourteen podcast actors on the Store were read before this one was written. Every one of them extracts: episodes, metadata, host contacts, transcripts, chart positions. One of them extracts audio URLs.

None of them checks whether any of it still works.

### Inputs

Give it shows in whatever form you have them — `1200361736` or `https://podcasts.apple.com/us/podcast/the-daily/id1200361736`. Or a chart name (`top`) to survey what is currently being listened to. Or a sample size, to draw shows at random from the sitemaps Apple advertises and measure a slice of the directory itself.

`episodesFromEachEnd` picks episodes from **both ends** of the feed. The newest is what a listener presses first. The oldest is where rot appears years earlier — a back catalogue quietly stops playing long before the current episode does.

### Severities, and what each one cost to set

Every threshold came from measuring 300 shows drawn from Apple's own sitemaps in August 2026. A symptom that appears on a fifth of healthy shows is not a warning.

| Finding | Severity | Why there |
|---|---|---|
| `feed_dead` | critical | 0.7% of shows. The directory is sending new subscribers to nothing. |
| `feed_domain_unregistered` | critical | Whoever registers the name decides what every current subscriber receives next. |
| `owner_domain_unregistered` | critical | Ownership verification mail goes to an address anyone could arrange to receive. |
| `episode_audio_dead` | high | 0.5% of 588 episodes. Rare enough to mean something. |
| `feed_unreachable`, `owner_mail_undeliverable` (no DNS) | high | The name is still held, so it is not open to anyone — but nothing arrives. |
| `dormant_years` (5+) | medium | 5.8% of shows. |
| `new_feed_url_ignored`, `block_requested_but_listed` | medium | The publisher said something and the directory did not act on it. |
| `dormant_years` (2+), `looks_fresh_but_dormant`, `feed_moved_host` | low | 18.6%, 14.2% and 1.7%. Common enough to be context. |
| `feed_blocked`, `episode_audio_blocked` | low | A server refusing us is not a dead link. |
| `episode_audio_undetermined` | low | Reported so the run's own blind spots are visible. |

Three things are measured and **never** reported as faults, because measuring showed how ordinary they are: **14.6%** of shows publish no owner address, **30.5%** use a free mail provider, and **98.6%** have a website — so its absence says nothing.

### What it will not do

**It does not pretend to be a listener.** Most large podcasts serve audio through a per-listener redirect service that stitches in advertising, and those services answer an automated request with `404 Missing redirect URL`. That is not a dead episode. Six of the nine shows with apparently-dead audio in the first 300-show sample were on one such platform, and every one of them was publishing that week. They are reported as `episode_audio_undetermined` with the host's own words quoted, and the report says plainly that whether the audio is there could not be established.

**It does not run keyword searches.** `itunes.apple.com/robots.txt` names `/search*`. Apple's public chart feeds and the sitemaps `podcasts.apple.com/robots.txt` advertises cover the same need, so those are read instead.

**It does not touch a mail server.** The owner address is checked against DNS only — is this domain registered, does it resolve, does it publish a mail exchanger. Whether a particular mailbox exists cannot be asked without bothering a stranger's server.

**It does not download audio.** `HEAD` answers whether the file is there. A host that refuses `HEAD` is asked for a single byte.

**It does not guess at registration.** `.io`, `.co`, `.jp` and others publish no RDAP service, so no registry can be asked. Those come back `unknown`, never "free" — the most damaging thing this Actor could get wrong is telling you a domain is available when it is not.

**It does not tell you why.** Apple returns the same empty answer for a show that was removed and one that never existed. A feed that 404s may be a migration in progress. The finding says what was observed.

### Output

One row per show, with the directory record, what the feed said, the episode probes, and the findings.

Three key-value records alongside it:

- `SUMMARY` — counts by severity and by finding, the query, the method, and the base rates the severities were set from
- `ACTION_LIST` — findings ranked, worst first
- `CUSTODY_REPORT` — the domain and owner-address questions on their own, with an explicit note about what `unknown` means

### Notes on accuracy

The redirect chain behind a podcast enclosure is longer than it looks: `pdst.fm` → `clrtpod.com` → `pscrb.fm` → `prefix-v4.pscrb.fm` → `dts.podtrac.com` → the host → the CDN → the file. Seven hops, for an episode published this week and playing fine. This Actor follows twelve. An earlier version stopped at five and called **7.8% of healthy episodes broken** — which is how that number is known.

A connection failure is retried once before it is reported. One bad second on this end is not evidence about someone else's episode.

# Actor input Schema

## `shows` (type: `array`):

Apple Podcasts show ids or URLs, in whatever form you have them. Both 1200361736 and https://podcasts.apple.com/us/podcast/the-daily/id1200361736 work.

## `charts` (type: `array`):

Audit the shows currently on these public Apple charts, for surveying rather than auditing a known list. Use top. Keyword search is deliberately not offered: itunes.apple.com/robots.txt names the search endpoint, and a chart is the better sample anyway.

## `chartLimit` (type: `integer`):

How many shows to take from each chart.

## `sampleSize` (type: `integer`):

Draw this many shows from the sitemaps podcasts.apple.com advertises in its robots.txt. Use this to measure a slice of the directory rather than a list you already have. Leave at 0 to skip.

## `country` (type: `string`):

Two-letter country code for the storefront the directory record is read from. A show can be listed in one storefront and absent from another.

## `episodesFromEachEnd` (type: `integer`):

How many episodes to check the audio of, taken from the newest end and the oldest end of the feed. Newest is what a listener presses first; oldest is where rot appears years earlier. Set to 0 to skip audio entirely.

## `checkOwnerAddress` (type: `boolean`):

Look at the domain behind the feed's owner email - the address Apple and Spotify send ownership verification to - and report whether it is registered and shaped to receive mail. Addresses at public mail providers are skipped. No mail server is ever contacted.

## `checkWebsite` (type: `boolean`):

Follow the site the feed links to and report where it ends up. One request per show.

## `dormantAfterDays` (type: `integer`):

How long without a new episode before it is worth noting. Default is 730 days. For context, 18.7% of a 300-show sample were past that, which is why this is a low-severity note rather than an alarm.

## `veryDormantAfterDays` (type: `integer`):

The point at which dormancy is reported at medium severity instead. Default is 1825 days (five years), which 5.8% of the same sample were past.

## `onlyIssues` (type: `boolean`):

Drop the rows that came back clean. Useful for a large sweep, though the clean rows are what tell you the run worked.

## `maxShows` (type: `integer`):

Upper bound on how many shows are audited in one run, applied after charts and samples are expanded.

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

How many shows to work on at once. Feeds and audio come from many different hosts, so this is gentle on each of them, but lower it if you are auditing many shows from one network.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for any single request before calling it unreachable.

## Actor input object example

```json
{
  "shows": [
    "https://podcasts.apple.com/us/podcast/the-daily/id1200361736"
  ],
  "chartLimit": 25,
  "sampleSize": 0,
  "country": "us",
  "episodesFromEachEnd": 3,
  "checkOwnerAddress": true,
  "checkWebsite": true,
  "dormantAfterDays": 730,
  "veryDormantAfterDays": 1825,
  "onlyIssues": false,
  "maxShows": 200,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

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

No description

## `csv` (type: `string`):

No description

## `actionList` (type: `string`):

No description

## `custodyReport` (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 = {
    "shows": [
        "https://podcasts.apple.com/us/podcast/the-daily/id1200361736"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aiqlabs/rss-feed-checker").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 = { "shows": ["https://podcasts.apple.com/us/podcast/the-daily/id1200361736"] }

# Run the Actor and wait for it to finish
run = client.actor("aiqlabs/rss-feed-checker").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 '{
  "shows": [
    "https://podcasts.apple.com/us/podcast/the-daily/id1200361736"
  ]
}' |
apify call aiqlabs/rss-feed-checker --silent --output-dataset

```

## MCP server setup

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

```

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/mMTpfgMesNxDWB7cU/builds/P1EvDXdDutaMYx5Md/openapi.json
