MeloData API
Look up available audio features and metadata 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). The hyphenated form printed on releases (US-RC1-76-07839) is also accepted. Case-insensitive. Anything else is a 400. |
200 Response: Features Available
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"features": {
"bpm": 143.8,
"tempo_confidence": 0.93,
"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": null,
"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 and duration. A track we have not analyzed yet is queued and answered with HTTP 202 and a Retry-After header, the same as the features endpoint. A 202 is not billed.
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | ISRC identifier: 2 letters, 3 alphanumeric characters, then 7 digits. The hyphenated form (US-RC1-76-07839) is also accepted. Case-insensitive. Anything else is a 400. |
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"album": "A Night at the Opera",
"release_date": "1975-10-31",
"duration_ms": 354320
},
"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, and (Original Mix) matches the plain title. When the variant you named is not available, the closest one is returned: the same mix at another length before the original, and the original before someone else's remix.
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 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. A successful request counts as one API lookup, regardless of how many ISRCs it contains. This differs from saved bulk jobs, which count submitted rows.
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": "analyzing"
}
]
},
"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. A successful request counts as one API lookup. This endpoint uses the ordinary lookup allowance, separate from saved bulk jobs.
Request Body
{
"isrcs": ["USRC17607839", "GBAYE0601498"]
}Response
{
"data": {
"queued": 3,
"already_analyzed": 1,
"already_queued": 1,
"total": 5
},
"meta": { "request_id": "req_c3d4e5f6a1b2" }
}Tip
Saved Bulk Jobs
/v1/tracks/jobsSubmit titles and artists once, then retrieve matching and audio-feature results as they finish. Work continues when you stop polling. Temporary failures retry automatically, and completed tracks stay saved if processing is interrupted. Requires a direct MeloData API key.
curl -X POST https://melodata.voltenworks.com/api/v1/tracks/jobs \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-library-2026-09-07" \
-d '{"tracks":[{"title":"Blinding Lights","artist":"The Weeknd"}]}'The 202 response contains a job ID and status_url. Reuse the same Idempotency-Key when retrying a submission after a network error. The same key and inputs return the original job; changed inputs return 409. Keys accept 8-128 letters, numbers, periods, colons, underscores or hyphens.
| Plan | Rows/month | Rows/job | Active jobs |
|---|---|---|---|
| Free | 25 | 10 | 1 |
| Dev | 1,000 | 250 | 3 |
| Pro | 10,000 | 250 | 3 |
| Scale | 50,000 | 250 | 3 |
Note
GET /v1/tracks/jobslists your latest 50 unexpired jobs and monthly allowance.GET /v1/tracks/jobs/{id}returns ordered tracks, total, remaining, counts and expires_at. Poll every 30 seconds while processing.POST /v1/tracks/jobs/{id}/retryretries only failed items after the job finishes. Use a fresh Idempotency-Key for each retry cycle. Repeating that key is safe. At most two cycles per job.
Item statuses are pending, retrying, analyzing, completed, partial, unavailable, not_found, needs_review and failed. A finished job means every row has an outcome, including unsuccessful matches. Weak matches are marked needs_review and are not analyzed automatically. Partial results contain available metadata and features; unavailable means no audio source was found.
Each processing cycle has a 24-hour deadline. Download your results within seven days of submission. Retries preserve completed results and do not extend expiry. Expired jobs return 410. Minimal job metadata and submission keys are retained for 90 days; monthly allowance totals remain until account deletion. Deleting your account removes all bulk records.
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
Completed feature records include source and analysis_version fields so you can branch on them programmatically.
Two sources of features
source: essentialive analysisFor an uncached ISRC, the worker attempts to find a preview through iTunes or Deezer, decode it with ffmpeg, and analyze it with Essentia (open-source MIR library). Completed results are cached for later requests. 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. Archive rows carry ab_1.1 (the original sample) or ab_1.2 (the full archive, added October 2026). On ab_1.2 rows, loudness, tempo_confidence and time_signature are null, because the published archive data does not include them. We do not return the archive's key on any archive row: checked against songs with known keys, it was right less often than our own analysis. These fields fill in over time: when you request features for an archive track, we queue an analysis of its audio preview in the background and add key, key_confidence, loudness, time_signature and tempo_confidence where they are still null. Until then the response returns them as null; request the track again later. On an archive row, a key always comes with a key_confidence. A value the archive already has is never changed, and the request is billed the same as any other. Tracks with no preview stay null.
Check accuracy for your recordings
BPM, key, time signature, and rhythmic features describe the available preview. Tempo and key can change within a recording, and detection can be wrong. Check confidence fields when available and test recordings you know before relying on the measurements.
What's an approximation
For source: essentia rows:
- loudness attempts integrated LUFS over the preview window, with an RMS-derived dB fallback if the LUFS calculation fails. The response does not identify the fallback separately. Neither value establishes full-track loudness; do not use this field as a guaranteed LUFS or compliance measurement.
- 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, and instrumentalness 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 null for every row, live or archive. It is not computed yet and is 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. The retry interval is not a completion guarantee. Apply a retry limit and handle partial or unavailable results. Three strategies for handling this:
Strategy 1: Simple RetryRecommended
Read the Retry-After header and wait. A retry may still return HTTP 202. A later HTTP 200 can be complete, partial or unavailable and counts toward lookup usage.
const API_KEY = process.env.MELODATA_API_KEY;
const BASE = "https://melodata.voltenworks.com/api/v1";
async function getFeatures(isrc) {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch(`${BASE}/tracks/${isrc}/features`, {
headers: { Authorization: `Bearer ${API_KEY}` },
signal: AbortSignal.timeout(20000),
});
if (res.status === 202) {
if (attempt === 3) throw new Error("Analysis still pending");
const delay = Number(res.headers.get("Retry-After") ?? 30);
if (!Number.isFinite(delay) || delay < 0 || delay > 120) {
throw new Error("Retry later using the Retry-After header");
}
await new Promise(resolve => setTimeout(resolve, delay * 1000));
continue;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
}
const result = await getFeatures("USRC17607839");
// A successful response can be partial or unavailable.
console.log(result.data?.features?.bpm ?? null);
console.log(result.data?.features?.key ?? null);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,
"tempo_confidence": 0.93,
"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 through batch analyze. A successful request counts as one API lookup. Later feature requests are billed separately and may still return pending or unavailable results.
See Batch Analyze for the request format, and use the bounded polling example above when retrieving features.
Code Examples
JavaScript / Node.js
const API_KEY = process.env.MELODATA_API_KEY;
const BASE = "https://melodata.voltenworks.com/api/v1";
async function getFeatures(isrc) {
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch(`${BASE}/tracks/${isrc}/features`, {
headers: { Authorization: `Bearer ${API_KEY}` },
signal: AbortSignal.timeout(20000),
});
if (res.status === 202) {
if (attempt === 3) throw new Error("Analysis still pending");
const delay = Number(res.headers.get("Retry-After") ?? 30);
if (!Number.isFinite(delay) || delay < 0 || delay > 120) {
throw new Error("Retry later using the Retry-After header");
}
await new Promise(resolve => setTimeout(resolve, delay * 1000));
continue;
}
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
}
const result = await getFeatures("USRC17607839");
// A successful response can be partial or unavailable.
console.log(result.data?.features?.bpm ?? null);
console.log(result.data?.features?.key ?? null);Python
import os, time, requests
BASE = "https://melodata.voltenworks.com/api/v1"
headers = {"Authorization": f"Bearer {os.environ['MELODATA_API_KEY']}"}
def get_features(isrc):
for attempt in range(4):
res = requests.get(
f"{BASE}/tracks/{isrc}/features",
headers=headers, timeout=20,
)
if res.status_code == 202:
if attempt == 3:
raise RuntimeError("Analysis still pending")
delay = float(res.headers.get("Retry-After", 30))
if not 0 <= delay <= 120:
raise RuntimeError("Retry later using Retry-After")
time.sleep(delay)
continue
res.raise_for_status()
return res.json()
result = get_features("USRC17607839")
# A successful response can be partial or unavailable.
features = (result.get("data") or {}).get("features") or {}
print(features.get("bpm"))
print(features.get("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 .
# Request batch analysis (a successful request counts as one 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/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 | Not published |
| Python | Coming soon | Not published |
| Ruby | Planned | Not published |
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 not in database. 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: you've used this month's 1,000 free lookups. Upgrade to Dev ($19/mo, 25,000 lookups) to keep going: https://melodata.voltenworks.com/dashboard/billing?plan=dev&from=quota_429",
"status": 429,
"code": "quota_exhausted",
"upgrade_url": "https://melodata.voltenworks.com/dashboard/billing?plan=dev&from=quota_429"
},
"meta": {
"request_id": "req_b2c3d4e5f6a1"
}
}
// Headers: X-Quota-Used: 1000, X-Quota-Limit: 1000
// upgrade_url is null on Scale, which has no plan above it.Changelog
Faster first answers, better name matching, and corrected tempos on about 1,400 tracks.
- Fixed: about 1,400 tracks reported double their real tempo. Their
bpmis now the correct value (for example 178.2 is now 89.1), and each now carries atempo_confidence. Nothing else on those tracks changed. - Faster: a new track starts analyzing right away. After a quiet spell, a 202 used to wait up to 30 seconds before analysis began. It now starts within a second or two.
- Better: track search finds more songs. Searching by name now handles a title written as "Artist - Title", featured-artist credits ("feat.", "ft."), full-width characters and Cyrillic artist names. When we take the artist from inside the title,
confidenceis capped at 0.85. - Fixed: re-analyzing a track that was already queued returned a 500. It now returns 200 and keeps the one queued job.
Every feature response now says how likely its BPM is to be right.
- Added:
tempo_confidence. A number from 0 to 1 next tobpmin feature responses, batch results and thetrack.analyzedwebhook. It is the chance thatbpmis within 4% of the true tempo, not half or double. We measured it against songs with known tempos, so a value of 0.9 means about 9 in 10 such BPMs are right. Use it to skip or double-check low-confidence tempos. - It can be null.
tempo_confidenceis null when there is nothing to measure it from: the track has no audio preview, or a fresh analysis heard a different tempo than the storedbpm. Archive tracks get one once their preview is analyzed.
About 1.77 million more tracks answer straight away, from the full AcousticBrainz archive.
- Added: the full AcousticBrainz archive, about 1.77 million tracks. The catalog grows from about 87,000 tracks to about 1.85 million. Features for these ISRCs now come back right away instead of a 202 and a wait. They carry
source: "acousticbrainz_archive"andanalysis_version: "ab_1.2". - Some fields are null on archive rows. On
ab_1.2rows,loudness,tempo_confidenceandtime_signatureare null, because the published archive data does not include them. BPM, energy, danceability, valence and the other archive features are included on nearly every row. - Changed: archive rows no longer return the archive's key. Checked against public sets of songs with known keys, the archive key was exactly right on 42% of them and our own analysis on 61%. So
keyandkey_confidenceon every archive row (ab_1.1andab_1.2) now come from our own analysis of the audio preview. The first request for one of these tracks returnskey: nulland queues that analysis; a later request returns the key with itskey_confidence. On an archive row, a key always comes with a confidence. Tracks with no preview keepkey: null. Billing is the same. - Filling in over time:
loudness,time_signatureandtempo_confidenceon archive rows. When you request features for anab_1.2track, we analyze its audio preview in the background and add those three where they are null, along with the key. Archive values never change, the response is not slowed down, and billing is the same. Tracks with no preview stay null. - Filled: missing names and IDs on existing tracks. Tracks that were missing an artist ID, a MusicBrainz recording ID, a title or an artist name have them filled in where the archive has them. Features we analyzed ourselves did not change.
- Fixed:
bpmis never 0. About 130 archive tracks returnedbpm: 0, which is not a tempo. They now returnbpm: null, the same as any track where no tempo was detected. - Corrected: the docs example showed a liveness value.
livenessis null on every row. It is not computed yet. Responses did not change.
A quota refusal now links to the plan that gets you going again.
- Added:
codeandupgrade_urlon the monthly quota 429. The error now carriescode: "quota_exhausted", so you can tell it apart from a rate limit 429, andupgrade_url, a link to the billing page with the next plan selected (null on Scale). The message names that plan, its price and its monthly lookups. The status code, headers and existing fields are unchanged, and the message still starts with "Monthly quota exceeded".
A track we do not have yet is queued instead of reported missing, and name lookups return the mix you asked for more often.
Tracks we have not analyzed yet
- Changed: metadata for a new track returns 202, not 404.
GET /v1/tracks/{isrc}/metadatanow queues a track we have not analyzed yet and answers 202 with a Retry-After header, the same as the features endpoint. Ask again after the wait and the metadata is there. A 202 is not billed. - Changed: batch features queues what it does not have. An ISRC in
POST /v1/tracks/batch/featuresthat we have not analyzed yet now comes back asanalyzingand is queued, instead ofnot_foundwith nothing queued. Send the same batch again later to collect it.
Name lookups
- Fixed: a lookup returning the wrong mix. When the exact mix or version you asked for was not available, a name lookup could return whichever version the provider listed first, such as another producer's remix or a cut from a DJ mix, even when the original or your mix at a different length was in the same results. It now returns the closest version: your mix at another length first, then the original. A different version is still reported as a weak match with a warning.
- "(Original Mix)" now matches the plain title. Most streaming catalogs list a track's original mix with no version name, so a lookup for
Offshore (Original Mix)now returnsOffshoreas an exact match instead of a weak one, and the other way round. An original extended mix still counts as the extended mix. - Part numbers and possessives.
Part Inow matchesPart 1, and a mix name written with a possessive, such asPvD's E-Werk Club Mix, matches the same name without it. - Cached lookups use the new matching. Lookup results cached before this release are no longer reused, so repeating a lookup uses the new matching rules instead of the previous result.
ISRCs with hyphens now work.
- Fixed: hyphenated ISRCs returning 400. An ISRC written the way it appears on releases and in label metadata, such as
GB-SMU-36-08834, was rejected as invalid. It is now read asGBSMU3608834on every endpoint that takes an ISRC, including batch lookups and recommendation seeds, and responses always return the plain 12-character code. Only that exact grouping is accepted, so other punctuation is still a 400.
Fewer failed lookups, and a 404 that tells you what to do next.
- Fixed: a single brief provider failure returning 503. A name lookup that failed because a music provider timed out or answered with a temporary error is now tried once more before the request gives up. A refusal that will not change on a second attempt is not retried, so this costs nothing when it cannot help.
- A 404 now says which kind of 404 it is. Every unmatched lookup used to return the same sentence. It now tells you which of three things happened: candidates came back and none was confident enough, so the exact release title, the mix or version name or a featured artist usually closes the gap; or the recording was recognized but no source we use publishes an ISRC for it, which is common for independent and back-catalog releases and means retyping will not help; or nothing matched anywhere. The confidence rules themselves have not changed.
Name lookups: more matches, fewer failures.
- Fixed: name lookups failing while the service was busy. Lookup capacity is now set from your plan's rate limit rather than a single shared allowance, so a steady job no longer runs into repeated 503 responses.
- More titles with mix and remix names now resolve. When an exact provider match comes back empty, the lookup widens the search and re-checks every result against the same title, artist and recording-version rules. How strictly a match has to score before it is returned has not changed.
- Fixed: a busy provider being recorded as "not found". When a provider declined a request, that could previously be remembered as a missing track for up to an hour, including for other callers. Those responses are now treated as retryable and are never stored as a result.
- More accurate retry guidance. A 503 now reports the real time until capacity frees up instead of a fixed 60 seconds, so a retry lands when it can actually succeed.
- Saved bulk jobs: submit track titles and artists, follow progress, and retrieve ordered audio-feature results.
- Interrupted jobs resume automatically. Retry failed items without repeating completed work.
- Separate included bulk allowances on every plan. Existing prices and lookup quotas stay the same.
- Fixed usage history showing Invalid Date. Monthly labels now stay in the correct billing month across time zones, with readable month and year labels in the breakdown.
- Fixed: mix-title lookups returning 503. Corrected a provider-query issue affecting some titles with parenthesized mix names. The original mix name is still checked when matching the recording.
- Fixed: artist matching for collaborations. A track can now resolve under an individually credited main artist when the provider uses a collaboration name. The credit is verified against the same recording.
- Improved error reporting. Failed lookups and handled server errors now include diagnostic details to help identify the cause.
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 attempts integrated LUFS (EBU R128), with an RMS-derived dB fallback. Previous versions returned a positive Vickers scalar in the hundreds for live-analyzed rows. See Data Sources & Limitations for the fallback and preview-window caveats. - 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). The
livenessfield is in the response but is alwaysnull, planned for a future release - 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