Apple Reviews for Top Podcasts Chart
Fetch an Apple Podcasts chart and read ratings and written reviews for its podcasts.
Before you start
- API token for the reviews endpoint
Choose your example
Your selection applies to every step. HTTP uses built-in clients without the Pod Engine libraries.
Use Node.js 22 or newer. Save the complete script as recipe.mjs.
npm install @podengine/sdk
# macOS / Linux shell; set the same variable in your shell on Windows
export PODENGINE_API_KEY="YOUR_API_KEY"
node recipe.mjs Use Node.js 22 or newer. Save the complete script as recipe.mjs. No package installation is needed.
# macOS / Linux shell; set the same variable in your shell on Windows
export PODENGINE_API_KEY="YOUR_API_KEY"
node recipe.mjs Use Python 3.10 or newer. Save the complete script as recipe.py.
python -m pip install podengine
# macOS / Linux shell; set the same variable in your shell on Windows
export PODENGINE_API_KEY="YOUR_API_KEY"
python recipe.py Use Python 3.10 or newer. Save the complete script as recipe.py. No package installation is needed.
# macOS / Linux shell; set the same variable in your shell on Windows
export PODENGINE_API_KEY="YOUR_API_KEY"
python recipe.py Set up the client
Start your file with this setup, then append each step in order. The complete script below includes everything.
import { PodEngine } from '@podengine/sdk';
const apiKey = process.env.PODENGINE_API_KEY;
if (!apiKey) throw new Error('Set PODENGINE_API_KEY before running this recipe');
const pe = new PodEngine({
apiKey,
baseUrl: process.env.PODENGINE_API_URL || 'https://api.podengine.ai',
}); const apiKey = process.env.PODENGINE_API_KEY;
if (!apiKey) throw new Error('Set PODENGINE_API_KEY before running this recipe');
const baseUrl = process.env.PODENGINE_API_URL || 'https://api.podengine.ai';
async function request(path, body) {
const response = await fetch(`${baseUrl}${path}`, {
method: body === undefined ? 'GET' : 'POST',
headers: { Authorization: apiKey, 'Content-Type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error(`Pod Engine request failed: ${response.status} ${response.statusText}`);
return (await response.json()).data;
} import os
from podengine import PodEngine
pe = PodEngine(
api_key=os.environ["PODENGINE_API_KEY"],
base_url=os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai"),
) import json
import os
from urllib.parse import urlencode, quote
from urllib.request import Request, urlopen
api_key = os.environ["PODENGINE_API_KEY"]
base_url = os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai")
def request(path, body=None):
req = Request(
base_url + path,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": api_key, "Content-Type": "application/json"},
method="POST" if body is not None else "GET",
)
# urlopen raises HTTPError for non-success responses.
with urlopen(req, timeout=30) as response:
return json.load(response)["data"] Complete runnable script
import { PodEngine } from '@podengine/sdk';
const apiKey = process.env.PODENGINE_API_KEY;
if (!apiKey) throw new Error('Set PODENGINE_API_KEY before running this recipe');
const pe = new PodEngine({
apiKey,
baseUrl: process.env.PODENGINE_API_URL || 'https://api.podengine.ai',
});
const { chart } = await pe.charts.getLatestChart({
chartType: 'apple',
country: 'us',
category: 'top podcasts',
positionsLimit: 5,
});
if (!chart) throw new Error('No chart available for these options');
console.log(`Found ${chart.positions.length} chart positions`);
for (const position of chart.positions) {
const podcast = position.podenginePodcast;
if (!podcast) continue; // Some chart entries have not been matched to Pod Engine.
const { podcastReviews } = await pe.podcasts.getPodcastReviews({
podcastIdOrSlug: podcast.id,
country: 'gb',
limit: 100,
});
console.log(podcast.title);
for (const aggregate of podcastReviews.applePodcastsReviewsByCountry) {
console.log(`${aggregate.country}: ${aggregate.rating} (${aggregate.reviewsCount} ratings)`);
}
if (!podcastReviews.applePodcastsReviewText) {
console.info('No written review data available');
continue;
}
const { total, reviews } = podcastReviews.applePodcastsReviewText;
console.log(`${total} reviews total; showing ${reviews.length}`);
console.dir(reviews, { depth: null });
} const apiKey = process.env.PODENGINE_API_KEY;
if (!apiKey) throw new Error('Set PODENGINE_API_KEY before running this recipe');
const baseUrl = process.env.PODENGINE_API_URL || 'https://api.podengine.ai';
async function request(path, body) {
const response = await fetch(`${baseUrl}${path}`, {
method: body === undefined ? 'GET' : 'POST',
headers: { Authorization: apiKey, 'Content-Type': 'application/json' },
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error(`Pod Engine request failed: ${response.status} ${response.statusText}`);
return (await response.json()).data;
}
const { chart } = await request(
'/api/v1/charts/latest?chartType=apple&country=us&category=top+podcasts&positionsLimit=5'
);
if (!chart) throw new Error('No chart available for these options');
console.log(`Found ${chart.positions.length} chart positions`);
for (const position of chart.positions) {
const podcast = position.podenginePodcast;
if (!podcast) continue; // Some chart entries have not been matched to Pod Engine.
const { podcastReviews } = await request(
`/api/v1/podcasts/${encodeURIComponent(podcast.id)}/reviews?country=gb&limit=100`
);
console.log(podcast.title);
for (const aggregate of podcastReviews.applePodcastsReviewsByCountry) {
console.log(`${aggregate.country}: ${aggregate.rating} (${aggregate.reviewsCount} ratings)`);
}
if (!podcastReviews.applePodcastsReviewText) {
console.info('No written review data available');
continue;
}
const { total, reviews } = podcastReviews.applePodcastsReviewText;
console.log(`${total} reviews total; showing ${reviews.length}`);
console.dir(reviews, { depth: null });
} import os
from podengine import PodEngine
pe = PodEngine(
api_key=os.environ["PODENGINE_API_KEY"],
base_url=os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai"),
)
data = pe.charts.get_latest_chart(
chart_type="apple", country="us", category="top podcasts", positions_limit=5
).model_dump(by_alias=True, mode="json")
chart = data["chart"]
if chart is None:
raise RuntimeError("No chart available for these options")
print("Chart positions:", len(chart["positions"]))
for position in chart["positions"]:
podcast = position["podenginePodcast"]
if podcast is None:
continue # Some chart entries have not been matched to Pod Engine.
data = pe.podcasts.get_podcast_reviews(
podcast_id_or_slug=podcast["id"], country="gb", limit=100
).model_dump(by_alias=True, mode="json")
reviews = data["podcastReviews"]
print(podcast["title"])
for aggregate in reviews["applePodcastsReviewsByCountry"]:
print(aggregate["country"], aggregate["rating"], aggregate["reviewsCount"])
text = reviews["applePodcastsReviewText"]
if text is None:
print("No written review data available")
continue
print("Total reviews:", text["total"], "Showing:", len(text["reviews"]))
for review in text["reviews"]:
print(review) import json
import os
from urllib.parse import urlencode, quote
from urllib.request import Request, urlopen
api_key = os.environ["PODENGINE_API_KEY"]
base_url = os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai")
def request(path, body=None):
req = Request(
base_url + path,
data=json.dumps(body).encode() if body is not None else None,
headers={"Authorization": api_key, "Content-Type": "application/json"},
method="POST" if body is not None else "GET",
)
# urlopen raises HTTPError for non-success responses.
with urlopen(req, timeout=30) as response:
return json.load(response)["data"]
data = request(
"/api/v1/charts/latest?chartType=apple&country=us&category=top+podcasts&positionsLimit=5"
)
chart = data["chart"]
if chart is None:
raise RuntimeError("No chart available for these options")
print("Chart positions:", len(chart["positions"]))
for position in chart["positions"]:
podcast = position["podenginePodcast"]
if podcast is None:
continue # Some chart entries have not been matched to Pod Engine.
data = request(
f"/api/v1/podcasts/{quote(podcast['id'], safe='')}/reviews?country=gb&limit=100"
)
reviews = data["podcastReviews"]
print(podcast["title"])
for aggregate in reviews["applePodcastsReviewsByCountry"]:
print(aggregate["country"], aggregate["rating"], aggregate["reviewsCount"])
text = reviews["applePodcastsReviewText"]
if text is None:
print("No written review data available")
continue
print("Total reviews:", text["total"], "Showing:", len(text["reviews"]))
for review in text["reviews"]:
print(review) Request the first five positions in the US Apple top podcasts chart. Each position contains chart information and a podenginePodcast when the entry has been matched to our database. Handle a missing chart before reading its positions.
const { chart } = await pe.charts.getLatestChart({
chartType: 'apple',
country: 'us',
category: 'top podcasts',
positionsLimit: 5,
});
if (!chart) throw new Error('No chart available for these options');
console.log(`Found ${chart.positions.length} chart positions`); const { chart } = await request(
'/api/v1/charts/latest?chartType=apple&country=us&category=top+podcasts&positionsLimit=5'
);
if (!chart) throw new Error('No chart available for these options');
console.log(`Found ${chart.positions.length} chart positions`); data = pe.charts.get_latest_chart(
chart_type="apple", country="us", category="top podcasts", positions_limit=5
).model_dump(by_alias=True, mode="json")
chart = data["chart"]
if chart is None:
raise RuntimeError("No chart available for these options")
print("Chart positions:", len(chart["positions"])) data = request(
"/api/v1/charts/latest?chartType=apple&country=us&category=top+podcasts&positionsLimit=5"
)
chart = data["chart"]
if chart is None:
raise RuntimeError("No chart available for these options")
print("Chart positions:", len(chart["positions"])) - Change country and category to select a different chart. Category names include "top podcasts", "technology", and "true crime".
Skip chart positions without a matched Pod Engine podcast, then fetch reviews for each remaining podcast. This example scopes reviews to the GB storefront and prints aggregate ratings plus up to 100 written reviews. Python SDK examples convert typed response models to dictionaries with API field names using model_dump(by_alias=True, mode="json").
for (const position of chart.positions) {
const podcast = position.podenginePodcast;
if (!podcast) continue; // Some chart entries have not been matched to Pod Engine.
const { podcastReviews } = await pe.podcasts.getPodcastReviews({
podcastIdOrSlug: podcast.id,
country: 'gb',
limit: 100,
});
console.log(podcast.title);
for (const aggregate of podcastReviews.applePodcastsReviewsByCountry) {
console.log(`${aggregate.country}: ${aggregate.rating} (${aggregate.reviewsCount} ratings)`);
}
if (!podcastReviews.applePodcastsReviewText) {
console.info('No written review data available');
continue;
}
const { total, reviews } = podcastReviews.applePodcastsReviewText;
console.log(`${total} reviews total; showing ${reviews.length}`);
console.dir(reviews, { depth: null });
} for (const position of chart.positions) {
const podcast = position.podenginePodcast;
if (!podcast) continue; // Some chart entries have not been matched to Pod Engine.
const { podcastReviews } = await request(
`/api/v1/podcasts/${encodeURIComponent(podcast.id)}/reviews?country=gb&limit=100`
);
console.log(podcast.title);
for (const aggregate of podcastReviews.applePodcastsReviewsByCountry) {
console.log(`${aggregate.country}: ${aggregate.rating} (${aggregate.reviewsCount} ratings)`);
}
if (!podcastReviews.applePodcastsReviewText) {
console.info('No written review data available');
continue;
}
const { total, reviews } = podcastReviews.applePodcastsReviewText;
console.log(`${total} reviews total; showing ${reviews.length}`);
console.dir(reviews, { depth: null });
} for position in chart["positions"]:
podcast = position["podenginePodcast"]
if podcast is None:
continue # Some chart entries have not been matched to Pod Engine.
data = pe.podcasts.get_podcast_reviews(
podcast_id_or_slug=podcast["id"], country="gb", limit=100
).model_dump(by_alias=True, mode="json")
reviews = data["podcastReviews"]
print(podcast["title"])
for aggregate in reviews["applePodcastsReviewsByCountry"]:
print(aggregate["country"], aggregate["rating"], aggregate["reviewsCount"])
text = reviews["applePodcastsReviewText"]
if text is None:
print("No written review data available")
continue
print("Total reviews:", text["total"], "Showing:", len(text["reviews"]))
for review in text["reviews"]:
print(review) for position in chart["positions"]:
podcast = position["podenginePodcast"]
if podcast is None:
continue # Some chart entries have not been matched to Pod Engine.
data = request(
f"/api/v1/podcasts/{quote(podcast['id'], safe='')}/reviews?country=gb&limit=100"
)
reviews = data["podcastReviews"]
print(podcast["title"])
for aggregate in reviews["applePodcastsReviewsByCountry"]:
print(aggregate["country"], aggregate["rating"], aggregate["reviewsCount"])
text = reviews["applePodcastsReviewText"]
if text is None:
print("No written review data available")
continue
print("Total reviews:", text["total"], "Showing:", len(text["reviews"]))
for review in text["reviews"]:
print(review) - Omit country to merge storefronts, or select us, gb, au, or ca.
- Written reviews are returned newest first. Use limit (1–100) and offset to paginate; applePodcastsReviewText.total is the full count.
- The script limits the chart to five positions to keep the number of review requests small.