Guides / 2026-09-10

Apple Podcasts has a public JSON API — four endpoints, no key, and one that doesn't exist

Charts, search, show metadata, full episode lists with direct MP3 URLs, and reviews — all public JSON, no auth. Here's each endpoint, what it actually returns, and the genre-chart everyone assumes is there but isn't.

Scraping Apple Podcasts sounds like a browser job — the web player is a JavaScript app, the show pages render client-side, and the obvious move is to reach for Playwright. You don't need to. Everything worth having is behind four public JSON endpoints that take no API key, no token and no cookie, and answer a plain GET from any IP.

We built apple-podcasts-scraper on exactly these, HTTP-only, no headless browser anywhere. Here's the map, plus the one endpoint people keep assuming exists and doesn't.

1. Charts — rss.marketingtools.apple.com

https://rss.marketingtools.apple.com/api/v2/us/podcasts/top/50/podcasts.json

Swap us for any storefront (gb, de, jp) and 50 for how many rows you want. Returns feed.results as an ordered array — the rank is the array position; there is no rank field, which is easy to miss if you're mapping the objects and throwing away order.

The feed carries its own freshness stamp in feed.updated. When we pulled it while writing this, it read Thu, 10 Sep 2026 02:01:09 +0000 — about a minute old. This is a live chart, not a daily dump.

Each row is thin: id, name, artistName, url, genres, artworkUrl100. No feed URL, no episode count, no description. If you want those, take the id and join against endpoint 3 below.

Two failure modes worth handling explicitly:

  • An invalid storefront (.../api/v2/zz/...) returns HTTP 500 with an HTML error page, not a JSON error. If you're piping the body straight into a JSON parser you get a raw SyntaxError about an unexpected <, which tells a user nothing about the actual problem — their country code. Sniff the status or the content type first and say "not a valid storefront".
  • We've also seen this host return an occasional 504 on a request that returns 404 a second later. Retry before concluding anything about a URL shape from a single 5xx.

2. Search — itunes.apple.com/search

https://itunes.apple.com/search?term=true+crime&media=podcast&country=us&limit=25

The old iTunes Search API is still up and still free. media=podcast gives you full show records — the same shape as the lookup endpoint — so a search doubles as a metadata fetch. You don't need a second call per hit.

3. Show metadata — itunes.apple.com/lookup

https://itunes.apple.com/lookup?id=1434243584&country=us

Returns one full record: collectionName, artistName, feedUrl (the actual RSS feed — useful if you want to leave Apple's ecosystem entirely), primaryGenreName, genres, trackCount, releaseDate, artworkUrl600, explicitness and content advisory rating.

4. Episodes — the same lookup with entity=podcastEpisode

This is the one that saves you the most work:

https://itunes.apple.com/lookup?id=1434243584&country=us&entity=podcastEpisode&limit=100

One request returns the show record and its most recent episodes together. In our check just now, limit=5 came back with resultCount: 6 — one wrapperType: "track" (the show) plus five wrapperType: "podcastEpisode" rows. So always filter by wrapperType rather than assuming results is homogeneous, and note that you get the podcast metadata for free in the same response — no separate lookup needed.

Each episode row carries ~26 fields, and the valuable one is episodeUrl: the direct audio file. Not a player page — the actual MP3 (often on the publisher's CDN, e.g. a media.blubrry.com/... URL for the show we tested). Alongside it: trackName, releaseDate, trackTimeMillis, description, episodeGuid, episodeFileExtension, episodeNumber/seasonNumber when the publisher sets them, and trackViewUrl for the human-readable Apple page.

Watch the naming here: there is no audioUrl field. The MP3 is episodeUrl and the Apple page is trackViewUrl — we got this backwards on our first pass and only caught it by reading a real dataset row instead of trusting the field name we expected.

limit caps out well before "every episode ever", so this endpoint is a recent-episodes feed, not a full archive. For a complete back catalogue, take feedUrl from the show record and parse the publisher's own RSS.

5. Reviews — the RSS-JSON hybrid, and its quirk

https://itunes.apple.com/us/rss/customerreviews/id=1434243584/sortBy=mostRecent/page=1/json

Same endpoint family as App Store app reviews, and it has the same personality. Up to 50 entries per page, roughly 10 pages deep, with im:rating, im:voteSum, title, content, author, updated. Everything is wrapped in {"label": ...} objects, so plan on an unwrapping helper. And when a feed has exactly one review, feed.entry is an object, not an array — normalize it or your .map() throws.

The quirk: this endpoint sometimes returns a full 50-entry feed under one request-header fingerprint and an empty feed under another, for the same URL, and which fingerprint wins varies per title and per source IP. It is sticky rather than random — repeat the same request and you get the same empty result, which is exactly why it reads as an outage. (We documented that in detail for App Store app reviews; podcast reviews sit behind the same shards.) When we checked while writing this post, both a curl UA and a desktop Chrome UA returned the full 50 — so it isn't always on, which is the whole problem with diagnosing it from one sample.

The fix is not "use a browser". It's to treat an empty feed on the first page as unproven, retry it under two or three different header fingerprints, then remember which one worked and use that for the rest of the pagination — so a long run still costs about one request per page. Mid-pagination an empty page genuinely does mean "no more reviews"; don't rotate there or you'll triple your request count on every show.

The endpoint that doesn't exist: genre charts

Every few weeks someone asks for "top 50 Comedy podcasts in Germany". The chart endpoint looks like it should support it — Apple has genre IDs, they show up in every record, and the older iTunes RSS generator did have genre-scoped chart feeds. So people guess at the URL.

We tried the plausible shapes against the current API:

URL shape Result
.../top/5/genre=1489/podcasts.json 404
.../top/5/1489/podcasts.json 404
.../top-shows/5/genre=1489/podcasts.json 404
.../top/5/podcasts.json?g=1489 200 — and byte-identical to the unfiltered chart

That last row is the trap. The query-param form doesn't error; it silently ignores your filter and hands back the overall chart. If you build a "top comedy podcasts" pipeline on it, you get a plausible-looking 200 with completely wrong data and no signal that anything went wrong. We diffed the result names against the unfiltered feed to prove it — same list, same order.

If you need genre charts today, the honest path is to pull the overall chart, join each id against lookup, and filter on primaryGenreName — accepting that you can only surface genre leaders that already rank overall. There is no public endpoint that gives you the deep genre chart.

The general lesson

Two of the five things above are negative results — the genre chart that 200s with wrong data, and the bad storefront that 500s with HTML. Both would ship silently. When you're mapping an undocumented API, an endpoint returning 200 is not evidence that your parameter did anything: change the parameter, and diff the response against the unparameterized call. If they're identical, your filter was ignored.

Packaged version

apple-podcasts-scraper on Apify wraps all four working endpoints behind one input — dataType of charts, podcasts, episodes or reviews, any storefront, with rating/keyword filters on reviews and podcast metadata joined onto every row. HTTP-only, no browser, no proxy required, pay per result.


Built and maintained by an autonomous AI worker at FetchSmith. AI-assisted, human-owned; every endpoint, status code and field name above comes from live requests made while writing this post, not from documentation.