Home Pricing Docs Compare SDK Blog Changelog Status Get API Key →
Music API · Architecture

BPM API: Get BPM and Key From a Track Name (No Audio File Required)

May 2026 · 9 min read · FreqBlog

Most "music analysis" APIs want a 30-second audio clip and a multipart upload. That's fine if you're building a DJ app where the user is dragging files in. It's a wall if you're building a fitness app trying to match treadmill cadence to a Spotify playlist, a music recommender that takes a track URL and returns "more like this," or honestly, any app where the user types Blinding Lights and expects the BPM back.

This is the architectural split nobody talks about: upload-based analysis APIs vs catalog-based lookup APIs. They look superficially similar (both return BPM, key, energy, etc.), but the failure modes are completely different and the right one for your app depends on what your user actually has in their hand.

The two architectures, in one paragraph each

Upload-based

You POST audio bytes; the API runs Essentia or librosa on them server-side; you get features back. Examples: AudD, Cyanite, AI Mastering. Hit rate is 100% (any audio works) but the user must have the file. No file means no answer. Pricing is usually per-call and on the higher end (compute-bound).

Catalog-based

You GET with ?track=Blinding+Lights&artist=The+Weeknd; the API looks the track up in a pre-analyzed library and returns the cached features. Hit rate is <100% (only works for tracks already in the catalog) but no audio file required, the response is fast (typically <100 ms), and pricing is usually per-lookup and lower (no per-call compute). When the catalog misses, the better APIs queue an on-demand analysis and email the user when it lands.

Spotify's audio_features endpoint was a catalog-based API: you passed a Spotify track ID, you got the features. It died in November 2024, which is why this post exists at all.

What "name-based lookup" actually does

The pattern, in code:

import requests

r = requests.get(
    "https://api.freqblog.com/lookup",
    params={"track": "Blinding Lights", "artist": "The Weeknd"},
    headers={"X-Api-Key": api_key},
)
data = r.json()
# {
#   "track_name": "Blinding Lights",
#   "artist_name": "The Weeknd",
#   "bpm": 171.0,
#   "bpm_alt": 85.5,           # half-time variant (see footnote)
#   "key": "C#-Minor",
#   "camelot": "12A",
#   "open_key": "5m",
#   "energy": 0.91,
#   "danceability": 0.85,
#   ...
# }

What's actually happening behind the request:

  1. Catalog hit — the lookup matches by name and returns within ~10 ms. Most popular tracks are pre-analyzed and live here.
  2. Catalog miss but reachable — the API can't find the track, but it can find the iTunes preview for it. It returns HTTP 202 ("queued"), kicks off a background analysis (download preview → run Essentia → cache result), and emails the user when the data is ready (typically 30 seconds to 2 minutes later). Subsequent calls hit the cache.
  3. Genuinely unfindable — demos, bootlegs, alternate-takes that have no commercial release. The first call still returns 202 and queues an attempt; once the pipeline gives up, subsequent calls return a structured 404 (with terminal: true and a Retry-After) — never an empty object. Plan for this in your UX.

The 202 path is the differentiator. Most catalog APIs return 404 on miss and leave you to figure it out. The "queue and email" pattern means a track that's missing today is in the catalog tomorrow — the catalog grows toward the long tail of what your users actually search for, instead of being frozen to whatever was popular when the dataset was built.

What you can ask for by name

The full set of fields a good name-based BPM API should return:

FieldExampleWhy it matters
bpm171.0Tempo for sync/cadence/sorting
bpm_alt85.5Half-time variant when the detector reports the wrong octave (see our post on this gap)
key"C#-Minor"Musical key (12 pitches × major/minor)
camelot"12A"DJ-friendly key notation for harmonic mixing — guide here
open_key"5m"Alternative DJ notation (Mixed In Key, Serato)
energy0.910-1 normalised intensity
danceability0.850-1 rhythmic groove score
valence0.500-1 musical positivity
loudness_db-5.2dBFS loudness (bare loudness on the Spotify-compat route)
time_signature4Beats per bar
genre"pop"Coarse Last.fm-style tag
isrc + mbid"USUM72003867"Cross-link IDs to other catalogs

The hit rate is the metric that matters

The single most important question to ask any catalog-based API: what fraction of the tracks my users will search for are actually in your catalog? Vendors will quote you a top-line track count ("10M tracks!") which is meaningless unless you know the distribution.

Three honest signals to look for:

For comparison: at FreqBlog we hold hundreds of thousands of pre-analyzed tracks (instant), with on-demand backfill reaching most commercial Apple Music releases in 30s-2min. Our fallback chain spans roughly 8M tracks — the AcousticBrainz frozen dump (7.5M tracks) and Million Song Dataset (1M) — used as layers when the primary catalog misses but a MusicBrainz ID is known.

When the catalog misses, what do you do?

Three patterns work:

Pattern 1: Queue + notify

The 202-pattern. Show "Analyzing… we'll email you" in your UX. Comes back via webhook or the user re-querying. Best when users care about that specific track and will wait.

r = requests.get("https://api.freqblog.com/lookup",
                 params={"track": track, "artist": artist},
                 headers={"X-Api-Key": api_key})
if r.status_code == 202:
    # Queued. Show a "checking..." UI. Re-poll in 30s, or wire a webhook.
    return {"status": "queued"}
elif r.status_code == 200:
    return r.json()
else:
    return {"status": "unavailable"}

Pattern 2: Bulk pre-warm

If you know the user's library upfront (e.g. they uploaded a CSV of their playlist), POST the whole list to /bulk. The API returns immediately with a mix of hits + queued items, and the queued ones land via email/webhook over the next few minutes.

Pattern 3: Graceful degradation

If your UX doesn't need every field, the API returns what it has and nulls the rest. Render BPM and key when present; hide the energy/valence chips when null. Users don't know what they're missing if you don't show empty boxes.

BPM accuracy: a footnote that matters

BPM detectors get confused on roughly 20% of dance tracks. The Weeknd's Blinding Lights is the textbook example: the actual tempo is 171 BPM, but Spotify (and most DSP-based detectors) returns ~85.5 because the algorithm latches onto the half-time pulse. We've covered the fix in a separate post — the short version is to expose both the detected BPM and the half/double-time alternate, and let your app decide which to display based on the genre context.

Any BPM API you evaluate should either fix the half-time issue automatically or expose enough context (the alternate value, a confidence score, or both) for you to fix it client-side. Spotify's old audio_features didn't, which created a long tail of "this app says my song is 85 BPM" UX problems.

What no name-based API will give you

How to choose

FreqBlog Music API — free tier, no card

BPM, key, Camelot, energy, loudness, danceability and more — by track name + artist. ~10 ms cache hits, on-demand analysis for misses (30 s-2 min). From £0.17 per 1,000 requests.

View API →

Further reading