Building Off the Algorithm: 1,344 Anti-Algorithm Playlists, Start to Finish
The product: Fringe FM — playlists sorted by mood, activity and era, live at fringefm.net.
Where this came from
This whole project owes its existence to Sonosaurus — a previous build of mine that lets me request playlists by voice through Siri. The setup: a bot running on my home network listens for a Shortcut trigger from my phone or watch, takes a natural-language prompt ("something cinematic and Brazilian for cooking dinner", "the kind of thing you'd hear in a Berlin record shop at 4pm"), and three things happen at once. Claude generates a tracklist. The music starts playing on Sonos. The same tracklist hits the Spotify API to create a real, persistent Spotify playlist with a deliberately silly name — something like "Wrestling With Gravy" rather than "Chill Vibes 2026" — and the playlist link plus tracklist gets pushed to a Telegram bot so I can save it, share it, or come back to it later. End-to-end, voice-to-music-with-shareable-link in about twenty seconds.
Sonosaurus was a hobby tool — a faster way to get music going than scrolling through my own library or arguing with Spotify's algorithm. But two things kept nagging at me after I'd been using it for a few months.
First: the playlists Sonosaurus produced were genuinely better than what I was getting from any of Spotify's editorial or algorithmic suggestions. The deep-cut, taste-driven, anti-default-pick prompt I'd written for it was doing real curation work. Better than what Spotify served me, better than what my friends sent me, occasionally better than what I'd have picked for myself.
Second: Sonosaurus was a one-person tool. Every playlist it generated landed in my Telegram and on my Sonos — but the curation engine doing the work was, by any reasonable measure, capable of serving a lot more than one household. Friends would ask me to "do a Sonosaurus" for their dinner party. The Spotify links I'd share would get follows from people who weren't me. There was clearly demand for this kind of curation beyond what a voice-triggered home setup could serve.
The idea for this project was the obvious next step: take Sonosaurus's exact technical pattern — generate a tracklist with Claude, create a real Spotify playlist via the API, give it a memorable name — but instead of running it on-demand for me, run it ahead of time across every plausible mood/activity/era combination, save every output, and put the whole library on a public website. The single-user voice-controlled hobby becomes a 1,344-playlist public resource.
The Sonosaurus DNA is throughout this project. The prompt that selects the tracks is a direct descendant of the Sonosaurus system prompt, just with the discipline cranked up because public-facing playlists need to be more consistent than throwaway dinner-party background music. The playlist naming voice — the funny, slightly absurd, deliberately-not-corporate names — came directly from what Sonosaurus already did via Telegram. Even the Spotify-playlist-creation code is a near-port of the same logic, just batched across 1,344 cells instead of called one at a time. If Sonosaurus is the single-user, voice-triggered version of this idea, Fringe FM is the same idea scaled across every listening context I could think of, pre-generated, and made public.
The brief
I wanted to build a website that solved one specific problem: people who are tired of Spotify's recommendations and want to find music outside the algorithmic loop. Not another playlist app, not another "AI DJ", not a social music network. Something dumber and more useful — pre-curated playlists with deep, taste-driven track selection, organised by mood, activity, and era, ranked to be discoverable through search engines.
The mental model was record-collector tier curation at scale. The kind of selections you'd hear at a good independent record shop or read about in the back pages of Wire magazine. Light in the Attic, Music From Memory, Numero Group, Habibi Funk, kankyō ongaku — that whole reissue-driven, deep-cut sensibility, applied across every mood and listening context.
The constraint that defined the architecture: I'm one person, and I needed to ship the whole library. So everything had to be automatable, but the judgement of what made a good playlist couldn't be. The bet was that I could put enough taste into a system prompt that a language model would consistently apply it across 1,344 different contexts.
The taxonomy
The library is built on three dimensions multiplied together. 25 moods: melancholic, contemplative, euphoric, restless, defiant, nostalgic, tender, dreamy, anxious, sombre, hopeful, weary, joyful, lonely, focused, energised, bittersweet, yearning, meditative, playful, romantic, introspective, ethereal, brooding, uplifting. 20 activities: coffee, reading, driving, cooking, late-night, working, dinner-party, hangover, rainy-day, walking, studying, cleaning, travelling, early-morning, before-bed, gardening, running, solo-evening, road-trip, afternoon-slump. 3 eras: vintage (reissue-tier, pre-1990 lean), modern (post-2010 underground), timeless (cross-decade).
25 × 20 × 3 = 1,500 raw combinations. After filtering incompatible pairings (you don't need a euphoric playlist for bedtime, or a sombre one for running), the final library is 1,344 cells. Each cell becomes one playlist of 15 tracks.
The URL structure is /playlists/{mood}-{activity}-{era} — for example /playlists/melancholic-coffee-vintage or /playlists/euphoric-driving-modern. Hub pages roll up by single dimension: /melancholic lists all 36 melancholic playlists across activities and eras, /coffee lists all 70 coffee playlists. Natural internal linking, long-tail search coverage, no duplicate content.
How the tracks were chosen
This is the part where the actual work of curation happens — and the part where most "AI playlist" projects fail. Out of the box, large language models will give you Bonobo, Tycho, Khruangbin, Mac DeMarco, Nils Frahm, and Bon Iver in every playlist. They've been trained on every Spotify-Discover-Weekly-adjacent thing ever written, and they reach for the safe pick by default.
Getting Claude to produce taste-driven, deep-cut selections required a heavily engineered system prompt. The key components were these.
Numeric listener targets. Most picks needed to be artists with under 100,000 monthly Spotify listeners. The mix was specifically: 65% under-100K, 25% mid-tier, 10% accessibility anchors. Forcing the model to think about actual listener counts shifted the centre of gravity dramatically.
The First Track Rule. Three tiers of forbidden openers, in order of severity. Tier 1: AI defaults (Norah Jones, Sade, Massive Attack — the algorithm's comfort food). Tier 2: "indie respectable" — Khruangbin, Mac DeMarco, Big Thief, Japanese Breakfast — the artists that signal taste without actually demonstrating it. Tier 3: ambient defaults — Bonobo, Four Tet, Floating Points, Nils Frahm. These artists could appear later in a playlist as anchors but never as the opening track. The opener sets the brand promise: this is going to be different.
Era and geography mandates. Every playlist had to include at least 4 pre-2010 picks, at least 2 pre-1990 picks, 3-4 non-Anglo selections, and span at least 5 distinct subgenres. This forced diversity that the model wouldn't generate on its own.
Explicit reissue label naming. The prompt named specific labels — Light in the Attic, Music From Memory, Numero Group, Habibi Funk, Awesome Tapes from Africa, Soundway, Analog Africa, Glossy Mistakes — to give the model a vocabulary for the aesthetic. Once you tell it "think Music From Memory tier", you get Suzanne Ciani, Pauline Anna Strom, Hiroshi Yoshimura, Pep Llopis, Suso Saiz, Roberto Musci. Without that vocabulary, you get Tycho.
Curator angles. A list of ten "lenses" — second-person British humour, lost female composers, post-punk diaspora, kosmische, MPB deep cuts, chanson modernity, etc. — two random ones injected into each prompt to shift the focus and prevent the library feeling homogeneous.
On temperature
Worth a small detour because it matters more than people think.
When you call a language model, you set a parameter called temperature, usually between 0.0 and 2.0. It controls how much randomness the model uses when picking its next word at each step. At 0.0, the model always picks the single most probable word — same input produces same output, every time, completely deterministic. At 2.0, the model samples wildly from less probable options, which produces creative but often incoherent output. Most real-world applications sit between 0.3 (technical writing, factual answers) and 1.0 (creative writing, brainstorming).
For this project I started at 1.0 — the upper end of "creative but coherent". The output was genuinely interesting but a bit too wild. I'd get playlists where the model would commit to an obscurity so deep it had invented an artist that didn't exist, or include three tracks from genuinely-existing-but-only-on-Bandcamp artists that Spotify had never heard of. The kind of failure mode where you can see the model trying so hard to be adventurous that it's stopped being useful.
Dialled down to 0.9 and the difference was immediate. Still genuinely surprising picks — Pep Llopis, Sibylle Baier, Mort Garson's gardening album, kosmische deep cuts most people have never heard. But now mostly real tracks that actually existed on streaming services. The sweet spot for music curation in this style turned out to be a single decimal step below "maximum reasonable creativity."
Worth knowing if you're doing similar work: temperature is not a binary on/off for creativity. Small movements have large effects.
On the Batch API
The thing that made this project economically and practically feasible was Anthropic's Batch API.
A regular API call is synchronous: you send a request, the model thinks, the response streams back, you wait. For 1,344 sequential requests at maybe 8-15 seconds each, that's hours of wall-clock time before you can do anything else. Anthropic's Batch API inverts this: you upload all 1,344 requests as a single batch file, Anthropic processes them in parallel across their fleet, and you collect the results when they're done. Crucially, you also get a 50% discount on tokens for using it.
The trade-off is supposed to be that batch jobs can take up to 24 hours to complete. In reality, my 1,344 playlists came back in roughly 10 seconds. Anthropic's infrastructure is genuinely good at parallel inference, and a batch of this size barely registers.
The implications are significant. For one-time bulk generation tasks like this, the cost of Opus drops to about the same as Sonnet's regular pricing — meaning you can afford to use the best model for tasks you'd normally compromise on. Combined with prompt caching (the system prompt is identical across all 1,344 calls so it gets cached after the first one, dropping per-call costs further), the total Opus spend for generating the entire library was around $15.
The same library generated via individual synchronous calls would have cost roughly $30 and taken six to ten hours of wall-clock time. Batch turned it into "submit it, make tea, come back to a complete library."
This is the right tool for any "I need to do N variations of the same prompt" problem. Bulk content generation, dataset creation, evaluation runs, A/B testing different prompts at scale. The cost and time savings stack and they're substantial.
The human involvement
I want to be honest about this because it matters: the model picked the tracks, but I shaped how it picked them, and I made every architectural decision about what kind of library this should be.
Specifically, the human work was deciding the brand positioning (anti-algorithm, record-collector tier, not another mood playlist app); designing the taxonomy and compatibility rules; writing and iterating the system prompt — probably 30+ revisions across the course of the project, including the three-tier forbidden opener rule, the listener-count targets, the reissue label vocabulary, the era/geography mandates, and the temperature tuning; running test playlists, listening to them, flagging when the model was reaching for the safe pick, adjusting the prompt; auditing the final library for duplicates, opener leaks, structural integrity; renaming all 1,344 playlists using a separate Claude pass with the Sonosaurus-style humour brief, then re-auditing for duplicates and bland names; and making every call about what to do when things broke.
What the model did: produce 1,344 specific tracklists that followed those rules. With taste. Without me needing to write each one.
The track selection logic was working within a week. The brand voice and the discipline to keep the prompt tight took another month of iteration.
The pipeline
The mechanical steps from "taxonomy file" to "1,344 published playlists" went like this. Generate taxonomy — a Python script produces a JSON file listing all 1,344 mood/activity/era combinations. Generate playlists — Anthropic Batch API processes all 1,344 in parallel, each cell gets one Claude call with the full system prompt and its specific vibe metadata. Output: 1,344 JSON files, one per playlist, each containing 15 artist/title pairs. Audit the library — duplicate detection (no two playlists with identical tracklists), opener-leak detection (catch any forbidden artists as openers), structural integrity check. Rename playlists — separate Claude pass to give each playlist a distinctive, funny, British-leaning name in the Sonosaurus style. Initial Haiku run produced bland results; Sonnet was meaningfully better at the humour. Resolve track URIs — for each track in each playlist, search Spotify and Tidal to find the actual streaming URI. This is where most of the time and pain went. Create real playlists — for each platform, OAuth as the brand account, POST a new playlist, populate it with the resolved URIs, save the playlist ID back into the JSON. Generate the static site in Lovable — by this point each playlist JSON contains everything the site needs: name, mood, activity, era, vibe metadata, the 15 tracks, the Spotify URI, the Tidal URI, the Spotify playlist URL, the Tidal playlist URL. I handed the JSON files to Lovable to produce fringefm.net and described what I wanted: a page per playlist with embedded players for both services, hub pages by mood/activity/era, schema.org markup for SEO, and a homepage that surfaces the brand. Lovable generated the whole thing. The fact that the whole pipeline outputs structured data was the entire point — any frontend tool that can consume JSON can render this library.
Steps 1-4 took about a week. Step 5 on Spotify took two weeks. Step 5 on Tidal took twelve minutes. More on that below.
On letting tools do their jobs
A point worth making explicitly: the JSON-first pipeline meant the static site was a separate problem that could be solved with a separate tool. I didn't need to learn a framework, write a templating engine, or hand-code 1,344 HTML pages. Lovable took the structured data and produced the site. Same JSONs would have worked fine in Next.js, Astro, Eleventy, or anything else that reads files and renders templates. The discipline of keeping the data layer clean and platform-agnostic meant the frontend choice was reversible and low-stakes. If Lovable doesn't suit later, I swap it for something else, same JSONs, same site shape.
This is the underrated benefit of structuring AI-generated work this way. The model produces data, not deliverables. Every downstream step — naming, verification, playlist creation, site generation — operates on the same JSONs and adds to them. No regenerating, no re-prompting, no compounding errors. The library exists as data first and as anything else second.
What went wrong, and how Claude and I worked through it
A note before this section: Claude (Anthropic's assistant) wrote essentially all the code in this project. It also made most of the mistakes I'm about to describe. That's not a complaint — it's an accurate description of how AI-assisted development works in 2026. Claude is genuinely excellent at code, and also confidently wrong sometimes. Knowing where to apply scepticism is the human's job.
The opener leak problem. Early playlist generations kept defaulting to Norah Jones, Khruangbin, or Bonobo as opening tracks despite the prompt saying "be adventurous". When I flagged that the openers were too safe, Claude proposed the three-tier forbidden list. Once specific artists were named explicitly, Claude genuinely avoided them. The lesson: a vague instruction like "avoid algorithm-tier picks" isn't enough; you have to name the specific traps.
The duplicate playlist problem. Claude's first playlist-naming pass used Haiku for cost reasons and produced 189 duplicate names across 1,344 playlists ("Late Night Lounge" appeared seven times). The audit script Claude had also written caught this. We re-ran with Sonnet for the same task — meaningfully better at humour and variety — plus a global collision check. Worth knowing: humour and wordplay are tasks where model size genuinely matters. Haiku is fine for routine work; for anything depending on creative voice, Sonnet or Opus is the right call.
The Spotify rate-limiting saga. The first version of the Spotify script that Claude wrote had 5 parallel workers and no rate limiting. It got the Fringe FM app soft-banned for 10.5 hours within minutes. Each subsequent iteration was Claude rewriting its own previous attempt as new ban data came in. First attempt: 5 workers, no rate limit — soft-banned for 10.5 hours after ~60 tracks. Second attempt: Claude added a token bucket, dropped to 2 workers at 1 req/sec — soft-banned for 7 minutes. Third attempt: 1 worker at 0.5 req/sec, capped retry-after, abort on long bans — got through 446 tracks before another ban. Fourth attempt: Claude pivoted from Client Credentials to user OAuth. User-authenticated traffic has dramatically more headroom because Spotify treats it as "this user is using their own data" rather than "this app is scraping us".
Worth noting that Sonosaurus had been hitting the same Spotify Create Playlist endpoint dozens of times a week for months without ever being rate-limited — but Sonosaurus only creates one playlist per call, with hours between calls. The pattern that triggers Spotify's abuse detection is volume and burstiness, not the endpoint itself. The same code running at "background music for a Wednesday evening" cadence is invisible to Spotify; running at "build an entire library in one go" cadence is exactly what their abuse layer is designed to catch.
Even after the OAuth pivot, Spotify imposes a daily quota on new apps. Early sessions capped out around 65 playlists per 24-hour window before being soft-banned for another 24 hours. The Spotify side ran as a Windows scheduled task firing every 25 hours, chipping away at the library overnight.
Interestingly, the per-day throughput climbed over time. By the second week, the same script was creating around 150 playlists per session before being throttled — more than double the early-run ceiling. Two things were going on, and both helped.
The first was a persistent local cache of resolved track URIs. The same track shows up in multiple playlists across the library — Caetano Veloso's "Cucurrucucú Paloma" might fit melancholic-cooking-vintage, melancholic-rainy-day-timeless, and tender-coffee-vintage. Once Claude had searched Spotify for that artist and title and stored the URI, every subsequent playlist that needed it skipped the API call entirely. As the cache filled up, each playlist required fewer real Spotify requests to assemble. By the second week the cache was hitting on roughly 30-40% of tracks, which meant the daily quota stretched proportionally further before being throttled.
The second was probably trust. I can't prove this mechanism, but the most likely explanation for the remaining speedup is that Spotify's abuse detection treats apps with sustained, predictable, well-behaved traffic patterns more leniently over time. New apps making bursty requests at unpredictable times get throttled aggressively; an app that does roughly the same thing every 25 hours from the same account looks less suspicious. The infrastructure is essentially learning that this app isn't a scraper.
Useful to know if you're building anything similar: the early days are the hardest. Cache aggressively. Stay polite and consistent. Both effects compound.
The Spotify endpoint removal that wasn't (and where Claude got it wrong). Spotify's February 2026 developer API migration removed POST /users/{user_id}/playlists — the historical endpoint for creating playlists. Claude read the changelog, saw "REMOVED" against playlist creation, and confidently told me dev mode apps could no longer create playlists at all. It walked me through a substantial architectural pivot: drop the "real Spotify playlists" model entirely, switch to per-track embeds, restructure the website to render 15 individual track players per page with a custom JS queue, the whole thing.
I was halfway down that path when I cross-checked with ChatGPT, which immediately pointed out that POST /me/playlists is the documented replacement endpoint — same purpose, different URL shape. The "REMOVED" line in the changelog only applied to the old users/{user_id} form. The new me/playlists form was right there in the same docs.
When I fed ChatGPT's response back to Claude, it acknowledged the mistake immediately and produced the correct script in about ten minutes. Cost of the detour: maybe three hours of work building toward an architecture I didn't need.
The lesson isn't "Claude is bad" — Claude was extremely good at every other stage of this build, including parts where it had to debug its own previous mistakes from clues in HTTP responses. The lesson is that even excellent assistants can confidently misread a single line of documentation, and the fix is to triangulate across multiple sources before committing to a major architectural decision. Trust but verify, especially for anything that would require throwing away work.
The Tidal endpoint discovery process. Tidal's developer API documentation isn't crawlable — their reference site is a JavaScript-rendered Swagger UI that returns an empty shell to scrapers. Claude built diagnostic scripts that probed various URL shapes and Accept-header combinations, with verbose request/response logging. Every probe returned 404 with an empty body. After several rounds of guessing, I gave up and just opened the docs in my browser, copied the visible endpoint list, and pasted it back to Claude. Claude spotted the issue immediately: the endpoint is GET /v2/searchResults/{query} — camelCase, not lowercase. Tidal's API gateway is case-sensitive on the path, so every previous attempt at /searchresults/ was being rejected at the routing layer before reaching the search service. Single-letter casing bug, hours of misdiagnosis on Claude's part because it couldn't see the docs directly.
The JSON-edit-with-regex disaster. When migrating a stale flag out of the playlist JSONs, Claude suggested a one-line PowerShell regex to strip the field. I ran it. The regex removed the field but left trailing commas behind, breaking the JSON in every file. 1,344 files damaged in one command. Claude then wrote a repair script using json.loads/json.dumps that fixed everything in one pass. The actual lesson Claude articulated afterward: never use regex to edit structured data, even for trivial changes. Use the data format's actual parser. Worth noting that Claude knew this rule going in — it just didn't apply it to a "small" task. AI assistants, like humans, can violate their own best practices when the task feels too quick to warrant care.
Spotify vs Tidal: night and day
The most striking part of this whole project is the difference between the two platforms' developer experiences.
Spotify's developer API for new apps is hostile. The rate limits aren't published, the abuse detection is sensitive to specific request patterns (consecutive failed searches trigger soft-bans even at conservative rates), and the daily quota for unverified apps starts somewhere around 800-1,000 API calls per 24 hours. Working through two weeks of scheduled restarts with gradually-increasing throughput is the only path forward for a small developer building a public-facing music site. The extended access tier that would unblock this requires 250,000 monthly active users — a chicken-and-egg requirement that effectively blocks any new app from being viable at launch.
Tidal's developer API for new apps is generous. Once the camelCase bug was fixed, the full library of 1,344 playlists was created in twelve minutes. No daily quota, no soft-bans, no scheduled restart pattern needed. The 1 req/sec rate limit Claude built into the script as a precaution turned out to be vastly more conservative than necessary. Tidal's user-authenticated API tolerated continuous traffic without issue.
The hit rate difference is interesting too. Spotify resolved roughly 84% of tracks; Tidal resolved closer to 78%. The catalogs overlap but aren't identical — particularly in the deep reissue territory where Music From Memory titles, Awesome Tapes from Africa releases, and certain Brazilian, Japanese, and library music catalogues are present on one service but not the other. Some of the deepest cuts in the library are on Tidal but not Spotify; others vice versa.
The implication for "anti-algorithm" branding. Tidal's audience already self-selects for caring about music over convenience. Their userbase is smaller than Spotify's by an order of magnitude, but it skews heavily toward audiophiles, DJs, music journalists, and the exact "bored with the algorithm" demographic this project targets. The brand probably fits better there. Spotify still gets the project's larger audience reach, but Tidal is where the actual readers are.
What I'd do differently
Three things, in order of how much they would have saved.
Start with Tidal, not Spotify. I assumed Spotify-first because Spotify is bigger. But Spotify's API is hostile to new apps and ate weeks of iteration. Tidal was twelve minutes. If I'd started with Tidal I'd have had a working product two weeks earlier and could have used that as a demo to apply for Spotify extended access.
Build the diagnostic-first pattern from the start. By the time we got to Tidal, Claude had landed on a useful pattern: build a small diagnostic script that does one operation end-to-end and prints every request/response in full detail. Before scaling to 1,344 of anything, run the diagnostic with one. This caught the camelCase bug immediately. Earlier in the project — through all the Spotify rate-limiting iterations — Claude was writing production-shaped scripts directly and learning their flaws by running them against the real API. Going diagnostic-first from day one would have saved most of those iterations. Worth establishing this as a default for any new API integration with an AI assistant.
Treat API documentation as untrustworthy until proven otherwise. Both Spotify and Tidal had documentation that didn't match runtime behaviour in important ways. Spotify's changelog implied playlist creation was removed entirely (it wasn't — just renamed). Tidal's npm wrappers referenced endpoints that returned 404 in production (they were lowercase in the wrapper code but actually camelCase). The only reliable knowledge came from running real HTTP requests and reading what came back.
Costs
For anyone considering similar work: Anthropic API (all generation, naming, regeneration) was under $25 total. Spotify and Tidal developer accounts are free. Lovable (static site) was covered by the free tier. Cloudflare Pages hosting is free at this scale. Domain: ~£12/year.
My time was a few weeks of calendar time, but rarely a deliberate sit-down session. Mostly five minutes here, twenty minutes there — whenever I had a spare moment between life and the children. The natural rhythm of the project turned out to be: prompt Claude, run something, hit a wall, hit Claude's session limit, go play with the kids until the quota reset, come back and pick up where I'd left off. The session limits were arguably load-bearing — they forced the breaks that would have been hard to take otherwise.
The economics of this kind of project are wildly different from a year or two ago. The model spend for generating 1,344 carefully-curated playlists was roughly the cost of a takeaway. The infrastructure is free. The work that mattered was the taste decisions, the prompt engineering, and the patience to keep debugging APIs that don't cooperate.
That's the part you can't shortcut.