DistroShield / API Docs Start free trial

API Reference · v1

DistroShield API

REST API for pre-upload music quality control. Each /v1/analyze request runs five modules in parallel — AI detection, duplicate check, metadata validation, recording fingerprint, and local catalog fingerprint — returning a combined recommendation (pass / review / block) before your track reaches any DSP. Built for distributors' ingest pipelines.

Base URL
https://api.distroshield.com
Current model
distroshield-v7c

Model-specific attribution (live since 2026-05-04)

The API returns a classification_origin field that identifies the generator. When classification is ai, this field is one of suno_unlicensed, licensed_ai (Udio / ElevenLabs), or unknown_ai. When classification is human or hybrid, the field mirrors that value. Additive and backward-compatible.

Attribution is informational — the operational decision is yours. Common practice for indie distributors: distribute AI tracks to DSPs but withhold Content ID monetization claims until licensing clarity. Some DSPs demonetize AI streams automatically; Believe and TuneCore block proactively. Pick the policy that fits your DSP relationships and catalog mix.

Authentication

Every request (except GET /health) requires a Bearer API key issued by DistroShield. Keep your key secret — it's your client identity and billing binding.

Authorization: Bearer ds_YOUR_API_KEY_HERE

Keys are provisioned manually during onboarding. Missing or invalid key → 401 unauthorized.

Quickstart

Minimal call that analyzes a track and returns classification. Replace YOUR_API_KEY and AUDIO_URL.

curl -X POST https://api.distroshield.com/v1/analyze \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "audio_url": "AUDIO_URL",
    "metadata": { "title": "Song A", "artist": "Artist A" },
    "client_track_id": "internal-123"
  }'

Typical latency: 3–6 seconds (audio download + inference). For high throughput, use POST /v1/batch.

Which endpoint should I use?

Both endpoints run the same underlying analysis pipeline and return the same response shape per track. The difference is latency and volume:

POST /v1/analyze Sync

One track per request. Response inline in ~3–6 s. Best when you need the verdict before returning control to the caller.

  • Consumer spot-checks (backend of a lookup UI)
  • Interactive dev testing
  • Low-volume integrations (< a few tracks/minute)
  • Any flow where "give me the verdict now" beats throughput
POST /v1/batch Async

Up to 1000 tracks per request. Queued and processed in background. Poll GET /v1/batch/:id or receive a webhook when each track completes.

  • Distributor ingest pipelines (daily / hourly batches)
  • Marketplace pre-acquisition due diligence
  • Ongoing catalog protection sweeps
  • Any workflow where throughput beats immediate response
Rule of thumb: if your integration hits DistroShield more than ~10 tracks/minute or runs unattended (cron, ingest queue, background job), use /v1/batch. If a human is waiting for the answer on the other end of the call, use /v1/analyze.
POST /v1/analyze

Analyze a track

Synchronously analyze one audio URL. Returns classification, AI score, and the derived recommendation (pass / review / block).

Request body

FieldTypeDescription
audio_urlstring, requiredPublicly fetchable HTTPS URL to the audio file (MP3, WAV, FLAC, M4A). Signed Firebase / S3 URLs are fine.
metadataobject, optionalTrack metadata (see below). Helps local signals.
metadata.titlestringTrack title.
metadata.artiststringArtist name. Names matching AI-generator patterns boost local_score.
metadata.isrcstringISRC code.
metadata.duration_secondsintegerTrack duration in seconds.
client_track_idstring, optionalYour internal track ID (≤255 chars). Echoed back for correlation.

Example

POST https://api.distroshield.com/v1/analyze
Authorization: Bearer ds_...
Content-Type: application/json

{
  "audio_url": "https://firebasestorage.googleapis.com/.../track.wav?token=...",
  "metadata": {
    "title": "Mi Bello Puerto",
    "artist": "Juan Pérez",
    "isrc": "USRC17607839",
    "duration_seconds": 187
  },
  "client_track_id": "mhm-2026-03296"
}

Response 200 OK

The response combines five independent quality signals — AI detection, duplicate check, metadata validation, recording fingerprint, and local catalog fingerprint — into a single recommendation. Details on each module below.

