MeloData API
Audio features and metadata for any track by ISRC. Get BPM, key, energy, danceability, valence, and more. Built for music apps, recommendation engines, playlist tools, and audio analysis pipelines.
Base URL
Quick Start
Create an account in the developer portal.
Generate an API key from the dashboard. Save it immediately; it is shown only once.
Make your first request:
curl -H "Authorization: Bearer melo_sk_YOUR_KEY" \
https://melodata.voltenworks.com/api/v1/tracks/USRC17607839/featuresNote
Authentication
All API requests require a Bearer token in the Authorization header. Keys are created in the developer dashboard and follow the format:
melo_sk_{64 hex characters} # 72 characters totalExample header
Authorization: Bearer melo_sk_a1b2c3d4e5f6...| Detail | Value |
|---|---|
| Prefix | melo_sk_ |
| Full key length | 72 characters |
| Shown | Once at creation. Store it securely. |
| Dashboard display | melo_sk_a1b2c3d4... (prefix only) |
| Max active keys | 5 per account |
Key Rotation
Create a new key, then update your application. 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.
| 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. Case-insensitive. |
200 Response — Features Available
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"features": {
"bpm": 143.8,
"key": "Bb",
"key_confidence": 0.87,
"energy": 0.72,
"danceability": 0.39,
"valence": 0.23,
"acousticness": 0.28,
"loudness": -7.2,
"instrumentalness": 0.01,
"speechiness": 0.05,
"liveness": 0.24,
"time_signature": 4
}
},
"meta": {
"request_id": "req_a1b2c3d4e5f6",
"quota": { "used": 4521, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" }
}
}202 Response — Analysis QueuedNOT BILLED
Headers include Retry-After: 30
{
"data": {
"isrc": "USRC17607839",
"status": "analyzing",
"estimated_seconds": 30
},
"meta": {
"request_id": "req_c3d4e5f6a1b2"
}
}200 Response — Unavailable
{
"data": {
"isrc": "XX1234567890",
"status": "unavailable",
"reason": "No audio source found for analysis. This track may be unreleased, region-locked, or have an incorrect ISRC."
}
}Note
Get Track Metadata
/v1/tracks/{isrc}/metadataReturns metadata for a track: title, artist, album, release date, duration, and genres (from the artist record).
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | ISRC identifier. Case-insensitive. |
{
"data": {
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"artist_id": "0383dadf-2a4e-4d10-a46a-e9e041da8eb3",
"album": "A Night at the Opera",
"release_date": "1975-10-31",
"duration_ms": 354320,
"genres": ["rock", "classic rock", "progressive rock"]
},
"meta": {
"request_id": "req_d4e5f6a1b2c3",
"quota": { "used": 4522, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" }
}
}Search Tracks
/v1/tracks/search?q={query}Search for tracks by title or artist name. Returns tracks that have been analyzed (excludes unavailable tracks).
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string (query) | required | Search query. Minimum 2 characters. |
| limit | integer (query) | optional | Results per page. Default 20, max 50. |
| offset | integer (query) | optional | Number of results to skip. Default 0. |
{
"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
/v1/artists/{id}Returns artist data including genres and the number of tracks we have analyzed for them. Artist IDs are MusicBrainz identifiers, returned from track metadata and search endpoints.
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string (path) | required | MusicBrainz artist ID. |
{
"data": {
"id": "0383dadf-2a4e-4d10-a46a-e9e041da8eb3",
"name": "Queen",
"sort_name": "Queen",
"country": "GB",
"genres": ["rock", "classic rock", "glam rock"],
"track_count": 187
},
"meta": { "request_id": "req_f6a1b2c3d4e5", "quota": { "used": 4524, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Get Recommendations
/v1/recommendationsReturns similar tracks based on audio feature similarity to your seed tracks. Optionally override target feature values to steer recommendations.
| Parameter | Type | Required | Description |
|---|---|---|---|
| seed | string (query, repeatable) | required | ISRC of a seed track. 1 to 5 seeds. |
| limit | integer (query) | optional | Max results. Default 10, max 50. |
| target_bpm | number (query) | optional | Override target BPM instead of averaging seeds. |
| target_energy | number (query) | optional | Override target energy (0.0 to 1.0). |
| target_danceability | number (query) | optional | Override target danceability (0.0 to 1.0). |
| target_valence | number (query) | optional | Override target valence (0.0 to 1.0). |
curl -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/recommendations?seed=USRC17607839&seed=GBAYE0601498&target_energy=0.8&limit=5"{
"data": {
"recommendations": [
{
"isrc": "GBAYE0601498",
"title": "Don't Stop Me Now",
"artist": "Queen",
"match_score": 0.91,
"features": { "bpm": 156.2, "energy": 0.84, "danceability": 0.55, "valence": 0.68 }
},
{
"isrc": "USIR10211015",
"title": "Mr. Brightside",
"artist": "The Killers",
"match_score": 0.86,
"features": { "bpm": 148.1, "energy": 0.78, "danceability": 0.42, "valence": 0.31 }
}
],
"seed_count": 2
},
"meta": { "request_id": "req_a1b2c3d4e5f6", "quota": { "used": 4525, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Note
match_score ranges from 0 to 1, where 1 is an exact feature match. Scores are based on Euclidean distance across BPM, energy, danceability, and valence.Batch Get Features
/v1/tracks/batch/featuresLook up audio features for multiple tracks in one request. Maximum 50 ISRCs per batch. Counts as N billed lookups (one per ISRC).
Request Body
{
"isrcs": ["USRC17607839", "GBAYE0601498", "XX0000000000"]
}Response
{
"data": {
"tracks": [
{
"isrc": "USRC17607839",
"title": "Bohemian Rhapsody",
"artist": "Queen",
"features": { "bpm": 143.8, "key": "Bb", "energy": 0.72, "danceability": 0.39, ... }
},
{
"isrc": "GBAYE0601498",
"title": "Don't Stop Me Now",
"artist": "Queen",
"features": { "bpm": 156.2, "key": "F", "energy": 0.84, "danceability": 0.55, ... }
},
{
"isrc": "XX0000000000",
"status": "not_found"
}
]
},
"meta": { "request_id": "req_b2c3d4e5f6a1", "quota": { "used": 4528, "limit": 25000, "resets_at": "2026-05-01T00:00:00Z" } }
}Important
Batch Pre-Analyze
/v1/tracks/batch/analyzeQueue ISRCs for analysis without returning features. This endpoint is free and does not consume quota. Use it to pre-warm the cache before you need the data.
Request Body
{
"isrcs": ["ISRC1", "ISRC2", "ISRC3", "ISRC4", "ISRC5"]
}Response
{
"data": {
"queued": 3,
"already_analyzed": 1,
"already_queued": 1,
"total": 5
},
"meta": { "request_id": "req_c3d4e5f6a1b2" }
}Tip
Reanalyze Track
/v1/tracks/{isrc}/reanalyzeRequest re-analysis for a track marked as unavailable. Available on Pro and Scale plans only. A 30-day cooldown applies between re-analysis attempts for the same ISRC.
| Parameter | Type | Required | Description |
|---|---|---|---|
| isrc | string (path) | required | ISRC of the unavailable track. |
200 Response
{
"data": {
"isrc": "XX1234567890",
"status": "queued_for_reanalysis",
"estimated_seconds": 30
}
}Error Responses
| Status | Condition |
|---|---|
| 403 | Free or Dev plan (Pro+ required) |
| 404 | Track not in database (use GET features first) |
| 409 | Track already has valid analysis data |
| 429 | 30-day cooldown has not elapsed |
Handling Cache Misses (202 Responses)
When you query a track that hasn't been analyzed yet, the API returns 202 Accepted and queues the track for analysis. The 202 is not billed. Analysis typically takes 15 to 30 seconds. Three strategies for handling this:
Strategy 1: Simple RetryRecommended
Read the Retry-After header and wait. The retry will be a cache hit (billed once).
async function getFeatures(isrc) {
const res = await fetch(
`https://melodata.voltenworks.com/api/v1/tracks/${isrc}/features`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (res.status === 202) {
const retryAfter = res.headers.get("Retry-After") || 30;
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return getFeatures(isrc); // retry — will be a cache hit
}
return res.json();
}Strategy 2: Webhook NotificationPro & Scale
Register a webhook URL in your dashboard. When analysis completes, we POST the result to your server. No polling required.
// POST https://your-app.com/webhooks/melodata
{
"event": "track.analyzed",
"data": {
"isrc": "USRC17607839",
"features": {
"bpm": 143.8,
"key": "Bb",
"energy": 0.72,
"danceability": 0.39,
"valence": 0.23,
...
}
}
}Strategy 3: Batch Pre-AnalyzeAll plans
If you have a list of ISRCs you will need later, submit them upfront via the free batch analyze endpoint. When you query them individually later, most will already be cached.
// Step 1: Pre-warm the cache (free, no billed lookups)
await fetch("https://melodata.voltenworks.com/api/v1/tracks/batch/analyze", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
isrcs: ["ISRC1", "ISRC2", "ISRC3", "ISRC4", "ISRC5"],
}),
});
// Step 2: Wait for analysis (30-60 seconds)
await new Promise((r) => setTimeout(r, 60000));
// Step 3: Query features — most will be instant cache hits
for (const isrc of isrcs) {
const res = await fetch(
`https://melodata.voltenworks.com/api/v1/tracks/${isrc}/features`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
const data = await res.json();
// process features...
}Code Examples
JavaScript / Node.js
const API_KEY = process.env.MELODATA_API_KEY;
const BASE = "https://melodata.voltenworks.com/api/v1";
// Basic feature lookup with 202 handling
async function getFeatures(isrc) {
const res = await fetch(`${BASE}/tracks/${isrc}/features`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (res.status === 202) {
const wait = Number(res.headers.get("Retry-After") || 30);
console.log(`Track ${isrc} is being analyzed. Retrying in ${wait}s...`);
await new Promise((r) => setTimeout(r, wait * 1000));
return getFeatures(isrc);
}
if (!res.ok) {
const err = await res.json();
throw new Error(`${res.status}: ${err.error.message}`);
}
return res.json();
}
// Batch lookup
async function batchFeatures(isrcs) {
const res = await fetch(`${BASE}/tracks/batch/features`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ isrcs }),
});
return res.json();
}
// Usage
const { data } = await getFeatures("USRC17607839");
console.log(`BPM: ${data.features.bpm}, Key: ${data.features.key}`);Python
import os
import time
import requests
API_KEY = os.environ["MELODATA_API_KEY"]
BASE = "https://melodata.voltenworks.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def get_features(isrc: str) -> dict:
"""Get audio features for a track. Handles 202 retry automatically."""
res = requests.get(f"{BASE}/tracks/{isrc}/features", headers=HEADERS)
if res.status_code == 202:
wait = int(res.headers.get("Retry-After", 30))
print(f"Track {isrc} analyzing. Retrying in {wait}s...")
time.sleep(wait)
return get_features(isrc)
res.raise_for_status()
return res.json()
def batch_features(isrcs: list[str]) -> dict:
"""Batch lookup for up to 50 ISRCs."""
res = requests.post(
f"{BASE}/tracks/batch/features",
headers={**HEADERS, "Content-Type": "application/json"},
json={"isrcs": isrcs},
)
res.raise_for_status()
return res.json()
# Usage
data = get_features("USRC17607839")
print(f"BPM: {data['data']['features']['bpm']}")
print(f"Key: {data['data']['features']['key']}")curl
# Get features for a single track
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
https://melodata.voltenworks.com/api/v1/tracks/USRC17607839/features | jq .
# Search for tracks
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/tracks/search?q=bohemian+rhapsody&limit=5" | jq .
# Batch feature lookup
curl -s -X POST \
-H "Authorization: Bearer melo_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"isrcs":["USRC17607839","GBAYE0601498"]}' \
https://melodata.voltenworks.com/api/v1/tracks/batch/features | jq .
# Get recommendations from seed tracks
curl -s -H "Authorization: Bearer melo_sk_YOUR_KEY" \
"https://melodata.voltenworks.com/api/v1/recommendations?seed=USRC17607839&seed=GBAYE0601498&limit=10" | jq .
# Pre-analyze a batch (free)
curl -s -X POST \
-H "Authorization: Bearer melo_sk_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"isrcs":["ISRC1","ISRC2","ISRC3"]}' \
https://melodata.voltenworks.com/api/v1/tracks/batch/analyze | jq .SDKs & Libraries
Official SDKs for JavaScript, Python, and Ruby are in development. For now, the API is straightforward to call with any HTTP client. The patterns above work with fetch, requests, curl, or any library that speaks HTTP.
| SDK | Status | Install |
|---|---|---|
| JavaScript / TypeScript | Coming soon | npm install melodata |
| Python | Coming soon | pip install melodata |
| Ruby | Planned | gem install melodata |
Note
AI Agent Skill
Building with an AI coding agent (Claude Code, Cursor, etc.)? Copy the MeloData agent skill to give your AI full API context, including endpoints, auth patterns, 202 retry handling, and integration examples.
melodata-api skill
SKILL.md + reference files for Claude Code, Cursor, Windsurf, and other AI agents. Includes all endpoints, auth, billing rules, 202 handling, and integration patterns.
Errors Reference
All error responses use the standard error envelope. Below is every error code the API can return.
| Status | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Invalid ISRC, missing required parameter, malformed JSON body, batch size exceeds 50 |
| 401 | Unauthorized | Missing Authorization header, invalid API key, revoked key, wrong key format |
| 403 | Forbidden | Endpoint requires a higher plan (e.g. reanalyze requires Pro+) |
| 404 | Not Found | Track or artist not in database. For tracks, use GET features to trigger analysis first. |
| 409 | Conflict | Track already has valid analysis (reanalyze endpoint only) |
| 429 | Too Many Requests | Rate limit exceeded or monthly quota exhausted. Check X-RateLimit-Reset header. |
| 500 | Internal Server Error | Unexpected server failure. Not billed. Include X-Request-Id in support requests. |
Example Error Response
// 401 Unauthorized
{
"error": {
"message": "Missing or invalid Authorization header",
"status": 401
},
"meta": {
"request_id": "req_f6a1b2c3d4e5"
}
}
// 429 Rate Limited
{
"error": {
"message": "Rate limit exceeded",
"status": 429
},
"meta": {
"request_id": "req_a1b2c3d4e5f6"
}
}
// Headers: X-RateLimit-Reset: 1711234567
// 429 Quota Exceeded
{
"error": {
"message": "Monthly quota exceeded. Upgrade your plan or enable overage billing.",
"status": 429
},
"meta": {
"request_id": "req_b2c3d4e5f6a1"
}
}
// Headers: X-Quota-Used: 25000, X-Quota-Limit: 25000Changelog
- 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