● v1 live sandbox: synthetic data

AMFTF Member API

Screen your catalogue against AMFTF's documented music-fraud records — fake and shell labels, stolen or re-delivered releases, AI-generated catalogues, impersonating artists and TikTok-store-only infringements — and act on matches inside your own workflow.

Your catalogue stays with you. Screening runs in one direction: you query AMFTF. We do not store, log or retain the identifiers you send — only request counts, for rate limiting. For catalogue-wide sweeps there is a daily bulk file you run locally, so millions of tracks can be checked without a single identifier reaching us.

Introduction

The Anti Music Fraud Task Force (AMFTF) documents music-distribution fraud: shell labels registered to publish other people's recordings, releases re-delivered under invented artist names, AI-generated catalogue published at volume, and sounds delivered to the TikTok store only, where they are invisible to any check that starts from a streaming service.

This API exists so that a distributor can find that material in its own catalogue — before it earns, before a rights holder complains, and without either side handing over data it should not have to.

Screen at ingest

Check a new delivery against documented records and the pre-delivery watchlist in one call.

Sweep the catalogue

Download a daily list — or a 117 KB filter — and screen millions of tracks locally.

Act with evidence

Every record carries a case id, an evidence link, and where audio analysis was used, a probability.

How screening works

There are two paths. Most members use both.

PathUse whenLoad on youLoad on AMFTF
A. Bulk file, screened locally
/v1/screening-list
Whole catalogue (millions of tracks)One download per dayNone
B. Batch check
/v1/check
Confirming hits · new deliveries · ad-hoc lookupsOne request per 1,000 idsSmall

Recommended shape: A for the nightly sweep, B to confirm the hits it produces and to screen new deliveries at ingest. The bulk filter is probabilistic, so every hit from path A should be confirmed through path B before anyone acts on it.

Sandbox & live

Every authenticated response carries a mode field, so you always know which dataset answered. (/v1/health is the one exception — it takes no key, so its mode describes the instance. See Status.)

ModeDataSigning
sandboxEntirely synthetic. Invented ISRCs (XX9AA…), UPCs, label names (EXAMPLE-SHELL-LLC) and ids. Nothing here is a real case and nothing is an allegation about any artist, label or distributor.May be relaxed on request for first contact
liveDocumented AMFTF case records.Always required

Build against sandbox first. The shapes are identical, so nothing changes when you move to a live key — but a sandbox key cannot leak a real case, and no real identifier ever enters your logs, tickets or test fixtures during integration. That is the point of it.

The key decides the dataset, not the deployment. A sandbox key is served the synthetic dataset and nothing else — there is no configuration in which it starts returning live records. If the synthetic dataset is ever unavailable on an instance, a sandbox key receives 503 sandbox_unavailable rather than falling back to live data. Failing is the correct behaviour there; quietly upgrading a test key to real case data is not.


Base URL & versioning

https://api.amftf.org/v1

TLS 1.2 or 1.3 only. HTTP is redirected to HTTPS; plain HTTP is never served.

  • Path-versioned. Breaking changes get a new path (/v2), never a silent change under /v1.
  • Additive within a version. New fields may appear at any time — parse defensively and ignore what you do not recognise.
  • 90 days' written notice before any breaking change, to every member with an active key.

Get a key

Keys are issued per member. Write to sher@pumpa.org.pk with your company name and a technical contact; sandbox keys are issued the same day.

You receive two values:

ValueLooks likeUsed for
keyamftf_sandbox_… / amftf_live_…Sent in the X-AMFTF-Key header. Identifies you.
secretrandom stringNever sent. Used to sign requests and to verify bulk files.

The secret is never transmitted. It signs requests; it does not travel in them. We send key and secret through separate channels and ask you to store the secret in a secrets manager, not in source control. If either is exposed, tell us and we rotate — rotation takes minutes and old keys can be disabled without downtime.

Your first request

The status endpoint needs no key. Start here to confirm connectivity:

