# Podcast Migration Checker (`agentworks-lab/podcast-migration-checker`) Actor

Compare old and new podcast RSS feeds for lost episodes, changed GUIDs, and migration redirect mistakes. Get actionable findings before or after switching hosts.

- **URL**: https://apify.com/agentworks-lab/podcast-migration-checker.md
- **Developed by:** [Jacob Fritz](https://apify.com/agentworks-lab) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.25 / podcast migration comparison

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

## Podcast Migration Checker

Moving a podcast between hosts? Compare the old and new public RSS feeds before cutover, then check the redirect after cutover. The report identifies episodes missing from the new feed, likely changed GUIDs, duplicate GUIDs, and missing audio enclosure URLs. It gives you a row for each finding, a suggested fix, and a summary you can export as JSON, CSV, or Excel.

Apple warns that changing episode GUIDs can create duplicate episodes and disrupt analytics. For a self-hosted migration, Apple calls for an HTTP 301 redirect from the old feed and an `<itunes:new-feed-url>` tag in the new feed, kept for at least four weeks. This Actor checks those signals; it is not affiliated with Apple and cannot guarantee directory acceptance. [Apple's migration guidance](https://podcasters.apple.com/support/837-change-the-rss-feed-url).

### Before moving

1. Enter the current and planned public RSS 2.0 feed URLs.
2. Leave **Migration already switched on** off.
3. Review missing episodes and GUID findings. Preserve the old GUID for each existing episode, even when its title or audio URL changes.
4. Save a copy of the old feed XML before switching hosts. You can paste it into **Old feed XML snapshot** later if the old URL starts redirecting.

### After moving

1. Enter the same two URLs and switch **Migration already switched on** on.
2. If the old URL redirects to the new feed, paste the saved old-feed XML snapshot. A redirect makes the old catalog unavailable at that URL, so the snapshot is necessary to compare episode identities.
3. Check the old URL's first HTTP response, final destination, the new-feed tag, and episode findings. Keep the redirect and tag for at least four weeks, according to Apple's guidance.

If the old URL is broken but you have its XML snapshot, the episode comparison still runs and reports the redirect failure. The Actor does not store the snapshot in its output.

### Sample and pricing

The prefilled NASA URLs are intentionally identical: they demonstrate the report and are **not a real migration**. With the snapshot empty and the migration switch off, this sample has no custom comparison charge. For an actual migration, enter the old and new public feeds for the **same show**. A run with a supplied old-feed XML snapshot is a completed comparison and triggers the custom event, even if the two URL fields match. The Store Pricing tab shows the live pay-per-completed-comparison price. A failed fetch, malformed feed, or comparison blocked before a report does not trigger the custom event. Platform usage is included in the event price.

### Output

The first dataset row summarizes preserved, likely changed, missing, and unverifiable episode GUIDs. Later rows contain actionable findings such as `GUID_CHANGED`, `EPISODE_MISSING`, `NEW_GUID_DUPLICATE`, `OLD_FEED_301_MISSING`, and `NEW_FEED_TAG_WRONG`. A title and publish-day match is only evidence of a *likely* GUID change; review it against the original show records. An old episode without a GUID cannot be verified automatically.

### Limits

- Public, unauthenticated HTTP(S) RSS 2.0 feeds only. No login, proxy, paid API, or audio download.
- Each feed is limited to 5 MB and three redirects, and each comparison to 2,000 episodes per feed. Oversized or unreadable feeds stop without a custom comparison charge.
- The report shows at most 300 finding rows, while summary counts include all findings.
- The checker reads published feed metadata. It does not confirm that an audio file plays, that every podcast app has updated its cache, or that a directory has accepted the move.
- Private addresses, embedded credentials, common tokenized URLs, and custom ports are rejected. Feed query strings and pasted XML are omitted from output and logs.

For a questionable finding, open an Actor issue with the Apify run ID and public feed URLs. Do not include private feeds, credentials, or customer data.

# Actor input Schema

## `oldFeedUrl` (type: `string`):

Public RSS 2.0 URL used before the move. Do not include credentials or tokenized URLs.

## `newFeedUrl` (type: `string`):

Public RSS 2.0 URL at the destination host. Replace the sample URL for a real comparison.

## `expectRedirect` (type: `boolean`):

Check that the old URL starts with HTTP 301 and the new feed has the correct new-feed-url tag. Leave off for a preflight comparison before cutover.

## `oldFeedSnapshotXml` (type: `string`):

Paste a public pre-migration copy of the old feed if its URL now redirects to the new one. Not saved in the output. Maximum 5 MB.

## Actor input object example

```json
{
  "oldFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
  "newFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
  "expectRedirect": false
}
```

# Actor output Schema

## `results` (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 = {
    "oldFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
    "newFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe"
};

// Run the Actor and wait for it to finish
const run = await client.actor("agentworks-lab/podcast-migration-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 = {
    "oldFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
    "newFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
}

# Run the Actor and wait for it to finish
run = client.actor("agentworks-lab/podcast-migration-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 '{
  "oldFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe",
  "newFeedUrl": "https://www.nasa.gov/feeds/podcasts/curious-universe"
}' |
apify call agentworks-lab/podcast-migration-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,agentworks-lab/podcast-migration-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/gNsSL0mUs4eHS6wla/builds/4zd7WqhfuLjDU9YYr/openapi.json
