TuneIn Scraper - Radio Stations, Podcasts & Stream URLs avatar

TuneIn Scraper - Radio Stations, Podcasts & Stream URLs

Pricing

from $1.20 / 1,000 directory results

Go to Apify Store
TuneIn Scraper - Radio Stations, Podcasts & Stream URLs

TuneIn Scraper - Radio Stations, Podcasts & Stream URLs

Scrape TuneIn by keyword, category, genre, location or URL. Extract stations and podcasts with artwork, genre, bitrate, frequency, location, language, contact details, playable stream URLs and episodes. Incremental mode tracks changes over time.

Pricing

from $1.20 / 1,000 directory results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

TuneIn Radio and Podcast Directory Scraper: Stations, Podcasts & Episodes

TuneIn Radio and Podcast Directory Scraper turns the TuneIn directory into a structured dataset: live radio stations, podcasts and podcast episodes, with artwork, genre, stream quality, dial position, location, language, the broadcaster's own website and published contact details, playable stream addresses and full episode lists. Search by keyword, browse the directory's own categories, genres and locations, or paste station, podcast and category links, then export to JSON, CSV or Excel, or read the results straight through the API.

Why This Scraper?

  • Three ways to find data. Search by keyword, browse the directory's own categories, genres and locations, or paste station, podcast and category links. No need to build addresses by hand.
  • One flat record for every kind. Stations, podcasts and episodes share the same shape, with a field a kind does not have present and null rather than missing, so the dataset opens cleanly as a table.
  • Enrichment you switch on. The full published profile, playable stream addresses and episode lists are each a separate toggle, billed only when you turn them on.
  • Worldwide coverage. Every genre, category and region the directory publishes, plus a locale-aware Local radio chart for one place's popular stations.
  • Built for schedules. Incremental mode classifies each result NEW, UPDATED, REAPPEARED, UNCHANGED or EXPIRED, and ignores what is playing right now so a live station is not re-billed every run.
  • Resume without re-paying. Continue one interrupted run and the actor skips everything it already collected there.
  • Fails loudly, not silently. A run whose requests were refused stops with a clear message instead of reporting an empty dataset.

Use Cases

  • Radio directory and aggregator apps: pull live stations with stream URLs, genre, dial position and location to power your own listings.
  • Podcast discovery tools: track podcast catalogs and their episode lists inside one topic, category or search term.
  • Local media research: chart the stations available near a place with Local radio and Locale coordinates.
  • Directory monitoring: schedule incremental runs to catch new stations and podcasts, or ones going off air, without re-paying for what has not changed.

Data You Get

Sample shape: values are illustrative placeholders, not from a live record.

FieldExample
itemType"station" (also "podcast", "episode")
guideId"s900001"
name / subtitle"Riverside Jazz Radio" / "Smooth jazz for your afternoon"
genreId / genreName"g11" / "Jazz"
imagestation, podcast or episode artwork
urlthe public TuneIn page for the result
websiteUrlthe broadcaster's own site (profile lookup)
callSign / frequency / band"WRVJ-HD3" / "87.9" / "FM" (stations, profile lookup)
location / language"Millbrook, US" / "English"
bitrateKbps / streamFormats128 / ["mp3"] (stations)
reliabilityPercentuptime score; excluded from incremental change detection
isAvailableon-air flag (profile lookup)
nowPlaying / nowPlayingImage / currentSong / currentArtist / currentAlbumwhat is on air right now; never counted as a change
email / phone / mailingAddressthe broadcaster's own published contact details (profile lookup)
streams / streamCountplayable stream addresses (stream lookup, stations only)
episodes / episodeCountthe published episode list (episode lookup, podcasts only)
durationSeconds / publishedLabelan episode's running time and publish label
hostsa podcast's listed hosts (profile lookup)
parentShowIdthe podcast an episode belongs to
sourceMode / sourceScopehow this run reached the result, for example "search" / "jazz"
scrapedAtthe ISO timestamp of the run, the same on every row of one run
changeType / changedFieldsincremental mode only, for example "UPDATED" / ["isAvailable"], alongside firstSeenAt / lastSeenAt

