← All guides

How to get a YouTube transcript into Notion

Written by the VidWords Team · · Updated · Report a correction

For one video, copy the transcript from VidWords with "Timestamps" or "Text only" selected and paste it into a Notion page. For many videos, a short script can call the VidWords API and create one Notion page per video through Notion's API. VidWords has no built-in Notion integration. It gives you the transcript as copied text or as a TXT, SRT, VTT, CSV or JSON file, and Notion takes it from there. A script has to respect two Notion limits: 2,000 characters per text object, and 100 blocks per request.

One video: copy and paste

  1. Open https://vidwords.com/?v=VIDEO_ID, or paste the link into vidwords.com.
  2. Next to Copy, choose Timestamps or Text only, then click Copy.
  3. Paste into a Notion page.

What lands in Notion depends on that choice. In the default Paragraphs layout, VidWords merges YouTube's short caption lines into paragraphs. It starts a new paragraph at each chapter, or once a paragraph has run for about 25 seconds of video. With Timestamps, each paragraph starts with a single [m:ss]; with Text only, the timestamps go and the paragraph breaks stay. If the video has chapters, each one is preceded by a ## Chapter title line, and Notion turns those into headings when you paste. Switch the layout to Lines if you want one line per caption instead.

Copying from YouTube's own transcript panel gives you a timestamp and a short fragment of text on alternating lines every few seconds, which is the reason to go through a tool at all.

One video: import a file

If you would rather keep a file, download the .txt (with timestamps) or .txt, text only version and bring it in with Notion's Import option, choosing Text & Markdown. Notion creates a page from the file.

The CSV export is a different shape: one row per caption line, with start, duration and text columns. Imported into Notion, that becomes a database with a row for every few seconds of speech. That is useful for searching quotes across a transcript, but it is not a page of notes. SRT and VTT are subtitle formats for video players and have no use in Notion.

A video library in Notion

If you save videos regularly, a database keeps them sortable. A layout that works well:

Inside each video's page, put your own notes at the top and the transcript underneath, inside a toggle (type > and a space at the start of a line). Notes stay readable and the full text is one click away when you need an exact quote. A link of the form https://www.youtube.com/watch?v=VIDEO_ID&t=125s opens the video at 2:05, so a timestamp in your notes can point straight at the moment.

The script below creates pages in exactly this database, using the first three properties.

Many videos: VidWords API to Notion API

This script fetches transcripts from VidWords and creates one Notion page per video. Each paragraph starts with a timestamp that links to that second of the video. Setup takes four steps:

  1. Create an API token on VidWords' API & MCP page.
  2. Create an internal integration in Notion's integration settings and copy its token.
  3. Connect the integration to your database from the database's ••• menu, under Connections. Without this step, Notion answers object_not_found.
  4. Copy the database's data source ID: open the database settings, go to Manage data sources, and choose Copy data source ID.
import os
import time
import requests

VIDWORDS_TOKEN = os.environ["VIDWORDS_TOKEN"]
NOTION_TOKEN = os.environ["NOTION_TOKEN"]
DATA_SOURCE_ID = os.environ["NOTION_DATA_SOURCE_ID"]

NOTION = "https://api.notion.com/v1"
NOTION_HEADERS = {
    "Authorization": f"Bearer {NOTION_TOKEN}",
    "Notion-Version": "2026-03-11",
    "Content-Type": "application/json",
}
TEXT_LIMIT = 1900      # Notion allows 2,000 characters per text object
BLOCKS_PER_CALL = 100  # and 100 blocks per request


def fetch_transcripts(ids):
    r = requests.post(
        "https://vidwords.com/api/transcripts",
        headers={"Authorization": f"Basic {VIDWORDS_TOKEN}"},
        json={"ids": ids},          # up to 50 ids or URLs per call
        timeout=300,
    )
    r.raise_for_status()
    return r.json()["results"]


def clock(seconds):
    s = int(seconds)
    h, m, s = s // 3600, s % 3600 // 60, s % 60
    return f"{h}:{m:02d}:{s:02d}" if h else f"{m}:{s:02d}"


