Get Episode Transcripts via iTunes API

Learn how to fetch podcast episodes from iTunes API and retrieve their transcripts from PodEngine

EpisodesTranscriptsiTunes APIIntegration
intermediate
15 minutes
PodEngine Team
Prerequisites
  • API token for authenticated endpoints
  • Node.js or JavaScript runtime environment
  • Understanding of async/await and fetch API
  • iTunes collection ID for the target podcast
1
Fetch Recent Episodes from iTunes
Use the iTunes API to retrieve recent podcast episodes

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 content
  • entity=podcastEpisode: Returns episodes instead of just podcast info
  • limit: 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
  ]
}
2
Lookup PodEngine Podcast ID
Convert the iTunes Apple ID to a PodEngine podcast ID

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}`);
3
Find Matching Episodes
Match iTunes episodes with PodEngine episodes by title

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:

  1. First attempt an exact match (case-insensitive)
  2. Fall back to normalized partial matching if exact match fails
  3. 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`);
4
Get Episode Transcripts
Retrieve transcripts for matched episodes or request transcription

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 hasTranscript field 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}`);
    }
  }
}