# Podcast Transcripts Scraper (`automation-lab/podcast-feed-episode-transcripts`) Actor

Discover new episodes from public podcast RSS feeds and export searchable transcripts, timestamped segments and SRT/VTT subtitles with bounded local speech recognition.

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

## Pricing

from $2.88 / 1,000 transcribed audio minutes

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

## Podcast Transcripts Scraper

Turn the newest episodes in public RSS feeds into searchable podcast transcripts. Supply the feeds once instead of enumerating audio URLs: receive episode metadata, plain text, timestamped segments, and optional SRT/VTT subtitle files.

Built for researchers indexing recurring shows, editorial teams searching interviews, and knowledge pipelines collecting newly published speech. Recognition runs locally on CPU using Whisper tiny; no transcription API key is required.

### What it does

1. Downloads public RSS 2.0 feeds with audio enclosures.
2. Applies an optional publication cutoff and previously indexed GUID exclusions.
3. Selects the newest eligible episodes, deduplicating GUIDs and audio URLs.
4. Downloads bounded audio, checks duration, and transcribes without clipping.
5. Saves one episode record and optional subtitle artifacts in run storage.

This is not a podcast search engine. It does not resolve Apple Podcasts or Spotify landing pages, authenticate to private feeds, or fetch an entire historical archive.

### Who is it for?

Research teams building a public-interview corpus, editors reviewing recent show episodes, and data engineers maintaining searchable episode indexes. It is not suitable for confidential recordings or a verbatim legal transcript.

### Why use it?

A direct-media transcriber needs individual file URLs. This Actor discovers those URLs from your feeds and preserves the episode identity alongside each transcript. Date and GUID filters make recurring indexing explicit and reproducible without hidden cross-run state.

Outputs are machine-readable and can be joined to your own episode index. Speech recognition is approximate: manually verify quotations, names, numbers, and consequential interpretations.

### Getting started

1. Copy the public RSS feed URL from the show's publisher.
2. Start with one episode and a duration bound suitable for the show.
3. Run the Actor and open **Episode transcripts** in the output tab.
4. Download subtitles from the links in `subtitleUrls`.
5. Save successful GUIDs externally for the next indexing run.

```json
{
  "feedUrls": ["https://feeds.npr.org/500005/podcast.xml"],
  "maxItems": 1,
  "episodesPerFeed": 1,
  "maxDurationMinutes": 10,
  "language": "en",
  "outputFormats": ["srt", "vtt"]
}
```

The example uses a public, frequently updated short news feed; episode identity changes over time.

### Inputs

| Field | Default | Behavior |
| --- | --- | --- |
| `feedUrls` | Required | 1–5 public RSS 2.0 URLs; strings, no credentials or query strings |
| `maxItems` | 1 | Global selected episode cap, 1–10 |
| `episodesPerFeed` | 1 | Eligible newest episodes selected per feed, 1–10 |
| `publishedAfter` | Unset | Strictly later than a UTC ISO timestamp ending in Z |
| `excludeGuids` | `[]` | Exact, case-sensitive GUID exclusions, up to 1000 |
| `maxFileSizeMb` | 50 | Hard per-audio bound, 1–100 MiB |
| `maxDurationMinutes` | 10 | Hard per-episode duration bound, 1–30 minutes |
| `language` | `auto` | Automatic detection or a supported two-letter Whisper code |
| `outputFormats` | `["srt","vtt"]` | Either/both formats; `[]` disables artifacts |

Feed downloads are capped at 2 MiB. Only `audio/*` enclosures are selected. Missing GUIDs use the enclosure URL. Duplicate feeds are removed.

### Selection and recurring indexing

Filters apply before per-feed limits. Entries are newest-first by RSS publication date; undated entries follow dated entries in feed order. With `publishedAfter`, undated entries are excluded. The cutoff is strictly greater than, not equal to, the supplied timestamp.

Per-feed selections are combined, deduplicated by GUID and enclosure URL, sorted newest-first across all feeds, then capped by `maxItems`. Failed episodes consume a selected slot; the Actor does not silently substitute older episodes. GUID collisions across shows are deduplicated too.

