# Music Catalog QA - DSP & ISRC Audit (`porcelain_vista/music-catalog-qa-dsp-isrc-audit`) Actor

Audit music catalog ISRC delivery and metadata across Spotify, Apple Music, and Deezer. Produces evidence-rich correction reports.

- **URL**: https://apify.com/porcelain\_vista/music-catalog-qa-dsp-isrc-audit.md
- **Developed by:** [Vlad Vitvitsky](https://apify.com/porcelain_vista) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Music Catalog QA prototype

A metadata-only prototype for auditing a music catalog across Spotify, Apple
Music, and Deezer. It detects missing platform deliveries, wrong artist
mapping, duplicate ISRCs, and conflicting title, artist, duration, date,
explicit, and label metadata.

The observation API also returns useful source metadata instead of discarding
it: album IDs and titles, source genres, normalized genres, artwork, track/disc
numbers, contributors, storefront availability, and streamability when the
source exposes those fields.

The prototype never downloads audio and never submits correction requests. It
produces evidence-rich findings and a correction packet for a label,
distributor, or artist manager to review.

### Run the deterministic demo

```powershell
./run-demo.ps1
```

Or run it directly:

```powershell
uv run python -m catalog_qa audit examples/catalog.csv `
  --fixtures examples/observations.json `
  --output demo-output
```

Open `demo-output/correction-report.md` and inspect:

- `summary.json` — counts by severity and issue type;
- `findings.csv` / `findings.json` — one row per issue;
- `observations.json` — normalized source observations;
- `correction-report.md` — human-readable correction packets.

### Try public live sources

```powershell
uv run python -m catalog_qa audit examples/live-catalog.csv --live --output live-output
```

Live mode uses public surfaces and can optionally use the official Spotify API:

- Deezer exact ISRC lookup;
- Odesli/Songlink cross-platform link resolution when a platform URL is supplied;
- Apple iTunes lookup/search for mapped track IDs or a title/artist fallback;
- Spotify oEmbed for a known Spotify URL;
- exact Spotify ISRC lookup when `SPOTIFY_CLIENT_ID` and
  `SPOTIFY_CLIENT_SECRET` are configured.

It is intentionally best-effort. Public endpoints can rate-limit, change, or
return incomplete fields. Every observation includes its source, retrieval
time, match method, confidence, and any error. A platform that cannot be
checked reliably is reported as `source_unavailable`, never as a missing
release. A `missing_platform` finding is emitted only after an exact lookup.

Apple title/artist search results are returned as `unverified` candidates. They
are never promoted to a verified release without a supplied/mapped URL or an
exact identifier lookup.

### Genre normalization

Source genres are always preserved in `observations[].genres`. By default,
known aliases are mapped deterministically into `genre_matches`, for example
`Dance` to `electronic/dance` and `dnb` to
`electronic/drum_and_bass`. Unknown values remain unmapped rather than being
guessed.

An optional Sentence Transformer mode handles descriptive or unfamiliar
English labels. Install it in the uv environment and run:

```powershell
uv sync --extra genres
uv run python -m catalog_qa audit examples/live-catalog.csv --live `
  --genre-normalization embeddings `
  --genre-model BAAI/bge-small-en-v1.5 `
  --output live-output
```

Every semantic result includes its score, acceptance state, and the top three
alternatives. The model is therefore a guarded fallback, not the source of
truth. Rules-only mode is the deployment default and does not load a model.
The default embedding model is pinned to BGE small English v1.5 revision
`5c38ec7c405ec4b44b94cc5a9bb96e735b38267a`; calibrated automatic acceptance
requires a score of at least `0.78` and a `0.02` lead over the second result.

The repeatable English evaluation set can be run with:

```powershell
uv run python -m catalog_qa.genre_eval examples/genre-eval.json `
  --model BAAI/bge-small-en-v1.5 `
  --holdout `
  --output genre-eval-bge.json
```

Current pinned-model results on 56 difficult positive descriptions and 16
non-music traps:

- deterministic rules: 281/281 canonical names and aliases mapped correctly;
- BGE and E5 both ranked the expected genre first for 53/56 difficult music descriptions (94.6%);
- calibrated BGE automatically accepted 23/56 positives, with no wrong accepted positive and 0/16 false accepts;
- E5 accepted 38/56 positives but also accepted 3/16 non-music traps;
- therefore BGE is the default and low-confidence results remain review-only with top-three alternatives.

Thresholds were selected on a stratified half of the set and separately
reported against the untouched half. This is a small contract-focused test,
not a claim of universal genre classification accuracy.

### Catalog columns

Required:

- `artist`
- `title`
- `isrc`

Optional:

- `row_id` (generated as `row-N` when omitted)
- `upc`, `release_date`, `duration_ms`, `explicit`, `label`
- `spotify_url`, `apple_music_url`, `deezer_url`
- `expected_spotify_artist_id`
- `expected_apple_music_artist_id`
- `expected_deezer_artist_id`

### Apify-shaped runner

The Docker image runs `python -m catalog_qa actor`. The Actor input accepts a
`catalog` array and a `mode` of `live` or `fixtures`. Optional Spotify client
credentials use an encrypted Apify secret input. On Apify, normalized audit
rows are pushed to the default dataset and `REPORT.md`, `SUMMARY.json`,
`FINDINGS.json`, and `OBSERVATIONS.json` are written to the default key-value
store. Outside Apify the same runner writes an `actor-output` directory.
Its `dataset-items.json` file mirrors the rows that the cloud Actor pushes to
the default dataset, so the complete API response can be inspected locally.

The default dataset always starts with an `audit_summary` row, followed by any
findings and then one normalized observation per checked platform. A clean
audit therefore remains a valid, evidence-rich Actor result.

The Actor output schema exposes API links for the dataset, summary, findings,
and Markdown report. Invalid catalog/fixture input fails with a non-zero exit
code; the local CLI also writes a JSON error object to stderr.

### Verify the contract

```powershell
uv run python -m unittest discover -s tests -v
```

### Scope and non-goals

This is an experiment, not a rights audit or legal opinion. It does not verify
ownership, royalties, publishing registrations, DDEX compliance, audio
identity, or every territory. High-severity findings should be confirmed by a
human before contacting a DSP or distributor.

# Actor input Schema

## `mode` (type: `string`):

Live public sources or supplied deterministic observations.

## `country` (type: `string`):

Two-letter storefront used for Apple and link resolution.

## `genre_normalization` (type: `string`):

Keep source genres and optionally add canonical genre matches. Embeddings requires the genres package extra.

## `genre_model` (type: `string`):

Hugging Face model ID or local model path used only in embeddings mode.

## `spotify_client_id` (type: `string`):

Enables exact Spotify ISRC lookup when supplied together with the client secret.

## `spotify_client_secret` (type: `string`):

Encrypted by Apify and used only to request a short-lived Spotify access token.

## `catalog` (type: `array`):

One object per expected recording. artist, title, and isrc are required; row\_id is generated when omitted.

## `fixtures` (type: `array`):

Only used in fixture mode. Each object must contain a catalog input\_ref and one supported platform.

## Actor input object example

```json
{
  "mode": "live",
  "country": "US",
  "genre_normalization": "rules",
  "genre_model": "BAAI/bge-small-en-v1.5",
  "catalog": [
    {
      "row_id": "example-1",
      "artist": "Daft Punk",
      "title": "One More Time",
      "isrc": "GBDUW0000053",
      "spotify_url": "https://open.spotify.com/track/0DiWol3AO6WpXZgp0goxAV"
    }
  ],
  "fixtures": []
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `findings` (type: `string`):

No description

## `observations` (type: `string`):

No description

## `report` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("porcelain_vista/music-catalog-qa-dsp-isrc-audit").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("porcelain_vista/music-catalog-qa-dsp-isrc-audit").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call porcelain_vista/music-catalog-qa-dsp-isrc-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,porcelain_vista/music-catalog-qa-dsp-isrc-audit"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/20CksuGkHv7uSsjiI/builds/kDfjmxyzrxLzkliZ5/openapi.json