curl -s https://api.amftf.org/v1/health

{"ok":true,"mode":"sandbox","list_version":"2026-08-07",
 "generated_at":"2026-08-07T00:00:00Z"}

Then a real screening call. This example uses a sandbox identifier, so you can run it verbatim once you have a sandbox key:

KEY="amftf_sandbox_…"
SECRET="…"
BODY='{"isrc":["XX9AA2600100"]}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" \
  | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -s https://api.amftf.org/v1/check \
  -H "X-AMFTF-Key: $KEY" \
  -H "X-AMFTF-Timestamp: $TS" \
  -H "X-AMFTF-Signature: sha256=$SIG" \
  -H 'Content-Type: application/json' \
  -d "$BODY"

Authentication

Three headers on every authenticated request:

HeaderValue
X-AMFTF-KeyYour key. Required.
X-AMFTF-TimestampUnix seconds. Must be within 300 seconds of our clock.
X-AMFTF-Signaturesha256=<hex> — see below.

Optionally, a key can be locked to an IP allow-list. Tell us the egress addresses and we restrict the key to them; a request from anywhere else is refused with 403 ip_not_allowed, even with the correct signature.

Request signing

signature = "sha256=" + HMAC_SHA256(secret, timestamp + "." + raw_body)

# raw_body is the exact bytes of the request body.
# For GET requests, raw_body is empty — sign timestamp + "." only.

Two mistakes account for nearly every signature failure, so they are worth stating plainly:

  1. Re-serialising the body after signing. If your HTTP client re-encodes JSON — different key order, different whitespace — the bytes on the wire no longer match what you signed. Serialise once, sign those bytes, send those bytes.
  2. A drifting clock. The 300-second window exists to stop a captured request being replayed later. If your server's clock is off by more than five minutes, every request fails with stale_timestamp. Run NTP.

The signature covers the body, so a proxy that modifies the payload in transit invalidates it — which is the intended behaviour, not an inconvenience.

Auth failures

StatusCodeMeaning
401no_keyX-AMFTF-Key header missing.
401bad_keyKey not recognised, or disabled.
401signature_requiredThis key requires signing; timestamp or signature absent.
401bad_timestampTimestamp is not unix seconds.
401stale_timestampOutside the 300-second window (replay protection).
401bad_signatureSignature does not match the body received.
403ip_not_allowedRequest IP is not on this key's allow-list.

We deliberately do not distinguish "unknown key" from "disabled key" in the response — an attacker should not learn which keys exist.


Identifier normalisation

Both sides must normalise identically. If they don't, screening does not error — it quietly misses, which is worse. These are the exact rules we apply, on our side and in the bulk filter:

TypeRuleExample
ISRC & Spotify idsUpper-case; keep A–Z0–9 onlyxx-9aa-26-00100XX9AA2600100
UPC / EAN / GTINDigits only, left-pad to 14 (GTIN-14)000000000001000000000000010
UPC — rejectedFewer than 8 digits, or all zeros → not an identifier, ignored0000000000000 → ignored
Names (advisory only)NFKD, strip diacritics, lower-case, non-alphanumerics → single spacesÉxemple Artist!exemple artist

The UPC rule matters more than it looks. The same release is written as UPC-12, EAN-13 and GTIN-14 across systems; without a canonical form one release looks like three different products and a real match slips through.

Exact vs advisory matches

Every match carries a match field. Treat these kinds differently — this is the single most important thing to get right in your integration.

matchBased onHow to use it
exactISRC · UPC · Spotify album idSafe to route straight into your takedown or hold queue.
originalISRC · UPC of the original release behind a redeliveryThe identifier you sent is the victim, not the offender. See below.
advisoryArtist name · title+artist pair · name similarityHuman review required. Also carries similarity (0–1) and review.

Do not auto-action advisory matches. Names collide legitimately — two unrelated artists can share one, and transliteration produces near-identical spellings for entirely different people. Names are supplied so a reviewer can compare in seconds, not so a script can act. We would much rather you tell us a name match was wrong than have a legitimate artist suspended over it.

