curl --request POST \
--url https://cue.vibeset.ai/v1/loop \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792"
}
'import requests
url = "https://cue.vibeset.ai/v1/loop"
payload = {
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({video_id: '69e5a58e73370adad5a10dc9', track_id: '14792'})
};
fetch('https://cue.vibeset.ai/v1/loop', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://cue.vibeset.ai/v1/loop",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'video_id' => '69e5a58e73370adad5a10dc9',
'track_id' => '14792'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://cue.vibeset.ai/v1/loop"
payload := strings.NewReader("{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://cue.vibeset.ai/v1/loop")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://cue.vibeset.ai/v1/loop")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}"
response = http.request(request)
puts response.read_body{
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792",
"track": {
"soundstripe_song_id": "14792",
"soundstripe_audio_file_id": "60184"
},
"applicable": true,
"plan": {
"version": 15,
"applicable": true,
"music_in": 0,
"music_out": 540,
"loop_start_a": 12.6,
"loop_end_b": 41.4,
"bars": 16,
"beats_per_bar": 4,
"repeats": 18,
"seam_score": 0.94,
"segments": [
{
"song_from": 0,
"song_to": 41.4,
"gain_db": 0,
"gain_in_db": 0
},
{
"song_from": 12.6,
"song_to": 41.4,
"gain_db": -0.4,
"gain_in_db": -0.1
}
],
"seam_fades": [
0.31
],
"seam_curves": [
0.72
],
"gain_glide_seconds": 1.2,
"start_fade_seconds": 0.5,
"end_fade_seconds": 2
}
}{
"video_id": "<string>",
"track_id": "<string>",
"job_id": "<string>",
"status": "processing",
"poll": "/v1/loop/jobs/run_9f2c",
"next": "<string>"
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}Loop a track across a long video
Loop a short track seamlessly across a LONG video: a downbeat-aligned, integer-bar
self-loop with musical seams and fades that covers the whole clip. Pass a track_id
for a video you own. Soundstripe tracks only.
The 200 is served from the loop-plan cache, which is keyed on the
(video_id, track_id) PAIR. The first loop of a track against a given video is
therefore a 202 + job_id, so poll GET /v1/loop/jobs/{job_id}. Looping the same
track over a second video builds a second plan.
applicable: false when there is nothing to loop. Then plan.reason says why, hint
explains it in words, retryable says whether calling again can help, and nothing is
billed.
Loop needs the video to be longer than the track by at least 30 seconds. As a guideline
it is for videos over 4 minutes. Shorter clips can play the track straight. See
/v1/align.
curl --request POST \
--url https://cue.vibeset.ai/v1/loop \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792"
}
'import requests
url = "https://cue.vibeset.ai/v1/loop"
payload = {
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({video_id: '69e5a58e73370adad5a10dc9', track_id: '14792'})
};
fetch('https://cue.vibeset.ai/v1/loop', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://cue.vibeset.ai/v1/loop",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'video_id' => '69e5a58e73370adad5a10dc9',
'track_id' => '14792'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://cue.vibeset.ai/v1/loop"
payload := strings.NewReader("{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://cue.vibeset.ai/v1/loop")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://cue.vibeset.ai/v1/loop")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_id\": \"69e5a58e73370adad5a10dc9\",\n \"track_id\": \"14792\"\n}"
response = http.request(request)
puts response.read_body{
"video_id": "69e5a58e73370adad5a10dc9",
"track_id": "14792",
"track": {
"soundstripe_song_id": "14792",
"soundstripe_audio_file_id": "60184"
},
"applicable": true,
"plan": {
"version": 15,
"applicable": true,
"music_in": 0,
"music_out": 540,
"loop_start_a": 12.6,
"loop_end_b": 41.4,
"bars": 16,
"beats_per_bar": 4,
"repeats": 18,
"seam_score": 0.94,
"segments": [
{
"song_from": 0,
"song_to": 41.4,
"gain_db": 0,
"gain_in_db": 0
},
{
"song_from": 12.6,
"song_to": 41.4,
"gain_db": -0.4,
"gain_in_db": -0.1
}
],
"seam_fades": [
0.31
],
"seam_curves": [
0.72
],
"gain_glide_seconds": 1.2,
"start_fade_seconds": 0.5,
"end_fade_seconds": 2
}
}{
"video_id": "<string>",
"track_id": "<string>",
"job_id": "<string>",
"status": "processing",
"poll": "/v1/loop/jobs/run_9f2c",
"next": "<string>"
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}{
"error": {
"type": "invalid_request",
"message": "Provide either 'video_id' or 'video_url'.",
"request_id": "a1b2c3d4e5f6"
}
}Authorizations
Your API key as a Bearer token, e.g. Authorization: Bearer vbsk_live_...
Headers
Suppresses a DUPLICATE BILLING ROW for a retry of this exact request. It does not return the earlier response and does not stop the loop re-running.
Body
A long video you own (or a shared/seed clip).
128The Soundstripe track to loop, as returned by /v1/match.
128When true, an APPLICABLE loop also carries a mix block, the duck-under-voiceover spec described by the Mix schema. There is no mix key on a not-applicable result, or when the measurement could not be read.
Response
The loop plan for the track, or an applicable: false refusal.
The catalog identifiers for the recording a loop plan describes. The song is the work, the audio file is the specific render you fetch. They are not interchangeable.
Show child attributes
Show child attributes
False when there is nothing to loop. Then plan explains why, hint says it in words, retryable says whether calling again can help, and nothing is billed.
How to lay the track under the clip. segments is the authoritative playback path. loop_start_a / loop_end_b / repeats describe the same loop but cannot express a final pass that exits into a come-down outro. On a not-applicable result only applicable, reason and (for video_not_longer) the three duration fields are present.
Show child attributes
Show child attributes
Present only when applicable is false, a plain-language reason and what to do.
Present only when applicable is false. True for timeout, profile_error, missing_profile and audio_unavailable: nothing was cached, so calling again in a few seconds normally reads back work that finished in the background. False means the answer is settled: pick another track.
Auto-mix (duck-the-music-under-the-voiceover) spec, returned when auto_mix is true. These are the same numbers the Cue app's Loop/Sync preview + export use, so a render with them matches the app. Applicable to whatever music you lay under the clip.
Show child attributes
Show child attributes
{
"voiceover": true,
"classification": "voice",
"music_gain_db": -4.4,
"video_gain_db": 11.4,
"duck": {
"envelope_points": [[0, 20], [1.2, 20], [1.9, 0], [4.8, 0]],
"envelope_hz": 40,
"speech_ranges": [[1.2, 4.8], [9.5, 14.1]],
"depth_db": 20,
"attack_ms": 375,
"release_ms": 1425
}
}