<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Freqblog</title>
    <description>The latest articles on Forem by Freqblog (@birrings).</description>
    <link>https://forem.com/birrings</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3873922%2F4ee0520d-f25c-4231-b050-9aaf049e8ae7.png</url>
      <title>Forem: Freqblog</title>
      <link>https://forem.com/birrings</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/birrings"/>
    <language>en</language>
    <item>
      <title>Spotify's audio_features API died in 2024. Here's what I built to replace it</title>
      <dc:creator>Freqblog</dc:creator>
      <pubDate>Sat, 11 Apr 2026 18:06:40 +0000</pubDate>
      <link>https://forem.com/birrings/spotifys-audiofeatures-api-died-in-2024-heres-what-i-built-to-replace-it-3dn3</link>
      <guid>https://forem.com/birrings/spotifys-audiofeatures-api-died-in-2024-heres-what-i-built-to-replace-it-3dn3</guid>
      <description>&lt;h1&gt;
  
  
  Spotify's audio_features API died in 2024. Here's what I built to replace it.
&lt;/h1&gt;

&lt;p&gt;If you've used &lt;code&gt;spotipy&lt;/code&gt; to pull BPM, key, energy or danceability for tracks, you've already hit the wall. Spotify quietly deprecated their &lt;code&gt;audio-features&lt;/code&gt; endpoint in November 2024. No replacement. No migration path. Just a 403.&lt;/p&gt;