The original object carries identifiers onlyisrc and upc. The original artist's and release's names are deliberately withheld: the artist whose work was re-delivered is the injured party, not a suspect, and naming them in a fraud feed serves no purpose. Identifiers lose nothing here — they normalise to an exact comparison, whereas names break on transliteration and spelling. Where AMFTF knows a delivery is a redelivery but holds no identifier for the original, original is null and delivery_type is still "redelivery".

match: "original" means the opposite of what a fraud match usually means. A TikTok-store-only redelivery takes somebody else's real release and delivers it again as though it were new. The original release is indexed too, so that if it is yours you find out. When you get this match, the record describes the redelivery — not your release. Your release is the one being copied. Do not take your own release down.

The similarity floor is 0.86; below that we do not report a name match at all. That threshold is a judgement, not a law of nature: lower, and the noise makes reviewers stop looking; a list nobody reads is worthless.

Findings & status

Two fields, because they answer different questions: what AMFTF has determined, and what has happened since.

amftf_finding — our determination

ValueMeaning
documentedAMFTF has investigated this release and assessed it as fraudulent. Evidence is on file and a case is open. This is a conclusion, not an open question.

status — where the case stands with the distributor

ValueMeaning
suspectedDocumented by AMFTF and awaiting the distributor's own verification. It is on your side to check and act — not on ours to decide.
verifiedThe distributor or platform checked it and agrees with our finding.
taken_downRemoved following an AMFTF report.
dismissedEvidence cleared it. We retire the flag and keep the record so the same release is not re-reported.

How to read suspected. It marks a case AMFTF considers proven on the evidence we hold, handed to you for verification against your own rights and delivery records — things only you can see. The conservative wording is deliberate: AMFTF does not make legal determinations about third parties, and a release can have an explanation that exists only in your contracts. The word is careful; the finding behind it is not tentative.

Every record carries case and evidence, and where audio analysis was used, ai_likelihood — so your reviewer sees the basis, not just a flag.

Distributor disclosure

This API never returns a distributor name. Every record carries an object instead:

{"known": true,  "disclosed": false}                     // we hold it, we don't publish it
{"known": false, "reason": "tiktok_store_only"}      // we do not know it
{"known": false, "reason": "not_recorded"}           // absence, stated as absence
CaseValueWhy
Release visible on a streaming serviceknown: true
disclosed: false
We hold delivery metadata, but naming the distributor of a release means disclosing one member's data to another. Cross-distributor matters are handled by AMFTF directly.
TikTok-store-only deliveryknown: false
reason: tiktok_store_only
Not published anywhere we can see it. We do not infer it.
Streaming release, metadata gave us nothingknown: false
reason: not_recorded
Absence, stated as absence.

known: false means we do not know. It never means "no distributor" or "an unidentified bad distributor". We would rather hand you a gap than a guess: a wrong distributor attribution inside a fraud dataset damages a company that did nothing, and it would end this association's usefulness the first time it happened.

You do not need this field to act. If an identifier we return matches your catalogue, it is yours to review; if it doesn't, it isn't your problem to solve. That test is more reliable than anything we could supply.

Data retention

  • Identifiers submitted to /v1/check are matched in memory and discarded. They are not written to disk, not logged, and not used to build any profile of your catalogue.
  • Access logs record method, path, status, response size and duration. Identifiers travel in request bodies only — this API never accepts them in a query string, precisely so they cannot end up in a log line, a proxy cache, or a browser history.
  • We retain per-key request counts and timestamps, for rate limiting.
  • Nothing a member submits is shared with another member, in any form.
  • The service reads a read-only snapshot of AMFTF case data. It holds no credentials for, and no route to, any AMFTF database.

Batch check

POST/v1/check

Screen up to 1,000 identifiers per request. Accepts several identifier types in one call. Non-matching identifiers are absent from the response — and are not retained.

