# Substack Podcasts Scraper (`automation-lab/substack-podcast-feed-episode-scraper`) Actor

Export and monitor public Substack podcast episodes, show notes, publication metadata, and public audio enclosure URLs from one or many publication or RSS feed URLs.

- **URL**: https://apify.com/automation-lab/substack-podcast-feed-episode-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.43 / 1,000 public podcast episodes

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/platform/actors/running/actors-in-store#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

## Substack Podcasts Scraper

Export and monitor public **Substack podcasts** without downloading audio or opening a browser. Give the Actor one or more publication homepages or RSS feed URLs and receive typed episode records with show notes, dates, GUIDs, images, categories, publication metadata, and public audio enclosure URLs.

The Actor is designed for podcast researchers, media teams, aggregators, newsletter analysts, and data engineers who need repeatable Substack podcast exports. It follows official public RSS feeds, supports publications on custom domains, and excludes paid/private feeds and subscriber-only episodes.

### What does Substack Podcasts Scraper do?

The Actor turns public Substack podcast feeds into integration-ready JSON, CSV, Excel, XML, RSS, or HTML through Apify Dataset exports.

It can:

- accept a Substack publication homepage or its `/feed` URL;
- process up to 100 feeds in one run;
- return only feed items that contain a public audio enclosure;
- extract publication and episode metadata;
- exclude records marked for paid subscribers;
- deduplicate episodes by GUID, canonical URL, or audio URL;
- filter episodes by publication date;
- add stable feed and fetch metadata for scheduled monitoring;
- optionally inspect public audio response headers without downloading audio;
- stop at a global `maxItems` limit.

### Who is this Substack podcast scraper for?

#### Podcast researchers

Build a current catalog of episodes, authors, publication dates, show notes, and audio links for analysis.

#### Media and editorial teams

Monitor public episodes across a list of Substack publications and compare scheduled run exports by `episodeId`.

#### Podcast directories and aggregators

Normalize several public feeds into one dataset while preserving each source feed and publication.

#### Data engineers

Send structured episode records to a warehouse, spreadsheet, webhook, n8n, Make, Zapier, or another Actor.

#### AI and search teams

Use titles, descriptions, categories, canonical URLs, and source metadata as inputs to lawful indexing or enrichment workflows.

### Why use this Actor?

- **Substack-specific access filtering:** public post metadata is used to exclude `only_paid` episodes even if an enclosure appears in the public RSS XML.
- **Feed and homepage inputs:** the Actor normalizes publication pages to their public feed automatically.
- **Custom-domain support:** redirects such as a `*.substack.com` URL to the publication's branded domain are followed.
- **Multi-feed provenance:** every row includes input URL, final feed URL, feed index, episode index, and fetch timestamp.
- **Low overhead:** direct HTTP and structured XML/JSON parsing avoid browser startup and media downloads.
- **Optional audio inspection:** request downloadable-audio headers only when your workflow needs them.

### What data can you extract?

| Field | Meaning |
| --- | --- |
| `episodeId` | Stable GUID, canonical URL, or audio URL used for deduplication |
| `title` | Episode title |
| `description` | Plain-text description or show notes, when present |
| `author` | Episode creator or publication author |
| `publishedAt` | Normalized ISO 8601 publication timestamp |
| `canonicalUrl` | Public Substack episode post URL |
| `guid` | RSS item GUID |
| `imageUrl` | Episode image, falling back to the publication image |
| `categories` | RSS categories |
| `duration` | RSS duration or whole seconds from public Substack metadata |
| `explicit` | Podcast explicit-content value, when published |
| `episodeNumber` | Podcast episode number, when published |
| `accessLevel` | Always `public`; paid-only records are not emitted |
| `audioUrl` | Public audio enclosure URL |
| `audioType` | Enclosure MIME type, such as `audio/mpeg` |
| `audioLengthBytes` | Enclosure length from RSS when supplied |
| `audioMetadata` | Optional HEAD response metadata; no audio is downloaded |
| `publicationTitle` | Publication or podcast title |
| `publicationDescription` | Publication description |
| `publicationAuthor` | Publication author |
| `publicationUrl` | Publication homepage |
| `publicationImageUrl` | Publication image |
| `sourceInputUrl` | Original user input |
| `sourceFeedUrl` | Final RSS URL after redirects |
| `feedIndex` | Zero-based successfully processed feed index |
| `episodeIndex` | Zero-based episode position in the source feed |
| `fetchedAt` | Shared ISO 8601 fetch timestamp for the run |

