# TIDAL Scraper - Tracks, Albums, Artists & Playlists (`abotapi/tidal-catalog-scraper`) Actor

Scrape TIDAL tracks, albums, artists, playlists and public mixes by search phrase or URL. Extract structured catalogue metadata with optional track lists, recurring change detection and app exports for ongoing music data workflows.

- **URL**: https://apify.com/abotapi/tidal-catalog-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 catalogue records

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

## TIDAL Music Catalogue Scraper

Collect structured TIDAL music catalogue records for research, discovery and catalogue monitoring. Search for tracks, albums, artists or playlists, or provide direct catalogue links. Public artist-radio mixes and Top/New music collections are supported through their links. Each result has a stable identity and a TIDAL URL, with optional track lists and recurring change detection.

### Why This Scraper?

- Four search result types: tracks, albums, artists and playlists.
- Direct links for individual catalogue entities and public mixes.
- Top music and New music collection records with their source section.
- Optional artist top tracks, album tracks and playlist contents.
- Resume interrupted collections or monitor changes across recurring runs.
- JSON, CSV and Excel dataset exports, plus optional app summaries.

### Data You Get

Fields vary by record type and country. Missing source values are returned as null or empty arrays, never invented. The examples below are fictional.

| Field | Example |
|---|---|
| `id` | `track:00000001` |
| `entityId` | `00000001` |
| `recordType` | `track`, `album`, `artist`, `playlist`, `mix` |
| `title` | `Example Song` |
| `url` | `https://tidal.com/track/00000001` |
| `countryCode` | `US` |
| `artists` | `[{"id":1,"name":"Example Artist"}]` |
| `album` | `{"id":2,"title":"Example Album"}` |
| `duration` | `240` seconds |
| `releaseDate` | `2026-01-01` |
| `explicit` | `false` |
| `popularity` | `50` |
| `audioQuality`, `audioModes` | `LOSSLESS`, `["STEREO"]` |
| `isrc`, `upc` | Catalogue identifiers where available |
| `numberOfTracks` | `12` |
| `description`, `copyright` | Published catalogue text |
| `creator`, `mixes`, `mixType` | Published creator and related mix metadata |
| `tracks`, `tracksTotal`, `tracksComplete` | Included tracks, available total and completeness |
| `sourceSection`, `sourceUrl`, `query` | Collection/search provenance |
| `imageUrl`, `raw` | Artwork URL and original catalogue fields |
| `changeType`, `changedFields` | `UPDATED`, `["title"]` |
| `firstSeenAt`, `lastSeenAt`, `scrapedAt` | Observation timestamps |

### How to Use

#### Search for tracks

```json
{"mode":"search","queries":["Daft Punk"],"searchType":"tracks","maxItems":3}
```

#### Search for albums in another catalogue region

```json
{"mode":"search","queries":["Daft Punk"],"searchType":"albums","countryCode":"AU","maxItems":3,"fetchDetails":true,"maxTracks":5}
```

#### Collect multiple entity links

```json
{"mode":"url","urls":["https://tidal.com/artist/8847","https://tidal.com/album/1550545","https://tidal.com/playlist/abd35446-6a67-4f2b-9f92-0636742c63cb"],"maxItems":3}
```

#### Public music collections and mixes

```json
{"mode":"url","urls":["https://tidal.com/view/pages/explore_top_music","https://tidal.com/mix/0002737adb8d077f4591c4cdb7ba60"],"maxItems":6,"maxTracks":5}
```

#### Coverage and limits

Search uses TIDAL's relevance ordering. `searchType` also applies to pasted search links such as `https://tidal.com/search?q=Daft%20Punk&page=2`. Artist, album, track and playlist links retain their own record type. Both current links and legacy `/browse/` links are accepted.

Top music and New music return the public catalogue entries exposed in those collections, with `sourceSection` where available. They are not a historical chart archive, and the actor does not assign invented chart ranks. Artist-radio mixes can change between runs. Personalized mixes and private collections are outside coverage.

`fetchDetails` adds complete entity metadata and nested track lists for artists, albums and playlists. For artists, the list is top tracks, not an exhaustive discography. `maxTracks` bounds nested results independently of dataset rows. Mixes already include their available list, and may expose fewer tracks than requested. Inspect `tracksComplete` and `tracksTotal` before treating a nested list as complete.

The default output cap is 20 rows. Zero removes that row limit, subject to the selected page bound and run safety limits. Larger collections can stop with partial results and be continued using Resume. Multiple phrases or links share the record allowance. No audio files, playback data or downloads are included.

### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `"search"` | Search by phrases or collect TIDAL links. |
| `queries` | array | `["Daft Punk"]` | Search mode only. Enter up to 50 phrases. Multiple phrases share the result limit. |
| `searchType` | string | `"tracks"` | Result type for phrase searches and pasted search links. Entity links keep their own type. |
| `urls` | array | `(empty)` | URL mode only. Multi-URL supported. Artist, album, track, playlist, mix, search, Top music and New music links are supported. Search phrases are ignored. A search link's page parameter sets the starting page. |
| `countryCode` | string | `"US"` | Two-letter country code such as US or AU. Applies to both modes; availability differs by country. |
| `maxItems` | integer | `20` | Total dataset record limit, including EXPIRED rows. Zero removes this limit; page and run safety limits still apply. |
| `maxPages` | integer | `1000` | Default 1000 leaves the record limit as the primary cap. Set a smaller value for a bounded sample. |
| `fetchDetails` | boolean | `false` | Adds full metadata and artist, album or playlist track lists. Successfully enriched records saved add a detail-enrichment surcharge, with one additional unit per 10 nested tracks returned. Mix links include their available track list without this surcharge. |
| `maxTracks` | integer | `20` | Maximum tracks inside an enriched artist, album or playlist, or a mix. Does not change dataset row count. tracksComplete states whether the whole available list is included. |
| `resumeFromRunId` | string | `(empty)` | Skip IDs collected in a previous run or dataset. For local datasets use local:NAME. Use Incremental mode for recurring monitoring instead. Combining Resume with an existing incremental baseline is rejected. |
| `incrementalMode` | boolean | `false` | First run emits NEW; later runs emit NEW, UPDATED and REAPPEARED records. Use Resume above to continue one run. Avoid overlapping runs of the same scope. |
| `stateKey` | string | `(empty)` | Optional incremental campaign name. Country, mode, phrases, links and detail settings always remain isolated, even with a matching label. Does not affect Resume. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Also return and bill extra UNCHANGED rows. Off by default; unrelated to Resume. |
| `emitExpired` | boolean | `false` | Incremental mode only. Also return and bill synthetic EXPIRED rows after a complete nonempty scan. Bounded, failed or resumed scans never expire records. |
| `proxy` | object | `(empty)` | Optional connection configuration. |
| `mcpConnectors` | array | `(empty)` | Export summaries via Model Context Protocol (MCP) connectors. Authorize under Apify Settings > API & Integrations, then select a connector. Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). Leave empty to skip. Complete dataset output is unchanged. |
| `notionParentPageUrl` | string | `(empty)` | Notion page URL or ID for new summary pages. Required for Notion; ignored by other connectors. |
| `maxNotifyListings` | integer | `50` | Maximum summaries per connector. Does not affect the dataset. |

#### Resume and recurring updates

`resumeFromRunId` continues one specific collection by skipping IDs in its previous run or dataset. A local dataset can be named as `local:NAME`. Saved progress also protects a resurrected run. Resume does not decide which catalogue records have changed.

`incrementalMode` monitors the same catalogue scope across separate runs. Its first run returns `NEW`; later runs normally return only `NEW`, `UPDATED` or `REAPPEARED`. Unchanged records are suppressed. `changedFields` lists changed top-level fields.

`emitUnchanged` returns and bills additional `UNCHANGED` rows. `emitExpired` returns and bills additional synthetic `EXPIRED` rows only after a complete, nonempty scan. Bounded, failed or resumed scans cannot establish expiry. Both options default to false.

`stateKey` names a campaign; mode, queries, links, country and detail settings remain isolated even with the same name. Avoid overlapping runs for the same scope. Combining Resume with an existing incremental baseline fails early; a first baseline may be bootstrapped with Resume.

### Send results into your apps (MCP connectors)

Authorize a connector under Apify Settings > API & Integrations and select it in `mcpConnectors`. For Notion, set `notionParentPageUrl` to the parent page. `maxNotifyListings` limits the summaries sent to each selected connector.

The export contains a title and a condensed, human-readable summary of key fields. It does not mirror the complete nested JSON. The full record always stays in the Apify dataset. Notion receives one summary page per record; other compatible connectors receive a best-effort record write. A failed export leaves the dataset available, and an empty connector selection skips export.

### Output Example

This fictional example shows an incremental track record. Non-incremental runs omit change-tracking fields. Enriched rows also include `enrichmentStatus`.

```json
{
  "id": "track:00000001",
  "entityId": "00000001",
  "recordType": "track",
  "title": "Example Song",
  "url": "https://tidal.com/track/00000001",
  "countryCode": "US",
  "artists": [
    {
      "id": 1,
      "name": "Example Artist"
    }
  ],
  "album": {
    "id": 2,
    "title": "Example Album"
  },
  "duration": 240,
  "releaseDate": null,
  "explicit": false,
  "popularity": 50,
  "numberOfTracks": null,
  "audioQuality": "LOSSLESS",
  "audioModes": [
    "STEREO"
  ],
  "isrc": "XXAAA0000001",
  "upc": null,
  "copyright": "Example rights holder",
  "description": null,
  "imageUrl": null,
  "creator": null,
  "mixes": null,
  "mixType": null,
  "trackNumber": 1,
  "volumeNumber": 1,
  "sourceUrl": null,
  "query": "Example Artist",
  "sourceSection": null,
  "tracks": [],
  "tracksTotal": null,
  "tracksComplete": null,
  "raw": {
    "id": 1,
    "title": "Example Song",
    "duration": 240
  },
  "scrapedAt": "2026-01-01T00:00:00+00:00",
  "changeType": "NEW",
  "changedFields": [],
  "firstSeenAt": "2026-01-01T00:00:00+00:00",
  "lastSeenAt": "2026-01-01T00:00:00+00:00"
}
```

