# Radio Airplay Tracker - Was My Song Played? iHeart & BBC Spins (`that_red_bird/radio-airplay-tracker`) Actor

Find every new spin of your songs and artists on iHeart stations (optional BBC): station, market, time. Metadata-based, no audio fingerprinting; scheduled runs return only new spins. Unofficial APIs, see README risks. Failed stations are never charged.

- **URL**: https://apify.com/that_red_bird/radio-airplay-tracker.md
- **Developed by:** [mohamed alaya](https://apify.com/that_red_bird) (community)
- **Categories:** Other, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 spins

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Radio Airplay Tracker - Was My Song Played? iHeart & BBC Spins

Tells you when your songs and artists are played on **iHeart radio stations** (about 3,600 US stations listed, around 90% publish what they play) and, optionally, BBC Radio. Built for independent artists, labels and radio pluggers who want proof of airplay without paying a monitoring subscription. Schedule it and each run returns **only new spins**.

It reads the "now playing / recently played" metadata that stations publish. There is no audio fingerprinting, so it sees what the station's playout system reports, not what the transmitter actually broadcast.

### Input examples

Watch your artist name and one song on the 300 biggest stations, check every 3 hours (schedule it):

```json
{ "artists": ["Olivia Dean"], "songs": ["The Weeknd - Starboy"], "maxStations": 300, "historyPerStation": 40 }
```

Only hip hop stations in New York and California:

```json
{ "artists": ["My Artist Name"], "genres": ["Hip Hop"], "markets": ["New York", "CA"], "maxStations": 500 }
```

Whole iHeart network, once a day (raise the history so a busy station is covered):

```json
{ "songs": ["Artist - Title"], "maxStations": 4000, "historyPerStation": 300, "lookbackHours": 24 }
```

`{}` works: with nothing to watch it returns a free sample of the latest spins from the 60 biggest stations.

### Input guide

| Field | Meaning |
|---|---|
| `artists`, `songs` | What to watch. Songs may be `Artist - Title`. Matching ignores case, accents, punctuation and "(feat. ...)". |
| `stationIds`, `stationNames`, `markets`, `genres` | Which stations to scan. Several filters are combined (all must match). |
| `maxStations` | Cap on stations, biggest audience first. |
| `historyPerStation`, `lookbackHours` | How many recent plays per station to check, and how far back to report. |
| `newOnly`, `stateStoreName` | Report each spin once (remembered in a named key-value store). |
| `includeBbc`, `bbcServices` | Optional BBC Radio now-playing feed (off by default). |
| `maxSpins` | Safety cap per run. |

### Output fields

| Field | Meaning |
|---|---|
| `type` | `spin`, or `error` for a station that failed. |
| `matched`, `matchedType` | Which watch entry matched, and whether it was an artist or song entry. |
| `artist`, `title`, `album`, `durationSec` | The play as reported by the station. |
| `playedAt` | When the play started (UTC). For BBC this is empty; see `firstSeenAt`. |
| `stationId`, `stationName`, `callLetters`, `frequency`, `band` | The station. |
| `market`, `state`, `genre`, `stationListeners` | Market, state, format, and iHeart's audience figure for the station. |
| `source` | `iheart` or `bbc`. |

Sample row:

```json
{"type":"spin","source":"iheart","matched":"Olivia Dean","matchedType":"artist","artist":"Olivia Dean","title":"A Couple Minutes","playedAt":"2026-10-04T21:30:48.000Z","stationId":"5902","stationName":"106.1 BLI","callLetters":"WBLI-FM","market":"Long Island","state":"NY","genre":"Top 40 & Pop","stationListeners":768100}
```

The run summary (key-value store record `SUMMARY`) includes spin counts per watched entry and **weekly totals** that accumulate across runs.

### Use it with

- Apify schedule every 1-3 hours, then Slack / email / Google Sheets integration for new spins.
- A spreadsheet of "who played us" for pitching radio promoters or proving airplay to a label.

### FAQ

**How is this different from WARM or Radiomonitor?** Those fingerprint broadcasts at thousands of stations worldwide. This Actor only reads iHeart's published metadata (and optionally the BBC's), so coverage is far narrower, but it costs cents.

**Why does a scheduled run only return new spins?** The Actor keeps the spin ids it already reported in the named key-value store. Delete that store to start fresh.

**A station shows nothing. Why?** Some stations (talk, some markets) publish no track metadata; those are skipped and not charged.

### Honest limits and risks (please read)

- **iHeart's API is unofficial.** The endpoints are the public JSON the iHeart website and apps use. They are not a documented partner API, there is no SLA, and iHeart can change or block them at any time. iHeart's Terms of Use prohibit obtaining information "through any means not intentionally made available" through their site; the track-history feed is public and unauthenticated, and this Actor makes a small number of read requests, but you should decide for yourself whether that fits your use. Use it for your own airplay monitoring, not for republishing iHeart's data.
- **BBC is opt-in and riskier.** The BBC's robots.txt and terms say "no scraping, crawling or systematic extraction" and no dataset building. The BBC feed also has no exact timestamps and only the latest few tracks, so it needs a schedule of every 10-15 minutes. It is off by default.
- Metadata is a market-level log. It is not audio fingerprinting and not an official audit; it cannot prove a broadcast to a collection society.
- Only the last recent plays are available per station: schedule often enough that `historyPerStation` covers the gap.
- Coverage is iHeart (US, plus a few BBC services). Not Spotify, not other radio groups.

