Describe what you want to hear. SpotifAI turns natural language into a Spotify playlist, personalised with your real listening history.
Stack: Python · FastAPI · Spotipy · Claude API (Anthropic) · DuckDB · Jinja2 · Vanilla JS
SpotifAI is currently in Spotify Development mode, which limits access to 25 whitelisted users. To try the demo, send your Spotify account email to hey@jeremymarchandeau.com and I’ll add you.
SpotifAI breaks Spotify's filter bubble by letting you generate playlists from free-text descriptions:
"Post-rock instrumental, Japanese influence, since 2010" "Late-night jazz fusion, low energy" "French 90s boom bap hip-hop with jazz samples"
Your prompt is interpreted by Claude, enriched with your actual Spotify listening profile (top artists, top tracks, recently played), and turned into a curated track list saved directly to your Spotify account.
- Natural language input — describe any genre, mood, era, tempo, geography, or artist influence
- Personalised recommendations — your Spotify listening history is used as context for Claude
- Spotify save — generates a playlist in your account with one click
- Listening history — all generated playlists stored locally in DuckDB
- Re-use prompts — relaunch any past playlist from the history dashboard
- 30s preview — inline audio previews directly in the track list
[Browser] — natural language prompt
↓
[FastAPI /generate]
↓ load user profile from DuckDB
[Claude API] — extract criteria (genres, artists, mood, year range…) → JSON
↓
[Spotify /search] — multiple targeted queries, deduplicated + filtered
↓
[Claude API] — generate playlist title + description
↓
[Spotify /me/playlists] — create playlist in user's account
↓
[DuckDB] — save to history
↓
[Browser] — display results + Spotify link
spotifai/
├── api/
│ ├── spotify.py # Spotipy wrapper: OAuth, user data, search
│ ├── llm.py # Claude API: criteria extraction, title generation
│ └── routes.py # FastAPI endpoints
├── core/
│ ├── profile.py # User profile sync logic
│ ├── generator.py # Playlist generation pipeline
│ └── prompts.py # LLM prompt templates
├── db/
│ ├── database.py # DuckDB connection + schema init
│ ├── models.py # Pydantic models
│ └── queries.py # CRUD operations
├── static/ # CSS + JS (vanilla, no build step)
├── templates/ # Jinja2 HTML templates
├── docs/ # Architecture decisions, task tracking, spec
├── main.py # FastAPI entry point
├── config.py # Settings via pydantic-settings
└── .env.example # Environment variable template
- Python 3.11+
- A Spotify Developer App (free)
- An Anthropic API key (Claude)
git clone https://github.com/jeremy6680/spotifai.git
cd spotifai
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtcp .env.example .envEdit .env with your credentials:
SPOTIFY_CLIENT_ID=your_spotify_client_id
SPOTIFY_CLIENT_SECRET=your_spotify_client_secret
SPOTIFY_REDIRECT_URI=http://127.0.0.1:8000/callback
ANTHROPIC_API_KEY=your_anthropic_api_key
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
SECRET_KEY=your_random_secret_key
DUCKDB_PATH=./data/spotifai.duckdbIn your Spotify Developer Dashboard:
- Open your app → Edit Settings
- Add
http://127.0.0.1:8000/callbackto Redirect URIs - Add your Spotify account email to User Management (required in Development mode)
- Save
Note: Use
127.0.0.1, notlocalhost— Spotify blockslocalhostredirect URIs in Development mode.
uvicorn main:app --reload --port 8000Open http://127.0.0.1:8000 in your browser.
- Log in with your Spotify account
- Sync your profile — click "Sync mon profil" to load your listening history into DuckDB (takes ~5s)
- Describe your playlist in the text field, or click a suggestion chip
- Generate — Claude interprets your prompt and Spotify returns matching tracks
- Save to Spotify — creates a playlist in your account (use the track links to add songs manually, see Known Limitations)
- History — all generated playlists are saved locally and relaunchable
Spotify imposes significant restrictions on apps in Development mode (i.e. not approved for Extended Quota):
| Endpoint | Status | Workaround |
|---|---|---|
GET /recommendations |
❌ Blocked since late 2024 | Replaced by /search + LLM-generated queries |
POST /playlists/{id}/tracks |
❌ Returns 403 | Playlist created empty; tracks displayed with individual Spotify links |
GET /audio-features |
❌ Deprecated since 2024 | Genres inferred by Claude from artist names |
GET /artists genres |
Same workaround as above |
In practice: SpotifAI creates an empty playlist in your Spotify account and displays the generated track list with an "Open in Spotify" link per track. You add the tracks you want manually.
This is an external Spotify constraint, not a code issue. If Spotify lifts these restrictions, full functionality can be restored with minimal changes to api/spotify.py.
Spotify's Development mode limits access to 25 whitelisted users. To add a user: Spotify Developer Dashboard → your app → User Management.
Key technical decisions are documented in docs/DECISIONS.md:
- ADR-007 — Genres inferred by LLM instead of Spotify API (empty arrays)
- ADR-008 —
/searchstrategy replacing/recommendations - ADR-009 — Partial Spotify save (empty playlist + individual track links)
- ADR-010 — No
/apiprefix on routes
This project is designed to evolve. Phase 2 (this version) is a functional single-user tool. Phase 3 targets:
- dbt medallion pipeline — bronze/silver/gold layers on top of the existing DuckDB
- CrewAI multi-agent system — Music Profiler, Criteria Interpreter, Playlist Curator
- Airflow event-driven sync — replace manual profile sync with polling-based triggers
- React frontend — replace Jinja2 templates (FastAPI routes stay unchanged)
- Open-source release — Docker, GitHub Actions, full documentation
Full roadmap in docs/SpotifAI_CDC_v2.md.
SpotifAI is deployed via Docker on a self-hosted Coolify instance (Hetzner).
# Build and run locally with Docker Compose
docker compose up --buildFor production deployment:
- Build pack:
Dockerfile(multi-stage,python:3.11-slim) - Exposed port:
8000 - Persistent volume:
/app/data(DuckDB file) - All credentials injected via environment variables — never baked into the image
See Dockerfile and docker-compose.yml at the project root.
- Spotify API — free for non-commercial use
- Claude API — ~$0.003 per playlist generation (claude-sonnet-4, as of March 2026)
Jeremy Marchandeau — web2data.org
This project is part of a web developer → data/AI engineering transition. See the blog series for articles covering the technical choices made here.