How to add subtitles to a video in Python
Add subtitles to a video in Python with Requests: POST the video URL to Sume's /v1/video-captions, poll the job, then read the captioned video_url.

To add subtitles to a video in Python without running ffmpeg or a speech model yourself, send the video's URL to a captions API and read back the captioned file. With Sume and the Requests library: POST the URL to https://api.sume.com/v1/video-captions with an Idempotency-Key, poll the returned status_url until terminal is true, then read GET /v1/video-captions/{id} for the captioned video_url.
Sume facts come from the Video captions and Jobs and results docs and the Sume API reference; Requests behavior comes from its Quickstart; all read on 2026-09-27. Sume's SDK is for TypeScript, and everything it does is reachable over plain HTTP, so this is a plain HTTPS call, like the Python samples in Sume's own docs.
What does the whole script look like?
Submit, poll, and read in 25 lines. With no wording field, Sume transcribes the speech and burns it; language is an optional speech-to-text hint. Keep the key in an environment variable on a trusted server or your own machine, never in frontend JavaScript or a mobile app.
import os, time, requests
API = "https://api.sume.com/v1"
AUTH = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def get(url):
r = requests.get(url, headers=AUTH, timeout=30)
r.raise_for_status()
return r.json()["data"]
body = {"video_url": "https://example.com/clip.mp4", "language": "en"}
r = requests.post(f"{API}/video-captions", json=body, timeout=30,
headers={**AUTH, "Idempotency-Key": "clip-captions-001"})
r.raise_for_status()
job = r.json()["data"]
status = get(job["status_url"])
while not status["terminal"]:
time.sleep(status["next_poll_after_seconds"] or 5)
status = get(job["status_url"])
caption = get(f"{API}/video-captions/{job['video_caption_id']}")
if caption["status"] != "completed":
raise RuntimeError(caption["error"])
print(caption["video_url"])What does each call send and return?
Each of these responses wraps its payload in data. The script reads only these fields:
json=makes Requests encode the body and setContent-Type: application/json.timeout=matters: if no timeout is set, Requests does not time out, and its docs warn a program can hang indefinitely.raise_for_status()raises on an error status. Requests' docs note that a JSON body that decodes does not mean the call succeeded, and Sume's errors are JSON, with a stableerror.codeto switch on.- Resending the submit with the same
Idempotency-Keyand the same body returns the original job instead of billing a second one.
| Step | Request | Fields the script reads |
|---|---|---|
| Submit | POST /v1/video-captions with video_url (required) and an Idempotency-Key header | status_url, video_caption_id |
| Poll | GET the status_url | terminal, next_poll_after_seconds |
| Read | GET /v1/video-captions/{id} | status, video_url, error |
How long should the script wait?
Poll until terminal is true, and wait next_poll_after_seconds between reads when it is present; back off when it is not. Stopping your script does not cancel the job: it keeps running and still bills, so store the job and pick it up again from status_url.
To skip polling, send webhook_url: Sume then delivers only the terminal events, job.completed, job.failed, or job.canceled, as a signed POST. A Python webhook receiver covers the server side; keep polling as a backup.
What can go wrong, and what does it cost?
A failed caption carries error.public_reason and error.next_action. The common cases:
caption_no_speech, next actionuse_overlay_captions: the clip has no audible speech. Send your own lines ascuesinstead; burn an SRT file into a video shows the shape.script_alignment_mismatchorscript_alignment_failed, next actionsimplify_script_text_or_omit: thescript_textyou sent did not line up with the speech.- A
400withcaption_hangul_text_latin_style: Korean text onslam,punch, ortiktok-green. Name a Hangul style. - Today the caption worker also refuses a source longer than 60 seconds or one with no audio stream. For longer videos, see add captions to a long video.
- Each accepted job reserves and captures the fixed amount listed on the Video captions page, for videos up to 60 seconds.
Sources
Related posts
More in Integrations
- Add Sume to Claude as a custom connector (remote MCP)
Add Sume's hosted MCP server to Claude under Customize > Connectors, see what Sume's OAuth consent grants, and decide whether to allow paid tools.
- Airflow HTTP sensor: wait for an AI video job to finish
Submit an AI video job with Airflow's HttpOperator, then wait with an HttpSensor in reschedule mode that passes once the job's status is completed.
- Airtable automation video generation API: a video per record
Use an Airtable Run a script action to call POST /v1/videos with a callback_url, then catch Sume's webhook in a second automation and save the URL.
- Amazon Q MCP server: add Sume's hosted MCP in the IDE
Amazon Q Developer in the IDE takes HTTP MCP servers. Add Sume's hosted MCP with an API-key header or OAuth, then set its paid tools to Ask.
Written by Sume