HTTP API examples
Upload a video
This page uploads a video to a project, reads the facts of the video and downloads a part of the uploaded file.
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 project id from the page Create a project.
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 project id from the page Create a project
PROJECT_ID=2
Python
import requests
CAPTIONBOB_URL = "http://127.0.0.1:9400"
HEADERS = {"Authorization": "Bearer <your API key>"}
# The project id from the page Create a project
project_id = 2
JavaScript
import { readFile, writeFile } from "node:fs/promises";
const CAPTIONBOB_URL = "http://127.0.0.1:9400";
const HEADERS = { Authorization: "Bearer <your API key>" };
// The project id from the page Create a project
const projectId = 2;
let response;
let answer;
Step 1: Upload the video
Send the video file as the raw body of the request, and its file name in the query value name. The server streams the body to a file within its upload limit. Then it probes the file and keeps only a video.
curl
ANSWER=$(curl -s -X POST "$CAPTIONBOB_URL/api/projects/$PROJECT_ID/videos?name=test.mp4" \
-H "Authorization: Bearer $CAPTIONBOB_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @test.mp4)
echo "$ANSWER"
VIDEO_ID=$(echo "$ANSWER" | jq -r '.video.id')
Python
with open("test.mp4", "rb") as video_file:
response = requests.post(
f"{CAPTIONBOB_URL}/api/projects/{project_id}/videos",
headers={**HEADERS, "Content-Type": "application/octet-stream"},
params={"name": "test.mp4"},
data=video_file,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
video_id = answer["video"]["id"]
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/projects/${projectId}/videos?${new URLSearchParams({ name: "test.mp4" })}`, {
method: "POST",
headers: { ...HEADERS, "Content-Type": "application/octet-stream" },
body: await readFile("test.mp4"),
});
if (!response.ok) {
throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
const videoId = answer.video.id;
Answer: status 201.
{
"video": {
"id": 2,
"projectId": 2,
"originalName": "test.mp4",
"sizeBytes": 44219360,
"width": 1080,
"height": 1920,
"frameRate": "25/1",
"durationSeconds": 42,
"hasAudio": true,
"currentVersion": null,
"versionCount": 0,
"createdBy": "editor",
"createdAt": "2026-10-28T01:34:09.734Z",
"updatedAt": "2026-10-28T01:34:09.734Z",
"latestRender": null
}
}
Step 2: Get the video
The answer holds the facts of the video and its newest render. A new video has no version and no render.
curl
curl -s "$CAPTIONBOB_URL/api/videos/$VIDEO_ID" \
-H "Authorization: Bearer $CAPTIONBOB_KEY"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/videos/{video_id}",
headers=HEADERS,
)
if not response.ok:
raise RuntimeError(response.text)
answer = response.json()
print(answer)
JavaScript
response = await fetch(`${CAPTIONBOB_URL}/api/videos/${videoId}`, { 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.
{
"video": {
"id": 2,
"projectId": 2,
"originalName": "test.mp4",
"sizeBytes": 44219360,
"width": 1080,
"height": 1920,
"frameRate": "25/1",
"durationSeconds": 42,
"hasAudio": true,
"currentVersion": null,
"versionCount": 0,
"createdBy": "editor",
"createdAt": "2026-10-28T01:34:09.734Z",
"updatedAt": "2026-10-28T01:34:09.734Z",
"latestRender": null
}
}
Step 3: Download a part of the video file
The route gives the uploaded file, whole or as one byte range. Ask for a range with the header Range. The answer of a range has the status 206.
curl
curl -s -D - -o first_bytes.mp4 "$CAPTIONBOB_URL/api/videos/$VIDEO_ID/file" \
-H "Authorization: Bearer $CAPTIONBOB_KEY" \
-H "Range: bytes=0-99"
Python
response = requests.get(
f"{CAPTIONBOB_URL}/api/videos/{video_id}/file",
headers={**HEADERS, "Range": "bytes=0-99"},
)
if not response.ok:
raise RuntimeError(response.text)
with open("first_bytes.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/videos/${videoId}/file`, { headers: { ...HEADERS, Range: "bytes=0-99" } });
if (!response.ok) {
throw new Error(await response.text());
}
await writeFile("first_bytes.mp4", Buffer.from(await response.arrayBuffer()));
console.log(response.status, response.headers.get("content-type"));
Answer: status 206. The body is the file. Its headers are:
HTTP/1.1 206 Partial Content
Content-Type: video/mp4
Content-Length: 100
Content-Range: bytes 0-99/44219360
Accept-Ranges: bytes