2026-11-05 · 4 min read
How to Add Captions to Podcast Clips
Podcast clips live or die in the feed, and the feed is muted by default. A clip without captions asks the viewer to turn sound on before they know if the clip is worth it. Most will not. Captioned clips fix that, but if you are cutting clips from every episode, captioning each one by hand in an editor eats the time you saved by clipping in the first place.
Why captions matter more for clips than episodes
- Clips are discovered in feeds where autoplay is silent.
- Podcast audio is conversational, so crosstalk and accents benefit from on-screen text.
- A captioned clip is watchable in places audio is not: offices, transit, waiting rooms.
- Caption text doubles as a hook when the first line is visible immediately.
Captioning clips with an API
caption.sh takes a JSON POST with the clip's source_url and returns a job_id. The clip can live anywhere reachable by URL: your storage, a CDN, or a temporary link from your clipping tool. Captioning is $0.10 per minute prepaid, and clips are short, so the cost per clip is pocket change.
curl -X POST https://api.caption.sh/v1/videos \
-H "Authorization: Bearer $CAPTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source_url": "https://example.com/talk.mp4"}'
Poll until the clip is ready
Caption jobs are asynchronous. Submit the job, keep the job_id, and poll the status endpoint until it reports done or failed. Bound the loop with a timeout so a bad job fails loudly instead of blocking your posting schedule.
import time
import requests
KEY = "your_api_key"
H = {"Authorization": f"Bearer {KEY}"}
job = requests.post(
"https://api.caption.sh/v1/videos",
headers=H,
json={"source_url": "https://example.com/talk.mp4"},
).json()
job_id = job["job_id"]
# A 429 means the transcription throughput ceiling is reached. Wait and retry.
deadline = time.time() + 1800
status = None
while time.time() < deadline:
r = requests.get(f"https://api.caption.sh/v1/jobs/{job_id}", headers=H)
status = r.json()["status"]
if status in ("done", "error"):
break
time.sleep(10)
if status == "done":
r = requests.get(f"https://api.caption.sh/v1/jobs/{job_id}/result", headers=H, allow_redirects=False)
print(r.headers["Location"]) # signed URL; results are stored for 24 hours, then deleted
elif status == "error":
raise SystemExit("captioning failed")
else:
raise SystemExit("timed out waiting for the job")
The result endpoint redirects to a signed download URL, and results are stored for 24 hours, then deleted. Pull the captioned clip down or copy it to your own bucket promptly. The API documents no webhook surface, so plan on polling.
A simple weekly workflow
A workable rhythm is: record the episode, cut the clips, submit each clip for captioning as part of the clipping step, and schedule the captioned versions. Because captions are burned into the file, they survive every repost, embed, and download without depending on each platform's caption features.