Nullable fields are returned as `null` when the public source does not publish them. `audioMetadata` is omitted when it is disabled or the optional HEAD request is rejected.

### How to scrape public Substack podcast episodes

1. Open the Actor input page.
2. Add one or more public publication or `/feed` URLs to **Substack publication or feed URLs**.
3. Set **Maximum episodes** for the combined dataset.
4. Optionally add date boundaries for a monitoring window.
5. Enable **Fetch downloadable audio metadata** only if response headers are useful to you.
6. Click **Start**.
7. Open the Dataset tab and export the episode rows in your preferred format.

A simple input is:

```json
{
  "startUrls": [
    { "url": "https://wethefifth.substack.com" }
  ],
  "maxItems": 10
}
```

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | ---: | --- |
| `startUrls` | array | required | 1–100 public Substack publication homepages or RSS feed URLs |
| `maxItems` | integer | `100` | Global episode limit from 1 to 10,000 |
| `publishedAfter` | string | none | Include episodes on or after this ISO 8601 timestamp |
| `publishedBefore` | string | none | Include episodes on or before this ISO 8601 timestamp |
| `fetchAudioMetadata` | boolean | `false` | Inspect public audio headers without downloading media |

Both date filters apply identically to homepage and direct-feed inputs. An item with no valid publication date is excluded when either boundary is active.

### Output example

A current public episode produces a record shaped like this:

```json
{
  "recordType": "episode",
  "episodeId": "https://www.wethefifth.com/p/the-fifth-gets-a-substitute-teacher",
  "title": "The Fifth Gets a Substitute Teacher (w/ Jesse Singal) #568",
  "description": "Kmele is joined by Jesse Singal for a new episode...",
  "author": "The Fifth Column",
  "publishedAt": "2026-07-29T11:03:18.000Z",
  "canonicalUrl": "https://www.wethefifth.com/p/the-fifth-gets-a-substitute-teacher",
  "guid": "https://www.wethefifth.com/p/the-fifth-gets-a-substitute-teacher",
  "imageUrl": "https://substackcdn.com/image/fetch/...",
  "categories": [],
  "duration": "6075",
  "explicit": null,
  "episodeNumber": null,
  "accessLevel": "public",
  "audioUrl": "https://api.substack.com/feed/podcast/208913612/....mp3",
  "audioType": "audio/mpeg",
  "audioLengthBytes": 0,
  "publicationTitle": "The Fifth Column (A Podcast)",
  "publicationUrl": "https://www.wethefifth.com",
  "sourceInputUrl": "https://wethefifth.substack.com",
  "sourceFeedUrl": "https://www.wethefifth.com/feed",
  "feedIndex": 0,
  "episodeIndex": 1,
  "fetchedAt": "2026-08-03T06:00:00.000Z"
}
```

The example is shortened for readability. Dataset rows retain all schema fields.

### How much does it cost to extract Substack podcast episodes?

This is a pay-per-event Actor. It charges one **Start** event of **$0.00005** per run and one **Public podcast episode** event for each row saved. It does not charge for non-audio posts, subscriber-only episodes, duplicate rows, failed feeds, filtered rows, or optional HEAD requests.

At the BRONZE tier, the episode price is **$0.0007128**:

| Useful output | Calculation | Price |
| ---: | --- | ---: |
| 1 episode | $0.00005 + 1 × $0.0007128 | **$0.0007628** |
| 10 episodes | $0.00005 + 10 × $0.0007128 | **$0.007178** |
| 100 episodes | $0.00005 + 100 × $0.0007128 | **$0.07133** |

Apify account tiers use lower per-episode prices at higher tiers. The Console always shows the active price before a run starts. The examples above exclude any unrelated integration or storage charges outside this Actor.

### Monitor new Substack podcast episodes

For repeat monitoring:

