Transcripts & Insights
Overview
When Transcodely generates captions for a video (the generate subtitle operation, see AI Captions), it also writes the transcript next to the caption file in three formats:
json: paragraphs, sentences and words with millisecond timings and per-word confidence. Built for code.txt: plain paragraphs, no timestamps. Built for search indexes, CMS fields and copy-paste.md: Markdown with a timestamp link on every paragraph. Built for show notes, docs sites and LLM context.
The transcript is part of the captions feature: it costs nothing on top of the captions fee, and you do not have to ask for it.
On top of the transcript you can opt in to insights: one AI pass over the transcript that returns title suggestions, a summary, key points, chapters, the questions the content answers, key terms, verbatim highlights and topics. Insights work for any kind of spoken content: courses, talks, interviews, product demos, podcasts, entertainment.
What you get
A generate track on output out_a1b2c3d4e5f6g7 that transcribes English produces these files in the job’s output destination:
| File | What it is |
|---|---|
{job_id}/subtitles/{output_id}_eng.vtt | The caption file (WebVTT by default, or SRT). |
{job_id}/subtitles/{output_id}_eng.transcript.json | The transcript, machine-readable. |
{job_id}/subtitles/{output_id}_eng.transcript.txt | The transcript as plain text. |
{job_id}/subtitles/{output_id}_eng.transcript.md | The transcript as Markdown. |
For a hosted video the files live under the video’s own storage prefix instead of {job_id}/subtitles/, next to its caption track.
With insights enabled, three more files land in the same folder once the insights are ready:
| File | What it is |
|---|---|
{output_id}_eng.insights.json | Every insights section, machine-readable. |
{output_id}_eng.insights.md | Every insights section as one Markdown document. |
{output_id}_eng.chapters.vtt | The chapters as a WebVTT chapters track. |
Turn it on
There is nothing extra to turn on for transcripts: every generate track produces them. To choose the style of the txt and md renditions, or to request insights, add the transcript and insights options to the track. This request captions an already-hosted video, writes a verbatim transcript and asks for insights:
{
"input_video_id": "vid_a1b2c3d4e5f6g7",
"outputs": [
{
"subtitle_tracks": [
{
"operation": "generate",
"transcript": { "style": "literal" },
"insights": {
"enabled": true,
"hint": "Beginner cooking class; friendly tone"
}
}
]
}
]
}| Option | Type | Description |
|---|---|---|
transcript.style | enum | How the txt and md renditions are written. One of: clean (default), literal. The json rendition always carries every word. |
insights.enabled | boolean | Run the insights pass after the job completes. Billed as described in Pricing. |
insights.hint | string | Optional guidance about the content, its audience or the tone you want, at most 300 characters. It steers the writing. It cannot change which sections you get or their format. |
Both options are valid only on a generate track. They are rejected on any other operation.
Styles: clean and literal
People do not speak in finished sentences. The clean style is deliberately conservative. It removes only two things:
- Hesitation sounds that are not words in the language, such as “um”, “uh”, “er” and “hmm” in English.
- Stutters on short function words, such as “the the” or “I I”: an immediate exact repeat of a word like “the”, “a”, “and”, “to” or “I”, when nothing separates the two copies.
Content words are never removed, even when repeated: “very, very” and “no, no, no” stay as spoken. A removed word never takes the end of a sentence with it: its full stop, question mark or exclamation mark moves to the word before. Clean never changes, reorders or adds a word, so a clean transcript is the literal transcript with some words taken out. The literal style keeps every recognized word.
The same paragraph in both styles:
- So um the the first thing you want to do is, uh, heat the pan before you add the oil.
+ So the first thing you want to do is, heat the pan before you add the oil.The lists of hesitation sounds and function words exist for English, German, Spanish, French and Bulgarian. For every other language nothing is removed, so clean and literal read the same.
In the json rendition, text and the paragraph and sentence text follow the style you chose. The words arrays are always verbatim, with the removed words flagged "filler": true, so you can render either style yourself.
The Markdown rendition
The Markdown file starts with YAML front matter, then one block per paragraph. Each block opens with a timestamp that links to #t=<seconds>:
---
transcodely_job: job_…
transcodely_output: out_…
language: eng
style: clean
duration_seconds: 1254.3
generated_at: 2026-10-24T10:00:00Z
---
# Transcript
**[00:00](#t=0)** First paragraph text…
**[01:35](#t=95)** Next paragraph…Timestamps read MM:SS under one hour and H:MM:SS from one hour on. The link target is always whole seconds. The heading is always the word “Transcript”, because the transcript is written before anyone knows the video’s title. The insights Markdown file uses the video title instead.
To make the timestamp links work on your own page, map #t=95 to a seek. See Deep links.
The plain-text rendition
The txt file holds the paragraphs separated by one blank line. It has no timestamps and no front matter. It is UTF-8 with LF line endings and ends with a newline.
The JSON rendition
The json file is version 2 of the transcript schema. Times are in milliseconds:
{
"version": 2,
"language": "eng",
"auto_generated": true,
"duration_ms": 1254300,
"style": "clean",
"text": "So the first thing you want to do is, heat the pan before you add the oil.\n\nNext paragraph…",
"speakers": [],
"paragraphs": [
{
"id": "p1",
"start_ms": 0,
"end_ms": 9500,
"text": "So the first thing you want to do is, heat the pan before you add the oil.",
"sentences": [
{
"start_ms": 0,
"end_ms": 4200,
"text": "So the first thing you want to do is, heat the pan before you add the oil.",
"words": [
{ "text": "So", "start_ms": 0, "end_ms": 180, "confidence": 0.97, "filler": false },
{ "text": "um", "start_ms": 180, "end_ms": 420, "confidence": 0.88, "filler": true }
]
}
]
}
],
"segments": [
{ "start_ms": 0, "end_ms": 4200, "text": "So um the the first thing you want to do is, uh, heat the pan before you add the oil.", "confidence": 0.97 }
]
}| Field | Description |
|---|---|
version | Always 2 for this shape. |
style | The style used for text and every paragraph and sentence text. Words always keep their verbatim text. |
text | The whole transcript in that style, paragraphs separated by a blank line. |
speakers | Reserved. Always empty: speakers are not identified. |
paragraphs[].sentences[].words[] | Every recognized word with its timing. confidence is omitted when the recognizer reports none. filler marks the words the clean style removes. |
segments | The version 1 caption segments, kept unchanged for existing consumers. |
A new paragraph starts only at the end of a sentence, and only when the speaker pauses for at least 0.8 seconds or the paragraph has reached 80 words or 40 seconds. The same audio always produces the same file, byte for byte.
Fetching a transcript
Transcripts are listed in two places, each with one entry per format:
- On the job:
subtitle_results[].transcript_renditions. - On a hosted video:
text_tracks[].transcript_renditions.
Each entry carries format (json, txt or md), storage_key, url and, for Transcodely-managed storage, a signed_url. The signed URL is time-limited and re-issued on every read, so fetch the video or job again rather than storing it. It follows the same rules as every other signed asset URL. In your own bucket, read the file by its storage_key with your own credentials.
| Attribute | Type | Description |
|---|---|---|
format | enum | One of: json, txt, md. |
storage_key | string | Key of the file in the output destination. |
url | string | Fully-qualified storage URL of the file. |
signed_url | string | null | Ready-to-fetch HTTPS URL, signed and time-limited. Managed storage only. |
Download the Markdown transcript of a hosted video:
curl -s -X POST https://api.transcodely.com/transcodely.v1.VideoService/Get
-H "Authorization: Bearer ak_abc123"
-H "Content-Type: application/json"
-d '{"id": "vid_a1b2c3d4e5f6g7"}'
| jq -r 'first(.video.text_tracks[].transcript_renditions[] | select(.format == "md") | .signed_url // empty)'
| xargs curl -s -o transcript.mdIn the dashboard, the Captions card on a video page has a download button for each format.
Jobs that completed before transcript renditions existed list no renditions. Their subtitle_results[].transcript_url still points at the older segment-level JSON.
Insights
What is in it
| Section | What it is |
|---|---|
title_suggestions | One to three suggested titles, each at most 80 characters. |
summary | A summary of the content, at most four sentences. |
key_points | Three to seven key points. |
chapters | Chapters ordered by start time and covering the whole video, at least two for anything three minutes or longer. Also written as chapters.vtt. |
questions | Up to six questions the content answers, each with an answer of at most two sentences and the time the answer starts. |
terms | Up to ten key terms, each with the definition the content gives and the time it is introduced. |
highlights | Quotable moments: verbatim quotes with the start and end time of their paragraph. |
topics | Up to ten short topic labels. |
Grounding rules
Every result is checked before it is saved. These checks are enforced, and a result that fails them is rejected:
- Highlights are quotes, never paraphrases. Each highlight must appear word for word in the transcript.
- Times come from the transcript. Every timestamp is the start of the transcript paragraph the item comes from. Chapters must be in order and inside the video’s duration.
- Sizes are bounded. Each section must have the number of items listed above, titles at most 80 characters, a summary of at most four sentences and answers of at most two.
- No links. No section may contain a URL.
If the result breaks a check, the pass retries once with the problems listed. If it still fails, the insights move to failed with insights_invalid and you are not charged.
Two more rules are instructions to the model rather than checks, so they are followed closely but not verified:
- Answers and definitions come from the content. They should say what the video says, not what the model knows.
- Same language. Insights are written in the transcript’s language unless your hint asks for another.
Async: watch for it or get a webhook
Insights run after the job completes, so they arrive a little after the captions. From the moment the job is created, Job.insights lists one entry per track that asked for them, with status pending. It then moves to ready or failed. Hosted videos carry the same entries on Video.insights.
Two webhook events tell you when that happens, both carrying a job snapshot like the other job.* events:
| Event | When |
|---|---|
job.insights.ready | The insights for one of the job’s tracks are ready. |
job.insights.failed | The insights for one of the job’s tracks could not be produced. |
A ready entry on the job:
{
"output_id": "out_a1b2c3d4e5f6g7",
"language": "eng",
"status": "ready",
"title_suggestions": ["How to Sear a Steak", "Searing Steak at Home: The Basics"],
"summary": "A beginner walkthrough of searing steak in a home kitchen. It covers heating the pan, choosing an oil and resting the meat.",
"key_points": [
"Heat the pan before adding oil.",
"Use an oil with a high smoke point.",
"Rest the steak for five minutes before cutting."
],
"chapters": [
{ "start_seconds": 0, "end_seconds": 95, "title": "Heating the pan" },
{ "start_seconds": 95, "end_seconds": 410, "title": "Searing" },
{ "start_seconds": 410, "end_seconds": 512.4, "title": "Resting and serving" }
],
"questions": [
{ "question": "Why heat the pan before the oil?", "answer": "Oil added to a cold pan breaks down before the pan is hot enough to sear.", "start_seconds": 12 }
],
"terms": [
{ "term": "Smoke point", "definition": "The temperature at which an oil starts to burn.", "start_seconds": 131 }
],
"highlights": [
{ "text": "heat the pan before you add the oil", "start_seconds": 0, "end_seconds": 9.5 }
],
"topics": ["cooking", "steak", "searing"],
"storage_key": "job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.insights.json",
"url": "gs://my-bucket/job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.insights.json",
"chapters_storage_key": "job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.chapters.vtt",
"chapters_url": "gs://my-bucket/job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.chapters.vtt",
"markdown_storage_key": "job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.insights.md",
"markdown_url": "gs://my-bucket/job_a1b2c3d4e5f6/subtitles/out_a1b2c3d4e5f6g7_eng.insights.md",
"generated_at": "2026-10-24T10:04:12Z",
"hint": "Beginner cooking class; friendly tone"
}On managed storage each file also has a signed URL: signed_url, chapters_signed_url and markdown_signed_url.
When insights fail
A failed entry carries an error_code, and no insights fee is charged:
error_code | Meaning |
|---|---|
insights_invalid | No result passed the grounding rules, even after a retry. |
insights_unsupported_origin | The API cannot write to the output destination, for example an http origin. |
insights_too_long | The transcript is over the size limit. |
insights_transcript_unavailable | The transcript could not be read. |
insights_refused | The content was declined. |
insights_unavailable | The service was unavailable. |
Chapters on hosted videos
For a hosted video, chapters.vtt is attached to the video as a text track of kind chapters. It is not listed in the streaming manifest. GetSeoMarkup turns the chapters into key moments for search engines.
The insights Markdown file
insights.md puts every section in one document under the video’s title, with ## Summary, ## Key points, ## Chapters (each linked as [MM:SS](#t=S)), ## Questions this covers, ## Key terms, ## Highlights and ## Topics.
Deployments without insights
If insights are not enabled on the deployment you call, a job that sets insights.enabled is rejected at creation with parameter_unsupported. You are never charged for insights that cannot run.
Pricing
| What | Price |
|---|---|
Transcripts (json, txt, md) | Included in the AI captions fee of €0.05 per source minute. |
| Insights | €0.02 per video + €0.005 per source minute. |
Insights appear on the job as two fees rows that are confirmed or voided together: insights_job (unit job, €0.02) and insights (unit source_minute, €0.005). The dashboard shows them as one Insights line. They are estimated when the job is created, confirmed when the insights are ready, and voided if they fail.
A 10-minute video with captions and insights costs €0.50 for captions and €0.07 for insights, on top of the encode and the processing fee. See the pricing page for the full rate card.
Privacy
Captions are transcribed on Transcodely’s own worker fleet. The audio is never sent to a third party.
Insights are different. To produce them, the transcript text is sent to Anthropic, our AI subprocessor. The audio and video are never sent. Anthropic does not use it to train models. Insights run only for the tracks where you set insights.enabled. Anthropic is listed on the Subprocessors page.
Related
- AI Captions: generating the caption track the transcript comes from.
- Video SEO: turning a transcript and its insights into search-ready pages.
- Subtitles reference: every field on the wire.
- Webhooks: the full event catalog.