&lt;p&gt;I needed these features for an algo trading dashboard project (don't ask). After finding nothing usable in the market, I built my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the market looked like
&lt;/h2&gt;

&lt;p&gt;I spent a week evaluating alternatives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MeloData&lt;/strong&gt; — Essentia-based (same engine I ended up using), but ISRC-only lookup. If you don't have an ISRC for every track, you're stuck. Priced per-request at $0.002 which sounds cheap until you're running a catalog.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GetSongBPM&lt;/strong&gt; — Free, name-based, but only BPM and key. No energy, loudness, mood, danceability. Missing half the fields I needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cyanite&lt;/strong&gt; — Mood-focused, $0.01/track, upload-only. Great for what it does but expensive at scale and no name-based lookup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Soundcharts / Chartmetric&lt;/strong&gt; — Enterprise ($250+/mo), overkill, different problem space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AcousticBrainz&lt;/strong&gt; — What I actually wanted. Shut down in 2022.&lt;/p&gt;

&lt;p&gt;None of them did what Spotify did: give you all the audio features for a track just by passing artist + title.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A REST API with name-based lookup, file upload analysis, and 17+ fields per track:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BPM + confidence&lt;/li&gt;
&lt;li&gt;Musical key + mode (major/minor) + Open Key notation&lt;/li&gt;
&lt;li&gt;Time signature&lt;/li&gt;
&lt;li&gt;Energy, loudness (dBFS)&lt;/li&gt;
&lt;li&gt;Danceability, valence, acousticness, instrumentalness, liveness, speechiness&lt;/li&gt;
&lt;li&gt;Mood (happy/sad/aggressive/relaxed/party)&lt;/li&gt;
&lt;li&gt;Genre, gender (vocal type), timbre&lt;/li&gt;
&lt;li&gt;Popularity score&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MTG-Essentia&lt;/strong&gt; &lt;code&gt;RhythmExtractor2013&lt;/code&gt; for BPM, &lt;code&gt;KeyExtractor&lt;/code&gt; for key&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Librosa&lt;/strong&gt; for energy, loudness, and the Phase 2 descriptors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AcousticBrainz&lt;/strong&gt; 7.5M-track fallback for mood, genre, highlevel features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; + SQLite (WAL mode) for caching — cache hits return in &amp;lt; 100ms&lt;/li&gt;
&lt;li&gt;Zero disk writes — audio processed from &lt;code&gt;io.BytesIO&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The migration
&lt;/h2&gt;

&lt;p&gt;If you were using Spotify's audio-features, the switch looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (Spotify):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;spotipy&lt;/span&gt;

&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spotipy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Spotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth_manager&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SpotifyClientCredentials&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;audio_features&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4uLU6hMCjMI75M1A2tKUQC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tempo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (FreqBlog API):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.freqblog.com/lookup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;track&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blinding Lights&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;artist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The Weeknd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bpm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;energy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field mapping:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spotify&lt;/th&gt;
&lt;th&gt;FreqBlog API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tempo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;bpm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;key&lt;/code&gt; (0-11 int)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;key&lt;/code&gt; ("C-Major", "F#-Minor")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;energy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;energy&lt;/code&gt; (0-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;loudness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;loudness&lt;/code&gt; (dBFS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;danceability&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;danceability&lt;/code&gt; (0-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;valence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;valence&lt;/code&gt; (0-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;instrumentalness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;instrumentalness&lt;/code&gt; (0-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mode&lt;/code&gt; ("major"/"minor")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;speechiness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;speechiness&lt;/code&gt; (0-1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;time_signature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;time_signature&lt;/code&gt; (int)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus fields Spotify never had: &lt;code&gt;mood&lt;/code&gt;, &lt;code&gt;genre&lt;/code&gt;, &lt;code&gt;open_key&lt;/code&gt; (Camelot notation), &lt;code&gt;timbre&lt;/code&gt;, &lt;code&gt;popularity&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-analyzed catalog + fallback
&lt;/h2&gt;

&lt;p&gt;The API doesn't re-analyze everything on every call. There's a nightly ingest that pulls from Apple Music charts (GB/US/AU Top 100, IE/CA Top 50) plus ~100 genre seed categories. Anything in the catalog returns in under 100ms.&lt;/p&gt;

&lt;p&gt;For anything not in the catalog, it falls back to the AcousticBrainz 7.5M-track dataset for highlevel features. As a last resort, it can analyze an uploaded audio file directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  BPM and Key search endpoints
&lt;/h2&gt;

&lt;p&gt;Two endpoints the original Spotify API never had:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find tracks near 128 BPM&lt;/span&gt;
GET /bpm?q&lt;span class="o"&gt;=&lt;/span&gt;128&amp;amp;tolerance&lt;span class="o"&gt;=&lt;/span&gt;2&amp;amp;limit&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;span class="c"&gt;# Find tracks in 8A (Am) by Camelot wheel key&lt;/span&gt;
GET /key?q&lt;span class="o"&gt;=&lt;/span&gt;8A&amp;amp;limit&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful for DJ tools, playlist generation, music recommendation engines.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server
&lt;/h2&gt;

&lt;p&gt;I also shipped an MCP server (&lt;code&gt;music-metadata-mcp&lt;/code&gt;) so Claude and other LLMs can call it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx music-metadata-mcp &lt;span class="nt"&gt;--api-key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tools: &lt;code&gt;lookup_track&lt;/code&gt;, &lt;code&gt;find_tracks_by_bpm&lt;/code&gt;, &lt;code&gt;find_tracks_by_key&lt;/code&gt;, &lt;code&gt;bulk_lookup&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt;: 500 req/mo (good for side projects)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hobbyist&lt;/strong&gt;: £9.99/mo — 3,000 req/mo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Starter&lt;/strong&gt;: £39/mo — 20,000 req/mo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional&lt;/strong&gt;: £129/mo — 150,000 req/mo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free key: &lt;a href="https://freqblog.com/music-api.html" rel="noopener noreferrer"&gt;freqblog.com/music-api.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OpenAPI docs: &lt;a href="https://api.freqblog.com/docs" rel="noopener noreferrer"&gt;api.freqblog.com/docs&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Built this because I needed it. If you're porting off Spotify's deprecated endpoints or building anything music-analysis related, happy to answer questions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tags: API, Python, Music, Audio, Spotify, Developer Tools&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>music</category>
      <category>python</category>
    </item>
  </channel>
</rss>