Request body

FieldTypeMatch
isrcstring[]exact
upcstring[]exact
spotify_album_idstring[]exact
artist_namestring[]advisory
pairs{title, artist}[]advisory

All fields are optional; supply at least one. The 1,000 limit counts every element across every field, including pairs.

Two fields were removed in v1.7: spotify_artist_id and tiktok_sound_id. Neither is part of the AMFTF dataset, so every query against them returned an empty result — and an empty result reads as “clean”. Sending them now returns 400 field_removed rather than a silently empty match list. Screen artists with artist_name, and TikTok-store-only deliveries with isrc or upc.

TikTok-store-only deliveries frequently carry no ISRC or UPC at all. Those records are still screenable — match them by artist_name (advisory) or by pairs (title + artist). Every TikTok-only record's own title and artist are indexed for name matching, so a delivery with no identifier is still caught by name. When it is a redelivery, the original release's ISRC/UPC are indexed too.

{
  "isrc": ["XX9AA2600100", "XX9AA2600110"],
  "upc": ["0000000000010"],
  "artist_name": ["Example Persona"],
  "pairs": [{ "title": "Example Title", "artist": "Example Persona" }]
}

Response fields

FieldTypeNotes
checkedobjectHow many identifiers we received, per field. Use it to confirm nothing was dropped in transit.
matches[]arrayEmpty array when nothing matched. Absence of a match is not proof of legitimacy — only that it is not in our records.
matchstringexact, original or advisory. See aboveoriginal means the identifier you sent is the victim of a redelivery, not the offender.
similaritynumber0–1. Advisory matches only.
release, artist, labelstringThe documented fraudulent release’s own title, artist and shell-label name — the offender’s, not a victim’s.
kindstringSee fraud kinds.
backdatedboolRelease date differs from upload date — the date was presented as earlier than it is. Evidence-grade.
ai_likelihoodnumber0–100. An audio-analysis probability, not a legal determination.
distributorobjectNever a name. See distributor disclosure.
case, evidencestringCase id and the evidence record behind it.
delivery_typestringTikTok records only: redelivery or new_delivery.
delivered_asobjectTikTok records: {title, artist} the fake was delivered under. These are indexed for artist_name / pairs matching, so a TikTok-only delivery with no ISRC/UPC is still caught by name.
originalobjectRedeliveries: {isrc, upc} of the original release (identifiers only — the victim’s name is withheld). null if not known.
release_date, upload_datestringWhen present. A gap between them is what backdated flags.
first_seen, updated_atstringWhen AMFTF first documented the record, and when it last changed (YYYY-MM-DD).
spotify_albumstringConvenience URL when a Spotify album id is on the record.

Screening list

GET/v1/screening-list

The full documented set as a downloadable file — dated, signed, and cheap to serve. The list_version header tells you exactly which build you received; poll it and re-download only when it changes rather than assuming a fixed schedule.

ParameterValuesNotes
formatjsonl (default) · csv · bloom
sinceYYYY-MM-DDOnly records with updated_at >= since. Incremental sync. Ignored for bloom.

Response headers

HeaderMeaning
x-amftf-list-versionStable, quotable date of this list. Record it if you need to show, for compliance, which version a catalogue was screened against.
x-amftf-list-signaturesha256=<hex> — HMAC over the body, using your own secret. Verify it: that proves the file is ours and was not altered in transit.
x-amftf-signature-basisThe exact formula, so you never have to guess: HMAC_SHA256(your_secret, list_version + '.' + body)
GET /v1/screening-list?format=jsonl&since=2026-08-01

{"isrc":"XX9AA2600100","upc":"0000000000010","status":"suspected","amftf_finding":"documented","kind":"ai_generated_catalogue","case":"AMFTF-EXAMPLE-0001","updated_at":"2026-08-01"}
{"isrc":null,"upc":"0000000000024","status":"taken_down","amftf_finding":"documented","kind":"fake_label_release","case":"AMFTF-EXAMPLE-0002","updated_at":"2026-08-02"}
{"isrc":"XX9AA2690001","upc":"0000000090001","status":"suspected","amftf_finding":"documented","kind":"tiktok_only_redelivery","case":"AMFTF-EXAMPLE-0002","updated_at":"2026-08-05"}

