# Spotify Playlist Tracker (`automation-lab/spotify-playlist-change-monitor`) Actor

Monitor public Spotify playlists and emit added, removed, moved, and metadata-changed track events with persistent snapshots and complete pagination.

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

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Spotify Playlist Tracker

Track changes in public Spotify playlists without Spotify OAuth or a user account.
This **Spotify playlist tracker** saves an ordered snapshot, compares it with the
previous run, and emits track-level `added`, `removed`, `moved`, and
`metadata_changed` events.

Use it as a scheduled playlist monitor, a catalog-quality signal, or a clean
input for notifications and downstream data workflows. The Actor follows every
page of the public playlist response instead of stopping at Spotify's 100-track
embed preview.

### What does Spotify Playlist Tracker do?

For each public playlist URL, the Actor:

1. resolves its public identity and owner;
2. fetches all available ordered track pages;
3. loads the prior snapshot for the same `monitorKey` and playlist;
4. compares track occurrences, positions, and public metadata;
5. writes only detected changes to the default dataset; and
6. saves the current snapshot for the next run.

A first run can emit every current track as `added`, which gives you a useful
baseline dataset immediately. Set `emitInitialAdds` to `false` when you want a
silent baseline instead.

### Who is it for?

- **Playlist curators** watching additions, removals, and ranking changes.
- **Labels and artist teams** tracking public playlist placements.
- **Music-data teams** feeding playlist changes into a warehouse.
- **Automation builders** sending Slack, email, or webhook notifications.
- **Catalog QA teams** detecting renamed tracks, artist changes, duration
  changes, explicit flags, and availability changes.

This Actor covers public playlist history from the moment you create a baseline.
It does not reconstruct changes that happened before the first run.

### Why use this playlist monitor?

A playlist export answers “what is in the playlist now?” This Actor answers
“what changed since my last check?”

Key differences from a one-time Spotify scraper:

- persistent, versioned snapshots across runs;
- occurrence-aware comparison when a track appears more than once;
- complete pagination for playlists longer than 100 tracks;
- separate move and metadata-change events;
- stable playlist, track, and monitor identifiers for integrations;
- no private account, user library, or OAuth client required.

### Getting started

1. Open the Actor input.
2. Add one or more public Spotify playlist URLs.
3. Choose a stable `monitorKey`, such as `weekly-editorial-monitor`.
4. Keep `emitInitialAdds` enabled for an immediate baseline export, or disable it
   for a silent first run.
5. Run the Actor and inspect **Playlist changes** in the dataset.
6. Schedule the same input hourly, daily, or weekly.
7. Connect the dataset to your notification, database, or automation workflow.

Reuse the same `monitorKey` on later runs. Changing it creates an independent
history. Do not enable `resetSnapshots` in a normal schedule.

### Input parameters

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `playlistUrls` | string array | required | One to 20 public `open.spotify.com/playlist/...` URLs or `spotify:playlist:...` URIs. |
| `monitorKey` | string | `default` | Snapshot namespace. Reuse it to continue one monitoring history. |
| `emitInitialAdds` | boolean | `true` | Emit every current track as `added` when no prior snapshot exists. |
| `resetSnapshots` | boolean | `false` | Ignore and replace prior snapshots for this run. |

Example input:

```json
{
  "playlistUrls": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
  ],
  "monitorKey": "todays-top-hits-daily",
  "emitInitialAdds": true,
  "resetSnapshots": false
}
```

### Monitoring several playlists

One run can check up to 20 playlists:

```json
{
  "playlistUrls": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
    "https://open.spotify.com/playlist/37i9dQZF1DWWQRwui0ExPn"
  ],
  "monitorKey": "editorial-and-focus-playlists",
  "emitInitialAdds": false
}
```

Each playlist has its own snapshot inside the monitor namespace. A temporary
failure on one URL is logged while other valid playlists continue. The run
fails when none of the supplied playlists can be checked.

### Output fields

Every dataset row is one detected change.

| Field | Meaning |
| --- | --- |
| `changeType` | `added`, `removed`, `moved`, or `metadata_changed`. |
| `playlistId`, `playlistUrl`, `playlistName` | Current public playlist identity. |
| `ownerName` | Current public owner display name, when available. |
| `monitorKey` | Namespace used for this comparison. |
| `trackId`, `trackUri`, `trackUrl` | Stable Spotify track identifiers. |
| `trackName`, `artists`, `album` | Human-readable track metadata. |
| `previousPosition` | Prior one-based position; null for additions. |
| `currentPosition` | Current one-based position; null for removals. |
| `changedFields` | Fields responsible for a move or metadata event. |
| `previousTrack` | Complete prior track row; null for additions. |
| `currentTrack` | Complete current track row; null for removals. |
| `previousSnapshotAt` | Timestamp of the compared snapshot. |
| `detectedAt` | Timestamp of the current snapshot. |

### Example change record

