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

1

Create an account in the developer portal.

2

Generate an API key from the dashboard. Save it immediately; it is shown only once.

3

Make your first request:

bash
curl -H "Authorization: Bearer melo_sk_YOUR_KEY" \
  https://melodata.voltenworks.com/api/v1/tracks/USRC17607839/features

Note

Coverage: MeloData combines an archive seed dataset with analysis of available audio previews. Uncached tracks may return HTTP 202 while analysis is queued. Follow Retry-After with a bounded retry policy; a later result may contain features, partial metadata or an unavailable status.

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 total

Example header

Authorization: Bearer melo_sk_a1b2c3d4e5f6...
DetailValue
Prefixmelo_sk_
Full key length72 characters
ShownOnce at creation. Store it securely.
Dashboard displaymelo_sk_a1b2c3d4... (prefix only)
Max active keys5 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.

PlanRequests / secondRequests / minute
Free5100
Dev10300
Pro251,000
Scale503,000

Rate Limit Headers

Every response includes these headers:

HeaderDescription
X-RateLimit-LimitMax requests in the current window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix 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

PlanPriceMonthly Quota
Free$01,000 lookups
Dev$19 / mo25,000 lookups
Pro$79 / mo200,000 lookups
Scale$299 / mo1,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

HeaderDescription
X-Quota-UsedBilled lookups this billing period
X-Quota-LimitMonthly quota for your plan

What Gets Billed

StatusBilled?Reason
2xxYesSuccessful lookup
202NoTrack is being analyzed
4xxNoClient error (bad request, invalid key, not found)
429NoRate limited or quota exceeded
5xxNoServer error (our fault)

Response Format

All responses use a consistent JSON envelope. Every response includes a unique request_id for debugging.

Success

