2026-11-01 · 5 min read

Captions for X Video: Burn In Subtitles Before You Post

Video on X plays muted more often than not. A clip that needs its audio to make sense loses most of its audience in the first second, so captions on the pixels themselves do real work there. Sidecar files are not an option on X the way they are on YouTube, which makes burned-in captions the practical route.

The workflow

caption.sh is an async job API. You POST a video URL to /v1/videos, the render happens on the service side, and you poll the job until it is done. The result is an MP4 with the captions burned into the frames, ready to upload to X like any other video. Pricing is prepaid at $0.10 per minute of video, and failed renders are not charged.

import requests, time

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

r = requests.post(f"{API}/videos", headers=H,
                  json={"source_url": "https://example.com/clip.mp4",
                        "options": {"font": "inter"}}, timeout=30)
r.raise_for_status()
job_id = r.json()["job_id"]

for _ in range(120):
    time.sleep(5)
    job = requests.get(f"{API}/jobs/{job_id}", headers=H, timeout=30).json()
    if job["status"] == "done":
        break
    if job["status"] == "error":
        raise SystemExit(f"render failed: {job}")
else:
    raise SystemExit("timed out waiting for render")

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

Details worth knowing

  • Submissions are rate limited per API key. If a submit answers 429, wait a few seconds and retry; jobs already accepted are unaffected.
  • The result endpoint redirects to a signed download URL; results are stored for 24 hours and then deleted. Download the file when the job finishes instead of treating the link as storage.
  • If your clip only exists as a local file, the API also documents an upload flow: the POST returns a signed upload URL, you PUT the bytes to it, and the job starts when the upload completes.
  • Videos that only exist behind your own login can be rendered by uploading directly rather than passing a URL.

If you caption clips for X on a schedule, the same script slots into a cron job or a CI step: submit, poll with a bounded loop, download, post. The caption styling lives in the options object, so changing the look is a config change, not a retime of an editor timeline.

Back to all articles