How to transcribe a podcast RSS feed automatically
A podcast RSS feed already contains the information your transcription worker needs: episode titles, publication dates, GUIDs and audio enclosure URLs. The job is to turn each new enclosure into a transcript once, then index it.
For one episode, manual transcription is enough. For a show, a network or a back catalogue, you need a small ingestion pipeline.
The useful shape is simple:
RSS feed
↓
Detect new episodes
↓
Submit audio URL to transcription API
↓
Poll jobs
↓
Store transcript and timestampsTechtuel exposes a podcast transcript API and an RSS transcription API for this workflow.
1. Read the RSS feed
Most podcast feeds expose each episode as an <item>. The audio URL normally lives in the <enclosure> tag.
Store at least:
- RSS GUID;
- episode title;
- publication date;
- episode page URL;
- audio enclosure URL;
- Techtuel job id;
- job status.
The GUID is usually the best external id. If the publisher keeps it stable, it prevents duplicate transcription jobs.
2. Detect episodes you have not indexed
import Parser from 'rss-parser'
const parser = new Parser()
type StoredEpisode = {
guid: string
}
export async function findNewEpisodes(
feedUrl: string,
storedEpisodes: StoredEpisode[],
) {
const feed = await parser.parseURL(feedUrl)
const knownGuids = new Set(storedEpisodes.map((episode) => episode.guid))
return feed.items
.filter((item) => item.guid && !knownGuids.has(item.guid))
.map((item) => ({
guid: item.guid!,
title: item.title ?? 'Untitled episode',
publishedAt: item.isoDate ?? null,
episodeUrl: item.link ?? null,
audioUrl: item.enclosure?.url ?? null,
}))
.filter((episode) => episode.audioUrl)
}In production, also deduplicate on normalized audio URL. Some feeds change GUIDs during migrations.
3. Submit each audio URL
For feed ingestion, use the asynchronous endpoint. Podcast episodes are long, and a backfill should not depend on one request staying open.
curl -X POST https://api.techtuel.com/v1/transcriptions \
-H "Authorization: Bearer $TECHTUEL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://cdn.example.com/podcast/episode-42.mp3"
}'Response:
{
"id": "job_9f2c",
"status": "processing"
}Persist that id immediately next to your episode row.
4. Poll until the job is terminal
There is no webhook. Polling keeps the integration predictable and avoids exposing a public callback endpoint.
const TERMINAL = new Set(['completed', 'failed'])
export async function waitForTranscript(jobId: string) {
while (true) {
const response = await fetch(
`https://api.techtuel.com/v1/transcriptions/${jobId}`,
{ headers: { Authorization: `Bearer ${process.env.TECHTUEL_API_KEY}` } },
)
const job = await response.json()
if (TERMINAL.has(job.status)) return job
await new Promise((resolve) => setTimeout(resolve, 15000))
}
}Treat claimed like processing: a worker has picked up the job, but the transcript is not ready.
5. Store the transcript
The completed job returns the full transcript and timestamped segments:
{
"id": "job_9f2c",
"status": "completed",
"title": "Hybrid retrieval in production",
"language": "en",
"minutes": 54.7,
"transcript": "Welcome to the show...",
"segments": [
{
"start_seconds": 0,
"text": "Welcome to the show."
},
{
"start_seconds": 5.8,
"text": "Today we are discussing retrieval systems."
}
]
}Store both the full transcript and the segments. The full transcript is useful for exports and summaries. The segments are useful for search, citations and RAG.
6. Build idempotency into your worker
Podcast ingestion fails in boring ways:
- feed temporarily unavailable;
- enclosure URL expires;
- publisher changes CDN;
- episode metadata changes;
- the same episode appears twice;
- a transcription job fails and needs retrying.
Keep the workflow resumable:
- persist the job id before polling;
- do not submit a new job if one is already in progress;
- retry only failed jobs;
- store the RSS GUID and audio URL;
- keep your own ingestion status separate from Techtuel's job status.
If a Techtuel job fails after a transient source problem, retry it with POST /v1/transcriptions/{id}/retry.
7. Index for search or RAG
Once stored, split the transcript into chunks with episode metadata:
- podcast title;
- episode title;
- episode URL;
- publication date;
- start time;
- text;
- Techtuel job id.
This gives your search result a citation that can send the listener back to the exact part of the episode.
For a deeper indexing walkthrough, read Turn podcast episodes into documents for RAG.
Start small
Begin with one feed, one worker and one table. Add concurrency after you know how many new episodes arrive per day and how long transcription takes for your catalogue.