def blocks_for(video_id, segments, every=25):
    """One paragraph per ~25 seconds of speech, each opening with a timestamp link."""
    groups = []
    for seg in segments:
        if not groups or seg["start"] - groups[-1][0] >= every:
            groups.append([seg["start"], seg["text"]])
        else:
            groups[-1][1] += " " + seg["text"]
    blocks = []
    for start, text in groups:
        link = f"https://www.youtube.com/watch?v={video_id}&t={int(start)}s"
        for i in range(0, len(text), TEXT_LIMIT):
            rich = [{"type": "text", "text": {"content": text[i:i + TEXT_LIMIT]}}]
            if i == 0:
                stamp = {"type": "text", "text": {"content": f"[{clock(start)}] ", "link": {"url": link}}}
                rich.insert(0, stamp)
            blocks.append({"object": "block", "type": "paragraph", "paragraph": {"rich_text": rich}})
    return blocks


def notion(method, path, body):
    for _ in range(3):
        r = requests.request(method, f"{NOTION}{path}", headers=NOTION_HEADERS, json=body, timeout=60)
        if r.status_code != 429:
            break
        time.sleep(float(r.headers.get("Retry-After", "1")))   # rate limited: wait and retry
    if not r.ok:
        raise RuntimeError(f"Notion {r.status_code}: {r.text}")
    time.sleep(0.35)        # stay under Notion's average of 3 requests a second
    return r.json()


def save(item):
    vid = item["id"]
    blocks = blocks_for(vid, item["segments"])
    properties = {
        "Name": {"title": [{"text": {"content": (item.get("title") or vid)[:TEXT_LIMIT]}}]},
        "URL": {"url": f"https://www.youtube.com/watch?v={vid}"},
    }
    if item.get("author"):
        properties["Channel"] = {"rich_text": [{"text": {"content": item["author"][:TEXT_LIMIT]}}]}
    page = notion("POST", "/pages", {
        "parent": {"type": "data_source_id", "data_source_id": DATA_SOURCE_ID},
        "properties": properties,
        "children": blocks[:BLOCKS_PER_CALL],
    })
    for i in range(BLOCKS_PER_CALL, len(blocks), BLOCKS_PER_CALL):
        notion("PATCH", f"/blocks/{page['id']}/children", {"children": blocks[i:i + BLOCKS_PER_CALL]})
    return page["url"]


for item in fetch_transcripts(["dQw4w9WgXcQ", "https://youtu.be/jNQXAC9IVRw"]):
    if item.get("error"):
        print(item["id"], "skipped:", item["error"], item.get("message", ""))
        continue
    print(item["id"], "->", save(item))

Why it is built this way:

These limits come from Notion's request-limits and append-block-children reference pages, checked on October 9, 2026.

Troubleshooting

Notion answersCauseFix
404 object_not_foundThe integration is not connected to the database, or the ID is wrongDatabase ••• menu, then Connections; re-copy the data source ID
400 validation_error mentioning a length of 2000A text object is too longSplit text below 2,000 characters
400 validation_error about the number of childrenMore than 100 blocks in one requestSend the rest with append calls
400 validation_error naming a propertyThe database has no property by that name or typeMatch Name, URL and Channel exactly, or edit the script
429 rate_limitedToo many requestsWait for Retry-After; keep the pause between calls
401 unauthorizedWrong or revoked Notion tokenCopy the integration token again

On the VidWords side, a video that cannot be read comes back as an item with an error code, and the script skips it. 403 email_unverified means the account's email address has not been confirmed yet.

No-code automation

n8n and Make can run the same flow without code: an HTTP step calls POST https://vidwords.com/api/transcripts, and a Notion step creates the page. The same Notion limits apply, so a transcript longer than 2,000 characters cannot go into a single text property or block. Split it in a code step first, or store the transcript as page content rather than as a property. The integrations guide covers the VidWords half, including error handling and batching.

What VidWords costs for this

Extract a transcript for Notion now →