Install the client and create a key
Use Python 3.10 or later on a server, a worker or a trusted local development machine. Create a Capslane workspace, open API Keys and store the key as CAPSLANE_API_KEY in your environment. A free workspace includes 50 transcript requests per month and 15 generated minutes, with one active generation at a time.
python -m pip install capslaneThe example uses a public YouTube fixture. A video URL or its eleven-character ID is accepted. Keep the key out of source control, notebooks you share and browser-delivered code. You do not need a YouTube account or a YouTube Data API key to authenticate to Capslane.
Retrieve captions or wait for a generated transcript
For a LangChain application, the optional Capslane document loader adds local chunking, playback citations and a complete passage-search example.
import json
import os
import sys
from capslane import CapslaneClient, CapslaneError
client = CapslaneClient(api_key=os.environ["CAPSLANE_API_KEY"], timeout=45)
try:
result = client.transcript("dQw4w9WgXcQ", mode="auto")
if "content" not in result:
print("Accepted job:", result["jobId"], file=sys.stderr)
result = client.wait_for_transcript(result, timeout=20 * 60)
print(json.dumps(result, indent=2))
except CapslaneError as error:
print(error.code, error.status, error.request_id, file=sys.stderr)
raise
Download transcript_sdk.py and run python transcript_sdk.py.
A native or cached response already contains content. If the response contains a job instead, pass that result to wait_for_transcript. The SDK polls the same job and raises an error for a failed or cancelled result. The example sets a 45-second timeout for each network call and a twenty-minute polling window.
Capslane checks the cache before applying the requested mode. A cached result can come from native captions or audio generation in any mode. Check source and cached in the response; mode controls what happens on a cache miss.
Stopping a client request or reaching a client timeout does not cancel an accepted server job. Resume with its job ID. JavaScript SDK 0.1.3 bounds both waits and in-flight reads within the polling deadline. Python checks its polling deadline between iterations. Each HTTP request also has its own timeout.
The maintained SDK source and installation details are available in the Python client repository.
Work with plain text, languages and timestamps
The public job endpoint returns canonical timestamped segments when complete. It does not reapply text or chunkSize from the initial request. Join the returned segment text locally if you need one string. A completed response also has jobId, so use content to recognize a ready transcript.
result = client.transcript("dQw4w9WgXcQ", mode="native", lang="en")
for segment in result["content"]:
start_seconds = segment["offset"] / 1000
print(start_seconds, segment["text"])
# Request one string when timing is not needed.
plain = client.transcript("dQw4w9WgXcQ", mode="native", text=True)
print(plain["content"])content = result["content"]
plain = content if isinstance(content, str) else " ".join(segment["text"] for segment in content)
print(plain)Segment offsets and durations are milliseconds. Divide by 1,000 for a playback URL or player API that expects seconds. The requested language is a preference: inspect lang in the response to see the track actually selected, and availableLangs for the tracks found. Capslane does not translate a transcript just because another language was requested.
For larger text chunks, pass chunk_size=500 with segmented output. Chunk size is a character budget, not a duration or token count. Whole source segments are preserved, so one segment can exceed that budget. Keep the original segment timing if your product links quotes to the video.
A complete example without an SDK dependency
This standard-library version uses one deadline across the initial request, waiting and subsequent network calls. It stops on HTTP errors and terminal job failures. A processing response with HTTP 200 continues to be polled until content is present.
"""Server-side example using the Python standard library."""
import json
import os
import time
from urllib.error import HTTPError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
def get_transcript(video, *, api_key=None, mode="auto",
base_url="https://capslane.com", timeout=1200, interval=2):
api_key = api_key or os.environ.get("CAPSLANE_API_KEY")
if not api_key:
raise ValueError("Set CAPSLANE_API_KEY on your server")
deadline = time.monotonic() + timeout
def get(path):
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError("Transcript job deadline exceeded")
request = Request(base_url + path, headers={
"x-api-key": api_key, "accept": "application/json",
})
try:
with urlopen(request, timeout=min(45, remaining)) as response:
return json.load(response)
except HTTPError as error:
try:
details = json.load(error)
except (ValueError, UnicodeError):
details = {}
raise RuntimeError(
f"HTTP {error.code}: {details.get('error', 'request_failed')} "
f"(request {details.get('requestId', 'unknown')})"
) from error
query = urlencode({"url": video, "mode": mode})
result = get("/v1/transcript?" + query)
job_id = result.get("jobId")
while "content" not in result:
if result.get("status") in {"failed", "cancelled"}:
raise RuntimeError(result.get("error", result["status"]))
if not job_id:
raise RuntimeError("Response contains neither content nor a job ID")
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError("Transcript job deadline exceeded")
time.sleep(min(max(0.001, interval), remaining))
result = get("/v1/transcript/" + quote(job_id, safe=""))
return result
if __name__ == "__main__":
print(json.dumps(get_transcript("dQw4w9WgXcQ"), indent=2))
Download transcript_http.py, set the environment variable and run python transcript_http.py. The downloadable source is also the source displayed here and is exercised against immediate results, multiple processing responses, HTTP failures and a deadline fixture.
Save a complete result for your next step
Once the SDK example above has finished waiting, save that result once. This keeps the original language, timestamps and source for search or ingestion, without requesting the same video a second time.
from pathlib import Path
# Run after the SDK example has returned complete content.
Path("transcript.json").write_text(
json.dumps(result, ensure_ascii=False, indent=2),
encoding="utf-8",
)In a batch, give each video a separate output path and record success separately from an accepted job. Keep transcript files out of source control. For a spreadsheet-based intake, the Sheets and Markdown workflows provide the same retrieval-to-storage pattern without a Python process.
Handle failures before adding retries
An invalid key requires a credential fix. An invalid video ID requires an input fix. A missing native track may justify a generated request only if your application permits it. A monthly quota error requires a quota decision. Retrying those cases in a tight loop will not solve them.
For a transient network or upstream error, use a small retry budget and backoff. Preserve the request ID for logs and avoid submitting another generated request while an existing job is still processing. A client timeout does not cancel an accepted server job: keep its ID and check its state before deciding what to do next.
Capslane or youtube-transcript-api?
youtube-transcript-api is a separate open source project for retrieving YouTube transcripts. It can fit an application where you want to operate extraction yourself. The Capslane Python package is named capslane and calls a managed API with workspace quotas, shared cache, request logs and optional generated fallback.
Choose based on the responsibilities your team wants to own. A managed API still has documented failure modes and availability limits. See the self-hosted versus managed guide and the benchmark methodology before relying on broad reliability claims.