{
  "analysis_id": "an_8_2HwDlNC1f_",
  "db_id": 42,

  // ---- Module 1: AI detection ----
  "ai_score": 0.0655,
  "classification": "human",
  "classification_origin": "human",       // mirrors classification when not ai
  "origin_confidence": 0.9775,
  "confidence": 0.9775,
  "signals": {
    "local_score": 0.15,
    "local_reasons": ["missing_artist"],
    "model_score": 0.0373,
    "weights": { "local": 0.25, "model": 0.75 }
  },
  "model_version": "distroshield-v13",
  "attribution_model_version": null,      // populated only when classification == "ai"

  // ---- Module 2: Duplicate check (only if metadata.isrc or title+artist given) ----
  "duplicate_score": 0,
  "duplicate_matches": [],
  "sources_checked": ["spotify", "deezer", "youtube"],

  // ---- Module 3: Metadata validation (only if metadata given) ----
  "metadata_validation": {
    "score": 0.95,
    "summary": { "high": 0, "medium": 0, "low": 1 },
    "issues": [
      { "severity": "low", "type": "ddex_missing_recommended",
        "detail": "Recommended field \"genre\" is missing" }
    ]
  },

  // ---- Module 4: Recording fingerprint (only if audio_url given) ----
  "recording_fingerprint": {
    "matched": false,
    "matches": [],
    "highest_score": 0,
    "distinct_artists_at_perfect_score": 0,
    "submitted_isrc_matched": false,
    "submitted_artist_matched": false,
    "review_reason": null
  },

  // ---- Module 5: Local catalog fingerprint (only if audio_url given) ----
  "local_fingerprint": {
    "duration": 224.5,
    "matches": [],
    "top_score": 0,
    "review_reason": null
  },

  // ---- Additional fields when classification is "ai" or "hybrid" ----
  // "attribution_predictions": [ { "label": "suno_unlicensed", "score": 0.99 }, ... ],
  // "attribution_model_version": "distroshield-v8-attribution",
  // "v7c_verification_predictions": [ { "label": "human", "score": 0.82 }, ... ],
  // "v7c_verification_model_version": "distroshield-v7c",
  // "v8_rescue": {                          // audit trail of the v8 rescue chain
  //   "applied": true,
  //   "rescue_class": "models_consensus_no_external",
  //   "v7c_agrees": true,
  //   "external_gate": { "passed": false, "isrc_in_dsp": false, "acr_match": false }
  // },
  // "vocals_stem_rescue": {                 // vocals-stem isolation rescue trail
  //   "applied": false,
  //   "reason": "skipped_models_consensus_ai"
  // }

  // ---- Combined decision ----
  "recommendation": "pass",
  "analyzed_at": "2026-08-04T00:28:12.000Z"
}

The same response, rendered

Here's what the JSON above looks like when your compliance panel renders it. Every field surfaced, verdict at the top, one recommendation at the bottom. The JSON is what the API returns; the UI is yours to design — this is one possible layout.

Mi Bello Puerto
Juan Pérez · USRC17607839 · 3:07
Pass
an_8_2HwDlNC1f_
Module 1 · AI
0.07
Human · v13 · 0.98 conf
Module 2 · Duplicates
0
Spotify · Deezer · YouTube
Module 3 · Metadata
1
low: ddex genre missing
Module 4 · Recording fp
No commercial match
Module 5 · Local fp
Not in local catalog
Signed certificate available · analyzed 2026-08-04 00:28 UTC
Recommendation Pass
How each JSON field maps to the rendering above
  • metadata.title / artist / isrc → header line
  • classification + recommendation → verdict badges (top-right and footer)
  • ai_score + confidence + model_version → Module 1 tile
  • duplicate_matches.length + sources_checked → Module 2 tile
  • metadata_validation.summary + top issue .detail → Module 3 tile
  • recording_fingerprint.matched → Module 4 tile
  • local_fingerprint.matches.length → Module 5 tile
  • analyzed_at + certificate download from GET /v1/analysis/:id/certificate → footer

Response fields