1. Save the same feed list in an Apify Task.
2. Schedule it daily or weekly.
3. Set `publishedAfter` to the beginning of the monitoring window when appropriate.
4. Compare datasets by `episodeId`.
5. Use `fetchedAt` to identify the run snapshot and `sourceFeedUrl` to group publications.

The Actor does not maintain a hidden cross-run database. This keeps each run reproducible and lets you choose the comparison destination.

Example monitoring input:

```json
{
  "startUrls": [
    { "url": "https://wethefifth.substack.com" },
    { "url": "https://www.lennysnewsletter.com/feed" }
  ],
  "publishedAfter": "2026-07-01T00:00:00Z",
  "maxItems": 100
}
```

### Export and integration workflows

#### Spreadsheet export

Open the Dataset and select CSV or Excel. Publication, episode, date, and source fields are immediately usable as columns.

#### Data warehouse

Use the Dataset API to pull JSON and upsert by `episodeId`. Keep `fetchedAt` as the observation timestamp.

#### Webhooks

Attach an Apify webhook to a Task run and notify your workflow after the dataset is ready.

#### n8n, Make, or Zapier

Run the Actor on a schedule, fetch dataset items, then route new IDs to a database, editorial queue, or notification channel.

#### AI enrichment

Pass public titles and show notes to a separate classifier or summarizer. Do not assume the extracted description is a complete transcript.

### Run with the Apify API

Replace `YOUR_TOKEN` with an Apify API token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~substack-podcast-feed-episode-scraper/runs?token=YOUR_TOKEN&waitForFinish=120" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":[{"url":"https://wethefifth.substack.com"}],"maxItems":10}'
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/substack-podcast-feed-episode-scraper').call({
  startUrls: [{ url: 'https://wethefifth.substack.com' }],
  maxItems: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
from apify_client import ApifyClient
import os

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/substack-podcast-feed-episode-scraper').call(run_input={
    'startUrls': [{'url': 'https://wethefifth.substack.com'}],
    'maxItems': 10,
})
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/substack-podcast-feed-episode-scraper"
```

#### Claude Desktop

Add this server to the `mcpServers` object in Claude Desktop's configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/substack-podcast-feed-episode-scraper"
    }
  }
}
```

#### Cursor

Open **Settings → Tools & MCP → New MCP Server** and use the same Apify MCP URL shown above.

#### VS Code

Add an HTTP MCP server in your VS Code MCP configuration using the same Apify MCP URL shown above.

Example prompts:

- “Extract the latest 10 public podcast episodes from `https://wethefifth.substack.com`.”
- “Combine these three public Substack podcast feeds and return episode IDs, dates, show notes, and audio URLs.”
- “Inspect downloadable audio metadata for the latest five public Lenny's Podcast episodes.”

### Is it legal to scrape public Substack podcast feeds?

Public RSS feeds are intended for syndication, but availability does not remove copyright, privacy, database-right, or contractual obligations. Use the records only for a lawful purpose, follow applicable terms and regulations, and obtain permission before republishing audio or substantial show-note content. This Actor does not bypass authentication or paywalls and is not legal advice.

### Limits and responsible use

- Only public, anonymous Substack publication and RSS URLs are supported.
- Paid/private feeds, personal subscriber feed URLs, authenticated content, and subscriber-only audio are out of scope.
- The Actor does not bypass paywalls, log in, or accept cookies/credentials.
- RSS commonly contains only the latest items; this Actor does not promise a publication's complete historical archive.
- `maxItems` applies across all feeds, in input order.
- Source fields can be missing or malformed; nullable output fields reflect what the publisher provides.
- Optional audio metadata depends on the audio host accepting HEAD requests.
- Audio files are linked, not downloaded, copied, or transcribed.
- Respect publisher rights, applicable law, contractual restrictions, and personal-data obligations.
- Do not republish copyrighted audio or show notes without permission.

### Troubleshooting

#### Why did the run say no episodes were saved?

The feed may contain articles but no public audio enclosures, all recent episodes may be subscriber-only, or your date window may exclude every episode. Open the public `/feed` URL and check that it is a podcast feed.

#### Why did one feed fail while other feeds produced rows?