The Actor saves no cross-run cursor. Store successful GUIDs and a publication watermark in your own database. On the next run pass them back as exclusions/cutoff. Feeds may remove old entries; increasing limits does not recover an archive. There are no built-in change alerts.

### Output data

| Field | Meaning |
| --- | --- |
| `feedUrl`, `showTitle` | Feed identity and RSS channel title |
| `guid`, `episodeTitle`, `episodeUrl` | Episode identity, title and optional page link |
| `publishedAt`, `audioUrl` | ISO publication date (nullable) and public audio enclosure URL |
| `status` | `succeeded` or `failed` |
| `transcript` | Recognized plain text; null on failure |
| `segments` | `{id,start,end,text}` records; times in seconds |
| `language`, `languageProbability` | Recognition language and confidence; null on failure |
| `durationSeconds`, `model` | Decoded duration and `tiny` model |
| `subtitleUrls` | Requested format download links; empty when disabled/failed |
| `processedAt`, `error` | Processing timestamp and safe error explanation |

A successful local NPR episode produced the opening text **“This is NPR News Now. The latest headlines every hour.”**, with segment 0 covering 0–4.44 seconds. Later recognition included errors in names, illustrating why tiny-model output needs review. Dataset records contain the full episode transcript rather than just this excerpt.

```json
{
  "showTitle": "NPR News Now",
  "status": "succeeded",
  "model": "tiny",
  "language": "en",
  "segments": [{"id": 0, "start": 0, "end": 4.44, "text": "This is NPR News Now."}]
}
```

This is an excerpt, not a complete output record. Dataset exports support JSON, CSV and other Apify formats. Keep nested segments in JSON when you need timestamps.

### Subtitle artifacts

SRT and VTT files use the recognized segment timing. Keys follow `EPISODE_001.SRT` and `EPISODE_001.VTT`; links are returned in each successful record. Files live in the run's default key-value store. Download before your storage retention period expires. There is no separate subtitle event charge.

### How much does it cost to transcribe podcast episodes?

Pay-per-event pricing consists of a **$0.005 start fee**, charged once for the first successfully recognized episode, plus rounded-up decoded audio minutes. Each episode rounds separately; 61 seconds counts as two minutes. Download/recognition failures, silent audio and oversized episodes have no audio-minute charge. Required charges are verified before transcript or subtitle output is exposed. A billing or subsequent storage failure is fatal; a storage failure can leave charges for recognized audio without a complete dataset row. Inspect the failed run before retrying.

BRONZE price: **$0.0048 per audio minute**. A five-minute episode costs approximately $0.029 including the start event; ten minutes costs $0.053. Each episode rounds independently.

| Spend tier | Price per audio minute |
| --- | --- |
| FREE | $0.00552 |
| BRONZE | $0.0048 |
| SILVER | $0.003744 |
| GOLD | $0.00288 |
| PLATINUM | $0.00288 |
| DIAMOND | $0.00288 |

Spend tiers reflect qualifying aggregate monthly Apify Store spend, not private per-Actor volume discounts. Prices are estimates, not a guaranteed invoice; the applicable live tier and maximum-charge budget govern the run. Storage/platform terms also apply. Refunds, disputes, fraud, taxes, corrections, and contractual clawbacks can affect finalized revenue or payouts.

A maximum-charge budget can stop output before ASR for the next complete episode if its combined start (when needed) and full minute charge cannot be covered; the final log explicitly says budget-limited and any returned dataset is partial. Avoid setting a budget too small for the requested duration.

### Limits and failure behavior

Audio is processed sequentially in bounded 60-second recognition chunks with episode-wide timestamps. A chunk boundary can split a phrase; the text remains approximate. Automatic language detection uses the first chunk with recognized speech and reuses that language for the episode. Downloads are bounded by bytes and a two-minute deadline across redirects. Recognition has a per-file timeout. There is no proxy or browser fallback and no automatic retry loop; retry a transient source failure in a new authorized run.

Over-duration or unknown-duration files fail, rather than returning a silently clipped transcript. Bad feeds fail discovery. Episode failures create uncharged error records and mark the run FAILED after preserving any successful transcripts. Billing or output-storage errors are fatal. A failed run can contain useful partial results: inspect `status`, never assume full completion.

### Integrations

