# Spotify Playlist Tracker - Followers, Tracks & Play Counts (`neverempty/spotify-playlist-tracker`) Actor

Follower count and track list for public Spotify playlists: the exact follower integer, total track count, and the tracks in the public page with their play counts. Turn monitoring on and later runs return a playlist only when followers or tracks changed.

- **URL**: https://apify.com/neverempty/spotify-playlist-tracker.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.80 / 1,000 playlist returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Spotify Playlist Tracker

Follower count and track list for public Spotify playlists: the **exact follower integer in the public playlist page** (33,757,306 - not "33.7M"), the playlist's total track count, and the tracks that are in the public page with position, artists and play count. With monitoring on, **a playlist comes back only when its follower count or its tracks changed**, with the previous figures, the difference, and the tracks that entered and left.

Paste playlists as page URLs, `spotify:playlist:` URIs or 22-character playlist IDs, mixed in one list.

**This Actor reads playlists only.** It does not look up the play count of an arbitrary track: the public track page does not carry that track's own play count. Play counts are reported for the tracks that are listed in a playlist page.

### What you get

One row per playlist:

| Column | Meaning |
|---|---|
| `playlistName`, `playlistId`, `playlistUrl` | The name Spotify returned for that ID, the ID and the page that was read |
| `followers` | Followers (saves) of the playlist, as an integer |
| `totalTracks` | How many tracks the playlist has, according to the page data |
| `tracksOnPage` | How many of those tracks were in the public page and are listed in `tracks` |
| `tracksCoverage` | The same two numbers as a sentence, e.g. `30 of 50 playlist tracks were in the public page and are listed in "tracks"` |
| `tracks` | The tracks in the page, in page order: `position`, `trackName`, `trackId`, `trackUrl`, `artistNames`, `albumName`, `playCount`, `playCountChange`, `durationMs` |
| `ownerName`, `ownerUsername`, `description`, `imageUrl` | The playlist owner, its description and cover image |
| `pageCountry` | The country Spotify served the page for |
| `input`, `scrapedAt`, `status` | What you typed, when it was read, `ok` for a playlist row |

Monitoring columns (filled only in monitoring mode): `change` (`first-check` or `changed`), `isFirstCheck`, `changedFields`, `previousFollowers`, `followersChange`, `previousTotalTracks`, `tracksEntered`, `tracksLeft`, `previousCheckedAt`, and `playCountChange` inside each track.

A value that is not in the page is `null`, never 0.

Measured on 2026-10-04 by an Apify run in the US:

| Playlist | followers | totalTracks | tracksOnPage |
|---|---|---|---|
| Today's Top Hits | 33757304 | 50 | 30 |
| RapCaviar | 15537525 | 50 | 30 |
| Rock Classics | 13639206 | 200 | 30 |

A home connection in Japan read the same three pages within the same minute: all 90 tracks and play counts were identical in the same positions, and the follower figures were 33757306, 15537526 and 13639206 - two of the three differed by 1 or 2 from the US read.

### Only the tracks in the public page

The public playlist page carries the first part of the playlist, not always all of it: on 2026-10-04 both 50-track playlists above had 30 tracks in the page. This Actor reads that one page and does not page through the rest, so `tracks` is **not** the full track list of a longer playlist. Every row says how many tracks were read out of how many (`tracksOnPage`, `totalTracks`, `tracksCoverage`).

### Why every row names the playlist it read

The figures are taken only from the block of page data whose key is the playlist ID you asked for, and each play count is taken from the same block as that track's own ID. If the page does not contain the playlist ID, no number is taken from it and you get an `unreadable` row instead. `playlistName` is what Spotify returned for the ID, and `input` is what you typed, so a mix-up is visible in the row itself.

### Input