Multi-feed runs continue after an individual invalid, private, removed, or temporarily unavailable feed. The run fails only when no useful public episode is saved.

#### Why is `audioLengthBytes` zero or null?

Some RSS feeds publish `length="0"`. Enable `fetchAudioMetadata` to request the public audio server's current `Content-Length`, when supported.

#### Does the Actor download podcast audio?

No. Normal runs read RSS and public post metadata. Optional enrichment sends HEAD requests only.

#### Can I scrape a private subscriber RSS URL?

No. Do not provide personalized feed tokens or credentials. This Actor intentionally supports public publications only.

#### Can I obtain transcripts?

No. The Actor returns public show notes and audio metadata, not transcripts. Send lawful public audio to a separate transcription workflow if you have the necessary rights.

### Related automation-lab Actors

- [Substack Scraper](https://apify.com/automation-lab/substack-scraper) — extract broader public Substack post data.
- [Spotify Podcast Episode Search](https://apify.com/automation-lab/spotify-podcast-episode-search) — discover podcast episodes through Spotify search workflows.
- [Spotify Podcast Charts Scraper](https://apify.com/automation-lab/spotify-podcast-charts-scraper) — track podcast chart positions and ranking data.

### FAQ

#### Do publication homepage URLs work?

Yes. The Actor converts the homepage to `/feed` and follows a redirect to a custom publication domain when Substack provides one.

#### Are non-podcast newsletter posts returned?

No. An RSS item must contain a public audio enclosure to become an episode record.

#### Are duplicates charged?

No. Duplicate episode IDs within a run are discarded before charging and dataset storage.

#### Can I process several feeds?

Yes. Supply up to 100 URLs. `maxItems` is the combined output limit.

#### Is this an official Substack API?

No. It is an independent Actor that reads anonymous public RSS and publication metadata surfaces. It is not affiliated with or endorsed by Substack.

# Actor input Schema

## `startUrls` (type: `array`):

Public Substack publication homepages or /feed URLs. Custom-domain Substack publications are supported.

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

Maximum number of public podcast episode records saved across all feeds.

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

Optional ISO 8601 date/time. Episodes older than this boundary are excluded, which is useful for recurring monitoring.

## `publishedBefore` (type: `string`):

Optional ISO 8601 date/time. Episodes newer than this boundary are excluded.

## `fetchAudioMetadata` (type: `boolean`):

Send a HEAD request to each public audio URL to add final URL, content type, size, and byte-range support. Audio files are not downloaded.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://wethefifth.substack.com"
    },
    {
      "url": "https://www.lennysnewsletter.com/feed"
    }
  ],
  "maxItems": 20,
  "publishedAfter": "2026-01-01T00:00:00Z",
  "fetchAudioMetadata": false
}
```

# Actor output Schema

## `overview` (type: `string`):

Public episode records displayed in the podcast episode view.

# 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 = {
    "startUrls": [
        {
            "url": "https://wethefifth.substack.com"
        },
        {
            "url": "https://www.lennysnewsletter.com/feed"
        }
    ],
    "maxItems": 20,
    "publishedAfter": "2026-01-01T00:00:00Z",
    "fetchAudioMetadata": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/substack-podcast-feed-episode-scraper").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 = {
    "startUrls": [
        { "url": "https://wethefifth.substack.com" },
        { "url": "https://www.lennysnewsletter.com/feed" },
    ],
    "maxItems": 20,
    "publishedAfter": "2026-01-01T00:00:00Z",
    "fetchAudioMetadata": False,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/substack-podcast-feed-episode-scraper").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 '{
  "startUrls": [
    {
      "url": "https://wethefifth.substack.com"
    },
    {
      "url": "https://www.lennysnewsletter.com/feed"
    }
  ],
  "maxItems": 20,
  "publishedAfter": "2026-01-01T00:00:00Z",
  "fetchAudioMetadata": false
}' |
apify call automation-lab/substack-podcast-feed-episode-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/substack-podcast-feed-episode-scraper"
        }
    }
}

```

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/EZIrK4rCqnGwCvt90/builds/3hmlCDTqCVdbnkCEn/openapi.json