Use compare_digest (or your language's constant-time equivalent) rather than == when comparing signatures.

Watchlist

GET/v1/watchlist

Pre-delivery screening: catch it before ingest rather than after it is live and earning. This is the highest-value endpoint for a distributor, and the cheapest to wire in.

{
  "list_version": "2026-08-07",
  "label_names":      ["EXAMPLE-SHELL-LLC", "EXAMPLE-HOLDINGS-LLC", …],
  "artist_names":     ["Example Persona", "Exemple Artist", …],
  "isrc_registrants": ["XX9AA", "XX8BB", …],
  "upc_prefixes":     ["0000000", "0000001", …],
  "note": "Prefix hits are the strongest signal in the dataset…"
}
FieldMatch strengthHow to use it
isrc_registrants
upc_prefixes
StrongDifferently-named shell labels sharing one ISRC registrant or GS1 prefix are usually one operator behind several masks. A prefix hit on a new delivery is worth a manual look even when nothing else matches.
label_names
artist_names
AdvisoryHuman review. Same caution as any name match.

Prefixes are not proof on their own — a registrant prefix can be reassigned, and legitimate labels sometimes share an issuer. They are the best early signal we have, which is a different claim from certainty.

Status

GET/v1/health

Public — no key required. Safe to poll from a monitor. Returns which dataset is loaded and its version, and nothing about any case.

{
  "ok": true,
  "mode": "live",
  "list_version": "2026-08-10",
  "generated_at": "2026-08-10T01:42:04+00:00",
  "sandbox_dataset": "2026-08-07"   // present when a synthetic dataset is loaded
}

On this endpoint mode describes the instance, not your key/v1/health takes no key, so it cannot report which dataset would answer you. A sandbox key sees "mode":"live" here and still receives synthetic records everywhere else. To confirm which dataset answered your request, read the mode field in that request's own response.

Endpoint index

GET/v1/

Public. A machine-readable summary of the live endpoints, the auth scheme, the normalisation rules and the retention policy — the same facts as this page, in JSON, so your client can assert against them rather than trusting a document that may be out of date.


Bloom filter format

GET /v1/screening-list?format=bloom returns a compact probabilistic filter: roughly 117 KB per 100,000 entries at a 1% false-positive rate. Millions of tracks can be screened in memory on your own hardware without a single identifier reaching us. Every hit is then confirmed through /v1/check, where false positives disappear.

File layout

line 1  JSON header, then a newline (\n)
rest    ceil(m / 8) raw bytes

// header
{"algo":"bloom-v1","m":958500,"k":7,"hash":"sha256-double",
 "bit_order":"lsb-first","n":100000,"list_version":"2026-08-07"}

Algorithm

Documented in full so you can reimplement it in any language and not depend on a library of ours:

m = header.m                       # bit count
k = header.k                       # 7

h = SHA256(normalised_identifier)  # 32 bytes; see Normalisation
a = big_endian_uint64(h[0:8])
b = big_endian_uint64(h[8:16])

for i in 0..k-1:
    bit = (a + i*b) mod m
    test bits[bit >> 3] & (1 << (bit & 7))     # LSB first

present  = all k bits set        # → confirm via /v1/check
absent   = any bit clear         # → definitively not in the list

Screening locally

The shape we recommend for a nightly catalogue sweep:

  1. Download ?format=bloom. Verify x-amftf-list-signature against your secret. Record x-amftf-list-version.
  2. Normalise your own identifiers using the rules above. This step is where integrations silently go wrong — test it against the sandbox values first.
  3. Filter in memory. A miss is definitive: that identifier is not in our records.
  4. Confirm the hits with POST /v1/check in batches of 1,000. False positives vanish here, and you get the case, evidence and status for the real ones.
  5. Routeexact to your action queue, advisory to a human.

Why the filter rather than the full list? Both are offered and either works. The filter is smaller, faster to check, and — because it is one-way — cannot be read back as a list of our records. Nothing about your catalogue leaves your systems in either case.


Fraud kinds

kindWhat it is
fake_label_releaseReleased under a shell label registered to publish other people's recordings.
impersonated_artistA profile using a real artist's name, or a near-spelling of it, to attract their audience.
ai_persona_artistAn invented artist with no real person behind it, used to publish AI-generated catalogue at volume.
ai_generated_catalogueRecordings assessed as machine-generated by audio analysis.
metadata_backdatingRelease date presented as earlier than the actual upload, typically to claim precedence over the original.
tiktok_only_redeliveryAn existing song re-delivered to the TikTok store alone under a changed artist or title, so creations pay the wrong party.
tiktok_only_new_deliveryA track that exists only in the TikTok store — typically AI-generated or under a fake persona, with no release anywhere else.
stream_manipulationArtificial streaming activity documented against the release.

TikTok-store-only deliveries are invisible to normal catalogue checks. They never appear on Spotify or Apple, so any screening that starts from a streaming service will never see them. That is exactly why the pattern is used. Payment on TikTok follows video creations rather than views, which is what makes the re-delivery worth doing.

What we do not send: creation counts, and stream counts. We hold both, and both are assembled from third-party sources. For your own catalogue your figures are authoritative — a weaker number from us would only move the conversation onto our data quality instead of onto the release. You get the identifiers, the case and the evidence; the counts you already have are better than ours.

Error codes

Errors are JSON, with a stable machine-readable code. Match on the code, never on the message — messages may be reworded.

{ "error": { "code": "too_many_identifiers",
            "message": "1500 identifiers in one request; the limit is 1000." } }
StatusCodeMeaning
400empty_bodyNo JSON body sent.
400bad_jsonBody is not valid JSON, or not an object.
400bad_fieldA field has the wrong type.
400nothing_to_checkNo identifiers supplied.
400bad_formatformat is not jsonl, csv or bloom.
400bad_sincesince is not YYYY-MM-DD.
401 / 403See auth failures.
404not_foundUnknown endpoint.
413too_many_identifiersMore than 1,000 identifiers in one request.
413body_too_largeBody exceeds 2 MB.
503sandbox_unavailableA sandbox key was used on an instance where the synthetic dataset is not loaded. Deliberately an error, not a fallback to live data — tell us and we restore it.
429rate_limitedRequest rate exceeded for this endpoint. Carries Retry-After. See rate limits.
429identifier_quotaIdentifiers-per-minute exceeded. Carries Retry-After.

On 429, wait for Retry-After seconds. Do not retry immediately with backoff of your own devising — the header tells you exactly how long, and hammering a limit is how a key ends up suspended.

Rate limits

LimitDefault
POST /v1/check — requests per minute, per key60
GET /v1/watchlist — requests per minute, per key120
GET /v1/screening-list — requests per hour, per key20
Identifiers per request1,000
Identifiers per minute, per key10,000
Request body2 MB
Clock skew±300 seconds

screening-list is limited per hour rather than per minute, because the list is built once a day: re-downloading it more often gains you nothing and it is by far the most expensive response we serve. Poll x-amftf-list-version — or /v1/health — and download only when it changes.

Every rate-limited response carries its own budget, so you never have to guess where you stand:

HeaderMeaning
X-AMFTF-RateLimit-LimitThe limit that applies to this endpoint for your key.
X-AMFTF-RateLimit-RemainingRequests left in the current window.
Retry-AfterOn 429 only — seconds to wait before retrying.

Higher limits on request — tell us the shape of your workload and we will raise them. The bulk file exists precisely so that a full catalogue sweep never needs to touch these limits.

Uptime target: 99.5% monthly. The service runs on AMFTF infrastructure separate from the member portal, so screening is unaffected by portal maintenance — case data and your existing key keep working throughout. Issuing a new key or rotating a secret does go through the portal, so those two actions can be delayed while it is down.

Roadmap

Phase 1 is live. The rest is deliberately unbuilt: the first partners to review shape it, and building endpoints before anyone has said what their workflow needs produces the wrong endpoints.

PhaseScopeStatus
1POST /check · GET /screening-list · GET /watchlist · GET /healthlive
2GET /fraud/labels · /fraud/releases · /fraud/tracks · /fraud/artists · /fraud/tiktok · GET /case/{id} — cursor-paginated, updated_since incremental syncplanned
3POST /outcomes — report what you actioned · optional webhook for newly documented matchesplanned

About POST /outcomes

The one thing we ask in return, and it stays optional. When you action a release we flagged (removed / under_review / dismissed, with a date), it closes the case in our records and lets us publish a distributor-responsiveness comparison that is evidence-based rather than estimated. Nothing else from a member's submission is stored or shared.

Changelog

DateChange
2026-08-10Per-endpoint rate limits. /check 60/min, /watchlist 120/min, /screening-list 20/hour — all per key. Responses now carry X-AMFTF-RateLimit-Limit and X-AMFTF-RateLimit-Remaining; 429 carries Retry-After.
2026-08-10Documentation corrections: match lists original alongside exact and advisory; the csv example had one column more in its rows than in its header; /v1/health now states that its mode describes the instance, not your key.
2026-08-15TikTok-only name screening. TikTok-store-only deliveries (which often have no ISRC/UPC) are now matched by artist_name and pairs — their title and artist are indexed for name matching. Previously only ISRC/UPC-bearing records were name-searchable.
2026-08-07v1.7 — spotify_artist_id and tiktok_sound_id removed from POST /check. Both now return 400 field_removed instead of a silently empty match list. Screen artists with artist_name, TikTok-store-only deliveries with isrc or upc.
2026-08-07v1 Phase 1 live. /check, /screening-list (jsonl · csv · bloom), /watchlist, /health. Sandbox dataset is synthetic.
2026-08-07distributor object replaces any distributor name field, across every endpoint.
2026-08-07Stream counts and TikTok creation counts removed from every endpoint — your own figures are authoritative for your catalogue.
2026-08-07The key's mode now selects the dataset. A sandbox key is never served live records; if the synthetic dataset is unavailable it returns 503 sandbox_unavailable.
2026-08-07TikTok records split by delivery_type: redelivery and new_delivery.

Attribution & scope

Reports and internal tooling built on this data should carry:

"Fraud intelligence provided by the Anti Music Fraud Task Force (AMFTF)."

Not exposed by this API, deliberately

  • Internal licensor identifiers.
  • AMFTF's prefix-network mapping and correlation graph.
  • Member-submitted confidential material.
  • The identity of any other distributor holding a matching release. Cross-distributor matters are handled by AMFTF directly, not disclosed member to member.
  • Stream counts and TikTok creation counts. Yours are authoritative for your own catalogue; ours are assembled from third-party sources. We send identifiers and evidence, not our numbers.

Accuracy

ai_likelihood is an audio-analysis probability, not a legal determination. Stream counts and TikTok creation counts are not part of this API — see fraud kinds. Every record released through this API — including every suspected one — has evidence on file and a named case behind it; nothing is published on suspicion alone.

Absence from our records is not a clean bill of health. It means the release is not in our documented set — not that it has been cleared.

Contact

Sher
AMFTF Head — Anti Music Fraud Task Force (AMFTF)
sher@pumpa.org.pk · amftf.org

For a key, an integration call, or to have a field added to v1: write, and mark up whatever in this document does not fit your workflow. That is how v1 gets finished.

If you believe a record here is wrong, tell us. A dataset like this is only useful if it is correctable, and a correction costs us far less than a wrong flag costs the company on the other end of it.