How to Use

  1. Pick a mode: search (by keyword), browse (the directory's own categories, genres and locations) or url (paste links or directory ids).
  2. Fill in the fields for that mode, and switch on the extra lookups you need: full profiles, playable streams, episode lists.
  3. Set the filters and Max results that fit your run, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword:

{ "mode": "search", "queries": ["jazz", "BBC Radio 4"], "maxItems": 50 }

Browse the popular stations near a city:

{
"mode": "browse",
"browseCategories": ["local"],
"localeLatLon": "40.7128,-74.0060",
"minReliabilityPercent": 90,
"maxItems": 100
}

Browse podcasts, with their episode lists:

{
"mode": "browse",
"browseCategories": ["podcast"],
"fetchEpisodes": true,
"maxEpisodesPerPodcast": 10,
"maxItems": 50
}

Paste a station link and a bare directory id:

{
"mode": "url",
"urls": [
"https://tunein.com/station/?stationId=34682",
"s24777"
]
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/tunein-radio-podcast-scraper").call(run_input={"mode": "search", "queries": ["jazz"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["name"], item["itemType"], item.get("url"))

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/tunein-radio-podcast-scraper').call({ mode: 'search', queries: ['jazz'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Limits and scope

  • Max results (maxItems, default 20) is the run's cap, and the only setting that limits how much it returns or costs. Use 0 for unlimited. Picking several keywords or categories shares this cap fairly across them: a scope that comes back short hands its unused share to the ones after it.
  • Max result pages (maxPages, default 0, no limit) bounds how many listing pages are read per browsed category or pasted category link. Does not apply to keyword search, which the directory answers in a single page.
  • Sub-category depth (maxBrowseDepth, default 2) bounds how many levels of sub-categories a browse walk may open. A scope choice, not a cost cap.
  • Max episodes per podcast (maxEpisodesPerPodcast, default 20) caps how many episodes are returned inside each podcast record, newest first. Use 0 for every published episode.

Resume and recurring updates

Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset id and results already collected there are skipped, not re-returned and not re-billed. Incremental mode (incrementalMode) is for scheduling the same scope again and again, for example daily. Each result is then classified NEW, UPDATED (with changedFields), REAPPEARED, UNCHANGED (suppressed and not billed unless emitUnchanged is on) or EXPIRED (only after a run that scanned the whole scope, and only with emitExpired; these rows are added on top of Max results). What is playing right now and the rolling reliability score never count as a change on their own; a station going off air is reported through isAvailable instead. stateKey names or shares the stored monitoring state. With incremental mode off, output is exactly as before.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch (by keyword), browse (the directory's own categories, genres and locations) or url (pasted links or ids).
queriesarray["jazz"]Keywords to look up. Search mode only.
browseCategoriesarray["local"]Directory verticals to browse: local, music, talk, sports, podcast, language or location. Browse mode only.
browseIdsarray(none)Specific genre, category or region ids, for example g11, c57943 or r101216. Browse mode only.
localeLatLonstring(none)Latitude and longitude for the Local radio category only, for example 51.5074,-0.1278. Leave empty to use the connection's own location. Browse mode only.
urlsarraysample linksTuneIn links, tun.in share links, or bare directory ids such as s34682. URL mode only.
itemTypesarray(all)Keep only station, podcast and/or episode results.
minBitrateKbpsinteger0Keep stations streaming at or above this bitrate. Stations only.
minReliabilityPercentinteger0Keep stations whose uptime score is at or above this percentage. Stations only.
streamFormatsarray(any)Keep stations offering one of these codecs, for example mp3. Stations only.
genreIdsarray(any)Keep results filed under one of these genre ids, for example g11.
fetchDetailsbooleanfalseRead each result's full published profile. One extra request per result; always on, and not charged, in url mode.
fetchStreamsbooleanfalseAdd each station's playable stream addresses. One extra request per station.
fetchEpisodesbooleanfalseAdd each podcast's published episode list. One extra request per podcast.
maxEpisodesPerPodcastinteger20Cap on episodes returned inside each podcast record, newest first. 0 for every episode.
maxItemsinteger20Maximum results returned across the whole run. 0 for unlimited.
maxPagesinteger0Maximum listing pages read per browsed or pasted category. 0 means no limit. Does not apply to keyword search.
maxBrowseDepthinteger2How many levels of sub-categories a browse walk may open.
resumeFromRunIdstring(none)Continue one interrupted run without re-collecting or re-billing its results.
incrementalModebooleanfalseReturn only new and changed results on scheduled runs over the same scope.
stateKeystring(none)Name or share an incremental-mode monitoring campaign. Auto-derived from the scope when left empty.
emitUnchangedbooleanfalseAlso return (and bill) results that have not changed since the last run.
emitExpiredbooleanfalseAlso return (and bill) results no longer found, after a complete scan of the tracked scope.
proxyobjectApify ProxyConnection settings.

Output Example

Sample shape: values are illustrative placeholders, not from a live record.

{
"itemType": "station",
"guideId": "s900001",
"name": "Riverside Jazz Radio",
"subtitle": "Smooth jazz for your afternoon",
"description": "An illustrative record. Values below are made up, not scraped.",
"slogan": "Jazz, all day, every day",
"image": "https://cdn-profiles.tunein.com/s900001/images/logoq.png",
"url": "https://tunein.com/station/?stationId=900001",
"websiteUrl": "https://www.riverside-jazz-example.org/",
"callSign": "WRVJ-HD3",
"frequency": "87.9",
"band": "FM",
"genreId": "g11",
"genreName": "Jazz",
"location": "Millbrook, US",
"latitude": null,
"longitude": null,
"regionId": "r200099",
"timezone": "Eastern",
"timezoneOffsetMinutes": -300,
"language": "English",
"bitrateKbps": 128,
"reliabilityPercent": 98,
"streamFormats": ["mp3"],
"isAvailable": true,
"isMusic": true,
"contentClassification": "music",
"nowPlaying": "Sample Track Title",
"currentArtist": "Sample Artist Name",
"email": "studio@example-radio.org",
"phone": "+1-202-555-0100",
"mailingAddress": "12 Sample Avenue, Millbrook",
"twitterId": "RiversideJazzFM",
"streams": [
{ "url": "https://stream.riverside-jazz-example.org/live_128k", "bitrateKbps": 128,
"mediaType": "mp3", "reliabilityPercent": 98, "isDirect": true, "position": 0 }
],
"streamCount": 1,
"episodes": [],
"episodeCount": 0,
"sourceMode": "search",
"sourceScope": "jazz",
"scrapedAt": "2026-01-01T00:00:00Z"
}

A podcast record carries episodes[], episodeCount and hosts, with the bitrate and stream fields left empty. An episode record carries durationSeconds, publishedLabel and parentShowId instead.

Broadcaster contact details

email, phone and mailingAddress are the broadcaster's own published business contact, exactly as TuneIn publishes it on the station's public profile page. They are returned only when fetchDetails is on (or always, in url mode), and they are never written to the run log.

Plan Requirement

The default connection, included on every Apify plan, is enough for this site and is the default. A residential proxy is available if you specifically want one, but the actor never selects a paid option on its own.

FAQ

How much does it cost?

You pay per result returned, with the full profile, stream addresses and episode lists billed only when you switch them on. The Pricing tab shows the current rates. Use Max results to cap the cost of any run.

This actor collects only publicly available directory data. You are responsible for how you use it: follow TuneIn's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

Can I get only new or changed stations and podcasts on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared results, and unchanged ones are not billed unless you ask for them.

What counts as a change in incremental mode?

Almost everything, except what is playing right now and the rolling reliability score, which drift on their own and are excluded so a live station is not re-billed every run. A station going off air is still reported, through isAvailable flipping to false.

Why did my run fail instead of returning an empty dataset?

If the directory refuses every request, the run stops with a clear message so "nothing matched" is never confused with "nothing could be read". Run it again in a few minutes.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, schedule it from the Schedules tab, or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

๐Ÿ”— More scrapers you might like

Pair this actor with these related scrapers from the same team:

๐Ÿงฉ Anghami Scraper
Scrape Anghami's public catalog by keyword or player URL. Rows carry their kind (song...
๐Ÿงฉ Pocket Casts Podcast Scraper
Scrape Pocket Casts podcast data, including listener ratings, full episode archives...
๐Ÿงฉ WEBTOON Scraper
Scrape the WEBTOON catalog into structured data. Extract series, genres, rankings and...
๐Ÿงฉ GoodShort Scraper
Scrape the GoodShort short-drama catalog: titles with exact views, follows, genres and...
๐Ÿงฉ GasBuddy Scraper
Scrape GasBuddy station by station across the US and Canada. Returns brand, address...
๐Ÿงฉ PineDrama Scraper
Scrape the PineDrama short-drama catalogue: series with watch counts, episode counts...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.