AI Music Detection API
Submit a track by URL, then poll for the result. The API uses the same credits and returns the same report as the web app. Base URL: https://musicdetector.org/api/v1.
Authentication
Create a key under Dashboard → API keys and send it as a bearer token on every request:
Authorization: Bearer YOUR_API_KEYKeep keys server-side. Anyone with a key can spend its credits.
Billing
1 credit per started 5 minutes of audio, taken from duration_seconds: up to 5:00 costs 1 credit, 5:01–10:00 costs 2, 10:01–15:00 costs 3. Tracks longer than 15 minutes are rejected — split them first. If a detection fails, its credits are refunded automatically. See API pricing.
Create a detection
POST /api/v1/detections
| Field | Type | Description |
|---|---|---|
audio_url | string, required | Public or pre-signed https:// URL we can download. MP3, WAV, FLAC, M4A, AAC, OGG, OPUS or AIFF. Signed URLs should stay valid for at least 1 hour. |
duration_seconds | number, required | Track length in seconds, 5–900. Used for billing. |
file_name | string, optional | Your label for the track (max 200 characters), echoed back in results and history. |
curl -X POST https://musicdetector.org/api/v1/detections \
-H "Authorization: Bearer $AIMD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"audio_url": "https://cdn.example.com/deliveries/8841/track01.wav",
"duration_seconds": 214,
"file_name": "track01.wav"
}'HTTP/1.1 202 Accepted
{
"code": 200,
"message": "accepted",
"data": {
"id": "tsk-3f9a61c0b2d4e8a17c55mfz2k1",
"status": "processing",
"credits": 1,
"poll_url": "/api/v1/detections/tsk-3f9a61c0b2d4e8a17c55mfz2k1"
}
}Get a detection
GET /api/v1/detections/{id} — poll every 2–3 seconds until status is completed or failed. Most tracks finish in under a minute.
curl https://musicdetector.org/api/v1/detections/tsk-3f9a61c0b2d4e8a17c55mfz2k1 \
-H "Authorization: Bearer $AIMD_API_KEY"{
"code": 200,
"message": "success",
"data": {
"id": "tsk-3f9a61c0b2d4e8a17c55mfz2k1",
"status": "completed",
"created_at": "2026-09-24 12:41:07",
"file_name": "track01.wav",
"duration_seconds": 214,
"credits": 1,
"result": {
"verdict": "ai",
"ai_probability": 97.6,
"likely_source": "Suno",
"source_confidence": "clear",
"sources": [
{ "source": "Suno", "probability": 96 },
{ "source": "Mureka", "probability": 0.8 },
{ "source": "Udio", "probability": 0.6 }
],
"segments": [
{ "start": 0, "end": 107, "prediction": "ai_generated", "ai_probability": 98.1, "likely_source": "Suno" },
{ "start": 107, "end": 214, "prediction": "ai_generated", "ai_probability": 96.9, "likely_source": "Suno" }
],
"stems": [
{ "stem": "vocals", "prediction": "ai_generated", "ai_probability": 98.3, "likely_source": "Suno" },
{ "stem": "accompaniment", "prediction": "ai_generated", "ai_probability": 91.0, "likely_source": "Suno" }
],
"duration_seconds": 214,
"model_version": "lv1axtcv"
},
"error": null
}
}Result fields
| Field | Description |
|---|---|
verdict | ai (≥90), likely_ai (65–90), uncertain (35–65), likely_human (10–35), human (<10). |
ai_probability | 0–100 for the full mix. |
likely_source | Best-matching generator, or null when the track reads as human. |
source_confidence | clear when one generator dominates, unclear when scores are close, none for human reads. |
sources | Score per generator (0–100), highest first. |
segments | Per-section reads with start/end in seconds. |
stems | Separate vocals / accompaniment reads when available. prediction: "no_vocals" means no vocal was found. |
model_version | Detection model identifier. Store it with the result for audits. |
List detections
GET /api/v1/detections?limit=20&cursor=… returns your detections newest first with the same item shape. Pass next_cursor from the previous page to continue.
Errors
Errors use the same envelope: {"code": 400, "message": "…", "data": {"error": "INVALID_DURATION"}}.
| HTTP | error | Meaning |
|---|---|---|
| 400 | INVALID_AUDIO_URL | audio_url is missing, not https, or points to a private host. |
| 400 | INVALID_DURATION | duration_seconds missing or outside 5–900 seconds. |
| 401 | UNAUTHORIZED | Missing or invalid API key. |
| 402 / 403 | INSUFFICIENT_CREDITS | Not enough credits for this track. Buy a pack or wait for daily free credits. |
| 404 | NOT_FOUND | Unknown id, or the detection belongs to another account. |
| 429 | DETECTION_REJECTED | Rate limited. Back off and retry. |
| 502 | UPSTREAM_UNAVAILABLE | Temporary error. Retry with exponential backoff. |
Examples
Python
import os, time, requests
BASE = "https://musicdetector.org/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['AIMD_API_KEY']}"}
def detect(audio_url: str, duration_seconds: float, file_name: str = "") -> dict:
r = requests.post(f"{BASE}/detections", headers=HEADERS, json={
"audio_url": audio_url,
"duration_seconds": duration_seconds,
"file_name": file_name,
}, timeout=30)
r.raise_for_status()
detection_id = r.json()["data"]["id"]
for _ in range(120): # up to ~5 minutes
time.sleep(2.5)
d = requests.get(f"{BASE}/detections/{detection_id}", headers=HEADERS, timeout=30).json()["data"]
if d["status"] == "completed":
return d["result"]
if d["status"] == "failed":
raise RuntimeError(d["error"]) # credits are refunded automatically
raise TimeoutError(detection_id)
report = detect("https://cdn.example.com/deliveries/8841/track01.wav", 214, "track01.wav")
if report["verdict"] in ("ai", "likely_ai"):
print("route to AI policy queue:", report["likely_source"], report["ai_probability"])Node.js
const BASE = 'https://musicdetector.org/api/v1'
const headers = { Authorization: `Bearer ${process.env.AIMD_API_KEY}`, 'Content-Type': 'application/json' }
export async function detect(audioUrl, durationSeconds, fileName = '') {
const created = await fetch(`${BASE}/detections`, {
method: 'POST',
headers,
body: JSON.stringify({ audio_url: audioUrl, duration_seconds: durationSeconds, file_name: fileName }),
}).then(r => r.json())
if (created.code !== 200) throw new Error(created.message)
for (let i = 0; i < 120; i++) {
await new Promise(r => setTimeout(r, 2500))
const { data } = await fetch(`${BASE}/detections/${created.data.id}`, { headers }).then(r => r.json())
if (data.status === 'completed') return data.result
if (data.status === 'failed') throw new Error(data.error)
}
throw new Error('Timed out waiting for detection')
}Recommended routing
human,likely_human→ continue delivery.uncertain→ send to a human listener; heavy processing and partly AI tracks land here.likely_ai,ai→ apply your AI policy (label, ask the uploader, or hold). Keep the JSON report.
Detection is probabilistic. Do not use a single result as the only basis for takedowns or withholding payouts.
Limits & data
- Tracks: 5 seconds to 15 minutes. Files are downloaded from your URL once.
- Audio is deleted within 24 hours of the detection and never used for training.
- Batch endpoints and webhooks are on the roadmap. Need them now? Email support@musicdetector.org.