FieldMeaning
ai_scoreFinal score 0.0–1.0 (ensemble of model + local signals). Higher = more likely AI.
classificationDerived from ai_score against your client thresholds: human, hybrid, or ai.
classification_originSub-attribution when AI is detected. One of: human, hybrid, suno_unlicensed (Suno-style — Warner deal Nov 2025, UMG/Sony pending), licensed_ai (Udio / ElevenLabs — major-label deals), unknown_ai (MusicGen / Stable Audio / Riffusion / unknown). When classification is human or hybrid, this mirrors that value. Informational only — the distribution / Content ID decision belongs to your policy.
origin_confidenceConfidence (0–1) in the classification_origin attribution.
attribution_model_versionOptional. Identifier of the attribution model that produced classification_origin (e.g. distroshield-v8-attribution). null when only the primary binary model ran.
confidenceHow confident the model is in the classification (0.0–1.0).
signalsBreakdown: model_score (pure ML), local_score (metadata rules), weights used.
recommendationpass (ship it), review (human-in-the-loop), or block (do not deliver to DSP). Upgraded from pass to review when a strong duplicate, high-severity metadata issue, or recording-fingerprint match is detected.
review_reasonSet when recommendation was upgraded from pass. One of: duplicate_detected, metadata_issue, recording_fraud_match, cross_distributor_recording_fraud.
duplicate_score0–1. Highest confidence that this track already exists somewhere public. See Duplicate check.
duplicate_matchesUp to 10 ranked matches from Spotify, Deezer, YouTube.
sources_checkedWhich DSPs were queried successfully.
metadata_validationPer-field issues + overall score. See Metadata validation.
recording_fingerprintAudio-hash match against 100M+ commercial recordings. Catches re-uploads with metadata changed. See Recording fingerprint.
local_fingerprintAudio-hash match against DistroShield's own growing catalog. Catches cross-distributor fraud on indie / unreleased tracks that never entered commercial catalogs. See Local catalog fingerprint.
attribution_predictionsPer-class probabilities from the v8 attribution model. Present only when classification is ai or hybrid. Lets you render the decision-support breakdown (which AI subclass and by how much).
v7c_verification_predictionsPer-class probabilities from the v7c verification model. Third-opinion tie-breaker used in the rescue chain. Present only when the rescue chain ran.
v8_rescueAudit trail of the v8-attribution rescue chain: which rescue class applied, whether v7c agreed, external-gate result (ISRC in DSPs, ACR match). Present only when the rescue chain ran.
vocals_stem_rescueAudit trail of the vocals-stem isolation rescue path (last-chance rescue for modern commercial productions where the full-track primary model misfires). Present only when the rescue triggered.
db_idInternal integer ID. Use this to PATCH reviews or fetch later.
model_versionDetector version that scored this track (currently distroshield-v13). Pin to monitor drift.
⚠ Human-in-the-loop is mandatory. Never auto-reject a track without a human option to override. Tracks with recommendation: "block" should enter a review queue, not be deleted.

Response modules

Each /v1/analyze response is produced by five independent quality-control modules running in parallel. Each module is optional — fields appear only when the module ran:

Module 1
AI detection

Acoustic analysis of the audio itself, using DistroShield's fine-tuned classifier trained on real AI and human catalogs. Always runs. Returns ai_score, classification, signals.

Module 2
Duplicate check

Cross-references ISRC and title+artist against Spotify, Deezer, YouTube in parallel. Runs when metadata has ISRC or title+artist. Returns duplicate_score, duplicate_matches.

Module 3
Metadata validation

ISRC format validation, DDEX completeness, identity fraud detection (ISRC belongs to someone else), artist-name impersonation checks. Runs when metadata is provided. Returns metadata_validation.

Module 4
Recording fingerprint · copyright infringement (commercial)

Audio-fingerprint match against 100M+ commercial recordings. Detects copyright infringement — unauthorized re-uploads, covers of released tracks, sample use of commercial material, and cross-distributor identity fraud. Runs when an audio URL is provided. Returns recording_fingerprint.

Module 5 · NEW
Local fingerprint · copyright infringement (indie)

Audio-fingerprint match against DistroShield's own growing corpus. Detects copyright infringement on indie and unreleased material — masters previously submitted by another client, cover-of-a-cover chains, and cross-distributor abuse invisible to public catalogs. Every analysis grows the catalog automatically. Returns local_fingerprint.

Module 2 · Duplicate check

Checks whether the track already exists in major public music sources. Executes when metadata.isrc OR both metadata.title and metadata.artist are provided. Skipped otherwise.

