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.