For most of podcasting's history, an RSS feed told you very little: a title, a description, an audio file and a date. Everything else, such as who was speaking, what was said and where the interesting part started, was locked inside the audio. And the only way to know a feed had changed was to download it again and look.
Two developments have changed that. The Podcast Namespace (often called "Podcast 2.0") added standard RSS tags for transcripts, chapters, soundbites, people and much more. And Podping gave publishers a way to announce, in real time, that a feed has changed. Together they let you build features that were impractical a few years ago: search inside episodes, guest pages, shareable clips, and episode lists that are current within minutes.
This guide explains both from a developer's point of view, and shows how to use them through the podsearch.org API at https://podsearch.org/api/v2.
Part 1: the Podcast Namespace in five tags
The Podcast Namespace is maintained in the open by the Podcast Index community. It defines dozens of tags; five matter most for search and discovery features.
<podcast:transcript>
Links an episode to a transcript or closed-captions file. The type attribute gives the MIME type, such as text/plain, text/html, text/vtt, application/json or application/x-subrip (SRT). An optional language attribute declares the transcript's language, and rel="captions" marks a file with timecodes intended as captions. An episode may carry several transcript tags in different formats.
<podcast:transcript url="https://example.com/ep42/transcript.vtt" type="text/vtt" />
<podcast:transcript url="https://example.com/ep42/transcript.json" type="application/json" language="en" rel="captions" />
<podcast:chapters>
Points to an external JSON chapters file (MIME type application/json+chapters). Because chapters live outside the audio file, publishers can edit them after release. Chapters give you navigable sections with titles and start times, and sometimes images and links.
<podcast:soundbite>
Marks a highlight within an episode with a startTime and duration in seconds, plus an optional title as the tag's text. The specification recommends soundbites between 15 and 120 seconds. They are designed for previews, audiograms and discovery.
<podcast:soundbite startTime="1234.5" duration="42.25">Why the namespace matters</podcast:soundbite>
<podcast:person>
Names a person involved in a show or episode, with an optional role (host, guest, producer and many more from the namespace taxonomy), group, img and href. At the channel level it lists the regular cast; at the item level it replaces the cast for that episode, which is how guests are recorded.
<podcast:guid>
A globally unique, permanent identifier for a podcast, a UUIDv5 derived from the feed URL at the time it was assigned. It survives host migrations, which makes it the best key for de-duplicating shows that have moved between feed URLs.
How common are these tags?
Adoption varies a lot by hosting platform and by show. Many hosting platforms now generate some namespace tags automatically, but plenty of feeds still carry none. The practical consequence for developers: design every Podcast 2.0 feature as an enhancement, with a graceful fallback when data is missing.
- A transcript search toggle should still return title and description matches.
- A guest page should fall back to text search for the person's name.
- A chapter list should be hidden, not shown empty, when there are no chapters.
podsearch.org parses transcripts, chapters, soundbites and persons where feeds provide them. Coverage is therefore partial and grows as publishers adopt the tags.
Using transcripts through the API
Fetch the transcript for an episode
curl -s "https://podsearch.org/api/v2/episodes/987654?show_transcript=1" \
-H "X-ListenAPI-Key: $PODSEARCH_API_KEY"
When a transcript is stored for the episode, the response includes a transcript field with its text. When none exists, the field is absent, so check before you render.
ep = api.get("/episodes/987654", show_transcript=1)
text = ep.get("transcript")
if text:
print(text[:500])
else:
print("No transcript published for this episode")
Search inside episodes
Transcripts are most valuable as search input. Add deep_search=1 to /search to include episode content and transcripts in matching, or use the content: (alias transcript:) field with boolean syntax:
# Episodes where the spoken content mentions "carbon capture" and the show title mentions "energy"
curl -s -G "https://podsearch.org/api/v2/search" \
--data-urlencode 'q=content:"carbon capture" AND title:energy' \
--data-urlencode "syntax=dql" --data-urlencode "type=episode" \
-H "X-ListenAPI-Key: $PODSEARCH_API_KEY"
Transcript search finds things titles never mention: the episode where a guest tells a story about a particular city, the interview that touches on a niche technology, the sports show that discussed a specific player. It is also noisier, so a good UI labels these results ("mentioned in the episode") and ranks title matches first.
Build timestamps from captions
If you fetch a transcript file in a timed format such as VTT, SRT or the JSON format with rel="captions", you can deep-link to the moment a phrase is spoken. A minimal VTT search in Python:
import re, requests
def find_in_vtt(vtt_url, phrase):
vtt = requests.get(vtt_url, timeout=20).text
hits = []
for block in vtt.split("\n\n"):
m = re.search(r"(\d{2}:)?(\d{2}):(\d{2})[.,]\d{3} -->", block)
if m and phrase.lower() in block.lower():
h = int((m.group(1) or "0:")[:-1]); mi = int(m.group(2)); s = int(m.group(3))
hits.append(h * 3600 + mi * 60 + s)
return hits # seconds; append #t=SECONDS to many web audio players
Transcript files are hosted by publishers, so cache what you fetch, set a polite user agent, and respect errors.
Using people data
People data powers "guest" and "host" pages, which are among the most-searched entities in podcasting. There are two complementary ways to get it.
Structured, where tagged. The public podsearch.org service exposes a person search at /persons/search?q= that returns episodes whose feeds tag that name with <podcast:person>, along with the role, profile link and image when given:
curl -s "https://podsearch.org/persons/search?q=jane%20doe&limit=20"
Text search, everywhere. Most episodes that feature a guest do not tag them, but nearly all mention them in the title or description. A quoted phrase search with unique_podcasts=1 gives you a list of shows the person has appeared on:
res = api.get("/search", q='"jane doe"', syntax="dql", type="episode",
unique_podcasts=1, sort_by_date=1, page_size=30)
Combine both: show tagged appearances with confidence, and text matches as "also mentioned in". Be careful with common names; requiring the name in the episode title rather than anywhere in the description reduces false matches considerably (fq_episode_title="jane doe").
Chapters and soundbites
Chapters and soundbites live in the RSS feed itself: the <podcast:chapters> tag carries the URL of a JSON chapters file, and soundbites are inline tags with start times. The podsearch.org API returns each podcast's feed URL in the rss field, so the most direct way to build chapter navigation today is to read the episode's item from that feed and fetch its chapters file. Cache chapters files, because publishers can update them after release and you do not want to refetch on every play.
Good uses:
- Chapter navigation in your player, with chapter titles as a table of contents.
- Chapter-level search, indexing chapter titles in your own search so a query can land on the right section.
- Clip previews from soundbites on episode pages, which tend to convert browsers into listeners.
Part 2: freshness and Podping
A search index is only as good as its latest crawl. If a daily news show publishes at 6am and your app does not know until noon, you have lost the morning.
The polling problem
Without notifications, an indexer must poll every feed periodically. With millions of feeds, that forces a trade-off: poll often and spend enormous bandwidth re-downloading unchanged feeds, or poll rarely and miss new episodes for hours. Conditional requests with ETag and If-Modified-Since help, but they still require a request per feed per check.
What Podping does
Podping is a notification bus for podcast feed updates. When a publisher or hosting platform updates a feed, it sends a request to the Podping service with the feed URL. The service writes the URL to the Hive blockchain as a custom JSON operation, where anyone can watch for it. The project describes watchers seeing an update within about 20 seconds. Feeds can also declare that they use Podping with <podcast:podping usesPodping="true"/> in the channel.
Because the notifications are public, every indexer that watches the chain learns about the same updates at the same time, without the publisher having to notify each directory individually.
How podsearch.org uses it
podsearch.org runs a Podping watcher that streams these operations continuously. When a ping arrives for a feed it already knows, the feed jumps to the front of the polling queue and is typically re-fetched within seconds to minutes. New episodes are written to the database and indexed into search immediately, so they become searchable shortly after publish.
Feeds that do not send Podpings are polled on an adaptive schedule driven by how recently they last published: shows that published in the last week are checked far more often than shows that have been quiet for a year. This keeps active shows fresh without wasting requests on dormant ones.
Designing for freshness in your app
- Do not over-cache new-episode rows. A five-minute cache on "latest from your shows" lets you benefit from Podping speed. Cache older data longer.
- Sort by date when it matters.
sort_by_date=1on/searchreturns newest first;recency=1keeps relevance order but boosts new episodes. - Use publish-date filters.
published_afterin epoch milliseconds gives you "since my last check" queries for alerts and digests.
import time
def new_mentions_since(term, since_ms):
res = api.get("/search", q=term, type="episode", sort_by_date=1,
published_after=since_ms, page_size=50)
return res["results"]
last_check = int(time.time() * 1000) - 3_600_000 # one hour ago
for ep in new_mentions_since('"open source"', last_check):
print(ep["pub_date_ms"], ep["podcast"]["title_original"], "-", ep["title_original"])
Run a query like that every few minutes from a scheduled job and you have a near-real-time podcast alert system.
A note on publish dates
Publishers set episode dates themselves, and some are wrong: dates in the future, dates reset on re-upload, or "rewind" episodes re-dated to today. Treat pub_date_ms as the publisher's claim. If you need to know when an episode actually appeared, record the time you first saw it in your own system.
Putting it together: an episode page built on Podcast 2.0
Here is how the pieces combine on a single episode page, with the fallback for each element when the feed does not provide the data.
| Element | Primary source | Fallback |
|---|---|---|
| Title, artwork, date, audio | Episode object from /episodes/{id} | Always present |
| Show notes | description_original | Always present, may be short |
| Transcript tab | transcript from show_transcript=1 | Hide the tab |
| "Jump to" chapter list | <podcast:chapters> file from the feed | Hide the list |
| Highlight clip | <podcast:soundbite> | Hide the clip |
| Guests and hosts | <podcast:person> tags | Names parsed from the title, shown as plain text |
| "More from this guest" | Person search, then quoted-name search | Hide the row |
| "More like this" | /episodes/{id}/recommendations | Show recent episodes from the same podcast |
Build the page so every optional element is independent. A missing chapters file should never delay the transcript tab, and a slow third-party transcript host should never block the player. Load optional data after the page renders, with short timeouts.
Respecting publishers
Podcast 2.0 data is published voluntarily by creators and hosting platforms, often at real cost to them. A few habits keep your app a good citizen of the open ecosystem:
- Fetch politely. Cache transcript and chapters files, use conditional requests, and send a descriptive
User-Agentwith a contact URL. - Link back. Show the podcast's own website and keep the publisher's audio URL as the source of playback, which also keeps their download statistics accurate.
- Honor the feed. If a feed changes or removes a transcript, update your copy. Do not keep serving content a publisher has withdrawn.
- Credit people correctly. Use the names and roles from
<podcast:person>as given, and link to thehrefthe publisher chose.
These are small things, but the open podcast ecosystem works because indexers, apps and publishers trust each other with their data.
Frequently asked questions
What is Podcast 2.0?
It is the common name for the Podcast Namespace, an open set of RSS tags maintained by the Podcast Index community. It adds structured data such as transcripts, chapters, soundbites, people, a permanent podcast GUID and Podping support to ordinary podcast RSS feeds.
Do all podcasts have transcripts?
No. Only feeds that publish a <podcast:transcript> tag have transcripts that podsearch.org can ingest. Treat transcript features as enhancements with a fallback to title and description search.
How quickly do new episodes appear in search?
For feeds that send Podping notifications, typically within seconds to minutes of publish. Other feeds are polled on an adaptive schedule, so very active shows are checked often and dormant ones rarely.
Can I search transcripts through the API?
Yes. Use deep_search=1 on /search, or the content: field with syntax=dql. Fetch one episode's transcript with GET /episodes/{id}?show_transcript=1.
How do I get chapters?
Read the episode's <podcast:chapters> tag from the RSS feed, whose URL the API returns as rss, then fetch the JSON chapters file it points to. Cache it, since publishers may update chapters after release.
What is the best identifier for a podcast?
Use <podcast:guid> when the feed has one, and the RSS feed URL otherwise. Both are more stable than any single provider's internal ID.
Summary
Podcast 2.0 tags turn podcasts from opaque audio files into structured, searchable documents, and Podping turns slow polling into near-real-time updates. Build transcript search, people pages and chapter navigation as progressive enhancements with sensible fallbacks, keep new-episode caches short, and anchor your data on the feed URL and podcast GUID. The result is an app that feels current and finds the moments inside episodes, not just their titles.