Sources

SourceISRC lookupTitle+artist search
Spotify Web API✓ exact✓ fuzzy
Deezer public API✓ exact✓ fuzzy
YouTube Data API v3✓ fuzzy

Scoring

  • Exact ISRC match on any source → match_score: 1.0
  • Fuzzy title+artist match (≥0.5) → match_score: 0.5–1.0
  • duplicate_score is the top match's score + 5% boost per additional source that also matched

Example match

{
  "source": "spotify",
  "match_type": "isrc",        // or "title_artist"
  "match_score": 1.0,
  "id": "7qiZfU4dY1lWllzX7mPBI3",
  "title": "Shape of You",
  "artist": "Ed Sheeran",
  "url": "https://open.spotify.com/track/7qiZfU4dY1lWllzX7mPBI3"
}

Module 3 · Metadata validation

Validates the declared metadata across four dimensions. Runs locally (<10 ms) when metadata is provided.

Checks performed

CheckWhat it catches
ISRC formatStructure (12 chars: 2 letters + 3 alnum + 7 digits), country code validity, future-year detection.
DDEX completenessMandatory fields (title, artist) + recommended DDEX 4 fields (isrc, duration_seconds, album, genre, language, release_date). Missing recommended fields are consolidated into a single issue to avoid noise.
Identity fraudCross-references the declared title + artist against what Spotify / Deezer have registered under that ISRC. Catches the "I claim this ISRC as my new song" fraud where someone submits a track using another artist's ISRC.
Artist impersonationFuzzy match against a curated list of globally-famous + LATAM-top artists. Flags suspicious near-matches like "3d Sheeran" vs "Ed Sheeran".
Duration anomaly checksFlags duration-based fraud and anomaly patterns (including known Content ID abuse). Specific thresholds are kept private to avoid tipping off bad actors — legitimate tracks are not affected.

Issue structure

{
  "severity": "high",          // high | medium | low
  "type": "isrc_identity_artist_mismatch",
  "detail": "ISRC is registered on spotify as artist 'The Weeknd' but you declared 'Mi Artista'",
  "registered": {              // present on identity-mismatch only
    "source": "spotify",
    "artist": "The Weeknd",
    "title": "Blinding Lights",
    "url": "https://open.spotify.com/track/..."
  }
}

Scoring

metadata_validation.score starts at 1.0 and subtracts penalties: high -0.40, medium -0.15, low -0.05. Clamped to [0, 1]. Any high severity issue upgrades the overall recommendation from pass to review.

Module 4 · Recording fingerprint · copyright infringement (commercial catalog)

Copyright infringement detection against a catalog of 100M+ commercial recordings. The technical mechanism is audio fingerprinting: hashes the submitted audio and matches it against every commercially-released recording indexed. Detects unauthorized re-uploads of copyrighted material, covers of released tracks, sample use of commercial recordings, and the case other modules are structurally blind to: a fraudster re-uploading someone else's exact recording with the metadata changed.

Two vocabularies for the same feature — fingerprint is what labels and distributors know it as, copyright infringement detection is what marketplaces, rights-holders, and legal teams call it. The response field name is recording_fingerprint.

Executes when an audio_url is provided. Skipped otherwise.

Fields

FieldMeaning
matchedTrue when at least one recording in the catalog matches.
matchesUp to 10 ranked matches. Each contains title, artists, album, label, score (0–100), release_date, isrc, upc, plus direct URLs to spotify_url, deezer_url, youtube_url when available.
highest_scoreTop match's score (0–100). 100 = perfect audio match.
distinct_artists_at_perfect_scoreHow many different artist identities own a near-perfect (≥95) match. ≥2 means the same recording is registered under multiple identities across distributors — per-se evidence of cross-distributor identity fraud.
submitted_isrc_matchedTrue when the ISRC in your metadata matches one of the ISRCs returned by the fingerprint match. Surfacing-only — does NOT downgrade the flag (a fraudster can copy any ISRC).
submitted_artist_matchedSame idea, for artist name.
review_reasonSet to cross_distributor_recording_fraud when 2+ distinct artists match at score ≥95, or recording_fraud_match when at least one match is at score ≥80. Otherwise null.
errorOptional. Set when the fingerprint backend returned an error (e.g. timeout, billing issue). The pipeline continues without this signal in that case.