### Plan Requirement

An Apify account is required to run this actor on the platform. Review the displayed actor charges before starting a run.

# Actor input Schema

## `mode` (type: `string`):

Search by phrases or collect TIDAL links.

## `queries` (type: `array`):

Search mode only. Enter up to 50 phrases. Multiple phrases share the result limit.

## `urls` (type: `array`):

URL mode only. Multi-URL supported. Artist, album, track, playlist, mix, search, Top music and New music links are supported. Search phrases are ignored. A search link's page parameter sets the starting page.

## `searchType` (type: `string`):

Result type for phrase searches and pasted search links. Entity links keep their own type.

## `countryCode` (type: `string`):

Two-letter country code such as US or AU. Applies to both modes; availability differs by country.

## `maxItems` (type: `integer`):

Total dataset record limit, including EXPIRED rows. Zero removes this limit; page and run safety limits still apply.

## `maxPages` (type: `integer`):

Default 1000 leaves the record limit as the primary cap. Set a smaller value for a bounded sample.

## `fetchDetails` (type: `boolean`):

Adds full metadata and artist, album or playlist track lists. Successfully enriched records saved add a detail-enrichment surcharge. Mix links include their available track list without this surcharge.

## `maxTracks` (type: `integer`):

Maximum tracks inside an enriched artist, album or playlist, or a mix. Does not change dataset row count. tracksComplete states whether the whole available list is included.

## `resumeFromRunId` (type: `string`):

Skip IDs collected in a previous run or dataset. For local datasets use local:NAME. Use Incremental mode for recurring monitoring instead. Combining Resume with an existing incremental baseline is rejected.

## `incrementalMode` (type: `boolean`):

First run emits NEW; later runs emit NEW, UPDATED and REAPPEARED records. Use Resume above to continue one run. Avoid overlapping runs of the same scope.

## `stateKey` (type: `string`):

Optional incremental campaign name. Country, mode, phrases, links and detail settings always remain isolated, even with a matching label. Does not affect Resume.

## `emitUnchanged` (type: `boolean`):

Incremental mode only. Also return and bill extra UNCHANGED rows. Off by default; unrelated to Resume.

## `emitExpired` (type: `boolean`):

Incremental mode only. Also return and bill synthetic EXPIRED rows after a complete nonempty scan. Bounded, failed or resumed scans never expire records.

## `proxy` (type: `object`):

Optional connection configuration.

## `mcpConnectors` (type: `array`):

Export summaries via Model Context Protocol (MCP) connectors. Authorize under Apify Settings > API & Integrations, then select a connector. Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). Leave empty to skip. Complete dataset output is unchanged.

## `notionParentPageUrl` (type: `string`):

Notion page URL or ID for new summary pages. Required for Notion; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Maximum summaries per connector. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "Daft Punk"
  ],
  "urls": [
    "https://tidal.com/album/1550545"
  ],
  "searchType": "tracks",
  "countryCode": "US",
  "maxItems": 20,
  "maxPages": 1000,
  "fetchDetails": false,
  "maxTracks": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "queries": [
        "Daft Punk"
    ],
    "urls": [
        "https://tidal.com/album/1550545"
    ],
    "searchType": "tracks",
    "countryCode": "US",
    "maxItems": 20,
    "maxPages": 1000,
    "fetchDetails": false,
    "maxTracks": 20,
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "proxy": {
        "useApifyProxy": true
    },
    "maxNotifyListings": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/tidal-catalog-scraper").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 = {
    "mode": "search",
    "queries": ["Daft Punk"],
    "urls": ["https://tidal.com/album/1550545"],
    "searchType": "tracks",
    "countryCode": "US",
    "maxItems": 20,
    "maxPages": 1000,
    "fetchDetails": False,
    "maxTracks": 20,
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "proxy": { "useApifyProxy": True },
    "maxNotifyListings": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/tidal-catalog-scraper").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 '{
  "mode": "search",
  "queries": [
    "Daft Punk"
  ],
  "urls": [
    "https://tidal.com/album/1550545"
  ],
  "searchType": "tracks",
  "countryCode": "US",
  "maxItems": 20,
  "maxPages": 1000,
  "fetchDetails": false,
  "maxTracks": 20,
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "proxy": {
    "useApifyProxy": true
  },
  "maxNotifyListings": 50
}' |
apify call abotapi/tidal-catalog-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/tidal-catalog-scraper"
        }
    }
}
```

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/vHCAGQoslc7ETK8Tg/builds/yw2rLcVRHFDL1qZEC/openapi.json
