AudioVerify™
Identify the music used in a video. Submit a clip and AudioVerify tells you every stretch of music in it, in timeline order — what each one is, whether the account’s subscription covers it, and, when an outside service exposes it, the registered label (for example Epidemic Sound).
Behind one simple API, AudioVerify runs a three-tier pipeline: it matches against the Slipstream catalog first with audio fingerprinting, then falls back to external recognition for anything outside the catalog. You do not have to think about the tiers; you submit a video and read one normalized result.
The flow
Recognition runs asynchronously, so it is two steps:
- Submit the video. You get back a
job_id. - Poll the job until it is
completed, then walk thesegments.
For the submit step you have two options: send the file directly (simple, good for small clips), or upload it straight to S3 with a presigned URL (good for large clips, the bytes never pass through the API).
Step 1: Submit the video
Option A: upload the file directly (multipart form):
curl https://api.slipstreammusic.com/api/v4/video-recognition/ \
-X POST \
-H "Authorization: Bearer $PARTNER_API_KEY" \
-F "file=@/path/to/clip.mp4"Accepted formats: .mp4, .mov, .avi, .mpeg. Max 100 MB.
Option B: upload to S3 first, then submit the reference. Ask for a presigned URL, PUT the file to it, then submit the returned storage_key. This keeps large files off the API.
# 1. get a presigned URL — send content_length so an oversize file is
# refused before you upload it
curl https://api.slipstreammusic.com/api/v4/video-recognition/upload-url/ \
-X POST \
-H "Authorization: Bearer $PARTNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filename": "clip.mp4", "content_length": 18446572}'
# -> { "upload_url": "...", "storage_key": "video-recognition/...", "content_type": "video/mp4", "expires_in": 600 }
# 2. PUT the bytes to the signed URL (no auth header; the URL is signed)
curl "$UPLOAD_URL" -X PUT -H "Content-Type: video/mp4" --upload-file /path/to/clip.mp4
# 3. submit the storage_key
curl https://api.slipstreammusic.com/api/v4/video-recognition/ \
-X POST \
-H "Authorization: Bearer $PARTNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"storage_key": "video-recognition/...clip.mp4"}'Response (201 Created) for both options:
{
"job_id": "7b2c1e8a-0f4d-4a2b-9c11-7f5a2d9e3c10",
"status": "queued",
"track": null
}Hold on to job_id. That is all you need for step 2.
Send the Content-Type on the S3 PUT exactly as returned in content_type. It is pinned when the URL is signed, so a mismatched header is rejected. The same goes for content_length when you declare it — the body must carry exactly that many bytes.
Step 2: Poll for the result
Poll the job until status is completed:
curl https://api.slipstreammusic.com/api/v4/video-recognition/7b2c1e8a-0f4d-4a2b-9c11-7f5a2d9e3c10/ \
-H "Authorization: Bearer $PARTNER_API_KEY"While it runs you will see status: "queued" then status: "processing", with a progress naming the stage — extracting, matching, then finishing. Poll every 2 to 5 seconds. A clip with one song usually finishes in well under a minute; one carrying several songs takes a couple of minutes.
Reading the result
A video can use several songs back to back, so the result is a list. segments carries every stretch of music in timeline order, each with its own start_ms and end_ms. Walk it in order and report each one.
| Time range | Title | identified | cleared |
|---|---|---|---|
| 0:00 – 0:24 | B2 100 | true | true |
| 0:24 – 0:50 | — | false | null |
| 0:50 – 1:41 | Distant Orbit | true | false |
Each segment falls into one of three cases.
Cleared. The account’s subscription covers it. id links to the catalog track:
{
"identified": true,
"start_ms": 0,
"end_ms": 24985,
"id": "11111111-1111-1111-1111-111111111111",
"title": "Nothing To See",
"artist": "Auracle",
"cleared": true,
"isrc": "CAQOQ1521348",
"score": 99.24,
"external_label": null
}Not cleared. Either the music came from outside Slipstream, or it is a catalog track the account’s plan does not include — id is what tells those apart. Outside matches carry external_label when an outside service exposes it, and suggestions offers up to 3 catalog alternatives the account can actually use:
{
"identified": true,
"start_ms": 76069,
"end_ms": 101013,
"id": null,
"title": "Some Stock Track",
"artist": "A Stock Artist",
"cleared": false,
"isrc": "SE5Q52400123",
"score": null,
"external_label": "Epidemic Sound",
"suggestions": [
{
"id": "870112aa-...",
"name": "Mystery Water",
"artist": { "name": "Thomaz Ayê" }
}
]
}Not identified. Music plays there that no tier could name. Every track field is null; the time range still tells you where it is:
{
"identified": false,
"start_ms": 24985,
"end_ms": 50062,
"title": null,
"cleared": null,
"score": null
}An unidentified stretch is not silence and not a failure. It only means nothing could name it — so it cannot be reported as cleared either. Surface it by its time range and let the user confirm the rights themselves.
No music at all. A completed job with an empty segments and a null track is a normal outcome, not an error:
{ "job_id": "7b2c1e8a-...", "status": "completed", "track": null, "segments": [] }See The RecognitionSegment object for every field.
Error cases worth handling
| What | When | What to do |
|---|---|---|
| 400 on submit | Neither or both of file / storage_key sent, bad extension, or file too large | Show the validation detail to the user |
| 400 on upload-url | The declared content_length is over the limit | No URL is issued; the file is too large to submit |
| 501 on upload-url | The environment is not configured for S3 temporary storage | Fall back to the direct file upload (Option A) |
| 404 on poll | The job_id does not exist or has expired from cache | Submit the video again |
| 429 on submit | You hit your daily AudioVerify quota | Back off until resets_at in the response, then retry. Only the submit is metered. |
Empty segments on a completed job | No music was recognized | Treat as a valid “no match” result, not a failure |
Tips
- Read
segments, not justtrack.trackis the longest identified segment, kept so single-track integrations keep working. A video whose first song is cleared can still carry an outside track later. - Branch on
cleared, not onid.clearedanswers whether the account may use the track;idonly says whether the catalog has it. A catalog track outside the plan has anidandcleared: false. - Treat
score: nullas “no confidence reported”. External services do not expose a similarity of their own, so there is no percentage to show — name the match without one rather than inventing a number. - Poll, do not block. Submit returns immediately. Poll the
job_idevery few seconds and useprogressto say which stage it is at. - Use Option B for large files. The presigned upload keeps the bytes off the API and supports the full 100 MB limit comfortably.
- Only the submit is metered. Polling and upload URLs are free — pace your submissions against your daily quota, not your polls.
- Results are short-lived. A completed result stays available for about a day, then the
job_idexpires.