agentsclimarketplace

Spotify to tidal

Skill aalzubidy/spotify-to-tidal

Migrate all music (liked tracks, playlists, albums, artists, etc) from Spotify to Tidal. Supports independent export, import, verification, and HTML report generation phases. Use when the user says "migrate spotify to tidal", "export spotify library", "import to tidal", "verify tidal import", or "spotify to tidal report".From its SKILL.md

Install
npx -y skills add aalzubidy/spotify-to-tidal

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

14.8 KB, ~3.7k tokens by cl100k_base, as published. Nobody here has run it

Spotify → Tidal Music Migration

Five-phase migration on the official TIDAL v2 API (openapi.tidal.com/v2): (1) export Spotify library, (2) authenticate to Tidal via Authorization-Code + PKCE (one browser login), (3) pre-import analysis (read-only exact ISRC matching), (4) import to Tidal (batched writes from a resumable match cache), (5) verify everything landed by reading the account back. Each phase can run independently.

Matching is exact by ISRC (GET /tracks?filter[isrc]=..., batched) — no fuzzy guessing. Matches persist to a resumable cache (tidal-match-cache.json), so an interrupted run resumes instead of restarting. Requests use steady pacing plus exact Retry-After handling on 429.

Prerequisites

  • Python 3.8+
  • A Spotify Developer app with Client ID and Client Secret
  • A TIDAL Developer app (https://developer.tidal.com) with Client ID/Secret, a http://localhost:3030/callback redirect URI, and collection + playlists read/write scopes
  • requests library (pip install requests)
  • spotify-to-tidal.env file in your working directory (Spotify and Tidal creds)

Setup

Create a working directory and place your spotify-to-tidal.env file there:

mkdir -p spotify-tidal-working-directory
# Copy spotify-to-tidal.env template from the skill directory
cp .agents/skills/spotify-to-tidal/spotify-to-tidal.env ./spotify-tidal-working-directory/
# Edit it with your real credentials
# nano ./spotify-tidal-working-directory/spotify-to-tidal.env

All data files are written to the working directory — not to the skill directory. Output paths are always controlled by the DATA_DIR argument.

Modes

The skill detects the user's intent and runs one of these modes:

ModeTriggerWhat It Does
spotify-export"export spotify", "read my spotify library"Auth → fetch all → save JSON
spotify-report"spotify report", "analyze my spotify"Read export JSON → generate HTML
spotify-full"spotify full", "export and report spotify"Export + Report
tidal-auth"auth tidal", "login to tidal", "connect tidal"Run tidal_openapi.py auth (PKCE browser login)
tidal-analyze"analyze tidal import", "pre-check import"Read-only exact-ISRC matching → match cache + summary
tidal-import"import to tidal", "migrate to tidal"Read match cache → batch import → save result JSON
tidal-clean"clean tidal library", "remove duplicates", "delete albums"Selectively empty library categories (dry-run available)
tidal-verify"verify tidal import", "confirm migration"Read from Tidal → compare by ID and name → save verification JSON
tidal-report"tidal report", "import report"Read result JSON → generate HTML
tidal-verify-report"verification report"Read verification JSON → generate HTML
tidal-missing-report"missing tracks report", "what tracks didn't transfer", "tracks not on tidal"Read match cache → generate missing-tracks HTML
tidal-full"tidal full"Auth → Analyze → Import → Report
migrate"migrate", "move from spotify to tidal"Spotify-full → Tidal-full → Verify → combined report

If the user says "migrate my music" without specifying, assume migrate mode.

CRITICAL: Spotify is READ-ONLY — Never Write to Spotify

This is the most important rule in this skill. The AI MUST NEVER write to Spotify under any circumstances. No exceptions.

This skill only reads from Spotify (exporting your library). All write operations — importing tracks, creating playlists, following artists, deleting items — go to Tidal only.

The Spotify app credentials used by this skill do not even have write scopes (only playlist-read-private, playlist-read-collaborative, user-follow-read, user-top-read, user-read-email, user-read-private). Any attempt to write to Spotify would be rejected by the API — but the AI should not even try.

Do NOT:

  • Create, modify, or delete Spotify playlists
  • Add or remove tracks from Spotify
  • Follow or unfollow Spotify artists
  • Modify Spotify library items
  • Delete anything from Spotify

Only DO:

  • Read/export Spotify library data
  • Authenticate to Tidal via tidal_openapi.py auth (PKCE browser login)
  • Write/import to Tidal (Phase 5)
  • Delete from Tidal (clean-library, with explicit confirmation)
  • Verify Tidal state (Phase 6)

Human-in-the-Loop Checkpoints

The AI must follow these rules at all times. If any conflict, ambiguity, or uncertainty arises at any stage, stop and ask the user. Never guess.

Checkpoint A — After Spotify Export (Stats & Approval)

After Phase 1 completes and spotify-export.json is saved, the AI MUST:

  1. Read the export summary and present it to the user clearly with a full breakdown:

    CategoryCount
    Liked tracksliked_tracks count
    Playlistsplaylist count
    Playlist tracks (total)sum of all playlists[].tracks
    Grand total tracksliked + all playlist tracks
    Saved albumssaved_albums count
    Followed artistsfollowed_artists count

    Show a few sample track/artist names so the user can verify it looks right.

  2. Ask the user: "Ready to proceed with Tidal import? The import will process all the tracks listed above."

  3. Do NOT start Phase 2 (Tidal auth or import) until the user explicitly approves.

Checkpoint B — Before Tidal Import (Import Mode Selection)

import-all.py is append-only and idempotent — items already in Tidal are left alone (HTTP 409 treated as success), so re-running is safe.

OptionDescriptionSafety
Append (default)Add new items to Tidal. Nothing is deleted. Re-runnable.Safe ✅
ReplaceRun clean-library.py … --confirm FIRST to empty categories, then import.Destructive ⚠️

For "Replace" — double confirmation required:

  • Warn the user that the selected categories will be emptied on Tidal
  • clean-library.py itself requires typing yes delete everything
  • Only run it after the user explicitly asks for a fresh/replace import

The default (and safe) behavior is plain import-all.py (append). Only clean first if the user explicitly requests it.

Checkpoint C — Fallback Rule

If at any point the AI encounters an error, ambiguity, unexpected result, or anything it is unsure about: Stop and ask the user for guidance. Never make assumptions about the user's data or account.

Data Directory

All data lives in the working directory (the directory containing spotify-to-tidal.env):

FilePurpose
spotify-tokens.jsonSpotify OAuth tokens
spotify-export.jsonFull Spotify library export
tidal-openapi-session.jsonOfficial-API PKCE session (access/refresh tokens, user_id, country_code)
tidal-match-cache.jsonResumable ISRC/album/artist → Tidal ID match cache
tidal-analysis.jsonHuman-readable match summary
tidal-import-result.jsonImport results
tidal-verification-result.jsonRead-back verification vs actual Tidal state
spotify-report.htmlSpotify library HTML report
tidal-report.htmlImport results HTML report
verification-report.htmlVerification health check HTML report
migration-report.htmlCombined migration overview report
tidal-missing-tracks-report.htmlTracks not found in Tidal's catalog (ISRC + search misses)

The skill always checks for existing files before starting a phase. If tokens exist and are valid, they are reused (skip re-auth). If export JSON exists, the user is asked before re-exporting.

Phase 1 — Spotify Export

See spotify/SKILL.md for detailed instructions.

  1. Load credentials from spotify-to-tidal.env
  2. Start spotify/helpers/auth-server.py on 127.0.0.1:3030
  3. Open browser for Spotify OAuth authorization
  4. Exchange code for tokens, save to spotify-tokens.json
  5. Run spotify/helpers/fetch-all.py to pull all library data
  6. Saves spotify-export.json to data directory

Phase 2 — Tidal Authentication (PKCE, one-time)

Authenticate to the official TIDAL API using Authorization Code + PKCE. Opens a browser login and captures the redirect on http://localhost:3030/callback.

pip install requests   # one-time
python3 tidal/helpers/tidal_openapi.py <DATA_DIR> auth

Saves tidal-openapi-session.json (access/refresh tokens, user id, country). Tokens auto-refresh; re-auth only if the refresh token is revoked. Verify a single lookup with tidal_openapi.py <DATA_DIR> probe <ISRC>.

Phase 3 — Pre-Import Analysis (Recommended)

Read-only. Resolves every track by exact ISRC via the official GET /tracks?filter[isrc]=... (batched), with a capped text-search fallback for misses. Albums match by UPC barcode (then fuzzy), artists by name. Writes the resumable tidal-match-cache.json plus a human-readable summary.

python3 tidal/helpers/analyze-import.py \
    <DATA_DIR>/spotify-export.json \
    <DATA_DIR> \
    <DATA_DIR>/tidal-analysis.json

Re-running resumes from the cache (no repeated lookups). Useful flags: --batch N, --fallback-cap N, --no-fallback, --limit N (test slice). Present the summary to the user before importing.

Phase 4 — Clean Tidal Library (Optional, destructive)

Standalone cleanup, or to empty categories before a fresh import:

# Dry run — shows what would be deleted
python3 tidal/helpers/clean-library.py \
    <DATA_DIR> --categories tracks,albums,artists,playlists

# Actually delete (requires --confirm + typed confirmation)
python3 tidal/helpers/clean-library.py \
    <DATA_DIR> --categories tracks,albums,artists,playlists --confirm

Categories are independently selectable: tracks, albums, artists, playlists. Removes in batches (50/request) via the official API.

Phase 5 — Tidal Import

See tidal/SKILL.md for detailed instructions.

  1. Requires tidal-match-cache.json from Phase 3 (its source of truth).
  2. Run tidal/helpers/import-all.py:
    python3 tidal/helpers/import-all.py \
        <DATA_DIR>/spotify-export.json \
        <DATA_DIR> \
        <DATA_DIR>/tidal-import-result.json \
        [--no-liked] [--no-albums] [--no-artists] [--no-playlists] [--no-liked-playlist]
    
  3. Append-only and idempotent (already-present items return 409 = success).
  4. Batched writes (50/req): like tracks, save albums, follow artists, create playlists, add playlist tracks in Spotify order.
  5. After playlists: creates a "Liked Songs" UNLISTED playlist containing liked tracks in Spotify order — a browsable duplicate of the liked-track favorites. Skip with --no-liked-playlist.
  6. Steady pacing + exact Retry-After on 429.
  7. Saves tidal-import-result.json.

Phase 6 — Verification (Read-Back)

See tidal/SKILL.md for detailed instructions.

  1. Run tidal/helpers/verify-import.py:
    python3 tidal/helpers/verify-import.py \
        <DATA_DIR>/spotify-export.json \
        <DATA_DIR> \
        <DATA_DIR>/tidal-import-result.json \
        <DATA_DIR>/tidal-verification-result.json
    
  2. Reads actual account state via the official API (favorite tracks, albums, artists, playlists + their items).
  3. Cross-references expected Tidal IDs (from the match cache) against what is actually present; reports liked-track and per-playlist presence health.
  4. Saves tidal-verification-result.json.

Reports

Run reports/generate.py to build HTML reports. All inputs and outputs are paths inside the working directory (DATA_DIR):

# Spotify library report
python3 reports/generate.py \
  --type export \
  --input <DATA_DIR>/spotify-export.json \
  --output <DATA_DIR>/spotify-report.html

# Tidal import report
python3 reports/generate.py \
  --type import \
  --input <DATA_DIR>/tidal-import-result.json \
  --output <DATA_DIR>/tidal-report.html

# Tidal verification report
python3 reports/generate.py \
  --type verify \
  --input <DATA_DIR>/tidal-verification-result.json \
  --output <DATA_DIR>/verification-report.html

# Combined migration report
python3 reports/generate.py \
  --type migration \
  --spotify <DATA_DIR>/spotify-export.json \
  --tidal <DATA_DIR>/tidal-import-result.json \
  --output <DATA_DIR>/migration-report.html

# Missing tracks report (tracks not found on Tidal)
python3 reports/generate.py \
  --type missing \
  --spotify <DATA_DIR>/spotify-export.json \
  --cache <DATA_DIR>/tidal-match-cache.json \
  --output <DATA_DIR>/tidal-missing-tracks-report.html

Reports are self-contained HTML files with embedded CSS — no external dependencies. Open with any browser.

⚠️ IMPORTANT: Do NOT attempt to open the report in a browser (e.g. via open, xdg-open, or any similar command). On Linux systems these commands often fail with missing module errors. Simply print the full path to the generated HTML file so the user can open it manually.

Matching Method

MethodHowUsed for
ISRC exactGET /tracks?filter[isrc]=... (batched)Tracks (primary) — method: isrc
Text fallbacksearch by name+artist, fuzzy-confirm (Levenshtein ≥ 0.78)Tracks Tidal didn't return by ISRC — method: search
UPC barcodeGET /albums?filter[barcodeId]=...Saved albums (when export has UPC)
Album/artist fuzzyname search, fuzzy-confirmAlbums without UPC; followed artists

Track matches are stored in tidal-match-cache.json keyed by ISRC. Coverage is typically high-90s% by ISRC; the rest depends on Tidal catalog availability and the fallback. There is no fuzzy "confidence guessing" for tracks — a track is matched by exact ISRC or by a high-threshold confirmed search, or it is skipped.

Architecture

All scripts use the shared TidalOpenAPI module (tidal/helpers/tidal_openapi.py) which handles:

  • Authorization Code + PKCE auth with auto-refresh (tidal-openapi-session.json)
  • JSON:API requests (GET/POST/DELETE) with timeouts and retries
  • Steady-pace rate limiting (TIDAL_OPENAPI_MIN_INTERVAL, default ~8 req/s) plus exact Retry-After honoring on 429 — no burst-then-backoff
  • Cursor pagination for list endpoints
  • Matching (tracks_by_isrc, albums_by_barcode, search_*), writes (add_favorites, create_playlist, add_playlist_items), reads, and deletes

API Base: openapi.tidal.com/v2 (official developer API) Auth: Authorization Code + PKCE (user's own registered app) Credentials: TIDAL_CLIENT_ID / TIDAL_CLIENT_SECRET from spotify-to-tidal.env

What ships with it: 5 files

70.9 KB alongside SKILL.md, 2 of them executable

reports/

Keep looking

Skills are one crate of 326,835. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.