Copy-paste code examples
Working Python, JavaScript and cURL snippets for the Toucora API. Base URL: https://api.example.com/v1. Every request uses Authorization: Bearer YOUR_API_KEY; get a key from the dashboard.
TOUCORA_API_KEY in your environment (or replace YOUR_API_KEY). The host https://api.example.com below is rewritten to this deployment when the page loads. See the docs for the full endpoint reference and the OpenAPI spec.Get a transcript
GET/v1/transcripts/{videoId} returns a normalized JSON transcript with per-segment timings. Pass the video id or a full YouTube URL.
curl "https://api.example.com/v1/transcripts/dQw4w9WgXcQ?lang=en" \ -H "Authorization: Bearer YOUR_API_KEY"
import os
import requests
API = "https://api.example.com/v1"
API_KEY = os.environ["TOUCORA_API_KEY"]
resp = requests.get(
f"{API}/transcripts/dQw4w9WgXcQ",
params={"lang": "en"},
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=30,
)
resp.raise_for_status()
transcript = resp.json()
print(transcript["title"], "|", transcript["language"], "|", transcript["duration"], "s")
for segment in transcript["segments"]:
print(f'{segment["start"]:7.2f} {segment["text"]}')
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const res = await fetch(`${API}/transcripts/dQw4w9WgXcQ?lang=en`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
const transcript = await res.json();
console.log(`${transcript.title} | ${transcript.language} | ${transcript.duration}s`);
for (const { start, text } of transcript.segments) console.log(start, text);
Need a specific caption track? Call GET /v1/transcripts/{videoId}/languages first, then pass ?lang=. A batch of up to 50 ids is one POST /v1/transcripts with {"ids": [...]}.
Export formats
Add ?format= to get subtitles instead of JSON. txt (plain text), srt, vtt and markdown stream as text and preserve timing for SRT/VTT. timestamps=true prefixes TXT lines with [mm:ss]; clean=true strips [Music] and similar cues.
# TXT with timestamps, then SRT, VTT and Markdown sub-titles. curl "https://api.example.com/v1/transcripts/dQw4w9WgXcQ?format=txt×tamps=true&clean=true" \ -H "Authorization: Bearer YOUR_API_KEY" -o transcript.txt curl "https://api.example.com/v1/transcripts/dQw4w9WgXcQ?format=srt" \ -H "Authorization: Bearer YOUR_API_KEY" -o transcript.srt curl "https://api.example.com/v1/transcripts/dQw4w9WgXcQ?format=vtt" \ -H "Authorization: Bearer YOUR_API_KEY" -o transcript.vtt curl "https://api.example.com/v1/transcripts/dQw4w9WgXcQ?format=markdown" \ -H "Authorization: Bearer YOUR_API_KEY" -o transcript.md
import os
import requests
API = "https://api.example.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['TOUCORA_API_KEY']}"}
VIDEO = "dQw4w9WgXcQ"
# json (default) returns structured segments; the other formats return text.
for fmt, ext in [("txt", "txt"), ("srt", "srt"), ("vtt", "vtt"), ("markdown", "md")]:
resp = requests.get(
f"{API}/transcripts/{VIDEO}",
params={"lang": "en", "format": fmt, "timestamps": "true", "clean": "true"},
headers=HEADERS,
timeout=30,
)
resp.raise_for_status()
with open(f"{VIDEO}.{ext}", "w", encoding="utf-8") as fh:
fh.write(resp.text)
print(f"wrote {VIDEO}.{ext} ({len(resp.text)} bytes)")
import { writeFile } from "node:fs/promises";
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const VIDEO = "dQw4w9WgXcQ";
for (const [format, ext] of [["txt", "txt"], ["srt", "srt"], ["vtt", "vtt"], ["markdown", "md"]]) {
const url = new URL(`${API}/transcripts/${VIDEO}`);
url.searchParams.set("format", format);
url.searchParams.set("timestamps", "true");
url.searchParams.set("clean", "true");
const res = await fetch(url, { headers: { Authorization: `Bearer ${API_KEY}` } });
if (!res.ok) throw new Error(`export failed: HTTP ${res.status}`);
const text = await res.text();
await writeFile(`${VIDEO}.${ext}`, text);
console.log(`wrote ${VIDEO}.${ext} (${text.length} bytes)`);
}
Translate
POST/v1/translate translates a transcript while preserving timings. mode is translated (default), bilingual, or original. When no AI provider is configured the original transcript is returned with "fallback": true.
curl -X POST "https://api.example.com/v1/translate" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"video_id":"dQw4w9WgXcQ","target_lang":"es","mode":"bilingual"}'
import os
import requests
API = "https://api.example.com/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['TOUCORA_API_KEY']}",
"Content-Type": "application/json",
}
resp = requests.post(
f"{API}/translate",
headers=HEADERS,
json={"video_id": "dQw4w9WgXcQ", "target_lang": "es", "mode": "bilingual"},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
print(data["target_lang"], "translated" if data["translated"] else "fallback")
for segment in data["segments"]:
print(segment["text"])
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const res = await fetch(`${API}/translate`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ video_id: "dQw4w9WgXcQ", target_lang: "es", mode: "bilingual" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();
console.log(data.target_lang, data.translated ? "translated" : `fallback: ${data.reason ?? "original"}`);
for (const segment of data.segments) console.log(segment.text);
Summarize
POST/v1/summarize returns a TL;DR, key points and topics. length is short, medium, or detailed; output_lang writes the result in another language. When AI is disabled you still get an extractive summary with "fallback": true.
curl -X POST "https://api.example.com/v1/summarize" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"video_id":"dQw4w9WgXcQ","length":"short","output_lang":"en"}'
import os
import requests
API = "https://api.example.com/v1"
HEADERS = {
"Authorization": f"Bearer {os.environ['TOUCORA_API_KEY']}",
"Content-Type": "application/json",
}
resp = requests.post(
f"{API}/summarize",
headers=HEADERS,
json={"video_id": "dQw4w9WgXcQ", "length": "short", "output_lang": "en"},
timeout=120,
)
resp.raise_for_status()
data = resp.json()
print(data.get("summary", data))
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const res = await fetch(`${API}/summarize`, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ video_id: "dQw4w9WgXcQ", length: "short", output_lang: "en" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();
console.log(data.summary ?? data);
MCP server
The same tools are exposed over the Model Context Protocol at https://api.example.com/mcp (Streamable HTTP, JSON-RPC 2.0). Use it from MCP clients, or call it directly. See the docs MCP section for Claude Code, Codex and Claude Desktop setup.
curl -X POST "https://api.example.com/mcp" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_transcript",
"arguments": {"video_id": "dQw4w9WgXcQ", "format": "markdown"}
}
}'
import os
import requests
MCP_URL = "https://api.example.com/mcp"
HEADERS = {
"Authorization": f"Bearer {os.environ['TOUCORA_API_KEY']}",
"Content-Type": "application/json",
}
def mcp(method, params=None, request_id=1):
payload = {"jsonrpc": "2.0", "id": request_id, "method": method}
if params is not None:
payload["params"] = params
resp = requests.post(MCP_URL, headers=HEADERS, json=payload, timeout=60)
resp.raise_for_status()
return resp.json()
# Discover the available tools once (optional).
tools = mcp("tools/list", request_id=1)["result"]["tools"]
print([tool["name"] for tool in tools])
# Call a tool exactly like an MCP client would.
result = mcp(
"tools/call",
{"name": "summarize", "arguments": {"video_id": "dQw4w9WgXcQ", "length": "short"}},
request_id=2,
)
print(result["result"]["content"][0]["text"])
const MCP_URL = "https://api.example.com/mcp";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
async function mcp(method, params, id = 1) {
const res = await fetch(MCP_URL, {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
});
if (!res.ok) throw new Error(`MCP HTTP ${res.status}`);
const body = await res.json();
if (body.error) throw new Error(body.error.message);
return body.result;
}
const tools = await mcp("tools/list", {});
console.log(tools.tools.map((tool) => tool.name).join(", "));
const result = await mcp("tools/call", {
name: "summarize",
arguments: { video_id: "dQw4w9WgXcQ", length: "short" },
});
console.log(result.content[0].text);
Errors
All errors share one shape and include a request_id you can quote in support requests. Common codes: INVALID_API_KEY (401), INSUFFICIENT_CREDITS (402), VIDEO_NOT_FOUND / TRANSCRIPT_NOT_AVAILABLE (404), and RATE_LIMITED (429).
{
"error": {
"code": "VIDEO_NOT_FOUND",
"message": "The requested YouTube video could not be found.",
"request_id": "req_123"
}
}
# -i prints the status line and headers (including X-RateLimit-* on 429). curl -i "https://api.example.com/v1/transcripts/does-not-exist" \ -H "Authorization: Bearer YOUR_API_KEY" # 401 invalid key · 402 insufficient credits · 404 not found · 429 rate limited
import os
import requests
API = "https://api.example.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['TOUCORA_API_KEY']}"}
resp = requests.get(f"{API}/transcripts/does-not-exist", headers=HEADERS, timeout=30)
if not resp.ok:
error = resp.json().get("error", {})
raise SystemExit(
f'HTTP {resp.status_code} | {error.get("code")} | {error.get("message")} '
f'(request_id={error.get("request_id")})'
)
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const res = await fetch(`${API}/transcripts/does-not-exist`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) {
const { error } = await res.json();
throw new Error(`HTTP ${res.status} | ${error.code} | ${error.message} (request_id=${error.request_id})`);
}
Retries & backoff
Retry 429 (honouring Retry-After), 5xx responses and connection/timeout errors. Use exponential backoff with jitter and a retry cap. Never retry 400, 401, 402 or 404 — the request itself is wrong or unauthorized. Rate-limited responses also carry X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.
import os
import random
import time
import requests
API = "https://api.example.com/v1"
API_KEY = os.environ["TOUCORA_API_KEY"]
RETRYABLE = {429, 500, 502, 503, 504}
def backoff(attempt):
# exponential backoff with full jitter, capped at 30s
return random.uniform(0, min(30, 2 ** attempt))
def retry_delay(resp, attempt):
# prefer the server's Retry-After header when present
retry_after = resp.headers.get("Retry-After")
if retry_after:
try:
return float(retry_after)
except ValueError:
pass
return backoff(attempt)
def call_api(method, path, max_retries=5, **kwargs):
headers = {"Authorization": f"Bearer {API_KEY}", **kwargs.pop("headers", {})}
for attempt in range(max_retries + 1):
try:
resp = requests.request(method, f"{API}{path}", headers=headers, timeout=60, **kwargs)
except (requests.ConnectionError, requests.Timeout):
if attempt == max_retries:
raise
delay = backoff(attempt)
else:
if resp.status_code not in RETRYABLE:
if resp.status_code >= 400:
error = resp.json().get("error", {})
raise RuntimeError(f'{resp.status_code} {error.get("code")}: {error.get("message")}')
return resp
if attempt == max_retries:
resp.raise_for_status()
delay = retry_delay(resp, attempt)
print(f"retry {attempt + 1}/{max_retries} in {delay:.1f}s")
time.sleep(delay)
transcript = call_api("GET", "/transcripts/dQw4w9WgXcQ", params={"lang": "en"}).json()
print(len(transcript["segments"]), "segments")
const API = "https://api.example.com/v1";
const API_KEY = process.env.TOUCORA_API_KEY ?? "YOUR_API_KEY";
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const backoff = (attempt) => Math.random() * Math.min(30_000, 2 ** attempt * 500);
async function callApi(path, { method = "GET", body, maxRetries = 5 } = {}) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
let res;
try {
res = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${API_KEY}`,
...(body ? { "Content-Type": "application/json" } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
} catch (err) {
if (attempt === maxRetries) throw err;
await sleep(backoff(attempt));
continue;
}
if (!RETRYABLE.has(res.status)) {
if (!res.ok) {
const { error } = await res.json().catch(() => ({ error: {} }));
throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
return res;
}
if (attempt === maxRetries) throw new Error(`gave up after ${maxRetries} retries (HTTP ${res.status})`);
const retryAfter = Number(res.headers.get("Retry-After"));
await sleep(Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
}
}
const res = await callApi("/transcripts/dQw4w9WgXcQ?lang=en");
const transcript = await res.json();
console.log(transcript.segments.length, "segments");
# curl retries connection failures and 5xx responses, and honours a
# Retry-After header automatically. --retry-all-errors also covers 429.
curl --retry 5 --retry-delay 1 --retry-connrefused --retry-all-errors \
--fail-with-body \
"https://api.example.com/v1/transcripts/dQw4w9WgXcQ?lang=en" \
-H "Authorization: Bearer YOUR_API_KEY"
# For full control, loop with exponential backoff in a shell script.
attempt=0
until curl -sS --fail-with-body "https://api.example.com/v1/summarize" \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"video_id":"dQw4w9WgXcQ","length":"short"}' -o response.json; do
attempt=$((attempt + 1))
[ "$attempt" -ge 5 ] && { echo "giving up after $attempt attempts"; exit 1; }
sleep $((2 ** attempt))
done
cat response.json
Want runnable files instead of browser snippets? The same examples live in the examples/ directory of the project repository, split by language. Official SDKs for JavaScript, Python and Go are on the roadmap after the API stabilizes.