You have a YouTube link and you want what's in it as data: the transcript with timestamps, a short summary, maybe subtitles. The usual ways of getting that from code tend to break:
- The official YouTube Data API lets you download caption tracks only with the authorization of the channel that owns the video. For anyone else's video it does not help.
- Scraping the caption endpoints or running a downloader works on your laptop and then fails in production. YouTube blocks most cloud and datacenter IP ranges and increasingly asks for sign-in or proof-of-origin tokens.
- Captions are not the video. Many videos have no captions, auto-captions mangle names and jargon, and a transcript misses everything shown but not said: slides, code on screen, product demos.
This guide takes a different route. You send the link, and a multimodal model watches and listens to the video in one pass. You get back a timestamped transcript, a summary, a timestamped description of what is on screen, and subtitle files.
How it works
The Video API hands the YouTube URL to its model provider, which fetches the video on its own side. OpenCorpo never downloads YouTube videos. The model processes audio and frames together and returns four possible outputs:
| Output | What you get |
|---|---|
summary |
One paragraph about the whole video |
transcript |
Speech, as timestamped segments with speaker labels |
description |
What is on screen, as timestamped segments |
prompt |
A prompt to recreate a similar video with an AI generator (separate guide) |
You can request any combination. The price is the same: a video is billed once per started minute, whatever you ask for.
Step 1: Create an API key
Create an account at app.opencorpo.com and create a key under API keys. Your first 3 video analyses are free, each covering up to the first 5 minutes of the video.
export OPENCORPO_API_KEY=sk_live_...Step 2: Start an analysis
For a short video, the simplest call holds the request open until the job finishes (up to 90 seconds) with "wait": true:
curl -X POST https://api.opencorpo.com/v1/video/analyze \
-H "X-API-Key: $OPENCORPO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c1e9f40-talk-summary" \
-d '{
"source": {"type": "youtube", "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw"},
"outputs": ["summary", "transcript"],
"wait": true
}'The Idempotency-Key header makes retries safe: sending the same key again returns the same job instead of creating and billing a second one.
You get 200 with the finished job, or 202 if it is still running:
{
"id": "vj_4e7ce795b4a68215a7ac69db",
"status": "completed",
"source": { "type": "youtube", "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw" },
"requested_outputs": ["summary", "transcript"],
"model": "balanced",
"duration": "00:19",
"truncated": false,
"billed_minutes": 1,
"charged_eur": "0.050000",
"results": {
"summary": "A young man stands in front of an elephant enclosure at a zoo ...",
"transcript": [
{ "start": "00:01", "end": "00:04", "start_seconds": 1.0, "end_seconds": 4.0,
"speaker": "Speaker 1", "text": "Alright, so here we are in front of the, uh, elephants." }
],
"transcript_language": "en"
}
}Each segment has both a display timestamp ("00:01") and seconds (1.0), so you can link straight into the video with &t=1s or align the text with your own player.
Step 3: Wait for the job
Anything longer than a short clip takes more than 90 seconds. Start the job without wait, then long-poll GET /v1/video/jobs/{id}?wait=60, which returns as soon as the job finishes or after 60 seconds. No sleeping or tight polling loops needed.
Python
import os
import uuid
import requests
API = "https://api.opencorpo.com"
HEADERS = {"X-API-Key": os.environ["OPENCORPO_API_KEY"]}
def check(r):
if not r.ok:
err = r.json()["error"]
raise RuntimeError(f"{err['code']}: {err['message']}")
return r.json()
def analyze_youtube(url, outputs=("summary", "transcript"), idempotency_key=None):
# Reuse the same key if you retry this exact request after a network error.
key = idempotency_key or str(uuid.uuid4())
job = check(requests.post(
f"{API}/v1/video/analyze",
headers={**HEADERS, "Idempotency-Key": key},
json={"source": {"type": "youtube", "url": url}, "outputs": list(outputs)},
timeout=30,
))
while job["status"] in ("queued", "processing"):
job = check(requests.get(
f"{API}/v1/video/jobs/{job['id']}",
headers=HEADERS, params={"wait": 60}, timeout=100,
))
if job["status"] != "completed":
raise RuntimeError(f"job {job['id']} {job['status']}: {job.get('error')}")
return job
job = analyze_youtube("https://www.youtube.com/watch?v=jNQXAC9IVRw")
print(job["results"]["summary"])
for seg in job["results"]["transcript"]:
print(f"[{seg['start']}] {seg['speaker']}: {seg['text']}")Node.js (20+)
const API = "https://api.opencorpo.com";
const headers = { "X-API-Key": process.env.OPENCORPO_API_KEY };
async function check(res) {
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
return body;
}
export async function analyzeYoutube(url, outputs = ["summary", "transcript"]) {
let job = await check(await fetch(`${API}/v1/video/analyze`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ source: { type: "youtube", url }, outputs }),
}));
while (job.status === "queued" || job.status === "processing") {
job = await check(await fetch(`${API}/v1/video/jobs/${job.id}?wait=60`, { headers }));
}
if (job.status !== "completed") throw new Error(`job ${job.id} ${job.status}`);
return job;
}
const job = await analyzeYoutube("https://www.youtube.com/watch?v=jNQXAC9IVRw");
console.log(job.results.summary);A failed job carries an error object instead of results, for example youtube_private or youtube_unavailable, and is not charged.
Step 4: Export SRT or VTT subtitles
Once a job is completed, export its transcript as a subtitle file:
curl -o talk.srt \
"https://api.opencorpo.com/v1/video/jobs/vj_4e7ce795b4a68215a7ac69db/export?format=srt&source=transcript" \
-H "X-API-Key: $OPENCORPO_API_KEY"format can be srt, vtt, txt or json. Set source=description to export the on-screen description with the same timing. As a VTT track, that can be a starting point for an audio-description or "what's on screen" track, to review before use.
Analysing your own video files
The same endpoint works for files you own. Get a presigned upload URL, PUT the bytes straight to storage, then pass the upload_id. Files up to 500 MB go through this route and are deleted automatically after 7 days.
# 1. Ask for an upload URL
curl -X POST https://api.opencorpo.com/v1/uploads \
-H "X-API-Key: $OPENCORPO_API_KEY" -H "Content-Type: application/json" \
-d '{"filename": "keynote.mp4", "content_type": "video/mp4", "size_bytes": 84213555}'
# 2. Upload the file to the returned URL
curl -X PUT "$UPLOAD_URL" -H "Content-Type: video/mp4" --data-binary @keynote.mp4
# 3. Analyse it
curl -X POST https://api.opencorpo.com/v1/video/analyze \
-H "X-API-Key: $OPENCORPO_API_KEY" -H "Content-Type: application/json" \
-d '{"source": {"type": "upload", "upload_id": "up_..."}, "outputs": ["summary", "transcript", "description"]}'Options worth knowing
| Option | Values | When to use it |
|---|---|---|
options.model |
balanced (default), fast, quality |
quality costs 3× and helps with hard audio or dense scenes |
options.detail |
standard (default), high |
high costs 2×; use it for slides, screen recordings and text-heavy video |
options.language |
e.g. fr, de |
A hint for the spoken language; default is detection |
options.fps |
above 0, at most 2 | Frames analysed per second; the default is chosen for the outputs you request |
What it costs
Analysis is billed per started minute of analysed video, once per video: €0.05 / minute on the default model. A 12-minute talk is billed as 12 minutes whether you ask for one output or all four. Before a job starts, the cost of the longest analysis your balance allows is reserved, and only the actual length is charged. If your balance covers less than the whole video, the analysis stops there and the job is marked truncated. Live prices are on the pricing page.
Limits to plan for
- Public YouTube videos only. Private and sign-in-only videos fail with
youtube_private; removed or region-locked ones withyoutube_unavailable. Upload the file instead if you own it. - 20 minutes per video. Longer videos are analysed up to 20 minutes and marked
truncated. - 3 video jobs at a time per account; further jobs get
429 concurrency_limit. - No translation. Transcripts are written in the language spoken.
- Speaker labels are generic ("Speaker 1", "Speaker 2"), not names.
- Second-level timestamps. Fine for navigation and subtitles, not for frame-accurate editing.
- Model output. Names, numbers and jargon can be misheard. Review before you publish a transcript or quote from it.
- Your responsibility for the content. Make sure you have the right to process and reuse the videos you analyse.
Full request and response reference: Video API docs.
Frequently asked questions
Does it work for YouTube videos without captions?
Yes. The model listens to the audio and watches the frames itself; it does not read YouTube's caption tracks, so videos without captions work the same way.
Can I get SRT or VTT subtitles?
Yes. Once a job is completed, GET /v1/video/jobs/{id}/export?format=srt (or vtt, txt, json) returns the transcript as a subtitle file. The timestamped description can be exported the same way.
Which languages are supported?
The transcript is written in the language spoken in the video. You can pass a hint such as options.language = fr; transcripts are never translated.
What happens with videos longer than 20 minutes?
Up to 20 minutes are analysed per video. For a longer video, the first 20 minutes are analysed and the job is marked truncated.
Do private or unlisted videos work?
Use public videos. A private or sign-in-only video fails with youtube_private, and a removed or region-locked one with youtube_unavailable. Failed jobs are not charged. For your own private footage, upload the file instead.
Do I pay more for more outputs?
No. A video is billed once per started minute (€0.05 / minute on the default model), whatever combination of summary, description, transcript and prompt you request.