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).

If the video is headed for the creator’s own YouTube channel, you can ask for the channel in the same submit and it goes on their allowlist right there — see Naming the creator’s channel.

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:50—falsenull
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.

Naming the creator’s channel

Slipstream’s catalog is registered with the Content-ID systems the video platforms use, so a creator can see an automated claim on a video that uses a track they are entitled to. Registering their channel on the allowlist stops that at the source — and the moment they hand you a video to check is the moment they have the channel to hand.

Pass it as channel_url on the submit, in either form, and the channel is added to their allowlist as part of the same call. A handle, a channel page, a custom URL or a link to one of their videos all work, with or without https://. Send X-User-ID as well when you act for many creators, so the channel lands on that creator’s allowlist rather than yours:

curl https://api.slipstreammusic.com/api/v4/video-recognition/ \
  -X POST \
  -H "Authorization: Bearer $PARTNER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-User-ID: creator-4821" \
  -d '{"storage_key": "video-recognition/...clip.mp4", "channel_url": "youtube.com/@GoogleDevelopers"}'

The response — the 201, and every poll after it — then carries channel:

{
  "job_id": "7b2c1e8a-...",
  "status": "queued",
  "track": null,
  "channel": {
    "status": "added",
    "id": "9b5c3d2e-0a8e-4b7c-8c4d-2f9e5f6b8d7c",
    "channel_id": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
    "name": "Google for Developers",
    "thumbnail_url": "https://yt3.ggpht.com/..."
  }
}

The channel is on the allowlist by the time you read this. There is nothing further to call:

  1. added — tell the creator, by name, that their channel is covered. id is the allowlist entry, the same one List channels returns, in case you need to remove it later.
  2. already_added — tell them it was already covered.
  3. not_resolved or error — show detail. The compliance check is unaffected, and the creator can add the channel another time.

The add follows the channel endpoint’s rules: one channel, one allowlist, so a channel registered elsewhere comes back as error; a creator we have not seen before is registered by their first add; channels added under X-User-ID are not counted against your own account’s plan; and each add is charged to your channel-management allowance like a call to the channel endpoint, refunded when it is refused.

One thing to get right on your side: ask for the creator’s own channel, not any link. A link to a video resolves to whichever channel published it, creators often check clips they did not make, and there is no confirmation step between the submit and the add.

Naming a channel needs channel management enabled on your account, the same grant the channel endpoints use. It never changes the outcome of the check itself: a link that cannot be resolved comes back as a channel.status, not as an error. See The ChannelResult 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
channel.status of not_resolved or errorThe link did not resolve, the channel belongs to another account, or channel management is not enabled on yoursShow detail. The check itself ran; nothing else changes

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.
  • The submit adds the channel. Whatever channel_url resolves to lands on the creator’s allowlist, so collect their own channel and not a link they happened to have open.

Endpoint reference