Routing

  • • A single match at score ≥80 sets review_reason to recording_fraud_match and upgrades recommendation from pass to review.
  • • Two or more matches at score ≥95 with different artists takes precedence and sets review_reason to cross_distributor_recording_fraud — the highest-confidence form of fraud the API surfaces.
  • • The signal never auto-blocks. Human-in-the-loop is mandatory.

Module 5 · Local fingerprint · copyright infringement (indie / unreleased)

Copyright infringement detection against DistroShield's own growing catalog. Same audio-fingerprint mechanism as Module 4, but pointed at a different corpus. Fills the gap where the commercial catalog is structurally blind: indie and unreleased tracks that never entered any commercial catalog but were previously submitted to DistroShield by a different client. Catches the pattern where the same recording is re-submitted through multiple distributor pipelines with different identities — a common form of copyright abuse on indie material invisible to Spotify / Deezer / YouTube fingerprinting.

Two vocabularies for the same feature — local fingerprint for the technical audience, copyright infringement on indie catalog for the rights-holder audience. The response field name is local_fingerprint.

Executes when an audio_url is provided. Every analysis grows the catalog — no manual seeding needed. Runs in parallel with Module 4; fires independently, so both can flag on the same track (rare but possible for tracks that live in both commercial and internal catalogs).

Fields

FieldMeaning
durationDuration of the submitted audio, in seconds.
matchesRanked list of matches from the internal DistroShield catalog. Each contains track_id, client_id (the client who originally submitted the matching track), title, artist, isrc, and score (0–1 audio-hash similarity).
top_scoreBest match's similarity score (0–1). 1.0 = identical audio hash.
review_reasonSet to cross_distributor_recording_fraud when a match at score ≥0.95 comes from a different client_id — the same audio was submitted through multiple distributor pipelines. Otherwise null.
Why both Module 4 and Module 5: Module 4 (the commercial catalog) catches recordings already commercialized on major DSPs. Module 5 (the local catalog) catches recordings that never made it there — bedroom producers, unreleased material, distribution attempts blocked at other distributors. A fraudster reusing indie audio is invisible to Module 4 but caught by Module 5 as soon as any other client attempts to submit the same track.
POST /v1/batch

Batch analyze (async)

Submit many tracks at once. Returns a batch ID immediately; tracks are processed asynchronously by the worker queue. Results delivered via webhook (recommended) or polled via GET /v1/batch/:id.

Request body

{
  "tracks": [
    { "audio_url": "...", "metadata": {...}, "client_track_id": "..." },
    { "audio_url": "...", "metadata": {...}, "client_track_id": "..." }
  ],
  "webhook_url": "https://your-server.com/distroshield-webhook"
}

Response 202 Accepted

{
  "batch_id": "ba_17",           // prefixed form — use this in status_url
  "db_id": 17,                    // raw numeric ID (also accepted in GET /v1/batch/:id)
  "track_count": 2,
  "status": "queued",             // 'queued' | 'processing' | 'completed' | 'failed'
  "status_url": "/v1/batch/17"    // GET this to fetch per-track results
}

Response returns in <500ms — the server enqueues the tracks and hands back immediately. Two delivery options for the results:

  • Polling: your client hits GET /v1/batch/:id every 10–15s until status: "completed". Simpler to implement; no webhook infrastructure needed.
  • Webhook: if webhook_url is set, each track result is POSTed there as it completes with HMAC-SHA256 signature in X-DistroShield-Signature. Faster (no polling overhead) but requires public endpoint + signature validation.
GET /v1/batch/:id

Fetch batch status & results

Retrieve batch progress and per-track analysis results. The :id segment accepts either the raw numeric ID (17) or the prefixed form (ba_17) — both are equivalent. Scoped to your client — you can only fetch batches you created.

GET https://api.distroshield.com/v1/batch/17
Authorization: Bearer ds_...

Response 200 OK

