> ## Documentation Index
> Fetch the complete documentation index at: https://cue.vibeset.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Find where to cut

> Sync points for one track against a video, and where the music lands.

A shortlist tells you *which* track. Sync tells you *where*. `POST /v1/align` takes one track and
one video and returns labeled offsets into the song, best first: the drop, the downbeats, the
phrase starts. These are the points where the music lands with what's on screen.

Use it for a clip the track can cover on its own. When the video is longer than the song, you want
[loop](/docs/loop) instead.

## Align a track

```bash theme={null}
curl https://cue.vibeset.ai/v1/align \
  -H "x-api-key: vbsk_live_..." \
  -H "content-type: application/json" \
  -d '{"video_id": "69e5a58e73370adad5a10dc9", "track_id": "14792", "top_k": 6}'
```

```json 200 theme={null}
{
  "video_id": "69e5a58e73370adad5a10dc9",
  "track_id": "14792",
  "cached": true,
  "sync_points": [
    { "offset_seconds": 12.03, "score": 0.91, "label": "Drop" },
    { "offset_seconds": 27.44, "score": 0.74, "label": "Downbeat" }
  ]
}
```

`offset_seconds` is a position **in the song**: start the track there and it lines up with the top
of your clip. `score` is how strongly that point lands, 0–1. `label` says what the moment is
musically, and it is `null` on a track Cue could only align by energy.

No match call is required. `track_id` is the Soundstripe song id, so you can align a track you
picked yourself.

<ParamField path="top_k" type="integer" default="12">
  How many sync points to return, 1–50.
</ParamField>

## A cold pairing comes back as a job

Cue serves the `200` above only from cache. When nothing is cached for this pairing, the index has
to be built, and that takes longer than a request allows. So you get a `202` and a job:

```json 202 theme={null}
{
  "video_id": "69e5a58e73370adad5a10dc9",
  "track_id": "14792",
  "status": "processing",
  "job_id": "run_9f2c…",
  "poll": "/v1/align/jobs/run_9f2c…",
  "next": "Poll the job until status is 'complete', then read its sync_points."
}
```

Poll `GET /v1/align/jobs/{job_id}` until `status` is `complete`. The finished job returns the same
`sync_points`, plus `job_id`, `status` and `cached`.

A video whose ingest hasn't finished gives you a different `202`, with **no `job_id`** and a
`poll` of `/v1/videos/{video_id}`. Poll the video to `ready`, then call `/v1/align` again.

### Which answer you get, and what you pay

The warm read succeeds when either of these is true:

* Cue has stored alignment points for this exact `(video_id, track_id)` pair, from an earlier
  align or from a background warm.
* Both halves of the pairing are profiled: the **video's** sync profile, built during ingest, and
  the **track's** audio profile, built the first time that track is indexed for sync.

Miss both and you get the job and the cold rate. So the first align of a given track is normally
cold, later aligns of that track against your other videos are normally warm, and the decision is
made per call, per pairing, not once per track forever. `cached` on the response tells you which
rate you paid.

## Sync points on a match instead

If you want points for a whole shortlist rather than one track, pass `"include_sync": true`
to [`/v1/match`](/docs/match#sync-points-inline). That path is cache-only and never builds a profile,
so on a fresh video most tracks come back `"sync_status": "warming"` with an empty
`sync_points` array. `/v1/align` is the endpoint that actually builds the index.

`sync_status` is one of `warm` (points included), `warming` (this pairing isn't indexed yet, so call
again shortly), `unavailable` (no points could be read) and `error` (something failed on our side,
and the same call may succeed on a retry). `unavailable` and `error` used to be reported
identically, which meant an outage read as a settled fact about your track.

## Duck under voiceover

If the clip has narration, pass `"auto_mix": true` and the response carries a `mix` block: gains
for the two beds, plus `duck.envelope_points`, the automation curve telling you how far to pull
the music down at each instant. These are the same numbers the Cue app uses, so your render
matches the app. It reads a measurement taken at ingest, so it adds no charge.

When that measurement can't be read, the `200` from `POST /v1/align` carries `"mix": null` and the
completed job simply omits the key. Auto-mix is additive and never fails the align call, so treat
null and absent the same way and check before you read into the block. The four-step render recipe and
the full field-by-field description are on
[Loop](/docs/loop#duck-under-voiceover-auto-mix). The block is identical on both endpoints.

## What it costs

**$0.01** for a cold pairing, **$0.001** for a warm one. `auto_mix` adds nothing, and a call that
errors bills nothing. Sync points embedded in a match are included in the match price. See
[Metering & pricing](/docs/metering-and-pricing).

Over MCP the same thing is the [`get_sync_points`](/docs/mcp-tools#get_sync_points) tool.
