# SpotTransfer clones a playlist through YouTube Music's private API, and its progress file holds one playlist at a time

> SpotTransfer is a self hosted tool that copies a Spotify playlist into YouTube Music, with a plain Python desktop client and a TypeScript web frontend in the same repository. The mechanics are simple and the consequences are worth reading: you paste a live session credential into a text box, and starting a second transfer throws away the first one's progress.

**Pushan2005/SpotTransfer** — Spotify to YT-Music Playlist Sync Tool

- Repository: https://github.com/Pushan2005/SpotTransfer
- Website: https://spot-transfer.vercel.app
- Stars: 1,052 · Forks: 46
- Language: TypeScript
- License: GPL-3.0
- Published: 2026-08-24 · Updated: 2026-08-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/pushan2005-spottransfer

## A Python desktop client in a repository whose language tag is TypeScript

The repository holds two products in one tree. The desktop client is in `apps/desktop` and is described as plain Python. The web frontend is in `apps/web` and is what the repository's language tag is counting.

The quick start only covers the first one:

```bash
git clone https://github.com/Pushan2005/SpotTransfer.git
cd SpotTransfer/apps/desktop
python main.py
```

There is a ZIP route for people without Git, which is the same two commands in the extracted folder. The requirements are Git, Python 3.8 or newer, and on Debian or Ubuntu possibly `sudo apt install python3-tk`, because a desktop client in Python is almost certainly a Tk window.

What is missing is as notable as what is there. The top level is `.gitignore`, `LEGAL_NOTICE.md`, `LICENSE`, `README.md`, `apps/`, `bun.lock`, and `package.json`. There is no Python dependency manifest at the root, no requirements file, and no packaging metadata for the desktop client. So the instruction is to run `python main.py` in a directory whose dependencies are declared nowhere in the tree, and a first run is where you find out what is missing.

## Every root script changes into the web app and never touches the Python one

The root manifest is small. It declares one workspace, `apps/web`, is private, and every script begins with a directory change into that workspace and hands off to Bun:

```json
"dev": "cd apps/web && bun run dev",
"dev:all": "cd apps/web && bun run dev:open",
"build": "cd apps/web && bun run build",
"lint": "cd apps/web && bun run lint"
```

So the JavaScript toolchain in this repository builds, serves, and lints the web frontend, and has nothing to do with the desktop client that the instructions tell you to run. There is no script that packages the Python side and no script that runs its tests.

The package manager is pinned rather than merely required, in a field that names Bun and gives it the version 1.2.19. That is a deliberate pin, which is the right call for a toolchain where the lockfile is checked in, and it also means the two halves of the repository have different runtime expectations: Bun for one, whatever Python version for the other.

The web half is the one with a deployment. The project's homepage is a Vercel URL, so the hosted version is the frontend the root build produces, while the documented desktop path is the part with no build system at all.

## The app asks you to paste a live session credential into a text box

This is the part to understand before running anything. There is no login in the app and no OAuth. Instead, the instructions walk through signing in to YouTube Music in a browser, opening the developer tools, going to the Network tab, filtering the requests for the browse endpoint, finding one that succeeded with a status of 200, and copying its request headers. Firefox gets one set of instructions for copying request headers off a request, and Chrome and Edge get another, with a specific boundary: copy everything from the wildcard accept line to the end of the request headers block.

Those headers are a live session credential. They are what the browser is presenting to prove it is you, and pasting them into a third party script means that script can act as you against that service for as long as they remain valid.

Two questions follow from that, and the visible page answers neither. Whether the application writes those headers to disk, and where they end up if it does. And whether it sends them anywhere, since the hosted frontend at the project's homepage is a different program from the local script you were told to run.

There is no warning about the credential, no mention of scoping it, and no expiry advice beyond the fact that it expires on its own.

## Headers expire on long playlists, and the second transfer erases the first

The failure mode is documented, which is more than most tools do. The YouTube Music request headers expire periodically, and the page says this is most noticeable on large playlists. When it happens, the script pauses rather than failing, saves its progress to a JSON file inside the desktop directory, and prints what to do: get a fresh set of headers, paste them in, click Clone Playlist again, and the transfer resumes where it stopped.

So a long transfer is an interactive session with a credential that has a shorter life than the job. That is workable, and the pause-and-resume design is the right response to it.

The progress file is the flaw. There is one path, `apps/desktop/transfer_progress.json`, and the page states in bold that starting a transfer for a different playlist automatically discards any saved progress of the previous one. So there is no queue, no history, and no way to have two playlists in flight. One failed transfer of a large playlist, and the next attempt at anything else, and the record of how far the first one got is gone.

## The legal notice calls this a proof of concept on a private API

There is a dedicated section for it, and it is unusually blunt. The code is provided as is with no warranty of any kind. The author accepts no responsibility for the use or misuse of the repository or its contents, does not endorse any actions or consequences arising from its use, and is not responsible for copies, forks, or re-uploads by other users.

