Search for a track, get direct links to buy the file — across the stores that actually sell individual audio files.
You type an artist and a title. The app queries five stores in parallel and returns where that track can be purchased as a file you own:
| Store | How it's queried |
|---|---|
| Apple Music / iTunes | public itunes.apple.com/search endpoint |
| Bandcamp | public bcsearch_public_api endpoint |
| Beatport | HTML scraping (headless Chromium) |
| Traxsource | HTML scraping (headless Chromium) |
| Amazon Music | HTML scraping (headless Chromium) |
Results stream in one by one as each store answers, so the first links appear without waiting for the slowest one. A store that finds nothing is shown as such rather than hidden — knowing a track is not on Beatport is useful.
It is not a "where can I listen to this" aggregator. Spotify, YouTube, Tidal, Deezer, SoundCloud and Mixcloud are deliberately out of scope: none of them sells an individual file. If streaming links are what you want, this is the wrong tool.
There is no database, no user account and no login. The only state is a short-lived result cache and optional rate limiting.
npm install
npm run devThen open http://localhost:3000.
Headless Chromium is used for the three scraped stores. Locally it uses the Edge or Chrome installation already on your system; in a serverless deployment it uses a bundled Chromium build.
All environment variables are optional — the app is fully functional
without them. See .env.example.
| Variable | Effect if unset |
|---|---|
UPSTASH_REDIS_REST_URL |
falls back to an in-memory limiter (see below) |
UPSTASH_REDIS_REST_TOKEN |
same |
Rate limiting fails open by design — but not to no limit. If Redis is unconfigured or unreachable, requests fall back to an in-memory limiter using the same window (20 requests per 60 seconds), applied per running instance rather than globally. Search is the core feature and shouldn't die because a secondary guard is down; but a single instance still can't be flooded into launching unlimited headless browsers while Redis is unavailable.
npm run build # production build
npm run test # unit tests (Vitest)
npm run lint # ESLint
npx tsc --noEmit # type checkTests run against fixtures captured from real responses, never hand-written ones. When a store changes its HTML, the fixture is recaptured rather than the expectation loosened.
A note on the scraped stores: Beatport, Traxsource and Amazon Music expose no self-serve public API, so the app parses their public search pages. Expect those three to break whenever those pages are redesigned — that is the trade-off, and it is why each provider fails independently instead of taking the whole search down with it.
MIT — see LICENSE.