captionbob docs

Version 1.0.0
Contents ↓

HTTP API examples

Create a project

A project holds the videos of one job. This page creates a project, lists the projects, reads one project and changes it.

Before you start

Put your API key in place of <your API key>. The Quickstart shows how to make a key.

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>"

Python

import requests

CAPTIONBOB_URL = "http://127.0.0.1:9400"
HEADERS = {"Authorization": "Bearer <your API key>"}

JavaScript

const CAPTIONBOB_URL = "http://127.0.0.1:9400";
const HEADERS = { Authorization: "Bearer <your API key>" };
let response;
let answer;

Step 1: Create a project

Give the new project a name. A new project is active.

curl

ANSWER=$(curl -s -X POST "$CAPTIONBOB_URL/api/projects" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Product demos"}')
echo "$ANSWER"
PROJECT_ID=$(echo "$ANSWER" | jq -r '.project.id')

Python

response = requests.post(
    f"{CAPTIONBOB_URL}/api/projects",
    headers=HEADERS,
    json={"name": "Product demos"},
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)
project_id = answer["project"]["id"]

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Product demos" }),
});
if (!response.ok) {
  throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));
const projectId = answer.project.id;

Answer: status 201.

{
  "project": {
    "id": 2,
    "name": "Product demos",
    "state": "active",
    "createdBy": "editor",
    "createdAt": "2026-10-28T01:34:08.742Z",
    "updatedAt": "2026-10-28T01:34:08.742Z",
    "videoCount": 0
  }
}

Step 2: List the projects

The list holds all projects of all users, the last changed first.

curl

curl -s "$CAPTIONBOB_URL/api/projects" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY"

Python

response = requests.get(
    f"{CAPTIONBOB_URL}/api/projects",
    headers=HEADERS,
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects`, { 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.

{
  "projects": [
    {
      "id": 2,
      "name": "Product demos",
      "state": "active",
      "createdBy": "editor",
      "createdAt": "2026-10-28T01:34:08.742Z",
      "updatedAt": "2026-10-28T01:34:08.742Z",
      "videoCount": 0
    },
    {
      "id": 1,
      "name": "Launch videos",
      "state": "active",
      "createdBy": "editor",
      "createdAt": "2026-10-28T01:33:12.066Z",
      "updatedAt": "2026-10-28T01:33:13.076Z",
      "videoCount": 1
    }
  ]
}

Step 3: Get one project

The answer holds the project and its videos, the newest first. Each video has its newest render, which gives its status. A new project has no videos.

curl

curl -s "$CAPTIONBOB_URL/api/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY"

Python

response = requests.get(
    f"{CAPTIONBOB_URL}/api/projects/{project_id}",
    headers=HEADERS,
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects/${projectId}`, { 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.

{
  "project": {
    "id": 2,
    "name": "Product demos",
    "state": "active",
    "createdBy": "editor",
    "createdAt": "2026-10-28T01:34:08.742Z",
    "updatedAt": "2026-10-28T01:34:08.742Z",
    "videoCount": 0
  },
  "videos": []
}

Step 4: Rename the project

Send the new name in name.

curl

curl -s -X PATCH "$CAPTIONBOB_URL/api/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Product demos 2026"}'

Python

response = requests.patch(
    f"{CAPTIONBOB_URL}/api/projects/{project_id}",
    headers=HEADERS,
    json={"name": "Product demos 2026"},
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects/${projectId}`, {
  method: "PATCH",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Product demos 2026" }),
});
if (!response.ok) {
  throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));

Answer: status 200.

{
  "project": {
    "id": 2,
    "name": "Product demos 2026",
    "state": "active",
    "createdBy": "editor",
    "createdAt": "2026-10-28T01:34:08.742Z",
    "updatedAt": "2026-10-28T01:34:08.748Z",
    "videoCount": 0
  }
}

Step 5: Archive the project

The state archived moves the project to the Archive tab of the dashboard.

curl

curl -s -X PATCH "$CAPTIONBOB_URL/api/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state": "archived"}'

Python

response = requests.patch(
    f"{CAPTIONBOB_URL}/api/projects/{project_id}",
    headers=HEADERS,
    json={"state": "archived"},
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects/${projectId}`, {
  method: "PATCH",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({ state: "archived" }),
});
if (!response.ok) {
  throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));

Answer: status 200.

{
  "project": {
    "id": 2,
    "name": "Product demos 2026",
    "state": "archived",
    "createdBy": "editor",
    "createdAt": "2026-10-28T01:34:08.742Z",
    "updatedAt": "2026-10-28T01:34:08.754Z",
    "videoCount": 0
  }
}

Step 6: Make the project active again

The state active moves the project back.

curl

curl -s -X PATCH "$CAPTIONBOB_URL/api/projects/$PROJECT_ID" \
  -H "Authorization: Bearer $CAPTIONBOB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"state": "active"}'

Python

response = requests.patch(
    f"{CAPTIONBOB_URL}/api/projects/{project_id}",
    headers=HEADERS,
    json={"state": "active"},
)
if not response.ok:
    raise RuntimeError(response.text)
answer = response.json()
print(answer)

JavaScript

response = await fetch(`${CAPTIONBOB_URL}/api/projects/${projectId}`, {
  method: "PATCH",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify({ state: "active" }),
});
if (!response.ok) {
  throw new Error(await response.text());
}
answer = await response.json();
console.log(JSON.stringify(answer, null, 2));

Answer: status 200.

{
  "project": {
    "id": 2,
    "name": "Product demos 2026",
    "state": "active",
    "createdBy": "editor",
    "createdAt": "2026-10-28T01:34:08.742Z",
    "updatedAt": "2026-10-28T01:34:08.759Z",
    "videoCount": 0
  }
}