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.

5 min readSume
All posts

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 set Content-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 stable error.code to switch on.
  • Resending the submit with the same Idempotency-Key and the same body returns the original job instead of billing a second one.
From Video captions, Jobs and results, and the Sume API reference, read 2026-09-27.
StepRequestFields the script reads
SubmitPOST /v1/video-captions with video_url (required) and an Idempotency-Key headerstatus_url, video_caption_id
PollGET the status_urlterminal, next_poll_after_seconds
ReadGET /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 action use_overlay_captions: the clip has no audible speech. Send your own lines as cues instead; burn an SRT file into a video shows the shape.
  • script_alignment_mismatch or script_alignment_failed, next action simplify_script_text_or_omit: the script_text you sent did not line up with the speech.
  • A 400 with caption_hangul_text_latin_style: Korean text on slam, punch, or tiktok-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

All Integrations posts

Written by Sume