Get podcast listing links from a feed URL
For developers building podcast imports: get Apple Podcasts, Spotify and YouTube links from an RSS feed URL.
Before you start
- API token with access to podcast details
- An RSS feed URL
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, PodEngineApiError } 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 rssFeedUrl = process.env.PODENGINE_RSS_FEED_URL || 'https://anchor.fm/s/7c624c84/podcast/rss'; 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';
const rssFeedUrl = process.env.PODENGINE_RSS_FEED_URL || 'https://anchor.fm/s/7c624c84/podcast/rss';
const query = new URLSearchParams({ rssFeedUrl });
async function request(path) {
const response = await fetch(`${baseUrl}${path}?${query}`, {
headers: { Authorization: apiKey },
});
if (!response.ok) {
throw Object.assign(new Error(`Pod Engine request failed: ${response.status} ${response.statusText}`), {
status: response.status,
});
}
return (await response.json()).data;
} import os
import sys
from podengine import PodEngine, PodEngineAPIError
pe = PodEngine(
api_key=os.environ["PODENGINE_API_KEY"],
base_url=os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai"),
)
rss_feed_url = os.environ.get("PODENGINE_RSS_FEED_URL", "https://anchor.fm/s/7c624c84/podcast/rss") import json
import os
import sys
from urllib.error import HTTPError
from urllib.parse import urlencode
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")
rss_feed_url = os.environ.get("PODENGINE_RSS_FEED_URL", "https://anchor.fm/s/7c624c84/podcast/rss")
query = urlencode({"rssFeedUrl": rss_feed_url})
def request(path):
req = Request(f"{base_url}{path}?{query}", headers={"Authorization": api_key})
# urlopen raises HTTPError for non-success responses.
with urlopen(req, timeout=30) as response:
return json.load(response)["data"] Complete runnable script
import { PodEngine, PodEngineApiError } 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 rssFeedUrl = process.env.PODENGINE_RSS_FEED_URL || 'https://anchor.fm/s/7c624c84/podcast/rss';
// Optional: remove this block to go straight to the details call.
const { podcast } = await pe.podcasts.getPodcastIdLookup({ rssFeedUrl });
if (!podcast) {
console.log('Pod Engine does not track this feed');
process.exit(0);
}
let platforms;
try {
// The SDK URL-encodes rssFeedUrl and unwraps the API's data envelope.
({ platforms } = await pe.podcasts.getPodcastByFeedUrl({ rssFeedUrl }));
} catch (error) {
if (!(error instanceof PodEngineApiError) || error.status !== 404) throw error;
console.log('Pod Engine does not track this feed');
process.exit(0);
}
if (!platforms) throw new Error('No platform data returned; check the API version');
console.log('Apple Podcasts (certain):', platforms.apple.url);
if (platforms.spotify) {
console.log('Spotify (inferred):', platforms.spotify.url);
} else {
console.log('Spotify: no match yet; the show may still be on Spotify');
}
if (platforms.youtube) {
const confidence = platforms.youtube.tier === 'associated' ? 'passed checks' : 'confirm with the publisher';
console.log(`YouTube (${platforms.youtube.tier}; ${confidence}):`, platforms.youtube.url);
} else {
console.log('YouTube: no channel match yet');
} 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';
const rssFeedUrl = process.env.PODENGINE_RSS_FEED_URL || 'https://anchor.fm/s/7c624c84/podcast/rss';
const query = new URLSearchParams({ rssFeedUrl });
async function request(path) {
const response = await fetch(`${baseUrl}${path}?${query}`, {
headers: { Authorization: apiKey },
});
if (!response.ok) {
throw Object.assign(new Error(`Pod Engine request failed: ${response.status} ${response.statusText}`), {
status: response.status,
});
}
return (await response.json()).data;
}
// Optional: remove this block to go straight to the details call.
const { podcast } = await request('/api/v1/podcasts/id/lookup');
if (!podcast) {
console.log('Pod Engine does not track this feed');
process.exit(0);
}
let platforms;
try {
({ platforms } = await request('/api/v1/podcasts/by-feed-url'));
} catch (error) {
if (error.status !== 404) throw error;
console.log('Pod Engine does not track this feed');
process.exit(0);
}
if (!platforms) throw new Error('No platform data returned; check the API version');
console.log('Apple Podcasts (certain):', platforms.apple.url);
if (platforms.spotify) {
console.log('Spotify (inferred):', platforms.spotify.url);
} else {
console.log('Spotify: no match yet; the show may still be on Spotify');
}
if (platforms.youtube) {
const confidence = platforms.youtube.tier === 'associated' ? 'passed checks' : 'confirm with the publisher';
console.log(`YouTube (${platforms.youtube.tier}; ${confidence}):`, platforms.youtube.url);
} else {
console.log('YouTube: no channel match yet');
} import os
import sys
from podengine import PodEngine, PodEngineAPIError
pe = PodEngine(
api_key=os.environ["PODENGINE_API_KEY"],
base_url=os.environ.get("PODENGINE_API_URL", "https://api.podengine.ai"),
)
rss_feed_url = os.environ.get("PODENGINE_RSS_FEED_URL", "https://anchor.fm/s/7c624c84/podcast/rss")
# Optional: remove this block to go straight to the details call.
lookup = pe.podcasts.get_podcast_id_lookup(rss_feed_url=rss_feed_url)
if lookup.podcast is None:
print("Pod Engine does not track this feed")
sys.exit(0)
try:
# The SDK URL-encodes rss_feed_url and unwraps the API's data envelope.
data = pe.podcasts.get_podcast_by_feed_url(rss_feed_url=rss_feed_url)
except PodEngineAPIError as error:
if error.status != 404:
raise
print("Pod Engine does not track this feed")
sys.exit(0)
# Convert only the listing fields to dictionaries with API field names.
platforms = None if data.platforms is None else {
"apple": data.platforms.apple.model_dump(by_alias=True, mode="json"),
"spotify": None if data.platforms.spotify is None else data.platforms.spotify.model_dump(by_alias=True, mode="json"),
"youtube": None if data.platforms.youtube is None else data.platforms.youtube.model_dump(by_alias=True, mode="json"),
}
if platforms is None:
raise RuntimeError("No platform data returned; check the API version")
print("Apple Podcasts (certain):", platforms["apple"]["url"])
if platforms["spotify"] is not None:
print("Spotify (inferred):", platforms["spotify"]["url"])
else:
print("Spotify: no match yet; the show may still be on Spotify")
if platforms["youtube"] is not None:
youtube = platforms["youtube"]
confidence = "passed checks" if youtube["tier"] == "associated" else "confirm with the publisher"
print(f"YouTube ({youtube['tier']}; {confidence}):", youtube["url"])
else:
print("YouTube: no channel match yet") import json
import os
import sys
from urllib.error import HTTPError
from urllib.parse import urlencode
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")
rss_feed_url = os.environ.get("PODENGINE_RSS_FEED_URL", "https://anchor.fm/s/7c624c84/podcast/rss")
query = urlencode({"rssFeedUrl": rss_feed_url})
def request(path):
req = Request(f"{base_url}{path}?{query}", headers={"Authorization": api_key})
# urlopen raises HTTPError for non-success responses.
with urlopen(req, timeout=30) as response:
return json.load(response)["data"]
# Optional: remove this block to go straight to the details call.
data = request("/api/v1/podcasts/id/lookup")
if data["podcast"] is None:
print("Pod Engine does not track this feed")
sys.exit(0)
try:
platforms = request("/api/v1/podcasts/by-feed-url")["platforms"]
except HTTPError as error:
if error.code != 404:
raise
print("Pod Engine does not track this feed")
sys.exit(0)
if platforms is None:
raise RuntimeError("No platform data returned; check the API version")
print("Apple Podcasts (certain):", platforms["apple"]["url"])
if platforms["spotify"] is not None:
print("Spotify (inferred):", platforms["spotify"]["url"])
else:
print("Spotify: no match yet; the show may still be on Spotify")
if platforms["youtube"] is not None:
youtube = platforms["youtube"]
confidence = "passed checks" if youtube["tier"] == "associated" else "confirm with the publisher"
print(f"YouTube ({youtube['tier']}; {confidence}):", youtube["url"])
else:
print("YouTube: no channel match yet") Start with the feed URL you already have. These examples use This Week in Startups: https://anchor.fm/s/7c624c84/podcast/rss. Set PODENGINE_RSS_FEED_URL to try another feed. Call GET /api/v1/podcasts/id/lookup with rssFeedUrl to check whether Pod Engine tracks it. This call is free: it does not use podcastLookups. A successful response can contain data.podcast: null; stop if it does.
// Optional: remove this block to go straight to the details call.
const { podcast } = await pe.podcasts.getPodcastIdLookup({ rssFeedUrl });
if (!podcast) {
console.log('Pod Engine does not track this feed');
process.exit(0);
} // Optional: remove this block to go straight to the details call.
const { podcast } = await request('/api/v1/podcasts/id/lookup');
if (!podcast) {
console.log('Pod Engine does not track this feed');
process.exit(0);
} # Optional: remove this block to go straight to the details call.
lookup = pe.podcasts.get_podcast_id_lookup(rss_feed_url=rss_feed_url)
if lookup.podcast is None:
print("Pod Engine does not track this feed")
sys.exit(0) # Optional: remove this block to go straight to the details call.
data = request("/api/v1/podcasts/id/lookup")
if data["podcast"] is None:
print("Pod Engine does not track this feed")
sys.exit(0) - Remove this optional block to get the listing links in one request. An unknown feed returns 404 in the next step.
Call GET /api/v1/podcasts/by-feed-url with the URL-encoded rssFeedUrl query parameter. Read data.platforms in the HTTP response; SDKs unwrap data for you. Handle 404 when we do not track the feed. Let other failures surface so an authentication or service error is not mistaken for an unknown podcast. See the Podcast Details by Feed URL reference.
let platforms;
try {
// The SDK URL-encodes rssFeedUrl and unwraps the API's data envelope.
({ platforms } = await pe.podcasts.getPodcastByFeedUrl({ rssFeedUrl }));
} catch (error) {
if (!(error instanceof PodEngineApiError) || error.status !== 404) throw error;
console.log('Pod Engine does not track this feed');
process.exit(0);
} let platforms;
try {
({ platforms } = await request('/api/v1/podcasts/by-feed-url'));
} catch (error) {
if (error.status !== 404) throw error;
console.log('Pod Engine does not track this feed');
process.exit(0);
} try:
# The SDK URL-encodes rss_feed_url and unwraps the API's data envelope.
data = pe.podcasts.get_podcast_by_feed_url(rss_feed_url=rss_feed_url)
except PodEngineAPIError as error:
if error.status != 404:
raise
print("Pod Engine does not track this feed")
sys.exit(0)
# Convert only the listing fields to dictionaries with API field names.
platforms = None if data.platforms is None else {
"apple": data.platforms.apple.model_dump(by_alias=True, mode="json"),
"spotify": None if data.platforms.spotify is None else data.platforms.spotify.model_dump(by_alias=True, mode="json"),
"youtube": None if data.platforms.youtube is None else data.platforms.youtube.model_dump(by_alias=True, mode="json"),
} try:
platforms = request("/api/v1/podcasts/by-feed-url")["platforms"]
except HTTPError as error:
if error.code != 404:
raise
print("Pod Engine does not track this feed")
sys.exit(0) - The lookup ignores http versus https, host capitalization and a trailing slash. It also matches the original feed URL we tracked when a show has moved hosts.
Apple Podcasts is certain. Spotify is an inferred match; a null value means we have not matched the show, not that it is absent from Spotify. For YouTube, associated means the channel passed our association checks. A candidate is our best match that has not passed those checks: confirm it with the publisher before using it automatically. A null YouTube value means we have no channel match.
if (!platforms) throw new Error('No platform data returned; check the API version');
console.log('Apple Podcasts (certain):', platforms.apple.url);
if (platforms.spotify) {
console.log('Spotify (inferred):', platforms.spotify.url);
} else {
console.log('Spotify: no match yet; the show may still be on Spotify');
}
if (platforms.youtube) {
const confidence = platforms.youtube.tier === 'associated' ? 'passed checks' : 'confirm with the publisher';
console.log(`YouTube (${platforms.youtube.tier}; ${confidence}):`, platforms.youtube.url);
} else {
console.log('YouTube: no channel match yet');
} if (!platforms) throw new Error('No platform data returned; check the API version');
console.log('Apple Podcasts (certain):', platforms.apple.url);
if (platforms.spotify) {
console.log('Spotify (inferred):', platforms.spotify.url);
} else {
console.log('Spotify: no match yet; the show may still be on Spotify');
}
if (platforms.youtube) {
const confidence = platforms.youtube.tier === 'associated' ? 'passed checks' : 'confirm with the publisher';
console.log(`YouTube (${platforms.youtube.tier}; ${confidence}):`, platforms.youtube.url);
} else {
console.log('YouTube: no channel match yet');
} if platforms is None:
raise RuntimeError("No platform data returned; check the API version")
print("Apple Podcasts (certain):", platforms["apple"]["url"])
if platforms["spotify"] is not None:
print("Spotify (inferred):", platforms["spotify"]["url"])
else:
print("Spotify: no match yet; the show may still be on Spotify")
if platforms["youtube"] is not None:
youtube = platforms["youtube"]
confidence = "passed checks" if youtube["tier"] == "associated" else "confirm with the publisher"
print(f"YouTube ({youtube['tier']}; {confidence}):", youtube["url"])
else:
print("YouTube: no channel match yet") if platforms is None:
raise RuntimeError("No platform data returned; check the API version")
print("Apple Podcasts (certain):", platforms["apple"]["url"])
if platforms["spotify"] is not None:
print("Spotify (inferred):", platforms["spotify"]["url"])
else:
print("Spotify: no match yet; the show may still be on Spotify")
if platforms["youtube"] is not None:
youtube = platforms["youtube"]
confidence = "passed checks" if youtube["tier"] == "associated" else "confirm with the publisher"
print(f"YouTube ({youtube['tier']}; {confidence}):", youtube["url"])
else:
print("YouTube: no channel match yet")