### Price

Pay per event: **`spin` $0.002** per new spin returned, **`station-scanned` $0.0003** per station read. Example: a scheduled run over 300 stations with 5 new spins = 300 x $0.0003 + 5 x $0.002 = **$0.10**. Demo spins (empty watch list), error rows and stations without metadata are free.

# Actor input Schema

## `artists` (type: `array`):

Artist names, one per line. A spin matches when the station's artist field contains the name as whole words (featured artists included). Leave artists and songs empty to see a free sample of the latest spins.

## `songs` (type: `array`):

Song titles, or 'Artist - Title' to be exact (e.g. 'The Weeknd - Starboy'). Matching ignores case, punctuation, accents and '(feat. ...)' parts.

## `stationIds` (type: `array`):

Specific iHeart station IDs to scan (the id returned in the output as stationId).

## `stationNames` (type: `array`):

Scan stations whose name, call letters or description contains any of these words (e.g. 'KIIS', 'Z100').

## `markets` (type: `array`):

Scan stations in these markets (city, market name or 2-letter state), e.g. 'New York', 'CA'. Combined with the other filters (all given filters must match).

## `genres` (type: `array`):

Scan stations whose primary genre contains any of these words, e.g. 'Hip Hop', 'Country', 'Top 40'.

## `maxStations` (type: `integer`):

Maximum number of iHeart stations to scan, biggest audiences first. With no station filter this picks the biggest stations. Use 1000+ to cover the full network.

## `historyPerStation` (type: `integer`):

How many of each station's most recent plays to check. A busy station plays roughly 12-15 songs an hour; make sure this covers the gap between your scheduled runs.

## `lookbackHours` (type: `integer`):

Ignore plays older than this. On the first run it also limits how far back spins are reported.

## `newOnly` (type: `boolean`):

Report each spin once. The Actor remembers what it already reported in a named key-value store, so scheduled runs return only new spins.

## `stateStoreName` (type: `string`):

Name of the key-value store that remembers reported spins and weekly totals. Use the same name on every scheduled run.

## `includeBbc` (type: `boolean`):

Also read the BBC's now-playing music feed for the services below. Off by default: the BBC's terms discourage automated collection and the feed has no exact timestamps. Turn on only if you accept that risk.

## `bbcServices` (type: `array`):

BBC service ids to read when BBC is on, e.g. bbc_radio_one, bbc_radio_two, bbc_6music, bbc_radio_three, bbc_asian_network.

## `maxSpins` (type: `integer`):

Safety cap on spins returned (and charged) in one run. Spins above the cap are reported on the next run.

## `concurrency` (type: `integer`):

How many stations are read at the same time. Keep it modest to be polite to the source.

## Actor input object example

```json
{
  "artists": [],
  "songs": [],
  "stationIds": [],
  "stationNames": [],
  "markets": [],
  "genres": [],
  "maxStations": 60,
  "historyPerStation": 40,
  "lookbackHours": 24,
  "newOnly": true,
  "stateStoreName": "radio-airplay-state",
  "includeBbc": false,
  "bbcServices": [
    "bbc_radio_one",
    "bbc_radio_two",
    "bbc_6music",
    "bbc_radio_three",
    "bbc_asian_network"
  ],
  "maxSpins": 2000,
  "concurrency": 6
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `downloadCsv` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `count` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "artists": [],
    "songs": [],
    "stationIds": [],
    "stationNames": [],
    "markets": [],
    "genres": [],
    "maxStations": 60,
    "historyPerStation": 40,
    "lookbackHours": 24,
    "newOnly": true,
    "stateStoreName": "radio-airplay-state",
    "includeBbc": false,
    "bbcServices": [
        "bbc_radio_one",
        "bbc_radio_two",
        "bbc_6music",
        "bbc_radio_three",
        "bbc_asian_network"
    ],
    "maxSpins": 2000,
    "concurrency": 6
};

// Run the Actor and wait for it to finish
const run = await client.actor("that_red_bird/radio-airplay-tracker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "artists": [],
    "songs": [],
    "stationIds": [],
    "stationNames": [],
    "markets": [],
    "genres": [],
    "maxStations": 60,
    "historyPerStation": 40,
    "lookbackHours": 24,
    "newOnly": True,
    "stateStoreName": "radio-airplay-state",
    "includeBbc": False,
    "bbcServices": [
        "bbc_radio_one",
        "bbc_radio_two",
        "bbc_6music",
        "bbc_radio_three",
        "bbc_asian_network",
    ],
    "maxSpins": 2000,
    "concurrency": 6,
}

# Run the Actor and wait for it to finish
run = client.actor("that_red_bird/radio-airplay-tracker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "artists": [],
  "songs": [],
  "stationIds": [],
  "stationNames": [],
  "markets": [],
  "genres": [],
  "maxStations": 60,
  "historyPerStation": 40,
  "lookbackHours": 24,
  "newOnly": true,
  "stateStoreName": "radio-airplay-state",
  "includeBbc": false,
  "bbcServices": [
    "bbc_radio_one",
    "bbc_radio_two",
    "bbc_6music",
    "bbc_radio_three",
    "bbc_asian_network"
  ],
  "maxSpins": 2000,
  "concurrency": 6
}' |
apify call that_red_bird/radio-airplay-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,that_red_bird/radio-airplay-tracker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/bgqjjn0Lr2kJEfOaz/builds/i6mtI8a5mtvQJPXMg/openapi.json
