Spotify's app is great at finding music and mediocre at ordering it. If you've
ever built a two-hour playlist for an event and watched it lurch from a slow ballad
straight into peak-time energy because the app has no idea what a "set" is, you
know the gap. So I built a small open-source CLI to fill it —
setlisted — and in the process got a
front-row seat to Spotify quietly renaming fields in its API in a way that broke
things without looking broken. Both halves are worth sharing.
What it does
setlisted is headless — no UI, just a CLI you point at your account — and it does
three things the app doesn't:
- Discovery mixes that don't repeat. Build a mix that actually explores, without the same twelve tracks the algorithm always falls back to.
- Hour-targeted event sets. "Give me exactly 90 minutes" and it builds to the clock, not a rough guess you then trim by hand.
- Sequencing from metadata alone. This is the interesting one. It orders a playlist into a flow — a warm-up, a build, a peak, a comedown — using only the track metadata Spotify still exposes. And crucially, it'll sequence any playlist, not just ones it built. Point it at a pile of tracks in a random order and it hands back a set with an arc.
It's genre-agnostic by design. It works on a jazz-rap crate the same way it works
on an amapiano one, because it reasons about the shape of a set, not a hard-coded
genre model.
Why "from metadata alone" is the whole trick
Here's the constraint that shaped the entire project: the rich signals you'd
want for sequencing keep disappearing from the API. The obvious approach —
lean on detailed audio analysis and genre tags to order tracks — is a trap,
because those are exactly the fields a platform trims when it tightens developer
access.
So setlisted is deliberately built on the durable metadata — the stuff that's
been stable and is likely to stay — and treats anything richer as a bonus it can
live without. That's a design principle worth stealing for anything built on
someone else's API: assume the nicest field you're using will be taken away, and
architect so that losing it degrades your output instead of killing your program.
I did not arrive at that principle by being wise. I arrived at it by getting
burned.
The break: renamed fields that didn't announce themselves
Partway through, my playlist-reading code started returning nonsense — not
errors, nonsense. The kind of failure that's worse than a crash because
everything keeps running.
Spotify had reorganised part of its API response. A field that used to hold a
track object in a given position had been renamed, and — the part that cost me
an afternoon — the old name still existed in the response, now holding a
boolean instead of the object it used to. So my code, reading the old field,
didn't get an error or a null. It got True. It read a boolean where it expected a
song and sailed on, producing confidently wrong output from a value that was the
right shape to not trip any of my checks.
That's the whole lesson in one bug: an API doesn't have to go down to break you.
It can rename a field and leave a differently-typed value in the old one, and a
naive reader will trust it. The fix wasn't just "use the new name" — it was
teaching the tool to validate the type of what it reads, so a boolean where a
track should be is caught loudly instead of flowing downstream as truth.
And it wasn't a one-off: chasing that led me to more quietly-renamed fields,
including developer-tier metadata that had simply vanished for apps like mine. Each
one was invisible until something downstream produced a subtly wrong result.
What I'd tell anyone building on a third-party API
The music tool is the fun part. The transferable part is this, and it's cost me
real time to learn:
- Validate types, not just presence. "The field exists" is not "the field holds what I think." A renamed field leaving a boolean behind passes every existence check and every null check. Assert the type of what you read.
- Build on the durable subset; treat rich fields as bonuses. The nicest, most-detailed fields are the first to get restricted. If your program needs them, it's one policy change from dead. If it merely benefits from them, it degrades gracefully.
- A renamed field is a silent break, so diff the raw response. When output goes subtly wrong and nothing errored, stop debugging your logic and look at the actual bytes the API returned. The change is usually right there, hiding in a field name.
- Headless + deterministic makes the break findable. Because setlisted is a CLI with reproducible output, "this input used to produce that set and now doesn't" is a testable statement. A GUI that just "feels off" gives you nowhere to stand.
Grab it
setlisted is open source and MIT-ish — the repo's
here. If you build event sets, run
discovery mixes, or just want your playlists to have an arc instead of a random
order, it's built for exactly that, and it's built to keep working the next time
the platform reshuffles its API under everyone.
If you only take the engineering lesson and skip the music: type-check what you
read from an API you don't control, and never depend on the field most likely to
be taken away. That one habit would have saved me the afternoon I spent watching
a boolean pretend to be a song.
Built and maintained by someone who wanted better playlists than an algorithm
would give and got a masterclass in third-party-API fragility as a bonus. The tool
is genuinely what I use; the API war stories are unfortunately all real.
🤖 Drafted with AI assistance from my own homelab notes, logs and repos, then reviewed and edited before publishing.
Top comments (0)