- **`playlists`** - `https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M` (with or without `intl-xx/`, `?si=...` or the scheme), `spotify:playlist:37i9dQZF1DXcBWIGoYBM5M`, or `37i9dQZF1DXcBWIGoYBM5M`. Get the link from **Share > Copy link to playlist** in Spotify. Playlist names are not searched. Track, album, artist and embed links are not read and come back as an `invalid-input` row with the reason (for artists, see [Spotify Monthly Listeners & Followers Tracker](https://apify.com/neverempty/spotify-monthly-listeners-tracker)).
- **`maxPlaylists`** - with monitoring off, stop after this many playlist rows.
- **`monitoringMode`**, **`monitorMode`**, **`resetMonitoringState`** - see below.
- **`useProxy`** - requests go directly to Spotify; a residential proxy is tried once per refused page, at most three times in a run, only when Spotify answers HTTP 403 or 429; after that the run stops asking and lists what it did not read.

### Monitoring mode

Turn **monitoringMode** on and schedule the Actor. It remembers each playlist's follower count, total track count and the tracks found in the page and, on later runs:

- a playlist that has not changed is **not returned as a row**; it still costs one check (see "What is charged"), and if no playlist changed you get one free `no-change` row saying how many were compared;
- a playlist that changed comes back with `previousFollowers`, `followersChange`, `tracksEntered`, `tracksLeft`, `changedFields`, and a `playCountChange` for every track that was also in the page last time;
- a playlist it has never seen is returned once (`first-check`).

**Follower changes of 1 or 2 are not counted as a change.** The public follower figure varies by 1 or 2 between reads and connections, so a playlist is returned for followers only when the figure differs by more than 2 from the last figure you were sent. The remembered figure is not moved by such a run, so small differences add up and the playlist comes back once the total exceeds 2.

**monitorMode** chooses what counts as a change: `any-change` (followers or tracks), `followers-change`, or `tracks-change` (a track entered or left the tracks in the page, or the total track count differs). A change in play counts alone does not trigger a return.

`tracksEntered` and `tracksLeft` compare **the tracks in the page** between two reads. For a playlist longer than the page, a track that moved below the part in the page is listed in `tracksLeft` although it is still in the playlist, and a track that moved up into it is listed in `tracksEntered`. If the number of tracks in the page differs from last time while the total track count is the same, the two reads are not compared track by track: `tracksEntered` and `tracksLeft` stay empty for that run and it does not count as a track change.

If a figure was missing from the page on the read that was remembered and is present now, the playlist is returned once with that field in `changedFields` and a `null` difference.

Playlists are remembered **one by one, not per list**, in a key-value store named `spotify-playlist-monitoring` in your own account, so adding a playlist to the list does not return the others again. The remembered state of a playlist is updated only when a row for it was delivered, so each difference is measured against the last row you received. **resetMonitoringState** clears everything remembered.

Do not put the same playlist in two schedules that run at the same moment: Apify's key-value store has no atomic update, so two runs finishing together can overwrite each other's record and a playlist may be returned once more as `first-check`.

How often Spotify refreshes follower and play counts has not been measured by this Actor. On 2026-10-04, three reads of the same page 8 seconds apart returned identical figures, and four monitoring runs of three playlists within about two minutes returned no change after the first. Follower figures read from two countries in the same minute differed by 1 or 2 (see above), which is why a follower difference of 1 or 2 does not count as a change. If you schedule it more often than Spotify updates the page, the extra runs return `no-change`.

### What is charged

- **playlist-returned** - one per playlist row with `status: ok`.
- **playlist-checked** - monitoring mode only: one per playlist that was read and compared, whether or not it changed. Checks are charged together after the last playlist was read, so a run that is aborted or times out while reading charges nothing. A playlist that changed is charged one check and one row.
- Not charged: `no-such-playlist` (Spotify answers 404 for the ID), `invalid-input`, `duplicate-input`, `unreadable`, `blocked`, `no-change`, `not-read` and `budget-reached` rows. There is no start fee and no monthly fee.

If you set a maximum total charge for a run, monitoring mode reads only as many playlists as fit a check plus a row each, and lists the rest in a `not-read` row.

### Rows that explain instead of failing

| status | When |
|---|---|
| `no-such-playlist` | Spotify answered 404 (Page not found) for the ID: no such playlist, a private or deleted one, or an ID that is not a playlist |
| `invalid-input` | The line is not a playlist URL, URI or ID (track, album and artist links land here) - or no playlist was given |
| `duplicate-input` | The same playlist appeared earlier in the list |
| `unreadable` | The page came back without the playlist data, or its format changed. No figure is guessed |
| `blocked` | Spotify refused the request (403/429), or answered with a verification or login page |
| `not-read` | Lines that were not requested: after a `blocked` row or three unreadable pages in a row the run stops asking Spotify; also when `maxPlaylists` or the charge limit was reached |
| `no-change` | Monitoring: every playlist was compared and none had changed |
| `budget-reached` | The run's maximum total charge was reached before every ready row could be delivered |

### Limits

- **Tracks**: only the tracks in the public playlist page (see above). Not the full list of a longer playlist, and not a play count lookup for single tracks or albums.
- **Non-track items** (podcast episodes in a playlist) are not listed in `tracks`; they still count in `position` and in `totalTracks`.
- **Country**: Spotify builds the page for the country of the connection (`pageCountry`). Personalised and "made for you" playlists need a login and are not readable.
- Only the public playlist page (`open.spotify.com/playlist/<id>`) is requested, one page per playlist, about one request per second, with a User-Agent that identifies this Actor. No login, no Spotify API token, no embed pages. If Spotify stops serving the data in that page, or serves playlist items none of which can be read as a track, rows become `unreadable` rather than wrong.
- This Actor is not affiliated with, endorsed by or sponsored by Spotify. Spotify's terms of use restrict automated collection from its service; decide for yourself whether your use is permitted before you run it.

# Actor input Schema

## `playlists` (type: `array`):

Playlist page URLs (https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M), spotify:playlist: URIs, or the 22-character playlist IDs. Only open.spotify.com/playlist/<id> pages are requested. Track, album and artist links are not read and come back as an "invalid-input" row that is not charged; an ID Spotify has no public playlist page for (private, deleted or not a playlist) comes back as a "no-such-playlist" row that is not charged. Playlist names are not searched: use the link from the Share menu in Spotify.

## `maxPlaylists` (type: `integer`):

With monitoring off, the run stops after this many playlist rows and lists the lines it did not read in a row that is not charged. Monitoring mode ignores this and checks every playlist you listed.

## `monitoringMode` (type: `boolean`):

Off = return every playlist you listed, charged per playlist row. On = the follower count and the tracks found in the page are remembered, and later runs return a playlist only when its follower count differs by more than 2 from the last returned figure (the public figure itself varies by 1 or 2 between reads), a track entered or left the tracks in the page, or the total track count differs - together with the previous figures, the difference, and the tracks that entered and left. In monitoring mode each playlist that was read is charged as one check (playlist-checked), and a playlist that is returned is also charged as one playlist row (playlist-returned). The first monitoring run returns every playlist once. Playlists are remembered one by one, not per list, so adding a playlist does not return the others again. Do not put the same playlist in two schedules that run at the same moment.

## `monitorMode` (type: `string`):

Any change = the playlist is returned when its follower count or its tracks changed. The other two options watch only one of the two. A change in play counts alone does not trigger a return; play counts and their differences are reported in every returned row.

## `resetMonitoringState` (type: `boolean`):

Clears everything remembered for this Actor on your account, so the next monitoring run returns each playlist once again. This affects all your monitoring runs, because playlists are remembered one by one rather than per list.

## `useProxy` (type: `boolean`):

Requests go directly to Spotify. If Spotify answers HTTP 403 or 429, the page is requested one more time through an Apify residential proxy, at most three times in a run; if it is refused again, or a fourth page is refused, the run stops asking and lists what it did not read. Turn this off to never use a proxy.

## Actor input object example

```json
{
  "playlists": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    "37i9dQZF1DX0XUsuxWHRQd"
  ],
  "maxPlaylists": 100,
  "monitoringMode": false,
  "monitorMode": "any-change",
  "resetMonitoringState": false,
  "useProxy": true
}
```

# Actor output Schema

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

One row per Spotify playlist: name, ID and URL, owner, follower count as the integer in the page data, total track count, how many of those tracks were in the public page, those tracks with position, artists and play count and, in monitoring mode, the previous figures, the difference and the tracks that entered or left. IDs Spotify has no public playlist page for, lines that are not playlist links, pages that could not be read and runs where nothing changed come back as their own rows and are not charged.

# 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 = {
    "playlists": [
        "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
        "37i9dQZF1DX0XUsuxWHRQd"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/spotify-playlist-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 = { "playlists": [
        "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
        "37i9dQZF1DX0XUsuxWHRQd",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/spotify-playlist-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 '{
  "playlists": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    "37i9dQZF1DX0XUsuxWHRQd"
  ]
}' |
apify call neverempty/spotify-playlist-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/spotify-playlist-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/rsxrmglD6T3ahJlHr/builds/BjqBbZnY5s0WALjgi/openapi.json
