HTTP API examples
Apply an effect
To apply an effect, add a new version of the video with the effect and burn the version. A version holds the effect with its settings, the transcript and the plugins. This page uses the video and the transcription of the Quickstart.
Before you start
Put your API key in place of <your API key>. The Quickstart shows how to make a key.
This page uses the video id from the page Quickstart.
This page uses the transcription id from the page Quickstart.
CAPTIONBOB_URL is the default address of the server. Change it when your server listens on another host or port. The code of each step runs after this first code. The curl code runs in bash and reads values from the answers with jq. The Python code uses the package requests. The JavaScript code runs in Node.js 22 or later as an ES module, such as a file example.mjs.
The answers on this page are real answers of a captionbob server, recorded on 2026-10-28. Your ids, names and times are different.
curl
CAPTIONBOB_URL="http://127.0.0.1:9400"
CAPTIONBOB_KEY="<your API key>"
# The video id from the page Quickstart
VIDEO_ID=1
# The transcription id from the page Quickstart
TRANSCRIPTION_ID=1
Python
import time
import requests
CAPTIONBOB_URL = "http://127.0.0.1:9400"
HEADERS = {"Authorization": "Bearer <your API key>"}
# The video id from the page Quickstart
video_id = 1
# The transcription id from the page Quickstart
transcription_id = 1
JavaScript
import { readFile, writeFile } from "node:fs/promises";
import { setTimeout } from "node:timers/promises";
const CAPTIONBOB_URL = "http://127.0.0.1:9400";
const HEADERS = { Authorization: "Bearer <your API key>" };
// The video id from the page Quickstart
const videoId = 1;
// The transcription id from the page Quickstart
const transcriptionId = 1;
let response;
let answer;
Step 1: Get the transcript
A new version needs the transcript of the video.
curl
ANSWER=$(curl -s "$CAPTIONBOB_URL/api/renders/$TRANSCRIPTION_ID/file" \
-H "Authorization: Bearer $CAPTIONBOB_KEY")
echo "$ANSWER"
TRANSCRIPT="$ANSWER"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/renders/{transcription_id}/file",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
transcript = answer
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/renders/${transcriptionId}/file`, { headers: HEADERS });
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
const transcript = answer;
Answer: status 200.
{
"format_version": 1,
"language": "en",
"language_probability": 0.9990234375,
"duration_seconds": 41.991875,
"words": [
{
"text": "Dirty",
"start_seconds": 0,
"end_seconds": 0.22,
"probability": 0.9716796875
},
{
"text": "facts",
"start_seconds": 0.22,
"end_seconds": 0.62,
"probability": 0.78515625
},
{
"text": "about",
"start_seconds": 0.62,
"end_seconds": 1,
"probability": 0.99951171875
},
{
"text": "ancient",
"start_seconds": 1,
"end_seconds": 1.44,
"probability": 0.9482421875
}
]
}
The answer has 116 words. This page shows the first 4.
Step 2: Add a version with the effect
The new version becomes the current version. The answer gives the number of the current version and the number of versions.
curl
BODY=$(jq -n --argjson transcript "$TRANSCRIPT" '{
"version": {
"format_version": 1,
"style": {"name": "pebble", "settings": {}},
"transcript": $transcript,
"plugins": []
}
}')
ANSWER=$(curl -s -X POST "$CAPTIONBOB_URL/api/videos/$VIDEO_ID/versions" \
-H "Authorization: Bearer $CAPTIONBOB_KEY" \
-H "Content-Type: application/json" \
-d "$BODY")
echo "$ANSWER"
VERSION_NUMBER=$(echo "$ANSWER" | jq -r '.currentVersion')
Python
response = requests.post(
f"{CAPTIONBOB_URL}/api/videos/{video_id}/versions",
headers=HEADERS,
json={
"version": {
"format_version": 1,
"style": {"name": "pebble", "settings": {}},
"transcript": transcript,
"plugins": [],
},
},
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
version_number = answer["currentVersion"]
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/videos/${videoId}/versions`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
body: JSON.stringify({
version: {
format_version: 1,
style: { name: "pebble", settings: {} },
transcript: transcript,
plugins: [],
},
}),
});
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
const versionNumber = answer.currentVersion;
Answer: status 201.
{
"currentVersion": 2,
"versionCount": 2
}
Step 3: List the versions
The history of the video, the oldest version first.
curl
curl -s "$CAPTIONBOB_URL/api/videos/$VIDEO_ID/versions" \
-H "Authorization: Bearer $CAPTIONBOB_KEY"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/videos/{video_id}/versions",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/videos/${videoId}/versions`, { headers: HEADERS });
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
Answer: status 200.
{
"currentVersion": 2,
"versionCount": 2,
"versions": [
{
"number": 1,
"createdBy": "editor",
"createdAt": "2026-10-28T01:33:34.443Z"
},
{
"number": 2,
"createdBy": "editor",
"createdAt": "2026-10-28T01:34:09.760Z"
}
]
}
Step 4: Get the version
One version with its JSON.
curl
curl -s "$CAPTIONBOB_URL/api/videos/$VIDEO_ID/versions/$VERSION_NUMBER" \
-H "Authorization: Bearer $CAPTIONBOB_KEY"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/videos/{video_id}/versions/{version_number}",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/videos/${videoId}/versions/${versionNumber}`, { headers: HEADERS });
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
Answer: status 200.
{
"number": 2,
"version": {
"format_version": 1,
"style": {
"name": "pebble",
"settings": {}
},
"transcript": {
"format_version": 1,
"language": "en",
"language_probability": 0.9990234375,
"duration_seconds": 41.991875,
"words": [
{
"text": "Dirty",
"start_seconds": 0,
"end_seconds": 0.22,
"probability": 0.9716796875
},
{
"text": "facts",
"start_seconds": 0.22,
"end_seconds": 0.62,
"probability": 0.78515625
},
{
"text": "about",
"start_seconds": 0.62,
"end_seconds": 1,
"probability": 0.99951171875
}
]
},
"plugins": []
},
"createdBy": "editor",
"createdAt": "2026-10-28T01:34:09.760Z"
}
The answer has 116 words. This page shows the first 3.
Step 5: Burn the version
A burn burns the current version.
curl
ANSWER=$(curl -s -X POST "$CAPTIONBOB_URL/api/videos/$VIDEO_ID/renders" \
-H "Authorization: Bearer $CAPTIONBOB_KEY" \
-H "Content-Type: application/json" \
-d '{"kind": "burn"}')
echo "$ANSWER"
BURN_ID=$(echo "$ANSWER" | jq -r '.render.id')
Python
response = requests.post(
f"{CAPTIONBOB_URL}/api/videos/{video_id}/renders",
headers=HEADERS,
json={"kind": "burn"},
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
burn_id = answer["render"]["id"]
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/videos/${videoId}/renders`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/json" },
body: JSON.stringify({ kind: "burn" }),
});
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
const burnId = answer.render.id;
Answer: status 201.
{
"render": {
"id": 3,
"videoId": 1,
"versionNumber": 2,
"kind": "burn",
"state": "queued",
"requestedBy": "editor",
"apiKeyName": "Quickstart",
"doneSeconds": 0,
"totalSeconds": null,
"hasOutput": false,
"errorMessage": null,
"createdAt": "2026-10-28T01:34:09.766Z",
"startedAt": null,
"finishedAt": null
}
}
Step 6: Wait for the burn
Read the render every 2 seconds until its state is done or failed.
curl
while true; do
ANSWER=$(curl -s "$CAPTIONBOB_URL/api/renders/$BURN_ID" \
-H "Authorization: Bearer $CAPTIONBOB_KEY")
STATE=$(echo "$ANSWER" | jq -r '.render.state')
echo "$STATE"
if [ "$STATE" = "done" ] || [ "$STATE" = "failed" ]; then
break
fi
sleep 2
done
echo "$ANSWER"
Python
while True:
response = requests.get(
f"{CAPTIONBOB_URL}/api/renders/{burn_id}",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
state = answer["render"]["state"]
print(state)
if state == "done" or state == "failed":
break
time.sleep(2)
print(answer)
JavaScript
while (true) {
response = await fetch(`${CAPTIONBOB_URL}/api/renders/${burnId}`, { headers: HEADERS });
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(answer.render.state);
if (answer.render.state === "done" || answer.render.state === "failed") {
break;
}
await setTimeout(2000);
}
console.log(JSON.stringify(answer, null, 2));
Answer: status 200, the last answer of the wait.
{
"render": {
"id": 3,
"videoId": 1,
"versionNumber": 2,
"kind": "burn",
"state": "done",
"requestedBy": "editor",
"apiKeyName": "Quickstart",
"doneSeconds": 42,
"totalSeconds": 42,
"hasOutput": true,
"errorMessage": null,
"createdAt": "2026-10-28T01:34:09.766Z",
"startedAt": "2026-10-28T01:34:10.404Z",
"finishedAt": "2026-10-28T01:34:41.108Z"
}
}
Step 7: Download the video
The output file of the burn is the video with the effect.
curl
curl -s -D - -o pebble.mp4 "$CAPTIONBOB_URL/api/renders/$BURN_ID/file" \
-H "Authorization: Bearer $CAPTIONBOB_KEY"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/renders/{burn_id}/file",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
with open("pebble.mp4", "wb") as output_file:
output_file.write(response.content)
print(response.status_code, response.headers["Content-Type"], len(response.content))
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/renders/${burnId}/file`, { headers: HEADERS });
if (!response.ok) {
throw new Error(await response.text());
}
await writeFile("pebble.mp4", Buffer.from(await response.arrayBuffer()));
console.log(response.status, response.headers.get("content-type"));
Answer: status 200. The body is the file. Its headers are:
HTTP/1.1 200 OK
Content-Type: video/mp4
Content-Length: 24300517
Accept-Ranges: bytes