- **Research index:** upsert successful records keyed by feed URL plus GUID; index transcript text and retain segments for playback citations.
- **Editorial workflow:** export JSON to a review queue, download SRT/VTT, and manually correct quotations.
- **Scheduled collection:** use Apify schedules with your own watermark/GUID state. Schedules do not automatically update input state.
- **Data pipelines:** use a completion webhook to fetch the dataset and send it to your warehouse or search engine. Handle failed runs as partial.

### API usage

Start a run asynchronously and keep the returned run ID:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~podcast-feed-episode-transcripts/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"feedUrls":["https://feeds.npr.org/500005/podcast.xml"],"maxItems":1}'
```

JavaScript with the Apify client:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/podcast-feed-episode-transcripts')
  .call({ feedUrls: ['https://feeds.npr.org/500005/podcast.xml'], maxItems: 1 });
const result = await client.dataset(run.defaultDatasetId).listItems({ limit: 10 });
console.log(run.status, result.items);
```

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/podcast-feed-episode-transcripts').call(
    run_input={'feedUrls': ['https://feeds.npr.org/500005/podcast.xml'], 'maxItems': 1})
rows = client.dataset(run['defaultDatasetId']).list_items(limit=10).items
print(run['status'], rows)
```

### MCP usage

Select this stable Actor subset:

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

Equivalent **Claude Desktop, Cursor, and VS Code** setup configuration (use each client's supported HTTP MCP configuration):

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

Discover actual names and argument schemas with scoped `tools/list`. Actor selection also includes `get-actor-run`, `get-dataset-items`, `get-key-value-store-record`, and `abort-actor-run`; discovery alone does not authorize a run or abort.

Example prompt: “Transcribe the newest episode from this public RSS feed, then give me the transcript and subtitle links. Show any recognition uncertainty.”

For an authorized invocation, start once and retain the run/storage IDs; they are metadata, not results. `call-actor` waits at most 45 seconds (default 30). If nonterminal, check the same run with 2/4/8-second backoff capped at 10 seconds and a 120-second consumer deadline. Client timeout should exceed server wait plus transport margin. On timeout recover the known run, never restart merely to poll. At the deadline report pending with its ID.

Report terminal failures honestly: any available rows are partial, not a successful full result. Once successful, call the discovered `get-dataset-items` helper with explicit `limit`, `offset`, and `fields` (a comma-separated string). For example:

```json
{"datasetId":"<defaultDatasetId from the same run>","limit":20,"offset":0,"fields":"guid,showTitle,episodeTitle,publishedAt,status,transcript,subtitleUrls,error"}
```

Request `segments` only when timestamps are needed. Fetch pages of 20 source rows from offset 0, at most 100 rows and 64 KiB of serialized UTF-8 data admitted to model context, whichever comes first. Process pages host-side; omit/summarize an oversized row with disclosure. If your client cannot intercept oversized responses, do not claim a hard byte ceiling. Track source offsets; report partial/truncated status and continuation offset. Keep full transcripts in storage outside model context when needed. Discovery transport and caching do not guarantee token savings.

### AI and data handling

Whisper tiny via faster-whisper/CT2 runs on the Actor's CPU, with int8 inference. No media or transcript is sent to an external transcription/LLM provider, no model training occurs, and no diarization, translation or professional-grade accuracy is promised. Model weights are fetched during image construction, not from customer audio. Different languages, accents, music, crosstalk and noise can reduce accuracy.

Feed metadata, public audio URLs, recognized speech and subtitles are stored in your Apify run storage. Audio/WAV working files are deleted in normal cleanup; abrupt termination relies on container disposal. Output links follow Apify storage access rules; share them only deliberately. Apify retention depends on your account/settings; delete datasets, key-value records and logs through your account when no longer needed. We do not maintain an additional transcript archive.

Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed and reports are retained for 30 days. Do not submit secrets, private feed tokens or confidential audio. Public enclosures may contain publisher tracking query parameters and remain visible in your output URL.

### Legality and responsible use

Use only publicly available feeds/audio you are authorized to process. Public availability does not grant redistribution rights. Respect copyright, personal-data obligations and publisher terms; avoid sensitive recordings. Human review is required before consequential use. This Actor is independent of podcast publishers, NPR, Apple, Spotify and Apify; no affiliation or endorsement is implied. Apify's standard user terms apply.

### FAQ and troubleshooting

**Can I paste a show page?** No. Obtain the publisher's public RSS URL. Query-bearing feed URLs and credentials are rejected to reduce private-feed risk; this can also exclude some legitimate public feeds.

**Why is the result empty?** The feed may have no audio enclosures, the cutoff/exclusions may remove all entries, or your charge budget may be insufficient. Legitimately empty discovery is successful and uncharged.

**Why did a long episode fail?** Set the duration cap appropriately, up to 30 minutes. Longer episodes are outside the supported contract; the Actor never clips them silently.

**Can it identify speakers or search across all podcasts?** No. Use your downstream index for search; speaker diarization and podcast-directory discovery are not included.

**How do I report a problem?** Open an issue in this Actor's Apify Issues tab. Share the run ID and non-sensitive reproduction input, not secrets or private recordings.

### Related Actors

- [Audio Speech-to-Text Transcriber](https://apify.com/automation-lab/audio-speech-to-text-transcriber): transcribe individually supplied direct audio/video URLs instead of discovering RSS episodes.
- [Audio Extractor — Mono Speech Files](https://apify.com/automation-lab/direct-media-mono-speech-audio-export): produce preprocessing audio artifacts, not transcripts.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/podcast-feed-episode-transcripts/changelog.md

# Actor input Schema

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

1–5 public RSS 2.0 podcast feeds, with audio/\* enclosures. Credential-bearing URLs and feed query strings are rejected; do not submit private feeds. Duplicate feed URLs are removed.

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

Global selected episode cap, 1–10, default 1. Applied after per-feed filters, deduplication and newest-first sorting across feeds. Failed episodes use a selection slot; zero/unlimited is not supported.

## `episodesPerFeed` (type: `integer`):

Select up to 1–10 eligible episodes per feed, default 1, before global maxItems. Newest publication dates first; undated entries last in feed order. Only items currently present in the feed are available, not an archive.

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

Optional strict lower bound, e.g. 2026-01-01T00:00:00Z. Only publication dates later than this UTC ISO timestamp match. Undated episodes are excluded when set. No cross-run state is saved.

## `excludeGuids` (type: `array`):

Skip exact, case-sensitive episode GUID strings (at most 1000); no trimming or substring matching. Missing GUIDs use the enclosure URL. Combine with publishedAfter for recurring indexing.

## `maxFileSizeMb` (type: `integer`):

Per-episode hard download bound, 1–100 MiB, default 50. Oversized audio fails without transcript or audio-minute charge. Each feed is separately capped at 2 MiB.

## `maxDurationMinutes` (type: `integer`):

Reject, never clip, audio longer than this per-episode bound, 1–30 minutes, default 10. Unknown duration also fails. Successful audio is billed by rounded-up whole decoded minutes.

## `language` (type: `string`):

auto detects language; otherwise a supported two-letter Whisper language code such as en or es. Tiny-model accuracy varies; this does not translate speech.

## `outputFormats` (type: `array`):

Optional srt and/or vtt artifact formats; both by default. An empty list disables subtitle artifacts but retains transcript and segments. Duplicate format names are removed.

## Actor input object example

```json
{
  "feedUrls": [
    "https://feeds.npr.org/500005/podcast.xml"
  ],
  "maxItems": 1,
  "episodesPerFeed": 1,
  "excludeGuids": [],
  "maxFileSizeMb": 50,
  "maxDurationMinutes": 10,
  "language": "auto",
  "outputFormats": [
    "srt",
    "vtt"
  ]
}
```

# Actor output Schema

## `dataset` (type: `string`):

Episode transcript records, including uncharged failure rows.

## `files` (type: `string`):

Run-scoped requested subtitle files.

# 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://feeds.npr.org/500005/podcast.xml"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/podcast-feed-episode-transcripts").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://feeds.npr.org/500005/podcast.xml"] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/podcast-feed-episode-transcripts").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://feeds.npr.org/500005/podcast.xml"
  ]
}' |
apify call automation-lab/podcast-feed-episode-transcripts --silent --output-dataset

```

## MCP server setup

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

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/uDNzcb1Vb3ycxtFX1/builds/M9bRFdwgv3VXuElnY/openapi.json
