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

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

Pilot status: MeloData is running on a curated sample dataset while the full 3M+ track archive loads. Tracks not in the sample are analyzed on-demand (~30 seconds) and cached permanently. You will never get a dead end.

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. The old key remains valid for a 24-hour grace period, after which it is automatically revoked. This allows zero-downtime rotations.


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. Case-insensitive.

200 Response — Features Available

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

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, duration, and genres (from the artist record).

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

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. Minimum 2 characters.
limitinteger (query)optionalResults per page. Default 20, max 50.
offsetinteger (query)optionalNumber of results to skip. Default 0.
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" } }
}

Get Artist

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

ParameterTypeRequiredDescription
idstring (path)requiredMusicBrainz artist ID.
json
{
  "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

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. Counts as N billed lookups (one per ISRC).

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": "not_found"
      }
    ]
  },
  "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. This endpoint is free and does not consume quota. Use it to pre-warm the cache before you need the data.

Request Body

json
{
  "isrcs": ["ISRC1", "ISRC2", "ISRC3", "ISRC4", "ISRC5"]
}

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. Most will be instant cache hits.

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

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

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

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

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

javascript
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

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

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 .

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

SDKStatusInstall
JavaScript / TypeScriptComing soonnpm install melodata
PythonComing soonpip install melodata
RubyPlannedgem install melodata

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 or artist not in database. For tracks, 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.

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. Upgrade your plan or enable overage billing.",
    "status": 429
  },
  "meta": {
    "request_id": "req_b2c3d4e5f6a1"
  }
}
// Headers: X-Quota-Used: 25000, X-Quota-Limit: 25000

Changelog

v1.02026-03-24CURRENT
  • 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