AutomationFlowsSocial Media › Extract Transcripts From External Youtube Videos Using Youtube Transcript API

Extract Transcripts From External Youtube Videos Using Youtube Transcript API

ByJoel Cantero @joelcantero on n8n.io

Extracts a clean transcript from a videoId using youtube-transcript.io. AI summaries, sentiment analysis, keyword extraction Internal indexing/SEO Content pipelines (blog/newsletter) Batch transcript processing 📥 Input: , 🌐 API: POST to youtube-transcript.io 🧩 Parse: Normalizes…

Event trigger★★★★☆ complexity19 nodesHTTP RequestExecute Workflow TriggerStop And Error
Social Media Trigger: Event Nodes: 19 Complexity: ★★★★☆ Added:
Extract Transcripts From External Youtube Videos Using Youtube Transcript API — n8n workflow card showing HTTP Request, Execute Workflow Trigger, Stop And Error integration

This workflow corresponds to n8n.io template #11867 — we link there as the canonical source.

This workflow follows the Execute Workflow Trigger → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "L6ZTFOllD9qQVYlu",
  "name": "Transcript YouTube Video (Transcript API)",
  "tags": [],
  "nodes": [
    {
      "id": "fd8ce10b-25d3-455b-9121-2c5bf6192b1a",
      "name": "Set Variables",
      "type": "n8n-nodes-base.set",
      "position": [
        1248,
        736
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "a6f0f3c4-2d89-4c4e-9b43-1c8a0e22f001",
              "name": "youtubeVideoId",
              "type": "string",
              "value": "={{ $json.youtubeVideoId }}"
            },
            {
              "id": "3dc94d21-257f-430c-b087-95dad39bf829",
              "name": "apiToken",
              "type": "string",
              "value": "={{ $json.apiToken }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1b30c4f2-09a2-41b2-a681-b96c5fd1cae4",
      "name": "Get Transcript (YouTube Transcript API)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Calls youtube-transcript.io API. Response is set to Full Response + text, so we parse body safely in the next node.",
      "position": [
        1456,
        736
      ],
      "parameters": {
        "url": "https://www.youtube-transcript.io/api/transcripts",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true,
              "responseFormat": "text"
            }
          }
        },
        "jsonBody": "={\n  \"ids\": [\n    \"{{ $('Set Variables').item.json.youtubeVideoId }}\"\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Authorization",
              "value": "=Basic {{ $json.apiToken }}"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "ab4d562f-1c9e-4d88-bdd2-485e94dadc31",
      "name": "Parse API Response",
      "type": "n8n-nodes-base.code",
      "position": [
        1904,
        640
      ],
      "parameters": {
        "jsCode": "const videoId = $('Set Variables').item.json.youtubeVideoId;\n\n\n// n8n HTTP Request (fullResponse + text) usually returns: { statusCode, headers, body }\nconst raw = ($json && ($json.body ?? $json.data)) ?? null;\n\nlet parsed;\ntry {\n  parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;\n} catch (e) {\n  return [{ json: { videoId, status: 'error', error: 'Invalid JSON response from transcript API', source: 'youtube_transcript_api' } }];\n}\n\n// Support multiple possible shapes\nconst firstItem = Array.isArray(parsed)\n  ? parsed[0]\n  : (Array.isArray(parsed?.data) ? parsed.data[0] : parsed?.data?.[0]);\n\n// Common fields seen in transcript APIs: text / transcript / tracks[].transcript[]\nlet text = firstItem?.text ?? firstItem?.transcript ?? null;\n\nif (!text && firstItem?.tracks?.[0]?.transcript?.length) {\n  text = firstItem.tracks[0].transcript.map(t => t.text).join(' ');\n}\n\nconst language = firstItem?.language ?? firstItem?.lang ?? firstItem?.tracks?.[0]?.language\n\nreturn [{\n  json: {\n    videoId,\n    language,\n    text,\n    status: text ? 'found' : 'no_transcript',\n    source: 'youtube_transcript_api'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ba9d71c7-a461-46d5-9220-559e56868480",
      "name": "IF Has Transcript?",
      "type": "n8n-nodes-base.if",
      "position": [
        2128,
        640
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-transcript",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.text !== null && $json.text !== undefined && $json.text !== '' }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "ac4bf08b-396b-4b7d-bf0e-bf2e5e426a1d",
      "name": "Clean Transcript",
      "type": "n8n-nodes-base.code",
      "position": [
        2352,
        544
      ],
      "parameters": {
        "jsCode": "const videoId = $json.videoId;\nconst language = $json.language;\n\n\nlet text = $json.text;\n\n// Normalize to string\ntext = typeof text === 'string' ? text : JSON.stringify(text);\n\n// Basic cleaning (safe for most transcript sources)\ntext = text\n  .replace(/\\r?\\n/g, ' ')\n  .replace(/\\s+/g, ' ')\n  .replace(/\\[(music|m\u00fasica|musica)\\]/gi, '')\n  .trim();\n\nreturn [{\n  json: {\n    videoId,\n    language,\n    text,\n    wordCount: text ? text.split(' ').filter(Boolean).length : 0,\n    charCount: text ? text.length : 0,\n    status: 'success',\n    source: 'youtube_transcript_api'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "bbfe49d2-777c-4b3a-8aa9-acbe41414131",
      "name": "No Transcript Fallback",
      "type": "n8n-nodes-base.code",
      "position": [
        2352,
        736
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    videoId: $('Set Variables').item.json.youtubeVideoId,\n    language: $('Set Variables').item.json.preferredLanguage || 'es',\n    error: 'No transcript available for this video (or API returned empty).',\n    status: 'no_transcript',\n    suggestion: 'Try another language, or use a Whisper/ASR fallback.',\n    source: 'youtube_transcript_api'\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f2c51f08-d4a6-417c-bea6-e862dbc1457b",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        64,
        32
      ],
      "parameters": {
        "width": 760,
        "height": 1320,
        "content": "## \ud83d\ude80 Try It Out!\n\n### **YouTube Transcript API Extractor** *(Any Public Video)*\nExtracts a clean transcript from a **videoId** using **youtube-transcript.io**.\n\n---\n\n### \ud83c\udfaf **Use Cases**\n- AI summaries, sentiment analysis, keyword extraction\n- Internal indexing/SEO\n- Content pipelines (blog/newsletter)\n- Batch transcript processing\n\n---\n\n### \ud83d\udd04 **How It Works** *(5 Steps)*\n1. **\ud83d\udce5 Input**: `youtubeVideoId`, `apiToken`\n2. **\ud83c\udf10 API**: POST to youtube-transcript.io\n3. **\ud83e\udde9 Parse**: Normalizes the response format\n4. **\ud83e\uddf9 Clean**: Normalizes text and whitespace\n5. **\u2705 Output**: Transcript + metrics (`wordCount`/`charCount`)\n\n---\n\n### \ud83d\ude80 **How to Use**\nPayload:  \n`{\"youtubeVideoId\":\"xObjAdhDxBE\", \"apiToken\": \"xxxxxxxxxx\"}`\n\n\n**\u2699\ufe0f Setup**:\n- This sub-workflow is intended to be called from another workflow (Execute Workflow Trigger)\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a3052424-f30d-4e55-927e-c28bc032c336",
      "name": "Sticky Note - Input",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        880,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 360,
        "height": 420,
        "content": "## 1. Input Processing\n- Receives execution as a sub-workflow\n- Defines `youtubeVideoId` and `preferredLanguage`\n\nExample:\n{\n  \"youtubeVideoId\": \"xObjAdhDxBE\",\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "c4fd578a-b9af-4aab-bc36-1f2781804826",
      "name": "Sticky Note - API",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1376,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 280,
        "height": 420,
        "content": "## 2. Transcript API Request\n- Calls **youtube-transcript.io**\n- Does not require YouTube OAuth\n- Returns the response (Full Response + text)\n- The next node performs robust parsing"
      },
      "typeVersion": 1
    },
    {
      "id": "8700f14e-75cc-4382-b9e4-f4a23587f597",
      "name": "Sticky Note - Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2064,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 380,
        "content": "## 3. Parsing & Cleaning\n- Parses JSON even when the response arrives as a string (using safe parsing / try-catch) \n- Supports multiple response shapes (`text` / `transcript` / `tracks`)\n- Basic cleaning:\n  - `\\n` \u2192 space\n  - Collapses repeated whitespace\n  - Removes `[Music]` / `[M\u00fasica]`\n\nOutput: text ready for AI processing\n"
      },
      "typeVersion": 1
    },
    {
      "id": "1ebe6831-0b73-44f8-b7cf-6591109a293a",
      "name": "Sticky Note - Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 380,
        "content": "## \u2705 Final Output Format\n\nSuccess:\n{\n  \"videoId\": \"xObjAdhDxBE\",\n  \"language\": \"es\",\n  \"text\": \"Clean transcript text...\",\n  \"wordCount\": 1234,\n  \"charCount\": 5678,\n  \"status\": \"success\",\n  \"source\": \"youtube_transcript_api\"\n}"
      },
      "typeVersion": 1
    },
    {
      "id": "e22f8b25-b753-4507-97ba-ce8cd1e151a8",
      "name": "Sticky Note - Errors",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2720,
        736
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 200,
        "content": "## \u274c No Transcript\n\nWhen no transcript is available:\n- Returns a structured error (**No Transcript Fallback**)\n- Ends the execution with **Stop and Error**\n- Suggests an ASR/Whisper fallback\n"
      },
      "typeVersion": 1
    },
    {
      "id": "0f2675a3-3ef7-4135-93f7-3dc1a2406f0b",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        1680,
        736
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "OK",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "52d1c4eb-8389-41c7-b325-d2ffc7cbc0af",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 200
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "=Not enough credits",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "6aa821f2-f09a-46d2-baf2-358582ebd983",
                    "operator": {
                      "type": "number",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": 402
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "Generic Error",
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "96428dda-42b0-4a4a-8a17-f32b54738fe0",
                    "operator": {
                      "type": "number",
                      "operation": "notEmpty",
                      "singleValue": true
                    },
                    "leftValue": "={{ $json.statusCode }}",
                    "rightValue": ""
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.3
    },
    {
      "id": "94b03a2f-6b1d-4a98-9a49-53060135dad4",
      "name": "When Executed by Another Workflow",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "position": [
        1008,
        736
      ],
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "youtubeVideoId"
            },
            {
              "name": "apiToken"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "40f8b73d-fb7c-46ed-bd46-b9cca70cfca1",
      "name": "No Transcript",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        2576,
        736
      ],
      "parameters": {
        "errorMessage": "There's no transcript for this YouTube video (Transcript API)."
      },
      "typeVersion": 1
    },
    {
      "id": "366b70e0-b2c3-4857-92d5-f2ad844b46d7",
      "name": "Not Enough Credits",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        1904,
        880
      ],
      "parameters": {
        "errorMessage": "Not enough credits to fetch these transcripts"
      },
      "typeVersion": 1
    },
    {
      "id": "ebdb722e-17bc-40c8-82d6-6d73802b142c",
      "name": "Sticky Note - Errors1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        864
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 184,
        "content": "## \u274c Not Enough Credits\n\nWhen no credits is available:\n- Returns a structured error\n- Ends the execution with **Stop and Error**\n"
      },
      "typeVersion": 1
    },
    {
      "id": "96510955-1afc-4456-aa7d-3a80e9c60fb1",
      "name": "Generic Error",
      "type": "n8n-nodes-base.stopAndError",
      "position": [
        1904,
        1104
      ],
      "parameters": {
        "errorMessage": "Generic Error"
      },
      "typeVersion": 1
    },
    {
      "id": "07c4168c-2053-4c24-8359-c38ef676cd01",
      "name": "Sticky Note - Errors2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2032,
        1072
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 184,
        "content": "## \u274c Generic Error\n\nOther generic error\n- Ends the execution with **Stop and Error**\n"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "errorWorkflow": "I8euEHwLoUMOskgW",
    "timeSavedMode": "fixed",
    "availableInMCP": false,
    "executionOrder": "v1",
    "saveExecutionProgress": true,
    "timeSavedPerExecution": 10
  },
  "versionId": "67a1be7b-4dd8-44ba-9f05-bcd12ff30b07",
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Parse API Response",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Not Enough Credits",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generic Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Variables": {
      "main": [
        [
          {
            "node": "Get Transcript (YouTube Transcript API)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Has Transcript?": {
      "main": [
        [
          {
            "node": "Clean Transcript",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Transcript Fallback",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse API Response": {
      "main": [
        [
          {
            "node": "IF Has Transcript?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "No Transcript Fallback": {
      "main": [
        [
          {
            "node": "No Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Executed by Another Workflow": {
      "main": [
        [
          {
            "node": "Set Variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Transcript (YouTube Transcript API)": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Extracts a clean transcript from a videoId using youtube-transcript.io. AI summaries, sentiment analysis, keyword extraction Internal indexing/SEO Content pipelines (blog/newsletter) Batch transcript processing 📥 Input: , 🌐 API: POST to youtube-transcript.io 🧩 Parse: Normalizes…

Source: https://n8n.io/workflows/11867/ — original creator credit. Request a take-down →

More Social Media workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Social Media

YouTube Caption Extractor (Your Channel Only) Extracts clean transcripts from YOUR CHANNEL YouTube video captions using YouTube Data API v3.

Execute Workflow Trigger, HTTP Request, Stop And Error
Social Media

Extracts the auto-generated transcript from any YouTube video automatically. 📥 Input: only 🌐 Fetch: GET YouTube page HTML to extract INNERTUBEAPIKEY 🔑 Extract: Parse API key and video ID 🎥 Metadata: G

Execute Workflow Trigger, HTTP Request, XML +1
Social Media

Video explanation

HTTP Request, Execute Workflow Trigger, Postgres +1
Social Media

Wait Dropbox. Uses manualTrigger, httpRequest, executeWorkflowTrigger, stickyNote. Event-driven trigger; 20 nodes.

HTTP Request, Execute Workflow Trigger, Dropbox
Social Media

Imagine you want to automate a task where, based on a TikTok video link, you must retrieve the username of the creator of that video.

HTTP Request, Form Trigger, Stop And Error +1