2026-10-11 · 4 min read

Caption videos in Python with requests

The caption.sh API is three HTTP calls, so a short Python script with requests is all you need. This is the whole thing, with a capped poll loop and the error cases handled.

The script

import os, time, requests

API = "https://api.caption.sh/v1"
H = {"Authorization": f"Bearer {os.environ['CAPTION_API_KEY']}"}

def caption(video_url, out="captioned.mp4"):
    r = requests.post(f"{API}/videos", headers=H,
                      json={"source_url": video_url, "options": {"max_words": 3}})
    r.raise_for_status()
    job = r.json()["job_id"]
    for _ in range(60):
        s = requests.get(f"{API}/jobs/{job}", headers=H).json()["status"]
        if s == "done":
            break
        if s == "error":
            raise RuntimeError("caption job failed")
        time.sleep(5)
    else:
        raise TimeoutError("caption job did not finish")
    res = requests.get(f"{API}/jobs/{job}/result", headers=H)
    res.raise_for_status()
    open(out, "wb").write(res.content)
    return out

caption("https://example.com/clip.mp4")

Authentication

Create an API key in the caption.sh dashboard and keep it in an environment variable, as above. Do not hard-code it in the script or commit it.

Things worth knowing

  • The video has to be reachable by URL. For a local file, use the upload flow: the submit call returns a signed upload URL, you PUT the bytes to it, and the job starts when the upload finishes.
  • raise_for_status turns a 402 (prepaid balance empty) or a 422 (input rejected) into an exception. Neither is worth retrying.
  • For many clips, submit them all first and then poll, so the jobs run side by side.

When this is the wrong tool

caption.sh burns captions into the video. It does not export SRT or VTT sidecar files. If you need a transcript file, produce it with a transcription library and use caption.sh for the version people watch on a feed.

Try it

Judge the output in the playground at caption.sh first, then run the script above. The reference is at caption.sh/docs and the OpenAPI spec is at api.caption.sh/openapi.json.

Back to all articles