# Podcast Episode Monitor - RSS Updates & Show Notes (`maydit/podcast-episode-monitor`) Actor

Monitor public podcast feeds for new episodes and edited show notes. Stable IDs, topic evidence, audio URLs and published transcript/chapter links. No API key or audio downloads.

- **URL**: https://apify.com/maydit/podcast-episode-monitor.md
- **Developed by:** [Brandt May](https://apify.com/maydit) (community)
- **Categories:** Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 episode observations

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

![Follow episodes and edited show notes — illustrative workflow](https://api.apify.com/v2/key-value-stores/YGxD9JRpduom8Q5Zd/records/podcast-episode-monitor-6b06bf2e4807.png?signature=dxHw5QyTvG4ysXxicC0P)

### Follow episodes and edited show notes

For editorial research and podcast tracking: read public RSS or Atom feeds for episode metadata, show notes and published audio, transcript or chapter links. Topic matches keep the terms that matched the source.

[Open this Actor in Console](https://console.apify.com/actors/tHRxtIuqPy6dnaaEi/input) · [First run](#try-this-actor) · [Results](#result-preview) · [Cost](#usage-and-pricing) · [Full guide](#full-guide)

### Try this Actor

- **First run:** Use the existing **NASA Curious Universe** example below: up to 10 saved episodes from a 30-episode feed window. Inspect the first set before using new-or-edited-only output.
- **Read the results:** Open the default Dataset for episode rows and **SUMMARY** for feed coverage. Episode links and changed fields let you check the source behind a saved observation.
- **Keep in mind:** Audio is not downloaded or transcribed, and linked transcript or chapter files are not fetched. A feed is a bounded window, not a complete archive; check feed failures before interpreting zero rows as no new episodes.

**NASA Curious Universe - Latest Podcast Episodes** — copy this input into the JSON editor in Console.

```json
{"feedUrls":["https://www.nasa.gov/feeds/podcasts/curious-universe"],"maxResults":10,"maxEpisodesPerFeed":30}
```

### Result preview

The dataset view highlights these columns:

- **Episode ID** · `episodeId`
- **Show** · `showTitle`
- **Episode** · `title`
- **Published** · `publishedAt`
- **Duration (seconds)** · `durationSeconds`
- **Observation** · `changeType`

Use the original source links and the field definitions below when interpreting the results. CSV works well for flat columns; JSON preserves nested values.

### Usage and pricing

Base result rate on the FREE tier: **$2 per 1,000 episode observations**. The [Pricing tab](https://apify.com/maydit/podcast-episode-monitor/pricing) shows your plan's result rate and any Actor Start charge. Set a small result limit for the first run; the requested limit is not a guarantee of that many results.

***

### Full guide

Turn a public podcast watchlist into a deduplicated episode catalog. Track new episodes, corrected show notes, publication details, audio links and resource links supplied by the publisher. Use the results to keep a content catalog current or decide which episodes match your research topics.

No API key, account or proxy is required. The Actor fetches public RSS 2.0 and Atom feeds, respects robots rules, and rejects private networks and credential-bearing URLs. It does not download audio, transcribe recordings or fetch transcript/chapter files.

#### Quick start

Run with `{}` for 20 episodes from NASA's Curious Universe public feed. This is a demonstration, not a NASA endorsement. Supply your own `feedUrls` to create a watchlist.

Use the [copyable first-run input](#try-this-actor) above.

#### Repeat monitoring

Set `onlyNewOrChanged: true` and use the same `snapshotName`, feed URLs and filters on subsequent runs. The first observation emits matching episodes. Later runs emit newly observed episodes or records whose tracked metadata changed; unchanged runs correctly produce zero rows.

History is stored in the named key-value store `maydit-podcast-episode-history`. The scope includes the sorted requested feed URLs, keywords, match rule, date filter and per-feed window. Changing those values starts an independent scope. Changing `maxResults` does not reset history.

Each baseline advances only after its result row is saved. A rejected billing write, failed source or unprocessed episode leaves previous history intact. Run only one job at a time per scope: key-value storage does not provide a concurrent transaction lock. If a process stops after saving a row but before committing history, that episode can be replayed next time; deduplicate downstream using `episodeId` and `showNotesHash`.

Episodes use a hash of the final feed URL and GUID, falling back to the episode-page link or media URL when GUID is missing. Within a run, exact shared episode-page or media URLs also deduplicate mirrored feeds, retaining `sourceFeeds`. Feed moves to a new final URL can change IDs; different mirror inputs/scopes do not promise permanent cross-provider identity.

#### Inputs

| Field | Default | Behavior |
|---|---|---|
| `feedUrls` | NASA Curious Universe | Up to 20 public RSS 2.0 or Atom feed URLs |
| `maxResults` | 20 | Maximum saved matching episodes, up to 1,000 |
| `maxEpisodesPerFeed` | 100 | Newest usable episodes currently visible in each feed, up to 1,000 |
| `keywords` | none | Literal case-insensitive title/show-note phrases with evidence excerpts |
| `keywordMode` | `any` | Require any or all phrases |
| `publishedAfter` | none | Inclusive YYYY-MM-DD date at 00:00 UTC; unknown dates are excluded |
| `onlyNewOrChanged` | `false` | Compare with saved observations |
| `snapshotName` | `default` | Named monitoring scope, 1–100 characters |
| `maxRunSeconds` | 240 | All-network deadline, also bounded by platform timeout minus 25 seconds |

Keyword matching examines the title and first 100,000 characters of normalized show notes. It is literal phrase matching, not an AI topic or sentiment judgment. A published date is kept as both normalized `publishedAt` and original `publishedRaw`; missing/unparseable dates are not invented.

#### Output

One real episode per saved row:

- `episodeId`, `guid`, `identityBasis`, `showTitle`, `showUrl`, `feedUrl`, `sourceFeeds`
- `title`, `episodeUrl`, `publishedAt`, `publishedRaw`, `updatedAt`
- `enclosureUrl`, `enclosureType`, `audioUrl`, `durationSeconds`, `season`, `episodeNumber`, `episodeType`
- `showNotes`, `showNotesHash`, `showNotesTruncated`
- `transcriptLinks`, `chapterLinks`: only links declared using the Podcasting 2.0 namespace; these can be empty
- `matchedTerms`: phrase, matched field and short source excerpt
- `changeType`: `snapshot`, `new` or `edited`; `changedFields`, `previousObservedAt`, `observedAt`

Video podcast enclosures are supported through `enclosureUrl`; `audioUrl` remains null for video-only episodes. A note correction changes `showNotesHash`; a first observation is new without claiming that the episode itself was recently published. Publisher-supplied audio URLs may contain redirects or tracking parameters; they are copied as source metadata and never requested.

#### Coverage and failures

The Actor fetches at most 20 MiB per feed and inspects the requested episode window. Feeds can omit older episodes or shorten their archive; disappearing items are never reported as deleted. It reads feeds only and does not follow archive pagination or episode pages. It does not estimate audience size, verified sponsors, contact reachability or listening statistics.

`SUMMARY` reports source failures, visible/selected/invalid entry counts, deduplication, filters, unchanged episodes, normal window/result caps and deadline/spending stops. A successfully requested bounded window is `ok` even if the visible feed contains more episodes. Actual failures, rejected entries, time stops and spending stops set `incomplete`.

Malformed XML, DTD/entity declarations, robots blocks and source errors are not paid episode rows. If all feeds fail, the run fails. A valid feed with no matches succeeds with an explicit empty warning. If one feed fails, usable rows from the others are preserved with partial coverage. Ordinary body-size limits reject the feed as a whole; they do not parse an incomplete XML fragment as a successful feed.

Only saved dataset rows count as results. A spending cap stops output; unsaved episodes do not advance monitoring history. Repeated snapshot mode emits matching rows each time. New/edited mode is the option for avoiding unchanged redelivery.

Public feeds support syndication, but users remain responsible for their downstream content rights. This Actor returns source links and publisher-provided notes; it does not grant rights to redistribute recordings or transcripts. No private subscriber feeds or credentials are supported.

#### Three ready examples

##### NASA Curious Universe

```json
{"feedUrls":["https://www.nasa.gov/feeds/podcasts/curious-universe"],"maxResults":10,"maxEpisodesPerFeed":30}
```

##### NASA Houston We Have a Podcast

```json
{"feedUrls":["https://www.nasa.gov/feeds/podcasts/houston-we-have-a-podcast"],"maxResults":10,"maxEpisodesPerFeed":30}
```

##### Mars rover topic evidence

```json
{"feedUrls":["https://www.nasa.gov/feeds/podcasts/on-a-mission"],"keywords":["rover"],"maxResults":10,"maxEpisodesPerFeed":100}
```

#### Sources

- [Apple's public RSS feed requirements](https://podcasters.apple.com/support/823-podcast-requirements)
- [Podcasting 2.0 transcript and chapter tag specification](https://podcasting2.org/docs/podcast-namespace/1.0)
- [NASA Curious Universe](https://www.nasa.gov/podcasts/curious-universe/)

Local verification: `npm test`. Live behavior can differ by feed host and environment; inspect `SUMMARY` before treating a watchlist as fully checked.

### Help with your run

Open the **Issues** tab on this Actor with the run URL, the input field that caused trouble and what you expected to receive. Remove API keys and private information before posting.

[Explore more Mayd-It Actors](https://apify.com/maydit)

### Understand your run

Open **Run report** in the run’s Output tab for saved-record counts, reported source issues and limits, and suggested next steps. Use **CSV export** to take the saved results into a spreadsheet. The existing JSON results and detailed machine-readable summary remain available.

The report reflects this run’s recorded coverage. A successful run or a saved result does not establish that every possible source record was collected.

# Actor input Schema

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

Up to 20 public RSS 2.0 or Atom feeds. Only HTTP(S); no credentials, private networks or blocked robots paths. Omitted/empty input uses NASA Curious Universe as a small public demo.

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

Optional: up to 20 literal case-insensitive phrases, each at most 100 characters. Match episode titles and the first 100,000 characters of normalized show notes. Matching excerpts are included. No semantic or sentiment inference.

## `keywordMode` (type: `string`):

Require any or all keyword phrases. An empty keywords list keeps all episodes.

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

Optional real YYYY-MM-DD date, inclusive at 00:00 UTC. Episodes without a parseable publication date are excluded when this filter is used.

## `maxEpisodesPerFeed` (type: `integer`):

Inspect this many most recently published usable episodes per currently visible feed. Unknown dates follow dated entries. The feed may not contain the complete archive; disappearing entries are never declared deleted.

## `maxResults` (type: `integer`):

Maximum unique matching episodes saved across all feeds. A normal requested result cap is bounded coverage, not a source failure.

## `onlyNewOrChanged` (type: `boolean`):

Use named-store history to emit only new or changed matching episodes. First observations return matching episodes. Only successfully saved rows advance history; unchanged reruns can correctly return zero. Use one concurrent run per snapshot scope.

## `snapshotName` (type: `string`):

Name 1–100 characters. History also includes the sorted feeds, topic filters, date filter and per-feed window. Different scopes do not share baselines. No effect unless onlyNewOrChanged is true.

## `maxRunSeconds` (type: `integer`):

All requests respect this wall-clock budget and the platform deadline minus 25 seconds. Default 240 seconds. Partial coverage and spending stops are explicit in SUMMARY.

## Actor input object example

```json
{
  "feedUrls": [
    "https://www.nasa.gov/feeds/podcasts/curious-universe"
  ],
  "keywordMode": "any",
  "maxEpisodesPerFeed": 100,
  "maxResults": 20,
  "onlyNewOrChanged": false,
  "snapshotName": "default",
  "maxRunSeconds": 240
}
```

# Actor output Schema

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

One unique observed episode per saved row with metadata, show notes, published resource links and optional new/edited comparison.

## `runReport` (type: `string`):

Readable summary of saved records, reported source coverage, limits and next steps. Available when this run saves its SUMMARY.

## `csvExport` (type: `string`):

Download saved dataset records as a CSV file for spreadsheets and other tools.

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

Feed windows, rejected entries, failures, matched/unchanged counts and time or spending stops. Diagnostics are never paid episode rows.

# 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.nasa.gov/feeds/podcasts/curious-universe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maydit/podcast-episode-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 = { "feedUrls": ["https://www.nasa.gov/feeds/podcasts/curious-universe"] }

# Run the Actor and wait for it to finish
run = client.actor("maydit/podcast-episode-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 '{
  "feedUrls": [
    "https://www.nasa.gov/feeds/podcasts/curious-universe"
  ]
}' |
apify call maydit/podcast-episode-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,maydit/podcast-episode-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/tHRxtIuqPy6dnaaEi/builds/d8quRlJnoQiQIjRpn/openapi.json
