Three of the five take a video, so there’s one more group underneath them:
POST /v1/videos ·
POST /v1/videos/{id}/ingest · GET /v1/videos/{id}. Analysis is billed there, once, per minute
of the clip. The resulting video_id is reused by every match, sync and loop against it.
Beats stands apart: it takes audio you point us at, never a video_id, and it has its own queue,
worker and meter.
Base URL
/v1. There’s no separate sandbox host and no sandbox mode: customer
keys are always vbsk_live_, and every call is billed at the rates in
Metering & pricing. See Authentication.
Authentication
Every request carries your API key, either way round. Use whichever your client reaches for:Conventions
JSON in, JSON out
Send
content-type: application/json. Every response is JSON, success or error.Stable IDs
A
video_id is yours to reuse across match, sync, and loop. A track_id (from a match)
resolves in your catalog.Request IDs
Every response carries
X-Request-Id. Include it when you contact support.Timings, not recordings
Responses carry catalog identifiers and analysis. No audio is attached by default. See
Preview audio.
Expand
?expand=full on a match or underscore returns the full track object and the lane’s
candidate pool instead of the lean shape.Idempotency
Idempotency-Key is accepted on five endpoints, and it does two different things depending on
which one you send it to.
On POST /v1/beats it is a true idempotency key: a repeat with the same key returns the original
job_id and starts no second job, a key is honoured for 24 hours, and reusing one for a different
request is a 422.
On POST /v1/match, /v1/underscore, /v1/align and /v1/loop it only suppresses a duplicate
row in the billing ledger. The request itself re-executes in full and you get a freshly computed
response, so a retry is safe on your bill but is not free of work. Send a new key per distinct
request and reuse a key only when retrying that exact request. A key you pin across different
calls will silently suppress the billing for all of them.
POST /v1/videos and POST /v1/videos/{id}/ingest do not accept the header at all. Those are the
calls that trigger the per-minute analysis charge, and the meter dedupes them on a server-generated
key of its own.
Synchronous vs. async
Work that’s already warm returns200 immediately. Work that needs a first-time build returns
202, so a long build never trips a request timeout. There are two 202 shapes and they are
not interchangeable.
A job carries a job_id and a job URL. That covers a match on a video whose analysis isn’t
cached, a cold align, a cold loop, and any beats call:
202
job_id at all, and points at the video:
202
POST /v1/videos, and POST /v1/match called with a video_url, both return that second shape.
Branch on whether job_id is present rather than on the status code, and follow the poll path
you were actually given.
Poll until status is complete. Jobs are billed once, on completion, and polling is free. Each
family has its own job endpoint: GET /v1/match/jobs/{id}, /v1/align/jobs/{id},
/v1/loop/jobs/{id}, /v1/beats/{job_id}.
Errors & rate limits
Every/v1 error returns a JSON error object with a stable type, a human-readable message
and the request_id for that call. A 422 from request validation adds a details array. Keys
are rate limited per key, 60 requests per minute by default. See
Errors & rate limits for the full table.
Endpoints
Pick any endpoint from the sidebar for its parameters, schema, and a live Try it panel. The workflow table at the top of this page maps each one to the guide that shows it in use. The reference covers every endpoint you can call with an API key. Two/v1 routes are left out
because you can’t: the Stripe webhooks, which authenticate by signature rather than by key.
Prefer an agent to writing HTTP? The same surface is available over MCP, where each
tool wraps one endpoint and bills identically.