# FreqBlog Music Metadata

> Audio features + harmonic set-building for tracks by name/ISRC. Spotify audio-features replacement.

- name: com.freqblog/music-metadata
- version: 1.1.0
- connection class: unknown (unknown)
- trust: 51/100 flags: no-repository
- quality: 77/100 (good)
- owner: not claimed
- descriptor: https://api.protogrid.dev/v1/servers/com.freqblog%2Fmusic-metadata
- tools: https://api.protogrid.dev/v1/servers/com.freqblog%2Fmusic-metadata/tools

**Not classified yet.** The remote endpoints have not been probed successfully, or the listing has nothing to connect to.

## Connection (mcpServers)

```json
{
  "mcpServers": {
    "music-metadata": {
      "type": "http",
      "url": "https://mcp.freqblog.com/mcp"
    }
  }
}
```


## Trust

- hygiene: 80
- liveness: 28
- freshness: 65
- provenance: 45
- drivers: +dns-namespace -unreachable(1 failures) ~updated-98d-ago -no-repository
- Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed.

## Quality 77/100 (good)

### protocol: 75 (weight 25)

- warn `protocol.modern`: newest supported version is 2025-11-25; 2026-07-28 not supported, older versions are deprecated until 2027-07-28
- n/a `protocol.stateless`: only applies to 2026-07-28 servers
- pass `protocol.transport`: streamable HTTP
- n/a `protocol.list_ttl`: only applies to 2026-07-28 servers

### authorization: n/a (weight 15)

- n/a `auth.prm`, `auth.as_metadata`, `auth.cimd`: no remote uses OAuth
- n/a `auth.secret_in_url`: no templated URL

### tool hygiene: 79 (weight 30)

- pass `tools.descriptions`: every tool has a description
- warn `tools.description_length`: 5 tools have descriptions over 1,024 characters, which crowds the context
- pass `tools.schemas`: every tool has an object input schema
- pass `tools.annotations`: 12 of 12 tools declare readOnlyHint or destructiveHint
- fail `tools.directory_hints`: no tool declares all four of readOnlyHint, destructiveHint, idempotentHint and openWorldHint; missing: search_catalog (destructiveHint, idempotentHint); find_tracks_by_bpm (destructiveHint, idempotentHint); find_tracks_by_key (destructiveHint, idempotentHint); …
- pass `tools.token_cost`: about 6,567 tokens to load every tool
- pass `tools.api_dump`: tools are not a one-to-one API dump

### stability: 75 (weight 15)

- warn `stability.changes`: 18 tool changes in 30 days
- pass `stability.rug_pull`: no tool changed its meaning under the same name

### dependencies: n/a (weight 15)

- n/a `deps.known_vulns`, `deps.mcp_sdk_version`, `deps.resolvable`: no npm or PyPI package

