Migrating from Spotify Audio Features: a Field-by-Field Threshold Guide
Rewritten 26 September 2026. Earlier versions of this guide compared our values with Spotify’s and offered a ?calibrate=spotify mode. To comply with Spotify’s developer terms we no longer use Spotify’s data to calibrate or compare values, so those comparisons and the calibrated mode are gone (?calibrate= is still accepted but ignored). What remains is the field mapping and one piece of advice: re-derive your thresholds on our values rather than copying numbers tuned for another service. See the changelog for the detail.
You swapped the host from api.spotify.com to api.freqblog.com. Your GET /v1/audio-features/{id} handler still parses the response unchanged — same path, same field names, same JSON shape. What is not the same is how the numbers are made: we compute ours from signal analysis (Essentia for BPM and key, our own descriptors for the rest) and, where a recording has them, from AcousticBrainz’s open classifiers. Same names, different measurements — so any hard-coded threshold in your code needs checking against our values before you rely on it.
If you haven't done the migration yet: start with Spotify Audio Features Is Dead. Here's What to Use Instead in 2026 for the landscape and the host-swap code. This post is the next step — making sure your numbers behave once the calls are flowing.
Field mapping
Every field Spotify’s audio_features object carried is present in our /v1/audio-features/{id} response under the same name. Some are null where we have no trustworthy source — your code should already handle a null numeric field.
| Spotify field | Our field | What ours measures | Nullable? |
|---|---|---|---|
tempo | tempo (bpm on /lookup) | Essentia RhythmExtractor2013. bpm_alt on /lookup flags likely half-time readings. | No |
key | key (0–11, −1 if undetected) | Essentia KeyExtractor. /lookup adds the key name, camelot and open_key. | No |
mode | mode (0 minor / 1 major) | Same extractor as key. | No |
time_signature | time_signature | Essentia meter estimate; falls back to 4, so treat it as informational. | No |
loudness | loudness (dB) | Loudness of the 30-second analysis window. Compare tracks relative to each other. | No |
energy | energy | Perceptual intensity from RMS loudness, on our own scale (loud masters sit near the top). | No |
danceability | danceability | Tempo, beat strength and rhythmic regularity from our own analysis. Our own scale, and it runs high — re-derive any cut-off. | No |
valence | valence | Musical positiveness estimated from mode, tempo, brightness and energy. A coarse sorting signal. | No |
acousticness | acousticness | AcousticBrainz’s trained classifier where the recording has it; otherwise our spectral-flatness estimate, rescaled (order-preserving) onto AcousticBrainz’s distribution. | Rarely |
instrumentalness | instrumentalness | AcousticBrainz’s trained classifier where the recording has it. | Yes — null otherwise |
speechiness | speechiness | Not served for our own analyses. | Yes — null for our analyses |
liveness | liveness | Not served for our own analyses. | Yes — null for our analyses |
A more accurate analysis of the descriptor fields is in progress; when it ships, the null fields will start carrying values and the changelog will say so.
How to re-derive a threshold
- Pull a labelled sample from your own library. Take a few hundred tracks you already know the answer for — “danceable” vs not, acoustic vs electric — and fetch them in batches with
GET /v1/audio-features?ids=id1,id2,…(up to 100 per call). - Look at our distribution, not a remembered number. Sort the sample by the field and see where your two groups separate. That crossing point is your threshold. Percentiles work too: “top 20% by
energy” transfers across any scale. - Prefer ordering to absolute cut-offs for
valence,danceabilityandacousticness: they rank tracks usefully, but no single value is a verdict on one track. - Handle
null. Dropspeechinessandlivenessfrom filters for now, and treat anullinstrumentalnessas “unknown”, not 0. - Don’t filter on
time_signature. It is almost always 4.
Identifiers
/v1/audio-features/{id} accepts an ISRC (with or without hyphens) or a Spotify track ID, URI or URL; /lookup?track=…&artist=… takes a name. A raw Spotify ID is resolved per request by matching the track and can miss when a title is shared by several artists, so for reliable coverage key your lookups by ISRC — your Spotify track object already carries it at external_ids.isrc — or by name. In a batch, IDs we can’t resolve come back null and are not charged.
Try the drop-in — free tier, no card
Get an API key in seconds; the first 1,000 lookups per month are free.
Get API Key →Further reading
- Spotify Audio Features Is Dead. Here's What to Use Instead in 2026. — the landscape and the host-swap walkthrough
- Half-Time vs Double-Time BPM Detection: How We Fixed Spotify's Known Accuracy Gap
- Why DJ Platforms Disagree on Key 60% of the Time
- Camelot Wheel for Developers: Harmonic Mixing Without the Music Theory PhD
- Spotify's original (now-deprecated) audio-features reference