Building Spotify Receipt: OAuth against a locked-down API
How I built a Spotify stats app themed as a printable receipt — a proper server-side PKCE OAuth flow, and adapting honestly when the API kept saying no.

I wanted a small portfolio project to practise one specific thing properly: an OAuth flow against a real, documented third-party API. Spotify Receipt is the result — a Spotify Wrapped-style stats app that prints your listening as a retro thermal receipt you can share.
What it does
You log in with Spotify and get a receipt of your listening — top artists, tracks, genres, and the decades your music comes from — across three time ranges. Switch the range and the receipt reprints line by line, the paper held still. You can download it as an image or copy a link that unfurls with the receipt in Slack or iMessage. There's also a playlist-library view that summarises your whole collection.
How it's built
It's a single Next.js app deployed on Railway via push-to-deploy, gated by CI. Auth is the Authorization Code flow with PKCE, handled entirely server-side: the code exchange and token refresh happen in route handlers, tokens live in an encrypted httpOnly session (iron-session), and the browser never sees them. All data sits behind a MusicDataSource interface, with the Spotify implementation cached per user via Next's data cache to respect rate limits. The shareable image is rendered on the server with Satori, and share links are stateless — the receipt is compressed and HMAC-signed straight into the URL, so there's no database to store snapshots.
Adapting to a locked-down API
Spotify has quietly cut most of its API for new apps. Audio features, recommendations, and related artists all return 403; development mode is capped at five allowlisted users and requires the owner to have Premium. So the first thing the app does is probe the live API and record what actually works — and the plan adapted to those results rather than to what old tutorials assume. The clearest example came in the playlist phase: I designed and built per-playlist composition analysis, then discovered that reading a playlist's tracks returns 403 for owned and followed playlists alike. Rather than fake it, I pivoted to a collection-level playlist-library overview built purely from the metadata that does come back. Building honestly around what the API permits turned out to be the most interesting part of the project.
What I'd build next
A few directions, all designed to slot in behind the existing data-source interface: mood and energy via an external audio-analysis API to replace what Spotify removed; ingesting the user's own Spotify data export for true lifetime play counts the API never exposed; per-playlist share cards; and a keep-warm ping so shared links always unfurl instantly. If Spotify ever grants extended access, open public sign-up and richer playlist analysis become possible too.
Try the live demo, or read more on the project page.