json
{
  "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

json
{
  "error": {
    "message": "Invalid API key",
    "status": 401
  },
  "meta": {
    "request_id": "req_f6e5d4c3b2a1"
  }
}

Note

The X-Request-Id header is also set on every response. Include it in support requests for fast debugging.

Endpoints

Get Track Features

GET/v1/tracks/{isrc}/features

Returns 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.

ParameterTypeRequiredDescription
isrcstring (path)requiredInternational 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

json
{
  "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

json
{
  "data": {
    "isrc": "USRC17607839",
    "status": "analyzing",
    "estimated_seconds": 30
  },
  "meta": {
    "request_id": "req_c3d4e5f6a1b2"
  }
}

200 Response: Unavailable

json
{
  "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

See Handling 202s for retry strategies including simple polling, webhooks, and batch pre-analysis.

Get Track Metadata

GET/v1/tracks/{isrc}/metadata

Returns 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.

ParameterTypeRequiredDescription
isrcstring (path)requiredISRC 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.
json
{
  "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

GET/v1/tracks/search?q={query}

Search for tracks by title or artist name. Returns tracks that have been analyzed (excludes unavailable tracks).

ParameterTypeRequiredDescription
qstring (query)requiredSearch query. 2 to 200 characters. The SQL wildcard characters % and _ are rejected with a 400.
limitinteger (query)optionalResults per page. Default 20, max 50.
offsetinteger (query)optionalNumber of results to skip. Default 0, max 10000.
json
{
  "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

GET/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.

ParameterTypeRequiredDescription
titlestring (query)requiredTrack title. 2 to 200 characters.
artiststring (query)requiredArtist 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.

bash
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"
json
{
  "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" } }
}
StatusMeaning
200Resolved. Check confidence and warning before you rely on it.
400Missing or out-of-range title or artist.
404Every source answered and none of them knew this track. Not billed.
503No source could answer. Retry after the Retry-After header. Not billed.

Get Recommendations

GET/v1/recommendations

Returns similar tracks based on audio feature similarity to your seed tracks. Optionally override target feature values to steer recommendations.

ParameterTypeRequiredDescription
seedstring (query, repeatable)requiredISRC of a seed track. 1 to 5 seeds.
limitinteger (query)optionalMax results. Default 10, max 50.
target_bpmnumber (query)optionalOverride target BPM instead of averaging seeds.
target_energynumber (query)optionalOverride target energy (0.0 to 1.0).
target_danceabilitynumber (query)optionalOverride target danceability (0.0 to 1.0).
target_valencenumber (query)optionalOverride target valence (0.0 to 1.0).
bash
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"
json
{
  "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

POST/v1/tracks/batch/features

Look 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

json
{
  "isrcs": ["USRC17607839", "GBAYE0601498", "XX0000000000"]
}

Response

json
{
  "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

Each ISRC in the batch counts as one billed lookup. A batch of 50 ISRCs uses 50 from your quota.

Batch Pre-Analyze

POST/v1/tracks/batch/analyze

Queue 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

json
{
  "isrcs": ["USRC17607839", "GBAYE0601498"]
}

Response

json
{
  "data": {
    "queued": 3,
    "already_analyzed": 1,
    "already_queued": 1,
    "total": 5
  },
  "meta": { "request_id": "req_c3d4e5f6a1b2" }
}

Tip

Use batch pre-analyze when you have a playlist or catalog of ISRCs. Queue them all ahead of time, then query features individually when your users request them. Later requests can use completed cached results, but some recordings may remain pending or unavailable.

Saved Bulk Jobs

POST/v1/tracks/jobs

Submit 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.

bash
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.

PlanRows/monthRows/jobActive jobs
Free25101
Dev1,0002503
Pro10,0002503
Scale50,0002503

Note

Bulk allowance is separate from lookup quota. Each submitted row uses one bulk unit, including duplicates and unmatched tracks. Reading results, replaying a submission key, and retrying failed items use no extra units. There are no bulk overage charges.
  • GET /v1/tracks/jobs lists 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}/retry retries 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

POST/v1/tracks/{isrc}/reanalyze

Request 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.

ParameterTypeRequiredDescription
isrcstring (path)requiredISRC of the unavailable track.

200 Response

json
{
  "data": {
    "isrc": "XX1234567890",
    "status": "queued_for_reanalysis",
    "estimated_seconds": 30
  }
}

Error Responses

StatusCondition
403Free or Dev plan (Pro+ required)
404Track not in database (use GET features first)
409Track already has valid analysis data
42930-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 analysis

For 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 archive

Millions 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

If preview-window approximations don't fit your use case (audio mastering, compliance loudness reporting), get in touch, full-track analysis is on the roadmap as a paid tier through a licensed analysis partner.

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.

javascript
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.

json
// 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

javascript
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

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

bash
# 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.

SDKStatusInstall
JavaScript / TypeScriptComing soonNot published
PythonComing soonNot published
RubyPlannedNot published

Note

Want early access to an SDK? Let us know which language you need and we will prioritize accordingly.

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.

Get Skill

Errors Reference

All error responses use the standard error envelope. Below is every error code the API can return.

StatusMeaningCommon Causes
400Bad RequestInvalid ISRC, missing required parameter, malformed JSON body, batch size exceeds 50
401UnauthorizedMissing Authorization header, invalid API key, revoked key, wrong key format
403ForbiddenEndpoint requires a higher plan (e.g. reanalyze requires Pro+)
404Not FoundTrack not in database. Use GET features to trigger analysis first.
409ConflictTrack already has valid analysis (reanalyze endpoint only)
429Too Many RequestsRate limit exceeded or monthly quota exhausted. Check X-RateLimit-Reset header.
500Internal Server ErrorUnexpected server failure. Not billed. Include X-Request-Id in support requests.
503Service UnavailableA 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

json
// 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

v1.8.02026-10-13CURRENT

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 bpm is now the correct value (for example 178.2 is now 89.1), and each now carries a tempo_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, confidence is 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.
v1.7.02026-10-02

Every feature response now says how likely its BPM is to be right.

  • Added: tempo_confidence. A number from 0 to 1 next to bpm in feature responses, batch results and the track.analyzed webhook. It is the chance that bpm is 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_confidence is 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 stored bpm. Archive tracks get one once their preview is analyzed.
v1.6.02026-10-01

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" and analysis_version: "ab_1.2".
  • Some fields are null on archive rows. On ab_1.2 rows, loudness, tempo_confidence and time_signature are 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 key and key_confidence on every archive row (ab_1.1 and ab_1.2) now come from our own analysis of the audio preview. The first request for one of these tracks returns key: null and queues that analysis; a later request returns the key with its key_confidence. On an archive row, a key always comes with a confidence. Tracks with no preview keep key: null. Billing is the same.
  • Filling in over time: loudness, time_signature and tempo_confidence on archive rows. When you request features for an ab_1.2 track, 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: bpm is never 0. About 130 archive tracks returned bpm: 0, which is not a tempo. They now return bpm: null, the same as any track where no tempo was detected.
  • Corrected: the docs example showed a liveness value. liveness is null on every row. It is not computed yet. Responses did not change.
v1.5.12026-09-30

A quota refusal now links to the plan that gets you going again.

  • Added: code and upgrade_url on the monthly quota 429. The error now carries code: "quota_exhausted", so you can tell it apart from a rate limit 429, and upgrade_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".
v1.5.02026-09-29

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}/metadata now 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/features that we have not analyzed yet now comes back as analyzing and is queued, instead of not_found with 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 returns Offshore as 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 I now matches Part 1, and a mix name written with a possessive, such as PvD'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.
v1.4.32026-09-28

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 as GBSMU3608834 on 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.
v1.4.22026-09-21

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.
v1.4.12026-09-14

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.
v1.4.02026-09-07
  • 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.
v1.3.12026-09-07
  • 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.
v1.32026-08-20

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 202 it 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. resolve is a new endpoint, and no existing response gained or lost a field.
v1.22026-08-07

Archive metadata recovery. Three quarters of the catalog was missing values that were in the source data all along.

  • Fixed: key and key_confidence are 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_ms is 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, artist and album, 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_confidence is null on archive-sourced rows. AcousticBrainz publishes no BPM confidence, so there is nothing to return, and it is now stated the same way loudness and time_signature already 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: null or duration_ms: null may now return a value. If you cached those nulls, refresh them. Nothing that already had a value changed.
v1.12026-05-16

Audio feature accuracy fixes. Thank you to the developer who reported the bug.

  • Fixed: loudness now 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: energy now returns a 0..1 perceptual proxy derived from loudness. Previous versions returned 0 on every live-analyzed track.
  • Fixed: danceability is 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_version and source so 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.
v1.02026-03-24
  • Initial API release
  • Audio feature extraction (BPM, key, energy, danceability, valence, acousticness, loudness, instrumentalness, speechiness). The liveness field is in the response but is always null, 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