{
  "id": 17,
  "batch_id": "ba_17",
  "client_id": 42,
  "track_count": 2,
  "completed_count": 2,           // increment as worker processes each track
  "status": "completed",          // 'queued' | 'processing' | 'completed' | 'failed'
  "webhook_url": null,
  "created_at": "2026-08-04T00:26:09.000Z",
  "completed_at": "2026-08-04T00:31:47.000Z",
  "tracks": [
    {
      "client_track_id": "mhmusik_track_46101_1",
      "track_id": 3265,
      "title": "Heart of Stone",
      "artist": "Miguel Ángel García Zamora",
      "isrc": "USABC1234567",
      "analysis": {
        // SAME SHAPE as the POST /v1/analyze response (see above).
        // All five modules (AI detection, duplicate, metadata, recording,
        // local fingerprint) + recommendation + review_reason + rescue
        // audit trails included when applicable.
        "analysis_id": "an_...",
        "db_id": 3242,
        "ai_score": 0.7478,
        "classification": "ai",
        "classification_origin": "suno_unlicensed",
        "origin_confidence": 0.987,
        "recommendation": "block",
        "review_reason": null,
        "duplicate_score": 0,
        "duplicate_matches": [],
        "metadata_validation": { /* ... */ },
        "recording_fingerprint": { /* ... */ },
        "local_fingerprint": { /* ... */ },
        "attribution_predictions": [ /* ... */ ],
        "v7c_verification_predictions": [ /* ... */ ],
        "v8_rescue": { /* audit trail when rescue chain fired */ },
        "vocals_stem_rescue": { /* vocals-stem isolation rescue trail */ },
        "signals": { /* ... */ },
        "model_version": "distroshield-v13",
        "analyzed_at": "2026-08-04T00:28:12.000Z",
        "review_status": "none"    // owner-review workflow state (batch-specific)
      }
    },
    {
      "client_track_id": "mhmusik_track_46101_2",
      /* ... second track ... */
    }
  ]
}

Polling strategy

  • status: "queued" — batch accepted, no tracks processed yet. Poll again in 10–15s.
  • status: "processing" — worker started at least one track. Compare completed_count vs track_count for progress. tracks[] already contains partial results for completed tracks; queued tracks are absent from the array until the worker picks them up.
  • status: "completed" — all tracks processed, tracks[] is complete. Distribute results to your DB, matching each result by client_track_id.
  • status: "failed" — the whole batch failed catastrophically (rare — check API logs or contact support). Individual track failures don't set this; a single failed track appears in tracks[] with its own error payload.
Recommended polling interval: 10–15 seconds. Batches with heavy tracks (AI verdicts that trigger the vocals-stem rescue chain) can take 2–3 minutes per track on CPU inference; polling too aggressively wastes requests. If your workflow can accept a webhook, prefer that path.
GET /v1/analysis/:id

Retrieve a single analysis

Fetch a full analysis row by its db_id. Scoped to your client — you can only fetch analyses for tracks you own.

GET https://api.distroshield.com/v1/analysis/42
Authorization: Bearer ds_...

→ {
  "id": 42,
  "track_id": 58,
  "ai_score": "0.0655",
  "classification": "human",
  "confidence": "0.9775",
  "signals_json": "{...}",
  "model_version": "distroshield-v13",
  "review_status": "none",
  "review_verdict": null,
  "reviewer_email": null,
  "reviewed_at": null,
  "review_notes": null,
  "analyzed_at": "2026-04-24T16:27:01.000Z",
  "client_id": 1
}
GET /v1/analysis/:id/certificate

Download signed provenance certificate

Returns the signed PDF certificate for a completed analysis. Same format shipped to consumer /lookup users — same hash algorithm, same verify URL, same signed layout. This is the artifact your artist attaches to a takedown appeal, your compliance team files with DSPs, or a marketplace investor sees before tokenizing a royalty stream. Ownership is scoped to your API key: you can only download certificates for analyses tied to tracks your client submitted.

Query params

ParamTypeDescription
langstringOptional. es for Spanish rendering, anything else falls back to English (default).

Example

curl -X GET "https://api.distroshield.com/v1/analysis/42/certificate?lang=en" \
  -H "Authorization: Bearer ds_..." \
  -o distroshield-certificate-an_42.pdf

Response headers

HeaderValue
Content-Typeapplication/pdf
Content-Dispositionattachment; filename="distroshield-certificate-an_<id>.pdf"
Cache-Controlprivate, no-store

