AudioVerify™Overview

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:

  1. Submit the video. You get back a job_id.
  2. Poll the job until it is completed, then walk the segments.

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 rangeTitleidentifiedcleared
0:00 – 0:24B2 100truetrue
0:24 – 0:50falsenull
0:50 – 1:41Distant Orbittruefalse

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

WhatWhenWhat to do
400 on submitNeither or both of file / storage_key sent, bad extension, or file too largeShow the validation detail to the user
400 on upload-urlThe declared content_length is over the limitNo URL is issued; the file is too large to submit
501 on upload-urlThe environment is not configured for S3 temporary storageFall back to the direct file upload (Option A)
404 on pollThe job_id does not exist or has expired from cacheSubmit the video again
429 on submitYou hit your daily AudioVerify quotaBack off until resets_at in the response, then retry. Only the submit is metered.
Empty segments on a completed jobNo music was recognizedTreat as a valid “no match” result, not a failure

Tips

  • Read segments, not just track. track is 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 on id. cleared answers whether the account may use the track; id only says whether the catalog has it. A catalog track outside the plan has an id and cleared: false.
  • Treat score: null as “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_id every few seconds and use progress to 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_id expires.

Endpoint reference