- tools: 12, about 6,567 tokens to load them all
- tool set hash: a74f2f731b46436d22b553dfbcbd40e1
- tools last changed: 2026-10-19T21:55:33.239Z
- badge: [![protogrid quality](https://protogrid.dev/badge/com.freqblog/music-metadata.svg)](https://protogrid.dev/servers/com.freqblog/music-metadata)
- Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed.

### Tool changes

- 2026-10-19 `get_audio_features_batch` changed (description)
  - before: Get audio features for MANY tracks in one call (up to 50 processed) — ideal for analysing a whole playlist at once. Identify each item by name (`track`/`artist`), by `isrc` (matched exactly first — best for CJK / K-pop / niche tracks whose fuzzy name-match misses), or both (ISRC first, name as the fallback). One bad entry never fails the batch. Items beyond the 50-per-call cap come back with `found: false` and `backfill_status: "over_limit"`; an item missing BOTH `track` and `isrc` comes back `"invalid_no_query"`. Neither is processed or charged — the response's `skipped` field counts them, so split a long list into calls of <=50 and resubmit any skipped rows. Returns counts (`found` / `not_found` / `skipped`) plus a per-track `results` array, where each entry's `result` is the same feature object as get_audio_features (or null when not found), and `isrc` is echoed back. An item is billed only when it returns features or queues an on-demand ingest; an ISRC/name with no match anywhere is free. A track you queue and then collect costs ONE unit, not two — the call that collects it is free. For a single track, use get_audio_features.
  - after: Get audio features for MANY tracks in one call (up to 50 processed) — ideal for analysing a whole playlist at once. Identify each item by name (`track`/`artist`), by `isrc` (matched exactly first — best for CJK / K-pop / niche tracks whose fuzzy name-match misses), or both (ISRC first, name as the fallback). One bad entry never fails the batch. Items beyond the 50-per-call cap come back with `found: false` and `backfill_status: "over_limit"`; an item missing BOTH `track` and `isrc` comes back `"invalid_no_query"`. Neither is processed or charged — the response's `skipped` field counts them, so split a long list into calls of <=50 and resubmit any skipped rows. Returns counts (`found` / `not_found` / `skipped`) plus a per-track `results` array, where each entry's `result` is the same feature object as get_audio_features (or null when not found), and `isrc` is echoed back. An item is billed only when it returns features or queues an on-demand ingest; an ISRC/name with no match anywhere is free. A track you queue and then collect costs ONE unit, not two — the call that collects it is free. For a `found: false` item, `backfill_status` `queued` / `processing` / `deferred` means retry later; `unavailable` is terminal (carries `terminal`, `reason`, `retry_after_days` — treat it as a miss until then); null means no match anywhere. For a single track, use get_audio_features.
- 2026-10-18 `search_catalog` changed (description)
  - before: Full-text search the catalog by any mix of track / artist / album tokens. Use this to resolve a fuzzy, partial, or misspelled name into concrete tracks BEFORE calling get_audio_features. Each hit carries `chart_peak`: the best chart position we hold, 1-100 with 100 = a number-one. `null` means no placement we hold — treat that as UNKNOWN, not unpopular, and note it is NOT an audience-size figure. Coverage is Billboard year-end only so far. Returns lightweight stubs (itunes_track_id, track_name, artist_name, album, etc.) ranked by relevance — NOT audio features. Take the best match's track_name + artist_name and pass them to get_audio_features, or reuse its itunes_track_id as a `track_id` seed for discovery tools. ⚠ Each hit carries a `seedable` boolean. Only a hit with `seedable: true` can be used as a seed for get_recommendations / suggest_next_track / build_setlist / score_transition — those work off the similarity index, which holds only tracks we have analysed, and about a quarter of the catalogue is not analysed yet. **Prefer the highest-ranked hit with `seedable: true`.** Seeding with a `seedable: false` id returns a 404; if that track is the one you want, call get_audio_features on it first to queue analysis, then retry.
  - after: Full-text search the catalog by any mix of track / artist / album tokens. Use this to resolve a fuzzy, partial, or misspelled name into concrete tracks BEFORE calling get_audio_features. Each hit carries `chart_peak`: the best chart position we hold, 1-100 with 100 = a number-one. `null` means no placement we hold — treat that as UNKNOWN, not unpopular, and note it is NOT an audience-size figure. Placements come from the Apple Music and Deezer charts we track (since Aug 2026) and Billboard year-end charts (1960-2025). Returns lightweight stubs (itunes_track_id, track_name, artist_name, album, etc.) ranked by relevance — NOT audio features. Take the best match's track_name + artist_name and pass them to get_audio_features, or reuse its itunes_track_id as a `track_id` seed for discovery tools. ⚠ Each hit carries a `seedable` boolean. Only a hit with `seedable: true` can be used as a seed for get_recommendations / suggest_next_track / build_setlist / score_transition — those work off the similarity index, which holds only tracks we have analysed, and about a quarter of the catalogue is not analysed yet. **Prefer the highest-ranked hit with `seedable: true`.** Seeding with a `seedable: false` id returns a 404; if that track is the one you want, call get_audio_features on it first to queue analysis, then retry.
- 2026-10-16 `get_audio_features` changed (description)
  - before: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy and valence are models of how energetic / positive the preview sounds, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for the 30-second preview (not the whole track); speechiness and liveness are currently null for catalogue tracks. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
  - after: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy and valence are models of how energetic / positive the preview sounds, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for the 30-second preview (not the whole track); speechiness and liveness are currently null for catalogue tracks; time_signature is ASSUMED 4 (4/4) on every track, not measured. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
- 2026-10-13 `get_recommendations` changed (description)
  - before: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's): energy (a model of how energetic the preview sounds) and valence on our own scales, danceability/acousticness on AcousticBrainz's scale — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.
  - after: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's): energy and valence (models of how energetic / positive the preview sounds) on our own scales, danceability/acousticness on AcousticBrainz's scale — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.
- 2026-10-13 `get_audio_features` changed (description)
  - before: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy is a model of how energetic the preview sounds and valence our own raw value, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for the 30-second preview (not the whole track); speechiness and liveness are currently null for catalogue tracks. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
  - after: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy and valence are models of how energetic / positive the preview sounds, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for the 30-second preview (not the whole track); speechiness and liveness are currently null for catalogue tracks. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
- 2026-10-13 `tag_track` changed (description)
  - before: Get a compact, HONESTLY-LABELLED tag list for a track — energy / danceability / valence / acousticness / instrumentalness (instrumental vs vocal, judged on the 30-second preview), plus a mood tag and a broad genre tag. It is a tag-shaped projection of the same open-data analysis get_audio_features returns (no audio upload, no extra compute), so it costs the same 1 quota unit, charged only on a served result. The differentiator vs opaque taggers (e.g. Cyanite) is that EVERY tag carries its own `confidence` and `provenance`: - confidence: measured (a numeric audio feature, source in provenance) | derived (MIREX mood from valence+energy) | model-estimated (AcousticBrainz mood SVM probability — research-grade, raw prob in `value`) | catalog-genre (broad catalogue tag, not fine-grained). - provenance: librosa | panns | valence+energy | acousticbrainz | catalog. `value` is the [0,1] score for numeric tags and null for label-only tags (mood category, genre). Provide AT LEAST ONE identifier: `track` (optionally with `artist`), `isrc`, `mbid`, `spotify_id`, or `track_id` (catalog itunes_track_id). If you know several, send them all — they resolve by precedence (`track` > `isrc` > `track_id` > `mbid` > `spotify_id`) and the rest are ignored, so you never have to pick. The broad, reliable coverage is the MEASURED tags from our Essentia analysis over the analysed catalogue (plus on-demand by name); MBID/ISRC additionally reach 7.5M+ AcousticBrainz recordings WHEN you supply that identifier. Returns { track, count, tags:[{tag, category, value, confidence, provenance}], disclaimer }. For the full numeric feature set use get_audio_features; for nearest tracks use a discovery tool.
  - after: Get a compact, HONESTLY-LABELLED tag list for a track — energy / danceability / valence / acousticness / instrumentalness (instrumental vs vocal, judged on the 30-second preview), plus a mood tag and a broad genre tag. It is a tag-shaped projection of the same open-data analysis get_audio_features returns (no audio upload, no extra compute), so it costs the same 1 quota unit, charged only on a served result. The differentiator vs opaque taggers (e.g. Cyanite) is that EVERY tag carries its own `confidence` and `provenance`: - confidence: measured (a numeric audio feature, source in provenance) | derived (MIREX mood from valence+energy) | model-estimated (AcousticBrainz mood SVM probability — research-grade, raw prob in `value`) | catalog-genre (broad catalogue tag, not fine-grained). - provenance: librosa | panns | valence+energy | acousticbrainz | catalog. `value` is the [0,1] score for numeric tags and null for label-only tags (mood category, genre). Provide AT LEAST ONE identifier: `track` (optionally with `artist`), `isrc`, `mbid`, `spotify_id`, or `track_id` (catalog itunes_track_id). If you know several, send them all — they resolve by precedence (`track` > `isrc` > `track_id` > `mbid` > `spotify_id`) and the rest are ignored, so you never have to pick. The broad, reliable coverage is the MEASURED tags from our own analysis (librosa + a PANNs-based model) over the analysed catalogue (plus on-demand by name); MBID/ISRC additionally reach 7.5M+ AcousticBrainz recordings WHEN you supply that identifier. Returns { track, count, tags:[{tag, category, value, confidence, provenance}], disclaimer }. For the full numeric feature set use get_audio_features; for nearest tracks use a discovery tool.
- 2026-10-12 `tag_track` changed (description)
  - before: Get a compact, HONESTLY-LABELLED tag list for a track — energy / danceability / valence / acousticness, plus a mood tag and a broad genre tag (instrumentalness is currently null, so it is not tagged). It is a tag-shaped projection of the same open-data analysis get_audio_features returns (no audio upload, no extra compute), so it costs the same 1 quota unit, charged only on a served result. The differentiator vs opaque taggers (e.g. Cyanite) is that EVERY tag carries its own `confidence` and `provenance`: - confidence: measured (our Essentia analysis) | derived (MIREX mood from valence+energy) | model-estimated (AcousticBrainz mood SVM probability — research-grade, raw prob in `value`) | catalog-genre (broad catalogue tag, not fine-grained). - provenance: essentia | valence+energy | acousticbrainz | catalog. `value` is the [0,1] score for numeric tags and null for label-only tags (mood category, genre). Provide AT LEAST ONE identifier: `track` (optionally with `artist`), `isrc`, `mbid`, `spotify_id`, or `track_id` (catalog itunes_track_id). If you know several, send them all — they resolve by precedence (`track` > `isrc` > `track_id` > `mbid` > `spotify_id`) and the rest are ignored, so you never have to pick. The broad, reliable coverage is the MEASURED tags from our Essentia analysis over the analysed catalogue (plus on-demand by name); MBID/ISRC additionally reach 7.5M+ AcousticBrainz recordings WHEN you supply that identifier. Returns { track, count, tags:[{tag, category, value, confidence, provenance}], disclaimer }. For the full numeric feature set use get_audio_features; for nearest tracks use a discovery tool.
  - after: Get a compact, HONESTLY-LABELLED tag list for a track — energy / danceability / valence / acousticness / instrumentalness (instrumental vs vocal, judged on the 30-second preview), plus a mood tag and a broad genre tag. It is a tag-shaped projection of the same open-data analysis get_audio_features returns (no audio upload, no extra compute), so it costs the same 1 quota unit, charged only on a served result. The differentiator vs opaque taggers (e.g. Cyanite) is that EVERY tag carries its own `confidence` and `provenance`: - confidence: measured (a numeric audio feature, source in provenance) | derived (MIREX mood from valence+energy) | model-estimated (AcousticBrainz mood SVM probability — research-grade, raw prob in `value`) | catalog-genre (broad catalogue tag, not fine-grained). - provenance: librosa | panns | valence+energy | acousticbrainz | catalog. `value` is the [0,1] score for numeric tags and null for label-only tags (mood category, genre). Provide AT LEAST ONE identifier: `track` (optionally with `artist`), `isrc`, `mbid`, `spotify_id`, or `track_id` (catalog itunes_track_id). If you know several, send them all — they resolve by precedence (`track` > `isrc` > `track_id` > `mbid` > `spotify_id`) and the rest are ignored, so you never have to pick. The broad, reliable coverage is the MEASURED tags from our Essentia analysis over the analysed catalogue (plus on-demand by name); MBID/ISRC additionally reach 7.5M+ AcousticBrainz recordings WHEN you supply that identifier. Returns { track, count, tags:[{tag, category, value, confidence, provenance}], disclaimer }. For the full numeric feature set use get_audio_features; for nearest tracks use a discovery tool.
- 2026-10-12 `get_recommendations` changed (description)
  - before: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's): energy/valence on our raw scale, danceability/acousticness on AcousticBrainz's scale — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.
  - after: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's): energy (a model of how energetic the preview sounds) and valence on our own scales, danceability/acousticness on AcousticBrainz's scale — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.
- 2026-10-12 `get_audio_features` changed (description)
  - before: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy and valence are on our own raw scale (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness, speechiness and liveness are currently null for catalogue tracks. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
  - after: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key), energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more. This is the drop-in replacement for Spotify's deprecated /audio-features endpoint. Provide AT LEAST ONE identifier — if you know several, send them all rather than choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and the rest are ignored: - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd". - `isrc` — e.g. "USUM71900001". - `mbid` — a MusicBrainz recording UUID. - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading the track's title from Spotify's public embed page and matching it against our catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess — prefer `track`/`isrc` for full coverage). Returns a JSON object of features. Some feature fields may be null for tracks resolved via the fallback catalogs (only audio-derived values are present for fully analysed tracks). VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy is a model of how energetic the preview sounds and valence our own raw value, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones); danceability and acousticness are on AcousticBrainz's scale -- its classifier where available, else a model that listens to the preview, built on PANNs embeddings (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for the 30-second preview (not the whole track); speechiness and liveness are currently null for catalogue tracks. If a track name is not yet in the catalog, the API holds the request during the on-demand ingest and usually returns the fully analysed track inline in this same call; only if the ingest runs long does it fall back to a queued response you can re-poll shortly (~15s). If the track turns out not to be on any streaming source we can analyse, you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't retry it. If you only have a fuzzy or partial name, call search_catalog first to find the exact track.
- 2026-10-04 `get_recommendations` changed (description)
  - before: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. energy/danceability/valence are on FreqBlog's own scale (not Spotify's) — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.
  - after: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity (so a feature-close cross-genre track doesn't outrank same-genre picks). Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score, genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible" (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually use, so reordering seed_tracks changes it and a skipped seed never becomes the reference. With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track was pushed down — it may share a family with another of your seeds. `score` is the raw audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score, so the list is NOT strictly score-descending. Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids from search_catalog or the itunes_track_id field of a get_audio_features result. NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if both are given. TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher, nothing removed), over acousticness, danceability, duration_ms, energy, loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness` (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 — popularity recorded how a track entered our catalogue, not audience size; the other three are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's): energy/valence on our raw scale, danceability/acousticness on AcousticBrainz's scale — derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100}, max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune, the response adds a `filters` block saying what applied, how many tracks each bound removed (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`) — if the list comes back short, read that BEFORE assuming the catalogue is thin. Costs 2 quota units.

Full history: https://api.protogrid.dev/v1/servers/com.freqblog%2Fmusic-metadata/changes

## Remotes

- https://mcp.freqblog.com/mcp (streamable-http, auth none, reachable false, uptime30d 0.95689654)

## Packages

_none_

## Tools (12)

- `build_setlist`: Order a crate of 2-100 catalog tracks into a beat-matched DJ set that follows an energy
    arc, keeping each consecutive transition harmonically and tempo-smooth. `arc` is one of
    peak_time (default — builds to a peak then eases), warmup, cooldown, or flat.

    Returns the `arc`, `count`, an overall `flow_score` (0-100), the `tracks` in play order, the
    per-step `transitions` ({from_index, to_index, score, reason}), and `omitted` (ids not found
    in the catalog, or with no audio features on file yet). Feed tracks[].itunes_track_id into a Rekordbox/Serato export to drop the set
    straight into your DJ software. track_ids are catalog itunes_track_ids. Costs 5 quota units.
    
- `find_compatible_keys`: Given a Camelot key (e.g. "8A", "12B"), return the harmonically compatible keys for DJ
    mixing — the same key, the relative major/minor, and the adjacent +/-1 keys on the
    Camelot wheel. With `extended=true` also returns the +7/-7 energy-boost / energy-drop
    keys. Pure music theory — no catalog lookup and no quota cost. Pair with find_tracks_by_key
    to then pull actual tracks in each compatible key.
    
- `find_tracks_by_bpm`: Find catalog tracks near a target tempo. Returns tracks whose BPM is within
    +/-`tolerance` of `bpm`, ordered by closeness then by an internal catalogue ordering key (not an audience metric) — useful for DJ set
    planning, workout playlists, or tempo-matching. Each returned track carries full audio
    features. To also constrain by musical key, combine with find_tracks_by_key.
    
- `find_tracks_by_key`: Find catalog tracks in a given musical key — for harmonic mixing and key-locked
    playlists. `key` accepts Camelot ("8A"), Open Key ("1m"), or a key name ("A-Minor",
    "F#-Major"). Returns tracks ordered by an internal catalogue ordering key (not an audience metric), each with full audio features. To
    discover which keys mix well with a given key first, use find_compatible_keys.
    
- `get_audio_features`: Get audio features for ONE track — BPM, musical key (name + Camelot + Open Key),
    energy, danceability, valence, acousticness, loudness, mood, mood_vector, genre, time signature, duration and more.

    This is the drop-in replacement for Spotify's deprecated /audio-features endpoint.
    Provide AT LEAST ONE identifier — if you know several, send them all rather than
    choosing; they resolve by precedence (`track` > `isrc` > `mbid` > `spotify_id`) and
    the rest are ignored:
      - `track` (optionally with `artist`) — e.g. track="Blinding Lights", artist="The Weeknd".
      - `isrc` — e.g. "USUM71900001".
      - `mbid` — a MusicBrainz recording UUID.
      - `spotify_id` — a Spotify track ID, URI, or URL (resolved per request by reading
        the track's title from Spotify's public embed page and matching it against our
        catalogue — no Spotify IDs are stored; ambiguous titles miss rather than guess —
        prefer `track`/`isrc` for full coverage).

    Returns a JSON object of features. Some feature fields may be null for tracks resolved
    via the fallback catalogs (only audio-derived values are present for fully analysed
    tracks).

    VALUES are FreqBlog's own analysis, NOT on Spotify's scale: energy and valence are
    models of how energetic / positive the preview sounds, both on our own scales (derive thresholds from our values, don't copy Spotify-tuned ones);
    danceability and acousticness are on AcousticBrainz's scale -- its classifier where
    available, else a model that listens to the preview, built on PANNs embeddings
    (Kong et al. 2020, CC BY 4.0); instrumentalness is a vocal-detection model's score for
    the 30-second preview (not the whole track); speechiness and liveness are currently
    null for catalogue tracks; time_signature is ASSUMED 4 (4/4) on every track, not
    measured.

    If a track name is not yet in the catalog, the API holds the request during the
    on-demand ingest and usually returns the fully analysed track inline in this same call;
    only if the ingest runs long does it fall back to a queued response you can re-poll
    shortly (~15s). If the track turns out not to be on any streaming source we can analyse,
    you get a definitive not-found instead — that verdict is terminal for ~7 days, so don't
    retry it. If you only have a fuzzy or partial name, call search_catalog first to
    find the exact track.
    
- `get_audio_features_batch`: Get audio features for MANY tracks in one call (up to 50 processed) — ideal for
    analysing a whole playlist at once. Identify each item by name (`track`/`artist`), by
    `isrc` (matched exactly first — best for CJK / K-pop / niche tracks whose fuzzy
    name-match misses), or both (ISRC first, name as the fallback).

    One bad entry never fails the batch. Items beyond the 50-per-call cap come back with
    `found: false` and `backfill_status: "over_limit"`; an item missing BOTH `track` and
    `isrc` comes back `"invalid_no_query"`. Neither is processed or charged — the response's
    `skipped` field counts them, so split a long list into calls of <=50 and resubmit any
    skipped rows.

    Returns counts (`found` / `not_found` / `skipped`) plus a per-track `results` array, where
    each entry's `result` is the same feature object as get_audio_features (or null when not
    found), and `isrc` is echoed back. An item is billed only when it returns features or
    queues an on-demand ingest; an ISRC/name with no match anywhere is free. A track you
    queue and then collect costs ONE unit, not two — the call that collects it is free.
    For a `found: false` item, `backfill_status` `queued` / `processing` / `deferred` means
    retry later; `unavailable` is terminal (carries `terminal`, `reason`, `retry_after_days` —
    treat it as a miss until then); null means no match anywhere.
    For a single track, use get_audio_features.
    
- `get_recommendations`: Recommended tracks for one or more seed tracks — the drop-in for Spotify's removed
    GET /v1/recommendations. Blends up to 5 catalog seed tracks into a single point in
    audio-feature space and returns the nearest catalogue tracks, RE-RANKED by genre affinity
    (so a feature-close cross-genre track doesn't outrank same-genre picks).

    Returns `seeds` (each {id, found}), `count`, and `tracks` (each {track, score,
    genre_relation}; each track carries its `genre`). `genre_relation` is "same", "compatible"
    (different but mixable family), "cross" (unrelated), or "unknown" (either side has no mapped
    genre), measured against the PRIMARY seed — the first of your seed_tracks we could actually
    use, so reordering seed_tracks changes it and a skipped seed never becomes the reference.
    With a SINGLE seed the field is the ranking's own verdict, so it explains the order (same as
    suggest_next_track). With SEVERAL seeds the ranking considers ALL of them while the label stays
    relative to your primary seed, so a "cross" label on a multi-seed call does NOT mean the track
    was pushed down — it may share a family with another of your seeds. `score` is the raw
    audio-feature cosine similarity in [0,1]; genre affinity influences the ORDER, not the score,
    so the list is NOT strictly score-descending.
    Use cross_genre=strict to return same-genre-family tracks ONLY (off-genre dropped
    server-side), or allow to disable the genre ranking. seed_tracks are catalog itunes_track_ids
    from search_catalog or the itunes_track_id field of a get_audio_features result.

    NO id? Pass `track` (+ optional `artist`) instead and we resolve the name to the best catalog
    match and seed on it — the resolved track is echoed back as `seed_query`; seed_tracks wins if
    both are given.

    TUNING: `min`/`max` are HARD filters and `target` is a preference (nearer ranks higher,
    nothing removed), over acousticness, danceability, duration_ms, energy,
    loudness, tempo and valence. NOTE: `popularity` (2026-08-27), `liveness`, `speechiness`
    (2026-09-26) and `instrumentalness` (2026-09-27) are RETIRED as tunables and return a 400 —
    popularity recorded how a track entered our catalogue, not audience size; the other three
    are returned as null for catalogue tracks. Values are FreqBlog's own (not Spotify's):
    energy and valence (models of how energetic / positive the preview sounds) on our own scales, danceability/acousticness on AcousticBrainz's scale —
    derive thresholds from our values rather than reusing Spotify-tuned ones. e.g. min={"tempo": 100},
    max={"tempo": 130}, target={"energy": 0.8} for energetic 100-130 BPM tracks. When you tune,
    the response adds a `filters` block saying what applied, how many tracks each bound removed
    (`dropped_by`) and whether the bounds ran out of catalogue before `limit` (`limit_reached`)
    — if the list comes back short, read that BEFORE assuming the catalogue is thin.
    Costs 2 quota units.
    
- `get_related_artists`: Artists related to a seed artist — the drop-in for Spotify's removed
    GET /v1/artists/{id}/related-artists. No artist graph exists, so we derive one: build the
    seed artist's track-vector centroid, take its nearest catalogue tracks, aggregate by artist
    (each scored on its top-3 track similarities so a prolific artist can't dominate) plus a
    same-genre lift and a cross-genre penalty.

    Returns `artist`, `count`, and `related` (each {artist_name, score, match_count,
    sample_track_id}). Pass a sample_track_id straight to get_audio_features or
    suggest_next_track. Costs 2 quota units.
    
- `score_transition`: Score how well one catalog track mixes into another (0-100) — the pairwise DJ transition
    score no raw key/BPM API gives you. Combines Camelot-wheel key compatibility, octave-aware
    BPM proximity (half/double-time counts as a match), and energy smoothness.

    Returns the overall `score`, per-component scores (`harmonic`/`tempo`/`energy`), a `detail`
    block (key_relation, both Camelot keys, both BPMs, bpm_delta, bpm_octave_matched, both
    energies, energy_delta), and a one-line human `reason` (e.g. "8A->9A adjacent (+1), 126->128
    BPM (+2), energy +0.04 — clean uplifting mix"). Both ids are catalog itunes_track_ids — get
    them from search_catalog or the itunes_track_id field of a get_audio_features result. Costs
    1 quota unit.
    
- `search_catalog`: Full-text search the catalog by any mix of track / artist / album tokens. Use this to
    resolve a fuzzy, partial, or misspelled name into concrete tracks BEFORE calling
    get_audio_features.

    Each hit carries `chart_peak`: the best chart position we hold, 1-100 with 100 = a
    number-one. `null` means no placement we hold — treat that as UNKNOWN, not unpopular,
    and note it is NOT an audience-size figure. Placements come from the Apple Music and
    Deezer charts we track (since Aug 2026) and Billboard year-end charts (1960-2025).

    Returns lightweight stubs (itunes_track_id, track_name, artist_name, album, etc.) ranked
    by relevance — NOT audio features. Take the best match's track_name + artist_name and
    pass them to get_audio_features, or reuse its itunes_track_id as a `track_id` seed for
    discovery tools.

    ⚠ Each hit carries a `seedable` boolean. Only a hit with `seedable: true` can be used as
    a seed for get_recommendations / suggest_next_track / build_setlist / score_transition —
    those work off the similarity index, which holds only tracks we have analysed, and about
    a quarter of the catalogue is not analysed yet. **Prefer the highest-ranked hit with
    `seedable: true`.** Seeding with a `seedable: false` id returns a 404; if that track is
    the one you want, call get_audio_features on it first to queue analysis, then retry.
    
- `suggest_next_track`: Given a seed track, return the top-N catalog tracks to play NEXT, ranked by transition
    score. Each suggestion carries the same `score`, per-component scores and human `reason` as
    score_transition (e.g. "11B->11B same key, 118->117 BPM (-0.29), energy +0.12"), plus its
    `genre` and `genre_relation` to the seed. GENRE-AWARE by default (cross_genre=auto): off-genre
    picks that only coincidentally share the seed's key/BPM sink to the bottom — use
    cross_genre=strict for same-genre-family only, or allow for the old harmonic-only ranking. It
    is the seed's sonic neighbours re-ranked for a clean mix.

    Returns `seed`, `count`, and a `suggestions` array of {track, score, components, reason}.
    seed_track_id is a catalog itunes_track_id from search_catalog or a get_audio_features
    result. Pair with build_setlist to order a whole crate. Costs 3 quota units.
    
- `tag_track`: Get a compact, HONESTLY-LABELLED tag list for a track — energy / danceability / valence /
    acousticness / instrumentalness (instrumental vs vocal, judged on the 30-second
    preview), plus a mood tag and a broad genre tag. It is a tag-shaped
    projection of the same open-data analysis get_audio_features returns (no audio upload, no extra
    compute), so it costs the same 1 quota unit, charged only on a served result.

    The differentiator vs opaque taggers (e.g. Cyanite) is that EVERY tag carries its own
    `confidence` and `provenance`:
      - confidence: measured (a numeric audio feature, source in provenance) | derived (MIREX mood from valence+energy) |
        model-estimated (AcousticBrainz mood SVM probability — research-grade, raw prob in `value`) |
        catalog-genre (broad catalogue tag, not fine-grained).
      - provenance: librosa | panns | valence+energy | acousticbrainz | catalog.
    `value` is the [0,1] score for numeric tags and null for label-only tags (mood category, genre).

    Provide AT LEAST ONE identifier: `track` (optionally with `artist`), `isrc`, `mbid`,
    `spotify_id`, or `track_id` (catalog itunes_track_id). If you know several, send them all —
    they resolve by precedence (`track` > `isrc` > `track_id` > `mbid` > `spotify_id`) and the
    rest are ignored, so you never have to pick. The broad, reliable coverage is the
    MEASURED tags from our own analysis (librosa + a PANNs-based model) over the analysed catalogue
    (plus on-demand by name);
    MBID/ISRC additionally reach 7.5M+ AcousticBrainz recordings WHEN you supply that identifier.

    Returns { track, count, tags:[{tag, category, value, confidence, provenance}], disclaimer }.
    For the full numeric feature set use get_audio_features; for nearest tracks use a discovery tool.
    