Then the sentence that matters: the repository is solely intended as a proof of concept for educational purposes regarding the use of a service's private API. By using it, you acknowledge that the author makes no claims about the accuracy, legality, or safety of the code and accepts no liability for any issues that may arise. A longer version sits in a file called `LEGAL_NOTICE.md` at the root.

The code licence is GPL-3.0, with a LICENSE file beside that notice. So the source is copyleft and the warning is about conduct rather than about permission.

The mechanism explains the caution. A playlist is copied by driving an internal YouTube Music endpoint that no public client contract describes, authenticated as you by borrowed session headers. That is the definition of a private API, and it is the thing the notice is written about.

## No releases, so the instructions point at a moving branch

The repository has no GitHub releases. The quick start clones the default branch, which means the code you run is whatever is at the head of `main` at the moment you clone it, with no tag to pin and no artefact to download other than the ZIP of that same branch.

The last commit to the main branch is dated 2026-09-30, so the project is being worked on, and the homepage points at a deployment that is built from that same branch. That combination is fine for a proof of concept and awkward for anything else: there is no way to say which version of the script handled your playlist, and the progress file that lets a transfer resume is only meaningful within one checkout.

The acknowledgements name one outside dependency, a library called SpotAPI credited to another GitHub user, which is what reads the Spotify side. So the desktop client is Python calling a third party library, and the page does not say which endpoint of the Spotify service it reaches or under what credential.

## Rate limits, retries, and playlist visibility are all undescribed

A handful of questions a user would have on day one are not answered by the visible page, and their absence is itself informative.

There is no rate limit guidance. A playlist can be hundreds or thousands of tracks, and each track that lands on YouTube Music is a request from an account that Google can rate limit, and the page describes no throttle, no retry on failure, and no partial-track handling. It describes pausing when the headers expire and nothing about pausing when the service says no.

There is no statement about whether the created playlist is public or private. That single default changes who can hear the result.

There is no description of what happens to duplicates, whether re-running the same playlist appends to an existing one or creates a second, and no mention of what the Spotify side reads beyond the link you paste. And the hosted web version, which is the project's own homepage, is not documented in the quick start at all: the page covers the desktop path, and the web frontend exists in the tree with its own build scripts and its own deployment.

## Conclusion

Read the terms of both services before anything else. This tool drives an undocumented internal endpoint with a session credential lifted out of your browser, and the repository's own legal notice says it makes no claim about the legality of that. Whether your use survives that depends on agreements you have already accepted and cannot see from here. With that settled, SpotTransfer fits someone with one playlist to move who is willing to keep their YouTube Music session open and re-paste a header when it expires. Four things to check first. Whether you are pasting a live session credential into a script you have not read, and whether you want it kept in a text file after that. Whether the progress file is acceptable as it behaves, since a second playlist erases the first one's record. Whether the web version at the project's homepage does the same thing the same way, because the quick start documents only the desktop path. And what happens on a rate limit, which the visible page does not describe at all.

## FAQ

### How do I run SpotTransfer on my own machine?

Clone the repository and run python main.py from apps/desktop. You need Git and Python 3.8 or newer, and on Debian or Ubuntu possibly the python3-tk package. A ZIP download works as well, with cd apps/desktop and python main.py in the extracted folder.

### What does SpotTransfer need from my browser?

A signed in YouTube Music session and the request headers of a successful POST to the browse endpoint, copied from the browser's network tab and pasted into the app's headers box. The page gives separate instructions for Firefox and for Chrome or Edge, and in Chrome or Edge the copy runs from the wildcard accept line to the end of the request headers.

### What happens when the YouTube Music headers expire?

The script pauses the transfer, saves progress to apps/desktop/transfer_progress.json, and asks for fresh headers. Pasting them and clicking Clone Playlist again resumes from where it stopped. Starting a transfer for a different playlist discards the previous saved progress automatically.

### Does SpotTransfer publish releases?

No. The repository has no GitHub releases, and the quick start clones the main branch, so the code you run is whatever the branch head is. The last commit to the default branch is dated 2026-09-30.

### What license is SpotTransfer under and what does its legal notice say?

The code is GPL-3.0 with a LICENSE file at the root, beside a LEGAL_NOTICE.md. The notice states the code is provided as is without warranty, is solely intended as a proof of concept for educational purposes regarding a service's private API, and makes no claims about its accuracy, legality, or safety.

### What is the web version of SpotTransfer?

The repository holds a web frontend at apps/web next to the Python desktop client, and the project's homepage is a Vercel deployment. The quick start documents only the desktop path, while all four root package.json scripts change into apps/web and run Bun there.

## Sources

- [Official documentation](https://spot-transfer.vercel.app)
- [Official README](https://github.com/Pushan2005/SpotTransfer#readme)
- [Project repository](https://github.com/Pushan2005/SpotTransfer)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pushan2005-spottransfer
