The iTunes API provides a lookup endpoint that can fetch podcast metadata and recent episodes. By specifying the entity=podcastEpisode parameter, you can retrieve episode details including titles, descriptions, and release dates.
Key parameters:
id: The iTunes collection ID for the podcast. This is the appleId field in PodEngine.media=podcast: Specifies podcast contententity=podcastEpisode: Returns episodes instead of just podcast infolimit: Maximum number of episodes to return (default 50, max 200)
The response includes the podcast as the first result, followed by episodes in reverse chronological order.
Fetch Episodes from iTunes API
No authentication required for iTunes API
// Fetch recent episodes from iTunes API
const collectionId = '1200361736'; // The Daily podcast
const episodeLimit = 10;
const itunesResponse = await fetch(
`https://itunes.apple.com/lookup?id=${collectionId}&media=podcast&entity=podcastEpisode&limit=${episodeLimit}`
);
const itunesData = await itunesResponse.json();
const [podcast, ...episodes] = itunesData.results;
console.log(`Found ${episodes.length} episodes for "${podcast.collectionName}"`);
// Extract episode information
const episodeInfo = episodes.map(ep => ({
title: ep.trackName,
description: ep.description,
releaseDate: ep.releaseDate,
duration: Math.round(ep.trackTimeMillis / 1000), // Convert to seconds
episodeUrl: ep.episodeUrl
})); Example iTunes API Response
The response includes podcast metadata followed by episodes
{
"resultCount": 11,
"results": [
{
"wrapperType": "track",
"kind": "podcast",
"artistId": 121664449,
"collectionId": 1200361736,
"trackId": 1200361736,
"artistName": "The New York Times",
"collectionName": "The Daily",
"trackName": "The Daily",
"collectionCensoredName": "The Daily",
"trackCensoredName": "The Daily",
"artistViewUrl": "https://podcasts.apple.com/us/artist/the-new-york-times/121664449?uo=4",
"collectionViewUrl": "https://podcasts.apple.com/us/podcast/the-daily/id1200361736?uo=4",
"feedUrl": "https://feeds.simplecast.com/Sl5CSM3S",
"trackViewUrl": "https://podcasts.apple.com/us/podcast/the-daily/id1200361736?uo=4",
"artworkUrl30": "https://is1-ssl.mzstatic.com/image/thumb/Podcasts221/v4/ab/64/66/ab6466a9-9a7d-e20e-7a3d-bc5be37d29ce/mza_15084852813176276273.jpg/30x30bb.jpg",
"artworkUrl60": "https://is1-ssl.mzstatic.com/image/thumb/Podcasts221/v4/ab/64/66/ab6466a9-9a7d-e20e-7a3d-bc5be37d29ce/mza_15084852813176276273.jpg/60x60bb.jpg",
"artworkUrl100": "https://is1-ssl.mzstatic.com/image/thumb/Podcasts221/v4/ab/64/66/ab6466a9-9a7d-e20e-7a3d-bc5be37d29ce/mza_15084852813176276273.jpg/100x100bb.jpg",
"collectionPrice": 0.00,
"trackPrice": 0.00,
"collectionHdPrice": 0,
"releaseDate": "2025-10-07T09:45:00Z",
"collectionExplicitness": "notExplicit",
"trackExplicitness": "cleaned",
"trackCount": 2382,
"trackTimeMillis": 2237,
"country": "USA",
"currency": "USD",
"primaryGenreName": "Daily News",
"contentAdvisoryRating": "Clean",
"artworkUrl600": "https://is1-ssl.mzstatic.com/image/thumb/Podcasts221/v4/ab/64/66/ab6466a9-9a7d-e20e-7a3d-bc5be37d29ce/mza_15084852813176276273.jpg/600x600bb.jpg",
"genreIds": ["1526", "26", "1489"],
"genres": ["Daily News", "Podcasts", "News"]
},
{
"wrapperType": "podcastEpisode",
"kind": "podcast-episode",
"collectionId": 1200361736,
"trackId": 1001234567,
"collectionName": "The Daily",
"trackName": "Tuesday, October 7, 2025",
"releaseDate": "2025-10-07T09:45:00Z",
"description": "Today's episode covers the latest developments...",
"trackTimeMillis": 1800000,
"episodeUrl": "https://example.com/episode.mp3",
"episodeGuid": "abc-123-def-456",
"artworkUrl600": "https://is1-ssl.mzstatic.com/image/thumb/Podcasts221/v4/ab/64/66/ab6466a9-9a7d-e20e-7a3d-bc5be37d29ce/mza_15084852813176276273.jpg/600x600bb.jpg"
},
{
"wrapperType": "podcastEpisode",
"kind": "podcast-episode",
"collectionId": 1200361736,
"trackId": 1001234566,
"collectionName": "The Daily",
"trackName": "Monday, October 6, 2025",
"releaseDate": "2025-10-06T09:45:00Z",
"description": "A deep dive into the week's major stories...",
"trackTimeMillis": 2100000,
"episodeUrl": "https://example.com/episode2.mp3",
"episodeGuid": "xyz-789-uvw-012"
}
// ... more episodes
]
} - The iTunes API returns the podcast metadata as the first result, followed by episodes
- Episode data from iTunes does not include unique episode IDs that match PodEngine
- The trackTimeMillis field is in milliseconds, convert to seconds for consistency
Since iTunes and PodEngine use different ID systems, you need to convert the iTunes collection ID (Apple ID) to the corresponding PodEngine ID. The lookup endpoint allows you to find a podcast using various external IDs.
This step is crucial because all subsequent PodEngine API calls require the PodEngine podcast ID.
API Documentation: Podcast ID Lookup
Convert Apple ID to PodEngine ID
Requires API authentication
// Look up the PodEngine podcast ID using the Apple ID
const appleId = podcast.collectionId;
const options = {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": "YOUR_API_TOKEN"
}
};
const lookupResponse = await fetch(
`http://localhost:4000/api/v1/podcasts/id/lookup?appleId=${appleId}`,
options
);
const lookupData = await lookupResponse.json();
const podengineId = lookupData.data.podcast.id;
const podcastSlug = lookupData.data.podcast.slug;
console.log(`PodEngine ID: ${podengineId}`);
console.log(`Podcast Slug: ${podcastSlug}`); - This endpoint requires authentication - ensure your API token is valid
- Not all podcasts in iTunes may be indexed in PodEngine
- The lookup endpoint also supports Spotify IDs and RSS feed URLs
- The response includes both the PodEngine ID and slug for the podcast
Since iTunes doesn't provide unique episode IDs that match PodEngine's system, you need to match episodes by their titles. This can be challenging due to potential differences in formatting, special characters, or title variations between sources.
The matching function should:
- First attempt an exact match (case-insensitive)
- Fall back to normalized partial matching if exact match fails
- Handle special characters and formatting differences
This step fetches episodes from PodEngine and attempts to match them with the iTunes episodes retrieved earlier.
API Documentation: Get Podcast Episodes
Match Episodes Between Systems
Handles title variations and formatting differences
// Fetch episodes from PodEngine to find matches
const episodesResponse = await fetch(
`http://localhost:4000/api/v1/podcasts/${podengineId}/episodes?limit=20`,
options
);
const episodesData = await episodesResponse.json();
const podEngineEpisodes = episodesData.data.podcastWithEpisodes.episodes;
// Match iTunes episodes with PodEngine episodes by title
function findMatchingEpisode(itunesTitle, podEngineEpisodes) {
// First try exact match
let match = podEngineEpisodes.find(ep =>
ep.title.toLowerCase() === itunesTitle.toLowerCase()
);
if (!match) {
// Try partial match (iTunes sometimes has different formatting)
match = podEngineEpisodes.find(ep => {
const normalizedItunes = itunesTitle.toLowerCase().replace(/[^a-z0-9\s]/g, '');
const normalizedPodEngine = ep.title.toLowerCase().replace(/[^a-z0-9\s]/g, '');
return normalizedItunes.includes(normalizedPodEngine) ||
normalizedPodEngine.includes(normalizedItunes);
});
}
return match;
}
// Find matches for all iTunes episodes
const matchedEpisodes = episodeInfo.map(itunesEp => {
const podEngineEp = findMatchingEpisode(itunesEp.title, podEngineEpisodes);
return {
itunesTitle: itunesEp.title,
podEngineEpisode: podEngineEp,
matched: !!podEngineEp
};
});
console.log(`Matched ${matchedEpisodes.filter(m => m.matched).length} of ${matchedEpisodes.length} episodes`); - Title matching is not 100% reliable due to formatting differences
- Episodes may have different titles in iTunes vs the RSS feed
- Special episodes or bonus content may use inconsistent naming
- Consider implementing fuzzy matching for better results
- You may want to also compare release dates for additional verification
- The limit parameter helps ensure you get recent episodes for matching
Once you have matched episodes between iTunes and PodEngine, you can retrieve their transcripts. Episodes may or may not have transcripts available immediately.
For episodes with transcripts:
- Use the transcript-text endpoint to get the full text
- The transcript is returned as plain text without timestamps
For episodes without transcripts:
- Check the
hasTranscriptfield to determine availability - Use the transcription-request endpoint to request a new transcription
- Transcription typically takes 1-2 hours to complete
You can also retrieve timestamped transcripts using the transcript-timestamps endpoint for more detailed analysis.
API Documentation:
Retrieve or Request Transcripts
Handles both existing and missing transcripts
// Get transcript for matched episodes
for (const match of matchedEpisodes) {
if (!match.matched || !match.podEngineEpisode) {
console.log(`No match found for: ${match.itunesTitle}`);
continue;
}
const episode = match.podEngineEpisode;
console.log(`\nProcessing: ${episode.title}`);
// Check if episode has transcript
if (episode.hasTranscript && episode.transcriptId) {
// Fetch the transcript text
const transcriptResponse = await fetch(
`http://localhost:4000/api/v1/episodes/${episode.episodeId}/transcript-text`,
options
);
const transcriptData = await transcriptResponse.json();
const transcriptText = transcriptData.data.transcript.text;
console.log(`Transcript available (length: ${transcriptText.length} characters)`);
console.log(`First 500 characters: ${transcriptText.substring(0, 500)}...`);
} else {
console.log(`No transcript available for this episode`);
// Optionally request transcription
const requestTranscript = false; // Set to true to request
if (requestTranscript) {
const transcriptionRequest = await fetch(
`http://localhost:4000/api/v1/episodes/${episode.episodeId}/transcription-request`,
options
);
const requestData = await transcriptionRequest.json();
console.log(`Transcription requested: ${requestData.data.message}`);
console.log(`Check status using request ID: ${requestData.data.requestId}`);
}
}
} - Transcription requests are subject to rate limits and usage quotas
- Not all episodes may be eligible for transcription (e.g., too long, poor audio quality)
- Transcription requests are asynchronous - check status separately
- Use the transcript-timestamps endpoint for word-level timing information
- Consider implementing a polling mechanism to check transcription status
- Transcripts can be downloaded in various formats (txt, srt, vtt) using the download endpoint
- The Requests History endpoint can be used to track transcription progress