MeloData API
Audio features and metadata for any track by ISRC. Get BPM, key, energy, danceability, valence, and more. Built for music apps, recommendation engines, playlist tools, and audio analysis pipelines.
Base URL
Quick Start
Create an account in the developer portal.
Generate an API key from the dashboard. Save it immediately; it is shown only once.
Make your first request:
curl -H "Authorization: Bearer melo_sk_YOUR_KEY" \
https://melodata.voltenworks.com/api/v1/tracks/USRC17607839/featuresNote
Authentication
All API requests require a Bearer token in the Authorization header. Keys are created in the developer dashboard and follow the format:
melo_sk_{64 hex characters} # 72 characters totalExample header
Authorization: Bearer melo_sk_a1b2c3d4e5f6...| Detail | Value |
|---|---|
| Prefix | melo_sk_ |
| Full key length | 72 characters |
| Shown | Once at creation. Store it securely. |
| Dashboard display | melo_sk_a1b2c3d4... (prefix only) |
| Max active keys | 5 per account |
Key Rotation
Create a new key, then update your application. Both keys work at the same time, so rotations have zero downtime. Keys never expire on their own: revoke the old key from the dashboard once your deployment is fully on the new one. Revocation takes effect within 5 minutes.
Rate Limiting
Requests are rate-limited per account using a sliding window algorithm. Limits scale with your plan.
| Plan | Requests / second | Requests / minute |
|---|---|---|
| Free | 5 | 100 |
| Dev | 10 | 300 |
| Pro | 25 | 1,000 |
| Scale | 50 | 3,000 |
Rate Limit Headers
Every response includes these headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Max requests in the current window |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
Tip
429 responses do not count against your monthly quota. You are never billed for rate-limited requests.Quotas & Billing
| Plan | Price | Monthly Quota |
|---|---|---|
| Free | $0 | 1,000 lookups |
| Dev | $19 / mo | 25,000 lookups |
| Pro | $79 / mo | 200,000 lookups |
| Scale | $299 / mo | 1,000,000 lookups + SLA |
Overage billing is opt-in. When enabled, requests beyond your quota are billed at $0.002 per lookup. When disabled, requests are rejected with 429 once quota is exhausted.
Quota Headers
| Header | Description |
|---|---|
| X-Quota-Used | Billed lookups this billing period |
| X-Quota-Limit | Monthly quota for your plan |
What Gets Billed
| Status | Billed? | Reason |
|---|---|---|
| 2xx | Yes | Successful lookup |
| 202 | No | Track is being analyzed |
| 4xx | No | Client error (bad request, invalid key, not found) |
| 429 | No | Rate limited or quota exceeded |
| 5xx | No | Server error (our fault) |
Response Format
All responses use a consistent JSON envelope. Every response includes a unique request_id for debugging.
Success
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"features": { ... }
},
"meta": {
"request_id": "req_a1b2c3d4e5f6",
"quota": {
"used": 4521,
"limit": 25000,
"resets_at": "2026-05-01T00:00:00Z"
}
}
}Error
{
"error": {
"message": "Invalid API key",
"status": 401
},
"meta": {
"request_id": "req_f6e5d4c3b2a1"
}
}Note
X-Request-Id header is also set on every response. Include it in support requests for fast debugging.Endpoints
Get Track Features
/v1/tracks/{isrc}/featuresReturns audio features for a track identified by ISRC. If the track has not been analyzed yet, a 202 is returned and the track is queued for analysis. The 202 is not billed.
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | International Standard Recording Code: 2 letters, 3 alphanumeric characters, then 7 digits (for example USRC17607839). Case-insensitive. Anything else is a 400. |
200 Response: Features Available
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"features": {
"bpm": 143.8,
"key": "Bb",
"key_confidence": 0.87,
"energy": 0.72,
"danceability": 0.39,
"valence": 0.23,
"acousticness": 0.28,
"loudness": -7.2,
"instrumentalness": 0.01,
"speechiness": 0.05,
"liveness": 0.24,
"time_signature": 4
}
},
"meta": {
"request_id": "req_a1b2c3d4e5f6",
"quota": { "used": 4521, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" }
}
}202 Response: Analysis QueuedNOT BILLED
Headers include Retry-After: 30
{
"data": {
"isrc": "USRC17607839",
"status": "analyzing",
"estimated_seconds": 30
},
"meta": {
"request_id": "req_c3d4e5f6a1b2"
}
}200 Response: Unavailable
{
"data": {
"isrc": "XX1234567890",
"status": "unavailable",
"reason": "No audio source found for analysis. This track may be unreleased, region-locked, or have an incorrect ISRC."
}
}Note
Get Track Metadata
/v1/tracks/{isrc}/metadataReturns metadata for a track: title, artist, album, release date, duration, and genres (from the artist record).
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | ISRC identifier: 2 letters, 3 alphanumeric characters, then 7 digits. Case-insensitive. Anything else is a 400. |
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"artist_id": "0383dadf-2a4e-4d10-a46a-e9e041da8eb3",
"album": "A Night at the Opera",
"release_date": "1975-10-31",
"duration_ms": 354320,
"genres": ["rock", "classic rock", "progressive rock"]
},
"meta": {
"request_id": "req_d4e5f6a1b2c3",
"quota": { "used": 4522, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" }
}
}Search Tracks
/v1/tracks/search?q={query}Search for tracks by title or artist name. Returns tracks that have been analyzed (excludes unavailable tracks).
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string (query) | required | Search query. 2 to 200 characters. The SQL wildcard characters % and _ are rejected with a 400. |
| limit | integer (query) | optional | Results per page. Default 20, max 50. |
| offset | integer (query) | optional | Number of results to skip. Default 0, max 10000. |
{
"data": {
"results": [
{
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"release_date": "1975-10-31"
},
{
"isrc": "GBARL9300135",
"title": "Bohemian Rhapsody (Live)",
"artist": "Queen",
"album": "Live at Wembley",
"release_date": "1992-05-25"
}
],
"pagination": {
"offset": 0,
"limit": 20,
"count": 2
}
},
"meta": { "request_id": "req_e5f6a1b2c3d4", "quota": { "used": 4523, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Resolve to ISRC
/v1/tracks/resolve?title={title}&artist={artist}Turns a human track name into an ISRC, so you can use the rest of the API without already holding one. It checks our catalog first, then external music databases. This is a read: it never queues an analysis and never spends analysis budget. Take the ISRC it returns and call Get Track Features.
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string (query) | required | Track title. 2 to 200 characters. |
| artist | string (query) | required | Artist name. 2 to 200 characters. |
Every match carries a confidence between 0 and 1 and a confidence_level of exact, high, medium or low. Anything below 0.45 is not returned at all, it is a 404. Anything below high also carries a plain-English warning string. Read it: a name is not a unique identifier, and this endpoint tells you when it is guessing.
Recording variants matter. A live cut, a remix and an acoustic version are separate recordings with separate ISRCs and genuinely different features. If your query names a variant the match does not have (or the other way round), the confidence is capped below high and the warning is set. Include the variant in the title when you know it. Release-only qualifiers such as (2011 Remaster) are ignored, because that is the same performance.
Results are cached for 7 days on a hit and 1 hour on a miss, and the external providers are called under a shared budget. If we cannot reach any source you get a 503 with a Retry-After header rather than a 404, so a temporary outage is never reported to you as "no such track". Both the 404 and the 503 are free: only a successful resolve counts against your quota.
curl -G https://melodata.voltenworks.com/api/v1/tracks/resolve \
-H "Authorization: Bearer melo_sk_your_key_here" \
--data-urlencode "title=Bohemian Rhapsody" \
--data-urlencode "artist=Queen"{
"data": {
"query": { "title": "Bohemian Rhapsody", "artist": "Queen" },
"isrc": "GBUM71029604",
"matched": {
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"release_date": "1975-10-31"
},
"confidence": 1,
"confidence_level": "exact",
"warning": null,
"source": "catalog",
"cached": false,
"providers": { "deezer": "not_tried", "itunes": "not_tried" },
"analysis_queued": false
},
"meta": { "request_id": "req_a1b2c3d4e5f6", "quota": { "used": 4524, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}| Status | Meaning |
|---|---|
| 200 | Resolved. Check confidence and warning before you rely on it. |
| 400 | Missing or out-of-range title or artist. |
| 404 | Every source answered and none of them knew this track. Not billed. |
| 503 | No source could answer. Retry after the Retry-After header. Not billed. |
Get Artist
/v1/artists/{id}Returns artist data including genres and the number of tracks we have analyzed for them. Artist IDs are MusicBrainz identifiers, returned from track metadata and search endpoints.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (path) | required | MusicBrainz artist ID. |
{
"data": {
"id": "0383dadf-2a4e-4d10-a46a-e9e041da8eb3",
"name": "Queen",
"sort_name": "Queen",
"country": "GB",
"genres": ["rock", "classic rock", "glam rock"],
"track_count": 187
},
"meta": { "request_id": "req_f6a1b2c3d4e5", "quota": { "used": 4524, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Get Recommendations
/v1/recommendationsReturns similar tracks based on audio feature similarity to your seed tracks. Optionally override target feature values to steer recommendations.
| Parameter | Type | Required | Description |
|---|---|---|---|
| seed | string (query, repeatable) | required | ISRC of a seed track. 1 to 5 seeds. |
| limit | integer (query) | optional | Max results. Default 10, max 50. |
| target_bpm | number (query) | optional | Override target BPM instead of averaging seeds. |
| target_energy | number (query) | optional | Override target energy (0.0 to 1.0). |
| target_danceability | number (query) | optional | Override target danceability (0.0 to 1.0). |
| target_valence | number (query) | optional | Override target valence (0.0 to 1.0). |
curl -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/recommendations?seed=USRC17607839&seed=GBAYE0601498&target_energy=0.8&limit=5"{
"data": {
"recommendations": [
{
"isrc": "GBAYE0601498",
"title": "Don't Stop Me Now",
"artist": "Queen",
"match_score": 0.91,
"features": { "bpm": 156.2, "energy": 0.84, "danceability": 0.55, "valence": 0.68 }
},
{
"isrc": "USIR10211015",
"title": "Mr. Brightside",
"artist": "The Killers",
"match_score": 0.86,
"features": { "bpm": 148.1, "energy": 0.78, "danceability": 0.42, "valence": 0.31 }
}
],
"seed_count": 2
},
"meta": { "request_id": "req_a1b2c3d4e5f6", "quota": { "used": 4525, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Note
match_score ranges from 0 to 1, where 1 is an exact feature match. Scores are based on Euclidean distance across BPM, energy, danceability, and valence.Batch Get Features
/v1/tracks/batch/featuresLook up audio features for multiple tracks in one request. Maximum 50 ISRCs per batch. Counts as N billed lookups (one per ISRC).
Request Body
{
"isrcs": ["USRC17607839", "GBAYE0601498", "XX0000000000"]
}Response
{
"data": {
"tracks": [
{
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"features": { "bpm": 143.8, "key": "Bb", "energy": 0.72, "danceability": 0.39, ... }
},
{
"isrc": "GBAYE0601498",
"title": "Don't Stop Me Now",
"artist": "Queen",
"features": { "bpm": 156.2, "key": "F", "energy": 0.84, "danceability": 0.55, ... }
},
{
"isrc": "XX0000000000",
"status": "not_found"
}
]
},
"meta": { "request_id": "req_b2c3d4e5f6a1", "quota": { "used": 4528, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Important
Batch Pre-Analyze
/v1/tracks/batch/analyzeQueue ISRCs for analysis without returning features. This endpoint is free and does not consume quota. Use it to pre-warm the cache before you need the data.
Request Body
{
"isrcs": ["ISRC1", "ISRC2", "ISRC3", "ISRC4", "ISRC5"]
}Response
{
"data": {
"queued": 3,
"already_analyzed": 1,
"already_queued": 1,
"total": 5
},
"meta": { "request_id": "req_c3d4e5f6a1b2" }
}Tip
Reanalyze Track
/v1/tracks/{isrc}/reanalyzeRequest re-analysis for a track marked as unavailable. Available on Pro and Scale plans only. A 30-day cooldown applies between re-analysis attempts for the same ISRC.
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | ISRC of the unavailable track. |
200 Response
{
"data": {
"isrc": "XX1234567890",
"status": "queued_for_reanalysis",
"estimated_seconds": 30
}
}Error Responses
| Status | Condition |
|---|---|
| 403 | Free or Dev plan (Pro+ required) |
| 404 | Track not in database (use GET features first) |
| 409 | Track already has valid analysis data |
| 429 | 30-day cooldown has not elapsed |
Data Sources & Limitations
We're upfront about how the numbers are produced so you can decide whether they fit your use case. Every response includes source and analysis_version fields so you can branch on them programmatically.
Two sources of features
source: essentialive analysisWhen you query an ISRC we haven't seen, we resolve a 30-second preview from Deezer or iTunes, decode it with ffmpeg, and analyze it with Essentia (open-source MIR library). Results are cached forever. Current pipeline version is 1.2.
source: acousticbrainz_archivecached archiveMillions of tracks pre-analyzed by the AcousticBrainz community using Essentia on full-track files (frozen when the project shut down in 2022). Includes valence, acousticness, instrumentalness from trained classifiers that we don't run in the live pipeline. Current archive version is ab_1.1.
What's accurate
BPM, key, time signature, and rhythmic features are reliable from preview-based analysis because tempo and key are stable across a track. We've verified accuracy across a 25-track reference set spanning genres (loud modern pop, classic rock, quiet acoustic, hip-hop/R&B).
What's an approximation
For source: essentia rows:
- loudness is integrated LUFS computed over the preview window, not the full master. Deezer previews are 30 seconds from a region of the track Deezer chose, not necessarily the loudest part. For songs with wide dynamic range, preview-window LUFS can differ meaningfully from the integrated LUFS of the full song. Don't build mastering or compliance tools on top of this without acknowledging that limitation.
- energy is a perceptual proxy mapped from dB loudness into a 0..1 scale. It's monotonic and useful for relative comparison across tracks, but it is not the same as Spotify's trained “energy” classifier.
- speechiness reflects the preview window: if the preview catches a vocal section vs an instrumental break, the value can drift.
What we don't return (yet)
valence, acousticness, instrumentalness, and liveness return null for live-analyzed tracks. We don't run mood/timbre classifiers in the live pipeline; only archive-sourced rows have them. liveness is currently null for every row, planned for a future release.
Note
Handling Cache Misses (202 Responses)
When you query a track that hasn't been analyzed yet, the API returns 202 Accepted and queues the track for analysis. The 202 is not billed. Analysis typically takes 15 to 30 seconds. Three strategies for handling this:
Strategy 1: Simple RetryRecommended
Read the Retry-After header and wait. The retry will be a cache hit (billed once).
async function getFeatures(isrc) {
const res = await fetch(
`https://melodata.voltenworks.com/api/v1/tracks/${isrc}/features`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (res.status === 202) {
const retryAfter = res.headers.get("Retry-After") || 30;
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return getFeatures(isrc); // retry, will be a cache hit
}
return res.json();
}Strategy 2: Webhook NotificationPro & Scale
Register a webhook URL in your dashboard. When analysis completes, we POST the result to your server. No polling required.
// POST https://your-app.com/webhooks/melodata
{
"event": "track.analyzed",
"data": {
"isrc": "USRC17607839",
"features": {
"bpm": 143.8,
"key": "Bb",
"energy": 0.72,
"danceability": 0.39,
"valence": 0.23,
...
}
}
}Strategy 3: Batch Pre-AnalyzeAll plans
If you have a list of ISRCs you will need later, submit them upfront via the free batch analyze endpoint. When you query them individually later, most will already be cached.
// Step 1: Pre-warm the cache (free, no billed lookups)
await fetch("https://melodata.voltenworks.com/api/v1/tracks/batch/analyze", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
isrcs: ["ISRC1", "ISRC2", "ISRC3", "ISRC4", "ISRC5"],
}),
});
// Step 2: Wait for analysis (30-60 seconds)
await new Promise((r) => setTimeout(r, 60000));
// Step 3: Query features, most will be instant cache hits
for (const isrc of isrcs) {
const res = await fetch(
`https://melodata.voltenworks.com/api/v1/tracks/${isrc}/features`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const data = await res.json();
// process features...
}Code Examples
JavaScript / Node.js
const API_KEY = process.env.MELODATA_API_KEY;
const BASE = "https://melodata.voltenworks.com/api/v1";
// Basic feature lookup with 202 handling
async function getFeatures(isrc) {
const res = await fetch(`${BASE}/tracks/${isrc}/features`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (res.status === 202) {
const wait = Number(res.headers.get("Retry-After") || 30);
console.log(`Track ${isrc} is being analyzed. Retrying in ${wait}s...`);
await new Promise((r) => setTimeout(r, wait * 1000));
return getFeatures(isrc);
}
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status}: ${err.error.message}`);
}
return res.json();
}
// Batch lookup
async function batchFeatures(isrcs) {
const res = await fetch(`${BASE}/tracks/batch/features`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ isrcs }),
});
return res.json();
}
// Usage
const { data } = await getFeatures("USRC17607839");
console.log(`BPM: ${data.features.bpm}, Key: ${data.features.key}`);Python
import os
import time
import requests
API_KEY = os.environ["MELODATA_API_KEY"]
BASE = "https://melodata.voltenworks.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def get_features(isrc: str) -> dict:
"""Get audio features for a track. Handles 202 retry automatically."""
res = requests.get(f"{BASE}/tracks/{isrc}/features", headers=HEADERS)
if res.status_code == 202:
wait = int(res.headers.get("Retry-After", 30))
print(f"Track {isrc} analyzing. Retrying in {wait}s...")
time.sleep(wait)
return get_features(isrc)
res.raise_for_status()
return res.json()
def batch_features(isrcs: list[str]) -> dict:
"""Batch lookup for up to 50 ISRCs."""
res = requests.post(
f"{BASE}/tracks/batch/features",
headers={**HEADERS, "Content-Type": "application/json"},
json={"isrcs": isrcs},
)
res.raise_for_status()
return res.json()
# Usage
data = get_features("USRC17607839")
print(f"BPM: {data['data']['features']['bpm']}")
print(f"Key: {data['data']['features']['key']}")curl
# Get features for a single track
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
https://melodata.voltenworks.com/api/v1/tracks/USRC17607839/features | jq .
# Search for tracks
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/tracks/search?q=bohemian+rhapsody&limit=5" | jq .
# Batch feature lookup
curl -s -X POST \
-H "Authorization: Bearer melo_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"isrcs":["USRC17607839","GBAYE0601498"]}' \
https://melodata.voltenworks.com/api/v1/tracks/batch/features | jq .
# Get recommendations from seed tracks
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/recommendations?seed=USRC17607839&seed=GBAYE0601498&limit=10" | jq .
# Pre-analyze a batch (free)
curl -s -X POST \
-H "Authorization: Bearer melo_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"isrcs":["ISRC1","ISRC2","ISRC3"]}' \
https://melodata.voltenworks.com/api/v1/tracks/batch/analyze | jq .SDKs & Libraries
Official SDKs for JavaScript, Python, and Ruby are in development. For now, the API is straightforward to call with any HTTP client. The patterns above work with fetch, requests, curl, or any library that speaks HTTP.
| SDK | Status | Install |
|---|---|---|
| JavaScript / TypeScript | Coming soon | npm install melodata |
| Python | Coming soon | pip install melodata |
| Ruby | Planned | gem install melodata |
Note
AI Agent Skill
Building with an AI coding agent (Claude Code, Cursor, etc.)? Copy the MeloData agent skill to give your AI full API context, including endpoints, auth patterns, 202 retry handling, and integration examples.
melodata-api skill
SKILL.md + reference files for Claude Code, Cursor, Windsurf, and other AI agents. Includes all endpoints, auth, billing rules, 202 handling, and integration patterns.
Errors Reference
All error responses use the standard error envelope. Below is every error code the API can return.
| Status | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid ISRC, missing required parameter, malformed JSON body, batch size exceeds 50 |
| 401 | Unauthorized | Missing Authorization header, invalid API key, revoked key, wrong key format |
| 403 | Forbidden | Endpoint requires a higher plan (e.g. reanalyze requires Pro+) |
| 404 | Not Found | Track or artist not in database. For tracks, use GET features to trigger analysis first. |
| 409 | Conflict | Track already has valid analysis (reanalyze endpoint only) |
| 429 | Too Many Requests | Rate limit exceeded or monthly quota exhausted. Check X-RateLimit-Reset header. |
| 500 | Internal Server Error | Unexpected server failure. Not billed. Include X-Request-Id in support requests. |
| 503 | Service Unavailable | A dependency we rely on is degraded, so we could not safely serve the request. This is our problem, not yours, and it is never billed. Wait the number of seconds in the Retry-After header, then retry. |
Example Error Response
// 401 Unauthorized
{
"error": {
"message": "Missing or invalid Authorization header",
"status": 401
},
"meta": {
"request_id": "req_f6a1b2c3d4e5"
}
}
// 429 Rate Limited
{
"error": {
"message": "Rate limit exceeded",
"status": 429
},
"meta": {
"request_id": "req_a1b2c3d4e5f6"
}
}
// Headers: X-RateLimit-Reset: 1711234567
// 429 Quota Exceeded
{
"error": {
"message": "Monthly quota exceeded. Upgrade your plan or enable overage billing.",
"status": 429
},
"meta": {
"request_id": "req_b2c3d4e5f6a1"
}
}
// Headers: X-Quota-Used: 25000, X-Quota-Limit: 25000Changelog
Look up a track without an ISRC.
- Added: resolve a track by name. GET /v1/tracks/resolve takes a title and an artist and returns the ISRC, so you no longer need one before you start. It checks our catalog first, then external music databases. It is a read: it never queues an analysis and never spends analysis budget. Take the ISRC it returns and call Get Track Features. A weak match says so in the response instead of guessing silently.
- Fixed: a second request for a track already being analyzed returned 500. A uniqueness constraint added in v1.2 met three enqueue paths written before it, so whichever request lost the race got an error instead of the
202it should have had. Two concurrent requests for the same new track now both return 202. The analysis itself was never affected. - Fixed: a cache miss on the RapidAPI path returned 500. Five requests were affected. Nothing was billed.
- Changed: the resolution budget is now per account. Resolving a name calls external databases that publish their own rate ceilings, and that allowance used to be shared across every account. One caller could exhaust it for everyone else. Each account now has its own share.
- Note for existing integrations: nothing you already call changed shape.
resolveis a new endpoint, and no existing response gained or lost a field.
Archive metadata recovery. Three quarters of the catalog was missing values that were in the source data all along.
- Fixed:
keyandkey_confidenceare now returned for archive-sourced tracks. They were null on every one of them because the mapper read a field shape the archive does not use. Catalog coverage went from 23.1% to 98.8%. - Fixed:
duration_msis now returned for archive-sourced tracks. It was present in the source and never read. - Fixed: search by name. Archive-sourced tracks now carry
title,artistandalbum, so /v1/tracks/search finds them. The share of the catalog reachable by name went from 23.2% to 98.9%. If a title and artist you know we hold came back empty before, try it again. - Documented:
tempo_confidenceis null on archive-sourced rows. AcousticBrainz publishes no BPM confidence, so there is nothing to return, and it is now stated the same wayloudnessandtime_signaturealready are. - Documented: no single track carries all twelve features. Loudness and time signature come from live analysis only; valence, acousticness and instrumentalness come from the archive only. Every response reports its own
source, so you can check rather than take our word for it. See Data Sources & Limitations. - Note for existing integrations: an ISRC that returned
key: nullorduration_ms: nullmay now return a value. If you cached those nulls, refresh them. Nothing that already had a value changed.
Audio feature accuracy fixes. Thank you to the developer who reported the bug.
- Fixed:
loudnessnow returns integrated LUFS in negative dB (EBU R128). Previous versions returned a positive Vickers scalar in the hundreds for live-analyzed rows. - Fixed:
energynow returns a 0..1 perceptual proxy derived from loudness. Previous versions returned0on every live-analyzed track. - Fixed:
danceabilityis now properly normalized from Essentia's native 0..3 Streich metric. Previous versions clamped to 1.0, saturating most rhythmic tracks at the ceiling. - New: response now includes
analysis_versionandsourceso clients can distinguish corrected rows (1.2/ab_1.1/ab_1.1+recovered) from legacy ones. - New: Data Sources & Limitations section documenting what's computed live vs from archive, and which values are approximations.
- Data refresh: the entire AcousticBrainz seed pool was reprocessed with the corrected mapping. Saturated danceability values that were lost to the clamp have been recovered from the raw archive JSON (100% recovery rate).
- Note for existing integrations: if you were caching pre-v1.1 values, refresh them. Values for the same ISRC may now differ, they were wrong before.
- Initial API release
- Audio feature extraction (BPM, key, energy, danceability, valence, acousticness, loudness, instrumentalness, speechiness, liveness)
- Track metadata and search
- Artist data
- Similarity-based recommendations
- Batch feature lookup and batch pre-analysis
- Re-analysis for unavailable tracks (Pro+)
- 4 pricing tiers: Free, Dev, Pro, Scale
MeloData API by Voltenworks