What the PDF contains

  • SHA-256 hash of the analyzed audio — recomputable at any time to prove the audio hasn't been altered since analysis.
  • Verdict and model version — the classification (human, hybrid, or ai) and the detector model that produced it.
  • UTC timestamp at analysis time — establishes when the certificate was issued, prior to any subsequent takedown or dispute.
  • Track metadata as submitted (title, artist, ISRC) — ties the certificate to the release record.
  • Cross-DSP duplicate matches from the recording fingerprint module, when present.
  • Verifiable signature — anyone can validate the certificate at distroshield.com/verify without contacting DistroShield servers.

See a sample certificate PDF for the exact format your integration will receive.

GET /v1/analyses

List analyses (review queue)

Paginated list of analyses for your client. Use the status query param to filter — this is how your review queue UI fetches pending items.

Query parameters

ParamDefaultValues
statuspendingnone, pending, reviewed, overridden, all
classificationhuman, hybrid, ai
limit50Integer 1–200
offset0Integer ≥0

Example

GET /v1/analyses?status=pending&classification=hybrid&limit=20
Authorization: Bearer ds_...

→ {
  "count": 20,
  "limit": 20,
  "offset": 0,
  "items": [
    {
      "id": 42,
      "ai_score": "0.5231",
      "classification": "hybrid",
      "model_version": "distroshield-v13",
      "review_status": "pending",
      "analyzed_at": "2026-04-24T16:27:01.000Z",
      "track_id": 58,
      "title": "Track title",
      "artist": "Artist",
      "source_url": "https://..."
    },
    ...
  ]
}
PATCH /v1/analyses/:id/review

Record human verdict

After a reviewer decides whether a flagged track is actually AI or human, persist their verdict. These labels feed into future model retraining.

Request body

FieldTypeDescription
verdictstring, required
human — track is fully human-made
ai — track is fully AI-generated
uncertain — reviewer cannot decide
hybrid_confirmed — reviewer confirms the track legitimately mixes AI and human elements (e.g. AI-generated instruments with human vocals). Future model retraining uses this as a soft label so the model learns to produce intermediate scores instead of forcing every track into the binary human/ai decision.
notesstring, optional, nullableFree-text note about why (≤2000 chars). May be omitted or sent as null.
reviewer_emailstring, optional, nullableEmail of the person reviewing. May be omitted or sent as null; defaults to the client's contact_email.
PATCH https://api.distroshield.com/v1/analyses/42/review
Authorization: Bearer ds_...
Content-Type: application/json

{
  "verdict": "human",
  "notes": "Heavy autotune but definitely human — female reggaeton vocal",
  "reviewer_email": "j@mhmusik.com"
}

→ {
  "analysis_id": 42,
  "original_classification": "ai",
  "human_verdict": "human",
  "review_status": "overridden",
  "reviewer_email": "j@mhmusik.com",
  "reviewed_at": "2026-04-24T17:30:12.000Z"
}

review_status becomes overridden when the verdict disagrees with the model's original classification, else reviewed.

POST /v1/webhooks

Register a webhook

DistroShield will POST async analysis results to your URL. The secret returned is used to sign each delivery with HMAC-SHA256 — verify signatures to reject forgeries.

POST /v1/webhooks
Authorization: Bearer ds_...

{
  "url": "https://your-server.com/distroshield-webhook",
  "events": ["analysis.completed", "batch.completed"]
}

→ {
  "webhook_id": 3,
  "secret": "whsec_abc...",   // shown ONCE — store it securely
  "url": "https://...",
  "events": [...]
}

Each delivery retries with exponential backoff (30s → 4d, up to 8 attempts) until your endpoint responds 2xx.

Payload shape

Every delivery is a JSON POST to your registered URL. Body shape for the analysis.completed event:

{
  "event": "analysis.completed",
  "batch_id": "ba_...",              // present when the analysis came from POST /v1/batch; null for sync analyze
  "track_id": 58,
  "client_track_id": "internal-123", // whatever you passed when submitting the analysis; null if omitted
  "result": {
    // Identical shape to the POST /v1/analyze response body.
    // All fields documented at #analyze — ai_score, classification,
    // duplicate_matches, recording_fingerprint, local_fingerprint,
    // recommendation, analyzed_at, etc.
  }
}

