VinylMatch is a lightweight web app + API that matches Spotify playlist tracks to their corresponding vinyl releases on Discogs (and optionally links out to other vinyl webstores).
It's built for collectors, DJs, archivists, and anyone who wants to connect a digital playlist to physical releases without manually searching every record.
- Spotify OAuth login (hosted multi-user, session-cookie based)
- Playlist import + track metadata extraction
- Progressive Discogs matching with caching and safe fallbacks
- Optional Discogs user token/OAuth login (wishlist + library status)
- Custom vendor links via
config/vendors.json - Built-in rate limiting and structured JSON error responses
- Log in with Spotify
- Paste a Spotify playlist URL (or 22-char playlist ID) and load it
- For each track, compute a match candidate (Discogs master/release)
- Display results and vendor links in the web UI
- VinylMatch supports official/curated Spotify playlists when you paste a direct playlist URL (for example
https://open.spotify.com/playlist/...). - Spotify does not provide a public API endpoint to browse/search all official playlists globally.
- Because of this API limitation, official playlists must be opened via direct URL/ID instead of in-app browsing.
The matching engine (DiscogsService) uses progressive passes:
- Free-text search (artist + album)
- Light/strict normalization to reduce noise (punctuation, formatting, etc.)
- Structured search (master/release + optional year/track hints)
- Fallback to a Discogs web search URL if API matching is unavailable or inconclusive
- Java 21+
- Maven 3.8+ (or use the bundled Maven in
./.tools/maven/...on Windows) - Spotify API credentials (
SPOTIFY_CLIENT_ID,SPOTIFY_CLIENT_SECRET) - Optional: Discogs token (
DISCOGS_TOKEN) for API matching; without it, the app falls back to Discogs web search links
- Clone
git clone https://github.com/Loues000/VinylMatch.git
cd VinylMatch- Configure environment
cp config/env.example .env- Build and test
mvn test
mvn packageThe frontend is served as static files from src/main/frontend/.
Windows (PowerShell, with bundled Maven):
.\.tools\maven\apache-maven-3.9.6\bin\mvn.cmd test
.\.tools\maven\apache-maven-3.9.6\bin\mvn.cmd package- Run
java -jar target/VinylMatch.jarOpen http://localhost:8888/.
.\scripts\setup.ps1
.\scripts\run.ps1 -BuildVinylMatch is designed to run as a hosted multi-user app:
- Build and ship
target/VinylMatch.jar - Run under
systemd(or similar process manager) - Store runtime config only in environment variables
- Use Redis for persistent sessions in staging/production
Railway is a good fit for VinylMatch because the app already runs as a single Java JAR and exposes a health endpoint on /api/health/simple.
- Create a new Railway project from this GitHub repository.
- Add a Redis service in the same Railway project.
- Deploy the app service from the repo root.
- Add a custom domain in Railway once the first deploy succeeds.
- Update the Spotify app's redirect URI to match the final HTTPS domain exactly.
railway.jsonstarts the app withjava -jar target/VinylMatch.jar.- Railway should use
/api/health/simpleas the health check path. - Railway injects
PORT; the app already reads this environment variable. - The Maven build path for Railway should be
mvn -B -DskipTests clean package. - Avoid legacy dashboard overrides like
dependency:list install -Pproduction; they add work without changing the packaged JAR.
Set these in the Railway app service:
| Variable | Required | Railway note |
|---|---|---|
NVD_API_KEY |
Recommended | Free NVD API key for OWASP Dependency-Check; prevents shared-CI rate-limit failures |
SPOTIFY_CLIENT_ID |
Yes | From Spotify Developer Dashboard |
SPOTIFY_CLIENT_SECRET |
Yes | From Spotify Developer Dashboard |
SPOTIFY_REDIRECT_URI |
Yes | Set to https://your-domain/api/auth/callback |
PUBLIC_BASE_URL |
Yes | Set to https://your-domain |
DISCOGS_TOKEN |
Optional | Enables Discogs API matching |
DISCOGS_USER_AGENT |
Recommended | Required by Discogs API terms |
VINYLMATCH_MASTER_KEY |
Yes | Use a long random secret; keep stable across restarts |
REDIS_HOST |
Recommended | Map from Railway Redis host |
REDIS_PORT |
Recommended | Map from Railway Redis port |
REDIS_PASSWORD |
Recommended | Map from Railway Redis password |
If Railway exposes Redis through reference variables instead of fixed names, map them into the app's expected variables REDIS_HOST, REDIS_PORT, and REDIS_PASSWORD.
pom.xml already wires NVD_API_KEY into dependency-check-maven. If the NVD feed still returns a transient error, the build now continues instead of failing the whole deploy, but Railway should still set the API key for reliable vulnerability updates.
After Railway gives you either a generated domain or a custom domain:
- Set
PUBLIC_BASE_URL=https://your-domain - Set
SPOTIFY_REDIRECT_URI=https://your-domain/api/auth/callback - In Spotify Developer Dashboard, add that exact callback URL
- Redeploy the Railway service
Spotify redirect URIs must match exactly, including protocol, host, and path.
- Push code to GitHub.
- Railway redeploys the service from the connected repo.
- Open
https://your-domain/api/healthto confirm the deployment is healthy. - Test Spotify login with the hosted callback URL.
- Push to
developto run tests, buildVinylMatch.jar, and deploy staging via.github/workflows/deploy.yml. - Create and push a version tag like
v0.1.1to run the production release path. - Tagged releases publish
VinylMatch.jarto a GitHub Release and then deploy production. - Do not use branch pushes to
mainas the production release trigger.
- Run
mvn testandmvn packagelocally before cutting a release. - Bump the version in
pom.xmlif you are shipping a new public version. - Merge the release candidate to
developand verify staging. - Create an annotated tag such as
git tag -a v0.1.1 -m "VinylMatch v0.1.1"on the commit you want to ship. - Push the tag with
git push origin v0.1.1. - Confirm the GitHub Release contains
VinylMatch.jarand the production environment deploy completes successfully.
stagingshould holdSTAGING_SSH_HOST,STAGING_SSH_USER, andSTAGING_SSH_KEY.productionshould holdPRODUCTION_SSH_HOST,PRODUCTION_SSH_USER, andPRODUCTION_SSH_KEY.- Configure GitHub Environment protection rules if you want manual approval before production deploys.
See config/env.example for a complete template.
Security note:
- Never paste real credentials into docs or scripts.
- If credentials were ever exposed in plaintext, revoke/rotate them in Spotify/Discogs dashboards immediately.
- You can run
./scripts/check-doc-secrets.ps1to scan markdown docs for secret-like values.
| Variable | Required | Description |
|---|---|---|
SPOTIFY_CLIENT_ID |
Yes | Spotify OAuth client ID |
SPOTIFY_CLIENT_SECRET |
Yes | Spotify OAuth client secret |
SPOTIFY_REDIRECT_URI |
No | OAuth callback URL (defaults to http://127.0.0.1:PORT/api/auth/callback) |
PUBLIC_BASE_URL |
Recommended | Public base URL (e.g. https://vinylmatch.example.com) |
DISCOGS_TOKEN |
Optional | Default Discogs token used for API matching |
DISCOGS_USER_AGENT |
Recommended | User-Agent for Discogs API (required by Discogs TOS) |
PORT |
No | Server port (default 8888) |
CORS_ALLOWED_ORIGINS |
No | Comma-separated allowed origins |
RATE_LIMIT_PER_MINUTE |
No | Requests/minute per client+path (default 240) |
RATE_LIMIT_BURST |
No | Burst capacity (default max(30, perMinute/4)) |
- Create an app in the Spotify Developer Dashboard
- Add the redirect URI:
- Local:
http://127.0.0.1:8888/api/auth/callback - Local (IPv6):
http://[::1]:8888/api/auth/callback - Hosted:
https://your-domain.com/api/auth/callback
- Local:
- Set
SPOTIFY_CLIENT_IDandSPOTIFY_CLIENT_SECRET
Note: Spotify does not allow localhost as a redirect URI. Use 127.0.0.1 (or [::1]) and open the app via that same host.
Create config/vendors.json to add/override vendor links:
{
"vendors": [
{
"id": "discogs-market",
"label": "D",
"name": "Discogs Marketplace",
"urlTemplate": "https://www.discogs.com/sell/list?q={query}&format=Vinyl",
"enabled": true
}
]
}See config/vendors.json.example for more examples.
The server exposes REST endpoints under /api/*.
| Endpoint | Method | Description |
|---|---|---|
/api/auth/status |
GET | Check Spotify login state |
/api/auth/login |
POST | Start Spotify OAuth flow |
/api/auth/callback |
GET | OAuth callback handler |
/api/auth/logout |
POST | End session |
/api/playlist?id={playlist_id} |
GET | Playlist details + Discogs matches |
/api/user/playlists |
GET | Current user's playlists |
/api/discogs/search |
POST | Search Discogs by artist/album/year/track |
/api/discogs/batch |
POST | Batch search for multiple tracks |
/api/discogs/status |
GET | Discogs session status |
/api/discogs/login |
POST | Discogs token login (per user) |
/api/discogs/logout |
POST | Discogs logout |
/api/discogs/wishlist |
GET | Discogs wishlist (requires Discogs login) |
/api/discogs/wishlist/add |
POST | Add release to wantlist |
/api/discogs/library-status |
POST | Wishlist/collection flags for URLs |
/api/discogs/curation/candidates |
POST | Candidate matches (admin/curation) |
/api/discogs/curation/save |
POST | Save curated match |
/api/config/vendors |
GET | Vendor config for frontend |
src/main/java/Server/— HTTP server, routes, sessions, rate limitingsrc/main/java/com/hctamlyniv/discogs/— Discogs client, cache, normalization, modelssrc/main/java/com/hctamlyniv/DiscogsService.java— Discogs matching facadesrc/main/frontend/— static frontend (HTML/CSS/JS)
- No Discogs token: matching falls back to Discogs web search URLs.
- Sessions use Redis when configured and fall back to in-memory storage for local development.
- The app only uses metadata; it does not download or store any audio.
See License.txt.