SoundCloud makes you unlike, unfollow, and re-sort one click at a time.
Track Toolkit does it in batches — for DJs and collectors with thousands of tracks.
formerly known as SoundCloud Toolkit
🌐 Live site · 🔒 Security model · 🚀 Run locally
✅ 97% of operations succeed ·
🧰 20 tools on one dashboard ·
🧑💻 Built and run solo
The users and tracks badges are live and update daily. The success rate is from the production admin dashboard, September 2026.
- Features
- The problem
- How it works
- Run it locally
- Scope and non-goals
- Tradeoffs
- Known limitations
- What I'd do next
- Stack
- License
- Merge playlists past the 500-track cap — dedupes by track ID, drops dead tracks, and splits the output into numbered parts
- Bulk-unlike, bulk-unfollow, bulk-unrepost with browsable, searchable managers
- Library audits that find duplicates, unavailable tracks, and download links across every playlist
- Convert between likes and playlists in either direction, or build a playlist from your feed
- Export likes, playlists, followings, and reposts as TXT or CSV
- Your SoundCloud tokens never reach the browser — an OAuth2 + PKCE proxy holds them, AES-256-GCM-encrypted at rest
- No third-party scripts — no analytics, tag manager, widget, or font CDN on the page
- Account-safe by design — follow automation is hard-capped server-side so users can't get flagged
All 20 tools
| Playlists | Likes & Social | Library & Export | Discovery & Links |
|---|---|---|---|
| Combine Playlists | Like Manager | Library Audit | Link Resolver |
| Playlist Modifier | Following Manager | Export (TXT / CSV) | Batch Link Resolver (50 URLs) |
| Playlist Health Check | Repost Manager | Downloads | Genre Search |
| Keyword Search | Likes → Playlist | Recently Played | |
| Playlist Cloner | Playlist → Likes | ||
| Playlist Compare | Activity → Playlist | ||
| Following Library |
SoundCloud's site operates one item at a time. Unliking a track is one click; so is unfollowing an account or removing a repost. Playlists hard-cap at 500 tracks, and dead tracks — private, DMCA'd, region-blocked — sit in playlists silently. For DJs and collectors with libraries in the thousands, cleanup was effectively impossible. The toolkit runs those operations in batches through SoundCloud's OAuth API, and splits playlist output into numbered parts when it hits the cap.
I built the first version for my own library — thousands of likes and playlists sitting past the 500-track cap, with no way in the official site to clean any of it up in bulk.
flowchart LR
B[Browser<br/>Next.js static export] -->|HMAC-signed session cookie| E[Express on<br/>Azure App Service]
E -->|user + token lookup| P[(PostgreSQL<br/>AES-256-GCM tokens)]
E -->|server-side call<br/>refresh on 401, backoff on 429| S[SoundCloud API]
- One origin. The frontend is a Next.js static export. One Express app on Azure App Service serves it and the API from the same origin, acting as an OAuth2 + PKCE proxy: the browser never sees SoundCloud tokens. Tokens are AES-256-GCM-encrypted at rest in Postgres, and every SoundCloud call is made server-side with the user's decrypted token.
- Nothing third-party on the page. No analytics, no tag manager, no widget or font CDN — the CSP names no third-party script, style or font source. The threat model, CSRF layering, and session-lifetime limitations are written up in docs/SECURITY.md.
- Batched writes. Bulk writes run sequentially in 100-track batches with ~300 ms delays. Merges dedupe by track ID, filter out blocked and non-streamable tracks, and split into numbered playlists at SoundCloud's 500-track cap.
- State. Postgres (Azure Database for PostgreSQL) holds users, encrypted tokens, a per-operation log, and follow-action history. URL-resolve caching and background-job tracking are in-memory, per-process.
- Where it breaks first. Rate limiting is the most common failure point — SoundCloud's limits are undocumented.
Where the usage numbers come from
Both badges read /api/stats/public,
which serves counters the production retention job recomputes every day.
Users is everyone who has ever run an operation. Tracks processed is
the running total of tracks across every operation. Both live in the
metrics table so neither goes down when old operation_logs rows age out
(see server/lib/retention.js).
Requirements:
- Node 18+
- A Postgres 14+ database (production runs Azure Database for PostgreSQL Flexible Server)
- A SoundCloud OAuth app — client ID and secret from developers.soundcloud.com
git clone https://github.com/cole-hackman/tracktoolkit
cd tracktoolkit
npm install
cp .env.example .env # SoundCloud credentials, DATABASE_URL, generated secrets
npx prisma db push # create the schema
npm run dev # frontend on :3000, API on :3001Note: there is no mock mode. Without real SoundCloud credentials and a database the app does not run. Rate limiters are disabled in development.
In scope: batch operations on your own library — playlists, likes, followings, reposts — plus browsing and cloning public content from accounts you already follow.
Not in scope:
- Downloading tracks that aren't download-enabled. The download proxy accepts only SoundCloud's official per-track download endpoint and only redirects to SoundCloud's own CDNs.
- Follow automation. The discovery tool enforces hard server-side caps — 50 follows per 24 hours with cooldowns — no matter what the client requests.
- Multi-account management.
Static-export frontend served by the Express API. Express serves
frontend-UI/out/ as static files and /api from the same Azure App Service
origin. What it bought: cheap hosting, no server rendering to operate, and a
same-site Lax session cookie. What it cost: Next.js API routes are
unavailable, so all server logic lives in Express. The earlier split — Vercel
for the frontend, DigitalOcean for the API — needed SameSite=None cookies
across the www. and api. subdomains with a strict CORS allowlist, and
getting those right was the most fragile part of that deployment.
Server-enforced caps on the follow/discovery tool. SoundCloud flags aggressive follow activity. The caps live in the backend — 50 follows per 24 hours, 30-minute session cooldowns, 2–5 second jittered pacing between follows — and reversing a follow doesn't refund the budget, because the write still happened on SoundCloud's side. What it bought: users can't burn their accounts by hammering the tool. What it cost: the feature is deliberately slow, and that's the predictable complaint.
- SoundCloud's rate limits are undocumented. Requests back off on 429 and
respect
Retry-After, but bulk operations over a few hundred items still occasionally get throttled — and they're slow by design. - Merges silently drop blocked and non-streamable tracks. The API response includes the counts, but the UI doesn't itemize what was filtered. Silent from the user's perspective.
- SoundCloud's v2 reposts endpoint sometimes returns zero for users who have reposts. A v1 fallback chain mitigates this at the cost of latency, and can still miss items.
- In-memory state (the resolve cache, the follow-job registry) is per-process and assumes the single-instance deploy. A restart loses running job state; a second instance would fork it.
- Schema management mixes
prisma db pushwith migration files. A push from a branch whose schema is missing production tables would drop those tables — the sharpest foot-gun in the repo. - Observability is application logs plus the in-database operation log. No
metrics, no alerting, no error tracker. Scheduled automation is a GitHub
Actions cron pinging
/healthevery five minutes. - The Jest suite (crypto, merge logic, validation, the follow engine, the API
client wrapper, plus route-level authz/CSRF boundary tests under
tests/routes/) runs in CI on every push tomain, and a failure blocks the deploy. The frontend checks —tsc,next lint, the colour-contrast gate and the Playwright suite — are not in CI yet and are run by hand.
- Surface what a merge filtered out. The counts already come back in the API response; the UI drops them on the floor.
- Put the frontend checks in CI too. The backend suite gates the deploy;
the Playwright/axe suite,
tsc, lint and the contrast gate still depend on somebody remembering to run them. - Move background-job state from memory into Postgres so a restart doesn't orphan running follow sessions.
- Finish the AI library chat on
feature/ai-library-chat— the index tables are already in the production schema; the tool-calling chat loop isn't merged.
| Layer | Technology |
|---|---|
| Frontend | Next.js 15 (static export) · React 18 · TypeScript · Tailwind CSS 3.4 |
| Backend | Express · Prisma |
| Data | PostgreSQL (Azure Database for PostgreSQL Flexible Server) |
| Hosting | Azure App Service, deployed by GitHub Actions over OIDC |
| Testing | Jest (gates deploys) · Playwright + @axe-core/playwright · colour-contrast gate |
The Playwright suite (frontend-UI/e2e/) audits every user-facing page for
serious and critical accessibility violations and for horizontal overflow at
1280, 430, 390 and 360 px. The admin console is not in it.
PolyForm Shield 1.0.0 — see LICENSE. You may clone, run, modify and contribute to this code for any purpose except providing a product or service that competes with Track Toolkit. It is source-available, not OSI open source. Releases before 2026-09-21 were MIT and remain so for the copies obtained under it.
Track Toolkit was called SoundCloud Toolkit until September 2026.
SoundCloud's API Terms of Use forbid "SoundCloud" in an app's name or domain,
so the product renamed; the tools, accounts, and OAuth connection are
unchanged. The old soundcloudtoolkit.com hostnames redirect to
tracktoolkit.com.
Track Toolkit is not affiliated with or endorsed by SoundCloud.
Built by Cole Hackman.