2026-11-03 · 4 min read

Captions for Corporate Training Videos

Training videos get watched in open offices, on factory floors and on commutes, and a share of viewers will always watch muted. If completion matters for compliance or onboarding, captions are not a nice extra, they are how the content stays usable.

Teams updating a whole library do not need to reopen an editor for every module. The caption.sh API captions each video from its URL and returns a new file with the captions rendered in, which fits into a script over your existing library.

The captioning workflow

Submit the video, poll the job, download the result. This example captions one module and writes it next to the original.

import requests, time

KEY = "YOUR_CAPTION_API_KEY"
H = {"Authorization": f"Bearer {KEY}"}
API = "https://api.caption.sh/v1"

def caption_video(source_url, out_path):
    r = requests.post(f"{API}/videos", headers=H,
                      json={"source_url": source_url, "options": {}},
                      timeout=30)
    if r.status_code == 402:
        raise RuntimeError("out of credits, top up first")
    r.raise_for_status()
    job_id = r.json()["job_id"]

    deadline = time.time() + 60 * 60
    while time.time() < deadline:
        time.sleep(15)
        job = requests.get(f"{API}/jobs/{job_id}", headers=H, timeout=30).json()
        if job["status"] == "done":
            break
        if job["status"] == "error":
            raise RuntimeError(f"render failed: {job}")
    else:
        raise RuntimeError("render did not finish in time")

    mp4 = requests.get(f"{API}/jobs/{job_id}/result", headers=H, timeout=60)
    open(out_path, "wb").write(mp4.content)

Practical notes

  • Submissions are rate limited per API key. A 429 response means wait a moment and retry, it does not mean the job failed.
  • Rendering is prepaid at $0.10 per minute of video. A 402 response means the balance is empty, so top up before retrying.
  • Every render needs a bounded poll loop with three exits: done, error, and your own timeout. Do not poll forever.
  • The API documents no webhook surface, so polling is the intended way to learn a job finished.

Run your longest module first to check caption timing against dense terminology, then batch the rest. Because billing is per rendered minute, a library audit before you start keeps the credit spend predictable.

Back to all articles