This shortened example matches the current dataset contract:

```json
{
  "type": "playlist_change",
  "changeType": "moved",
  "playlistId": "37i9dQZF1DXcBWIGoYBM5M",
  "playlistUrl": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M",
  "playlistName": "Today’s Top Hits",
  "ownerName": "Spotify",
  "monitorKey": "todays-top-hits-daily",
  "trackId": "4cOdK2wGLETKBW3PvgPWqT",
  "trackUri": "spotify:track:4cOdK2wGLETKBW3PvgPWqT",
  "trackUrl": "https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT",
  "trackName": "Example Song",
  "artists": ["Example Artist"],
  "album": "Example Album",
  "previousPosition": 12,
  "currentPosition": 5,
  "changedFields": ["position"],
  "previousSnapshotAt": "2026-08-01T12:00:00.000Z",
  "detectedAt": "2026-08-02T12:00:00.000Z"
}
```

The complete record also contains `previousTrack` and `currentTrack` objects for
warehouse updates and audit trails.

### How change detection works

Tracks are matched by Spotify track ID and occurrence number. If the same track
appears twice, the first occurrence is compared with the first occurrence and
the second with the second.

- A new occurrence produces `added`.
- A missing occurrence produces `removed`.
- A changed one-based position produces `moved`.
- Changes to name, artists, album, duration, explicit status, or playability
  produce `metadata_changed`.

One track can produce both a move event and a metadata event in the same run.
This keeps filters simple for alerting and analytics.

### Persistent snapshots and monitor keys

Snapshots are stored in a dedicated Actor key-value store. Keys contain a hash
of `monitorKey` plus the playlist ID; the plain monitor key is still present in
output records for routing.

Choose monitor keys by workflow, for example:

- `label-a-daily-placement-watch`
- `editorial-playlists-eu`
- `catalog-qa-weekly`

A reset replaces the old baseline only after Spotify data has been fetched and
change records have been saved. Snapshot data contains public playlist and
track metadata, not Spotify credentials.

### How much does it cost to monitor Spotify playlist changes?

The Actor uses pay-per-event pricing:

- a one-time **run started** event costs **$0.00110** per run;
- each delivered change event costs **$0.00078257 at the BRONZE tier**;
- runs with no detected changes pay only the start event and platform usage;
- playlist snapshots and unchanged tracks have no separate event charge.

At the BRONZE tier, illustrative event charges are:

| Workflow | Delivered changes | BRONZE event-charge formula |
| --- | ---: | --- |
| Daily check with no changes | 0 | $0.00110 start |
| Small update | 5 | $0.00110 + 5 × $0.00078257 |
| First baseline of 50 tracks | 50 | $0.00110 + 50 × $0.00078257 |
| Large baseline of 100 tracks | 100 | $0.00110 + 100 × $0.00078257 |

Apify account tier pricing and platform usage can affect the final total shown
for a run. Check the Actor pricing tab for your active tier.

### Scheduling and alerts

Create an Apify Task with a stable input, then add a schedule. Daily monitoring
works well for editorial playlists; faster schedules may be useful for
short-lived campaign playlists.

Common downstream patterns:

1. run the Task on a schedule;
2. trigger a webhook when the run succeeds;
3. read dataset items;
4. filter by `changeType` or artist;
5. send a message or upsert records into a warehouse.

A successful run with zero dataset rows means no supported track change was
found since the previous snapshot.

### API usage with cURL

Start a synchronous run and receive dataset items:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~spotify-playlist-change-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "playlistUrls": ["https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"],
    "monitorKey": "api-monitor",
    "emitInitialAdds": true
  }'
```

Keep the token in a secret or environment variable, not in source control.

### JavaScript API example

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/spotify-playlist-change-monitor').call({
  playlistUrls: ['https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M'],
  monitorKey: 'javascript-monitor',
  emitInitialAdds: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Python API example

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("automation-lab/spotify-playlist-change-monitor").call(run_input={
    "playlistUrls": ["https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"],
    "monitorKey": "python-monitor",
    "emitInitialAdds": True,
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/spotify-playlist-change-monitor"
```

#### Claude Desktop, Cursor, and VS Code setup