Handling both sync (/v1/analyze response body) and async (webhook result field) with the same code is the intent — the shape is deliberately identical.

Verifying the HMAC signature

Every delivery includes an X-DistroShield-Signature header. Format: sha256=<hex>. Compute the same HMAC over the raw request body using the webhook secret you stored at registration; reject the delivery if it doesn't match. Node example:

import crypto from 'node:crypto';

app.post('/distroshield-webhook', (req, res) => {
  const signature = req.header('X-DistroShield-Signature'); // "sha256=abc123..."
  const secret = process.env.DISTROSHIELD_WEBHOOK_SECRET;   // from the /v1/webhooks response

  // IMPORTANT: use the RAW body bytes, not a re-serialized JSON.stringify(req.body).
  // With express.json(), req.rawBody must be captured via a verify hook.
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(req.rawBody)
    .digest('hex');

  // Constant-time compare to prevent timing attacks.
  const ok = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
  if (!ok) return res.sendStatus(401);

  const { event, result, track_id, client_track_id } = req.body;
  // ... process result.recommendation, result.classification, etc.
  res.sendStatus(200);
});

Missing or wrong signature → respond 401 and don't process the payload. Anyone can POST to your URL; the signature is what proves the payload originated from DistroShield.

GET /v1/usage

Usage metrics

Current period API usage for your client. Use to monitor your quota.

GET /v1/usage
Authorization: Bearer ds_...

→ {
  "period_start": "2026-04-01T00:00:00Z",
  "period_end": "2026-04-30T23:59:59Z",
  "requests": { "analyze": 312, "batch": 4, "total": 316 },
  "analyses_by_classification": {
    "human": 287, "hybrid": 18, "ai": 11
  }
}

Error codes

Errors return JSON with an error code. Some include additional context.

StatusCodeMeaning
400invalid_bodyRequest body didn't match the schema. Check the issues array.
400invalid_queryQuery parameters didn't validate.
400invalid_analysis_idThe :id path parameter wasn't a positive integer.
401unauthorizedMissing or malformed Authorization header (no Bearer token at all).
401invalid_api_keyBearer token present but doesn't match any active client — expired, rotated, or from the wrong environment (dev key against prod, or vice versa).
402quota_exceededTrial cap reached or Pro tier hit Enterprise threshold. Response includes usage object with current count and quota.
404not_foundResource ID doesn't exist OR doesn't belong to your client. Used for analyses, batches, and certificates. Deliberately doesn't distinguish "doesn't exist" from "not yours" — avoids leaking existence.
415unsupported_audio_formatThe audio file at audio_url could not be decoded. Response includes supported_formats array. Supported: WAV, FLAC, OGG, MP3, M4A, AAC.
429rate_limitedYou exceeded your rate limit window. Retry after the period resets.
500internal_errorUnexpected server error. Contact support with the request ID in response headers.
502audio_fetch_failedWe could not fetch the audio from the URL you provided. Check that the URL is reachable and not expired (e.g. signed URL TTL).
502inference_unavailableThe inference service is temporarily unavailable. Retry shortly.

Rate limits

Default: 300 requests per minute per API key. Response headers expose your current state:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 60

Exceeding returns 429 rate_limited. Need higher limits? Contact us for Pro/Enterprise tiers.

Detection model

Current production version: distroshield-v13 (primary, human vs AI) paired with distroshield-v8-attribution for generator attribution and distroshield-v7c as a verification tie-breaker. Trained on a curated mix of: real distributor-verified AI tracks, direct-from-source Suno and Udio samples, and thousands of human tracks from a real distributor catalog spanning reggaetón, latin pop, dembow, regional mexicano, cristiano/gospel, electronic, hip-hop, bachata, cumbia, folk and other Latin American styles. Continuously improved from production review verdicts.

AI detection
Complete recall on Suno & Udio
held-out samples direct from generator sources
Human protection
Ensemble-guarded
edge cases (heavy autotune, niche genres) caught by v8 + v7c + external catalog match

model_version is stamped on every analysis response so you can pin against drift or A/B new versions. When we promote a new primary model, the previous version stays available on request for hot rollback.

Scores are probabilistic. Human-in-the-loop review is mandatory. Never auto-block tracks without a human override path — the API is a gate, not a judge.