SonoVault is in open beta — signups are live. Get your free API key →
ResourcesMusic TrackingBuilding a Genre-Filtered Release Radar

Building a Genre-Filtered Release Radar

Surface same-day drops scoped to a genre or set of labels. Combine /v1/releases/new with /v1/tracks/browse for an A&R-grade release radar — JSON in, JSON out, ready for Slack, email, or a dashboard.

Why a release radar beats a search box

Catching new releases in your scene reactively — searching every now and then for an artist you like — leaks signal. Things drop on Wednesday, you don't notice until Friday, the buzz has already happened. An A&R or playlist curator needs the opposite: a scheduled pull of every release that fits a scope, delivered before anyone else has heard it.

The scope is the interesting part. “Every release” is too much — thousands per day. “Just this one label” is too narrow — covered by the label release trackerarticle. The sweet spot is a small set of labels OR a genre filter — show me today's House releases, plus everything on Drumcode and Ninja Tune.

Two endpoints, two roles. /v1/releases/new is the time axis — releases ordered by date./v1/tracks/browse is the filter axis — tracks filtered by label, artist, genre, or year. The radar joins them: take recent releases, intersect with genre-filtered tracks, deliver.
Build
1

Pull today's new releases

/v1/releases/new returns releases ordered by release date (newest first), with cursor pagination. Walk it until you hit your date cutoff:

GET/v1/releases/new?limit=100200 OK
{
  "results": [
    {
      "id":           789,
      "title":        "Resonance EP",
      "artist":       { "id": 3,  "name": "Bicep" },
      "label":        { "id": 42, "name": "Ninja Tune" },
      "release_date": "2026-03-22",
      "track_count":  4
    },
    /* … */
  ],
  "next_cursor": "2026-03-22:78:789"
}
2

Pull genre-filtered tracks for the same window

/v1/tracks/browse accepts genreId (the canonical id from /v1/genres) or genre (free-text name), plus year. Filter by your target genre to get 20 tracks ordered by release date:

TypeScriptgenre-browse.ts
// /v1/tracks/browse filtered by genreId and year.

const params = new URLSearchParams({
  genreId: "12",   // House (from /v1/genres)
  year:    "2026",
});

const res = await fetch(`${BASE}/tracks/browse?${params}`, {
  headers: { "x-api-key": API_KEY },
});
const { results } = await res.json();
// 20 tracks ordered by release date (newest first), all House releases.
💡Prefer genreId over genre— it's stable and unambiguous. Fetch the id once from GET /v1/genres and cache it.
3

Join: recent releases that match the scope

Combine the two: a release goes in the radar if it's on a watched label OR if any of its tracks appears in today's genre-filtered browse. One concrete implementation:

TypeScriptradar.ts
const API_KEY = process.env.SONOVAULT_API_KEY!;
const BASE    = "https://api.sonovault.now/v1";

interface Release {
  id: number; title: string;
  artist: { id: number; name: string };
  label:  { id: number; name: string } | null;
  release_date: string;
  track_count:  number;
}

// 1. Pull every release from the past N days, paginating.
async function recentReleases(days = 1): Promise<Release[]> {
  const cutoff = new Date(Date.now() - days * 86_400_000)
    .toISOString().slice(0, 10);

  const out: Release[] = [];
  let cursor: string | null = null;

  while (true) {
    const qs: Record<string, string> = { limit: "100" };
    if (cursor) qs.cursor = cursor;
    const res  = await fetch(`${BASE}/releases/new?${new URLSearchParams(qs)}`, {
      headers: { "x-api-key": API_KEY },
    });
    const page = await res.json();

    for (const r of page.results as Release[]) {
      if (r.release_date < cutoff) return out; // past our window, done
      out.push(r);
    }
    cursor = page.next_cursor;
    if (!cursor) break;
  }
  return out;
}

// 2. Filter releases to those matching a configured taxonomy.
const WATCHED_LABELS = new Set([1, 42, 128]); // Drumcode, Ninja Tune, etc.
const WATCHED_GENRE  = "House";

async function buildRadar() {
  const [recent, genreTracks] = await Promise.all([
    recentReleases(1),
    fetch(
      `${BASE}/tracks/browse?${new URLSearchParams({ genre: WATCHED_GENRE, year: new Date().getFullYear().toString() })}`,
      { headers: { "x-api-key": API_KEY } },
    ).then(r => r.json()),
  ]);

  // Release matches if it's on a watched label, OR if any of its tracks
  // show up in the recent House browse.
  const houseReleaseIds = new Set<number>();
  for (const t of genreTracks.results) {
    for (const r of t.releases) houseReleaseIds.add(r.id);
  }

  return recent.filter(r =>
    (r.label && WATCHED_LABELS.has(r.label.id)) || houseReleaseIds.has(r.id),
  );
}

const radar = await buildRadar();
console.log(`${radar.length} releases in scope today`);
for (const r of radar) {
  console.log(`  ${r.release_date}  ${r.artist.name} — ${r.title}  [${r.label?.name ?? "no label"}]`);
}

From here, render it however you want — a JSON file dropped to S3, a Slack post via webhook, an email digest, or an internal dashboard widget. The expensive part (API calls) is done; the rest is presentation.

Going further

  • Score by popularity. Releases are returned with their artist popularity inputs to the sort but no score is exposed in the response. To prioritise, fetch /v1/artists/:id and use release_count as a proxy for established artists, or just keep the date order which already lifts notable releases through the artist-popularity tiebreaker server-side.
  • Diff against yesterday's run.Cache the radar output keyed by release ID. Only post to Slack what wasn't already in yesterday's feed — eliminates noise from multi-day-window pulls.
  • Add the label tracker. See the label release tracker for the focused single-label variant — useful when one label warrants its own feed.

Frequently asked questions

What's the difference between /v1/releases/new and /v1/tracks/browse?

/v1/releases/new is a date-ordered feed of newly-released releases — no filtering, just chronological. /v1/tracks/browse filters tracks by label/artist/genre/year and supports a randomize mode. Use new for the time dimension, browse for everything else.

Can I filter the new-releases feed by genre directly?

No — /v1/releases/new returns releases in date order with no filter parameters. For genre filtering you either join with /v1/tracks/browse after the fact, or use /v1/tracks/browse with year=2026 andrandomize=false (which orders by release date too).

Both /v1/releases/new and /v1/tracks/browse are paid endpoints. Can I do this on the free tier?

Not at full fidelity. The free tier supports search, ISRC lookup, and platform links but not the discovery endpoints. Use Starter (€29/mo) or higher to run a release radar.

Ready to build?

Free API key. No credit card. 1,000 requests to get started.

Get Free API Key
More in Music Tracking
Music TrackingHow to Track New Releases from a Record Label6 min read