Claude Desktop, Cursor, and VS Code can use the same HTTP MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/spotify-playlist-change-monitor"
    }
  }
}
```

Example prompts:

- “Run my Spotify playlist monitor and summarize newly added tracks.”
- “Show tracks that moved into the top 10 since the previous snapshot.”
- “Check two public playlists and group removals by artist.”

### Integrations

Useful destinations include:

- Google Sheets for a simple change log;
- Slack, Teams, Discord, or email for alerts;
- Zapier and Make for no-code routing;
- BigQuery, Snowflake, or PostgreSQL for playlist history;
- webhooks for custom catalog and campaign systems.

Dataset rows are flat for filtering while the nested before/after track objects
preserve comparison detail.

### Limits and expected behavior

- Only public Spotify playlists are supported.
- Private playlists, user libraries, listening history, account analytics, and
  Spotify for Artists data are outside scope.
- The Actor does not download songs, audio files, or cover images.
- It reports history only after the first saved snapshot.
- Spotify may hide unavailable or non-track playlist entries from its public
  surface; only public track rows returned by Spotify are compared.
- Public endpoint or schema changes can temporarily interrupt extraction.
- A playlist can be checked only when Spotify exposes it without login.
- Input is limited to 20 playlists per run to keep checks bounded.

### Reliability and retries

Transient network errors, HTTP 429 responses, and temporary server errors are
retried with bounded exponential backoff. Invalid URLs and permanent not-found
responses are not retried blindly.

The Actor never silently treats a changed or blocked Spotify response as an
empty playlist. If all requested playlists fail, the run exits with an error so
schedules and integrations can alert you.

### Troubleshooting

#### The second run returned zero rows

That normally means no supported changes were detected. Confirm the same
`monitorKey` was reused and inspect the log line showing the previous snapshot.

#### Every track appeared as added again

The monitor key changed, `resetSnapshots` was enabled, or the previous named
storage was deleted. Restore the original key and keep reset disabled.

#### Spotify says the playlist is unavailable

Open the URL in a private browser window. If login is required or the playlist
is private, the Actor cannot monitor it.

#### A moved track generated two records

That track also changed public metadata. Move and metadata events are separate
by design so integrations can subscribe to either event type.

### Legality

Collect only public data you are authorized to process. Follow Spotify's terms,
Apify's platform rules, and applicable copyright, privacy, and database laws.
Do not use the Actor to bypass access controls or infer private listening
behavior. Store and retain results only as long as your workflow requires.

Spotify is a trademark of Spotify AB. This Actor is an independent automation
tool and is not affiliated with or endorsed by Spotify.

### Related Automation Lab Actors

- [Spotify Playlist Search and Metadata Scraper](https://apify.com/automation-lab/spotify-playlist-search-scraper)
  finds public playlists by keyword and exports current playlist details.
- [Spotify Songs Scraper](https://apify.com/automation-lab/spotify-songs-scraper)
  extracts current public track, album, artist, and playlist records.

Use the search Actor to discover playlists, then pass selected public URLs to
this tracker for recurring change detection.

### FAQ

#### Does this require Spotify OAuth?

No. It uses Spotify's anonymous public web-player data surface.

#### Can it monitor private playlists?

No. Private playlists and account data are intentionally unsupported.

#### Does it fetch playlists longer than 100 tracks?

Yes. It follows Spotify's public pagination until the reported playlist track
count is exhausted. The deep-workflow example fetches every current track from
All Out 2010s, even as Spotify changes that playlist's size.

#### Can I create independent histories for the same playlist?

Yes. Use different `monitorKey` values. Each key gets an independent snapshot.

#### Can I suppress first-run rows?

Yes. Set `emitInitialAdds` to `false`; the Actor saves the baseline and emits no
change rows until a later run differs.

#### Are unchanged tracks charged as events?

No. Only `added`, `removed`, `moved`, and `metadata_changed` dataset records use
the per-change event. Every run also uses the one-time start event.

# Actor input Schema

## `playlistUrls` (type: `array`):

One to 20 public open.spotify.com playlist URLs or spotify:playlist URIs.

## `monitorKey` (type: `string`):

Stable namespace for snapshots. Reuse the same key in scheduled runs; choose another key for an independent baseline.

## `emitInitialAdds` (type: `boolean`):

Emit every current track as an added event when no prior snapshot exists. Disable to create a silent baseline.

## `resetSnapshots` (type: `boolean`):

Ignore and replace prior snapshots for this run. Use carefully because this starts a new comparison baseline.

## Actor input object example

```json
{
  "playlistUrls": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
  ],
  "monitorKey": "my-playlist-monitor",
  "emitInitialAdds": true,
  "resetSnapshots": false
}
```

# Actor output Schema

## `overview` (type: `string`):

Open the default dataset using the playlist changes view.

# 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 = {
    "playlistUrls": [
        "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
    ],
    "monitorKey": "my-playlist-monitor",
    "emitInitialAdds": true,
    "resetSnapshots": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/spotify-playlist-change-monitor").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 = {
    "playlistUrls": ["https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"],
    "monitorKey": "my-playlist-monitor",
    "emitInitialAdds": True,
    "resetSnapshots": False,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/spotify-playlist-change-monitor").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 '{
  "playlistUrls": [
    "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
  ],
  "monitorKey": "my-playlist-monitor",
  "emitInitialAdds": true,
  "resetSnapshots": false
}' |
apify call automation-lab/spotify-playlist-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/spotify-playlist-change-monitor"
        }
    }
}

```

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/FxrTLt0NpkceHABlB/builds/VHVchgbALJaYWfVdD/openapi.json
