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
npx -y skills add aalzubidy/spotify-to-tidalAssembled 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/callbackredirect URI, and collection + playlists read/write scopes requestslibrary (pip install requests)spotify-to-tidal.envfile 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:
| Mode | Trigger | What 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:
-
Read the export summary and present it to the user clearly with a full breakdown:
Category Count Liked tracks liked_trackscountPlaylists playlist count Playlist tracks (total) sum of all playlists[].tracksGrand total tracks liked + all playlist tracks Saved albums saved_albumscountFollowed artists followed_artistscountShow a few sample track/artist names so the user can verify it looks right.
-
Ask the user: "Ready to proceed with Tidal import? The import will process all the tracks listed above."
-
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.
| Option | Description | Safety |
|---|---|---|
| Append (default) | Add new items to Tidal. Nothing is deleted. Re-runnable. | Safe ✅ |
| Replace | Run 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.pyitself requires typingyes 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):
| File | Purpose |
|---|---|
spotify-tokens.json | Spotify OAuth tokens |
spotify-export.json | Full Spotify library export |
tidal-openapi-session.json | Official-API PKCE session (access/refresh tokens, user_id, country_code) |
tidal-match-cache.json | Resumable ISRC/album/artist → Tidal ID match cache |
tidal-analysis.json | Human-readable match summary |
tidal-import-result.json | Import results |
tidal-verification-result.json | Read-back verification vs actual Tidal state |
spotify-report.html | Spotify library HTML report |
tidal-report.html | Import results HTML report |
verification-report.html | Verification health check HTML report |
migration-report.html | Combined migration overview report |
tidal-missing-tracks-report.html | Tracks 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.
- Load credentials from
spotify-to-tidal.env - Start
spotify/helpers/auth-server.pyon127.0.0.1:3030 - Open browser for Spotify OAuth authorization
- Exchange code for tokens, save to
spotify-tokens.json - Run
spotify/helpers/fetch-all.pyto pull all library data - Saves
spotify-export.jsonto 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.
- Requires
tidal-match-cache.jsonfrom Phase 3 (its source of truth). - 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] - Append-only and idempotent (already-present items return 409 = success).
- Batched writes (50/req): like tracks, save albums, follow artists, create playlists, add playlist tracks in Spotify order.
- 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. - Steady pacing + exact
Retry-Afteron 429. - Saves
tidal-import-result.json.
Phase 6 — Verification (Read-Back)
See tidal/SKILL.md for detailed instructions.
- 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 - Reads actual account state via the official API (favorite tracks, albums, artists, playlists + their items).
- Cross-references expected Tidal IDs (from the match cache) against what is actually present; reports liked-track and per-playlist presence health.
- 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
| Method | How | Used for |
|---|---|---|
| ISRC exact | GET /tracks?filter[isrc]=... (batched) | Tracks (primary) — method: isrc |
| Text fallback | search by name+artist, fuzzy-confirm (Levenshtein ≥ 0.78) | Tracks Tidal didn't return by ISRC — method: search |
| UPC barcode | GET /albums?filter[barcodeId]=... | Saved albums (when export has UPC) |
| Album/artist fuzzy | name search, fuzzy-confirm | Albums 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 exactRetry-Afterhonoring 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/
- generate.pyruns37.3 KB
- .gitignore35 B
- install.shruns7.1 KB
- LICENSE16.4 KB
- README.md10.0 KB