Submit a video

Start a recognition job. Submit the video in one of two ways: upload the file directly, or reference a file you already uploaded to S3 with a presigned URL. Either way you get back a job_id to poll for the result.

POST/api/v4/video-recognition/

Headers

HeaderRequiredDescription
AuthorizationYesBearer <PARTNER_API_KEY>.
Content-TypeYesmultipart/form-data for a file upload, application/json for a storage_key.
X-User-IDNoThe end user this check is for. It decides whose allowlist a named channel goes on, and nothing else — see Acting for your end users. Requires end-user support on your account; without it the header is ignored and the channel goes on your own allowlist.

Request

Send exactly one of file (a direct upload) or storage_key (a file already in S3). Either form also takes an optional channel_url — see Naming the creator’s channel.

File upload (multipart form data)

Best for small videos. The file is proxied through the API.

FieldTypeRequiredDescription
filebinaryOne of twoVideo file. Max 100 MB. Allowed extensions: .mp4, .mov, .avi, .mpeg.
channel_urlstringNoThe creator’s own YouTube channel, as a URL. See Naming the creator’s channel.
curl https://api.slipstreammusic.com/api/v4/video-recognition/ \
  -X POST \
  -H "Authorization: Bearer $PARTNER_API_KEY" \
  -F "file=@/path/to/clip.mp4"

Reference an uploaded file (JSON body)

Best for large videos. First call Get an upload URL, PUT the file to S3, then submit the returned storage_key here. The bytes never pass through the API.

FieldTypeRequiredDescription
storage_keystringOne of twoThe storage_key returned by Get an upload URL. Must start with video-recognition/.
channel_urlstringNoThe creator’s own YouTube channel, as a URL. See Naming the creator’s channel.
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/8f3c...2a_clip.mp4"}'

Naming the creator’s channel

If the video is going on the creator’s own YouTube channel, ask for the channel while you have them: pass its URL as channel_url, in either request form, and the channel is added to their allowlist as part of the submit. Any of these resolve:

ShapeExample
Handleyoutube.com/@GoogleDevelopers
Channel pageyoutube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw
Custom URLyoutube.com/c/GoogleDevelopers, youtube.com/user/GoogleDevelopers
A video on the channelyoutu.be/dQw4w9WgXcQ, youtube.com/watch?v=…, youtube.com/shorts/…

The scheme is optional, and a tab suffix such as /videos is ignored. An empty or null value is the same as leaving the field out.

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/8f3c...2a_clip.mp4", "channel_url": "youtube.com/@GoogleDevelopers"}'

The add follows the same rules as Add a channel: the channel lands on the end user named by X-User-ID, or on your own account without it; a creator we have not seen before is registered by it; and a channel can be on one allowlist only. There is no confirmation step, so ask the creator for their own channel: a video link resolves to whichever channel published it, and a clip they did not make would put someone else’s channel on their allowlist. Remove undoes it.

Naming a channel needs channel management enabled on your account, the same grant the channel endpoints use. An add made this way is charged to your channel-management allowance exactly as a call to Add a channel would be, and refunded when it is refused; the submit itself still counts against the AudioVerify quota. A used-up allowance refuses the add, not the check: it comes back as channel.status error.

Response (201 Created)

{
  "job_id": "7b2c1e8a-0f4d-4a2b-9c11-7f5a2d9e3c10",
  "status": "queued",
  "track": null
}

The track is always null here, and there are no segments yet. Poll GET /api/v4/video-recognition/{job_id}/ until status is completed to get the result.

When you sent a channel_url, the body also carries channel, and every poll of the job repeats it:

{
  "job_id": "7b2c1e8a-0f4d-4a2b-9c11-7f5a2d9e3c10",
  "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/..."
  }
}

channel.status is one of added, already_added, not_resolved or error; the field is absent when no channel was named. See The ChannelResult object for what each state means and what to do with it.

The channel never changes the outcome of the submit. A link that cannot be resolved, or a channel that cannot be added, comes back as a channel.status with a detail you can show — the HTTP status and the job_id are the same as without it, and the recognition runs regardless.

Recognition runs in the background. A clip with one song usually completes in well under a minute; one carrying several songs takes a couple of minutes, since each stretch is identified on its own. Poll the job rather than holding the request open.

Errors

HTTPBodyWhen
400{ "detail": "Provide exactly one of \file` or `storage_key`.” }`Neither or both fields were sent.
400{ "file": ["File extension is not allowed..."] }The file is not one of .mp4, .mov, .avi, .mpeg.
400{ "file": ["File size cannot exceed 100 MB."] }The upload is larger than the configured limit.
400{ "storage_key": ["Must start with \video-recognition/`.”] }`The storage_key is not in the recognition namespace.
401{ "detail": "Authentication credentials were not provided." }Missing or invalid partner key.
403{ "detail": "..." }The token is valid but does not have the AudioVerify (compliance_check) scope. Contact Slipstream to enable it.
429See Rate limits.You have hit your daily AudioVerify quota.

Rate limits

Only this submit endpoint is metered. Polling a job and requesting an upload URL are free. Each submit counts against the daily AudioVerify quota configured for your partnership.

When you exceed it, the submit returns 429 before any recognition runs, with the cap, your current usage, and when the window resets:

{
  "detail": "You have reached your daily AudioVerify limit.",
  "scope_code": "compliance_check",
  "window": "daily",
  "cap": 5000,
  "used": 5000,
  "resets_in": "7 hours",
  "resets_at": "2026-07-09T00:00:00+00:00"
}

Back off until resets_at, then retry.