{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "a4bb454b-5d7e-49c5-8af3-bf71b1a4aa61",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -384
      ],
      "parameters": {
        "width": 2800,
        "height": 224,
        "content": "### Requirements\n- YouTube Data API (OAuth2)\n- SupaData API Key\n- Airtable Token (if using base for deduplication)\n- RSS Feed from your YouTube Channel"
      },
      "typeVersion": 1
    },
    {
      "id": "979d6059-d909-4baf-a49a-1130fd58ad0a",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -1280
      ],
      "parameters": {
        "color": 3,
        "width": 2800,
        "height": 880,
        "content": "## YouTube Chapter Auto-Comment (RSS to YouTube)\n\nAutomatically posts chapter timestamps as a comment on your latest YouTube video \u2014 using your RSS feed and SupaData transcript API.\n\n---\n\n### Who it's for  \nCreators who want every video to have structured chapters \u2014 without writing them manually.\n\n---\n\n### What it does  \n- Watches your YouTube RSS feed  \n- Gets the latest video ID  \n- Skips videos already posted (Airtable check)  \n- Fetches transcript via SupaData  \n- Uses OpenAI to generate chapter timestamps  \n- Posts the chapters as a YouTube comment\n\n---\n\n### Requirements  \n- SupaData API key  \n- OpenAI API key  \n- YouTube OAuth2 credentials  \n- Airtable (optional)\n\n---\n\n### Limitation  \nYouTube API **does not allow pinning comments** \u2014 the comment will be posted, but not pinned.\n\n---\n\n### Airtable Template  \n\ud83d\udd17 [Duplicate this base](https://airtable.com/appUJQfAXniGZzwL8/shraiQrFHLF3xxhX7) to track posted videos"
      },
      "typeVersion": 1
    },
    {
      "id": "a9818346-9cb1-497a-8063-e7131bbc331f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -144
      ],
      "parameters": {
        "height": 736,
        "content": "This node triggers when your YouTube channel publishes a new video via its RSS feed.\n\nYou can get your RSS feed from:\nhttps://www.youtube.com/feeds/videos.xml?channel_id=YOUR_CHANNEL_ID\n\nRuns on a daily schedule (e.g., every 4am)."
      },
      "typeVersion": 1
    },
    {
      "id": "6079238b-6ebd-4a49-979d-7b8f18fa4655",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -144
      ],
      "parameters": {
        "color": 5,
        "width": 608,
        "height": 736,
        "content": "Before continuing, this workflow checks if the video ID already exists in Airtable.\n\nThis prevents double-posting chapters on the same video.\n\n- If found \u2192 workflow stops\n- If not found \u2192 workflow continues and adds the video ID to Airtable"
      },
      "typeVersion": 1
    },
    {
      "id": "aef3d533-2a32-46da-ba74-c77cbc649d8a",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        864,
        -144
      ],
      "parameters": {
        "color": 6,
        "width": 608,
        "height": 736,
        "content": "This node sends the video to SupaData's transcript API.\n\nURL format:\nhttps://api.supadata.ai/v1/transcript?url=https://youtu.be/{{ videoId }}\n\nSupaData returns a structured response with timestamps and chapters.\n\nYou must use a valid API key (set in HTTP credentials)."
      },
      "typeVersion": 1
    },
    {
      "id": "8483c76d-d48c-4e93-aae5-99a41818bf1a",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2144,
        -144
      ],
      "parameters": {
        "color": 2,
        "width": 608,
        "height": 736,
        "content": "### Add Chapters to YouTube Description\n\nThis section updates the description of your latest YouTube video.\n\n---\n\n#### Step 1: Fetch Original Description\n\n- Uses the YouTube API to get the current video description.\n- Retrieves the title, existing text, and metadata.\n\n---\n\n#### Step 2: Append Chapters to the Description\n\n- AI-generated chapter timestamps are appended at the bottom of the existing description.\n- The update is submitted back to YouTube using the update video API.\n\n---\n\nNote: No comment is posted. This step only updates the video description with chapter markers."
      },
      "typeVersion": 1
    },
    {
      "id": "7a8294fb-1178-43ae-a528-d2c29207cbce",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1648,
        400
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o",
          "cachedResultName": "gpt-4o"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9d71c11f-a240-4bab-8781-2d1e77327528",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1504,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 736,
        "content": "This step uses OpenAI to automatically generate YouTube-style chapter timestamps based on the full video transcript.\n\nWhat it does:\n\t\u2022\tReads the transcript from the SupaData API\n\t\u2022\tSends it to GPT-4 or GPT-3.5 via OpenAI\n\t\u2022\tReturns timestamped chapters like:"
      },
      "typeVersion": 1
    },
    {
      "id": "34cb277e-b7fd-4952-a4cf-f32fabe1b242",
      "name": "Get New YouTube Video",
      "type": "n8n-nodes-base.rssFeedReadTrigger",
      "position": [
        0,
        128
      ],
      "parameters": {
        "feedUrl": "https://www.youtube.com/feeds/videos.xml?channel_id=UCDILbVG2rHR_BwfUAqrtuug&nocache=1",
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        }
      },
      "executeOnce": false,
      "notesInFlow": false,
      "typeVersion": 1,
      "alwaysOutputData": false
    },
    {
      "id": "29b84fe8-6ccc-4660-9eba-87228ad80055",
      "name": "Check Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        240,
        128
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appUJQfAXniGZzwL8",
          "cachedResultUrl": "https://airtable.com/appUJQfAXniGZzwL8",
          "cachedResultName": "YouTube Video IDS"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblgRW5IYgTz1bUxB",
          "cachedResultUrl": "https://airtable.com/appUJQfAXniGZzwL8/tblgRW5IYgTz1bUxB",
          "cachedResultName": "Table 1"
        },
        "options": {},
        "operation": "search",
        "filterByFormula": "=FIND(\"{{ $json.id.split(\":\").pop().trim() }}\", {Video ID}) > 0\n"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1,
      "alwaysOutputData": true
    },
    {
      "id": "ff6ebf58-f461-4f84-9f4f-1f98ca83ad35",
      "name": "Is New Video?",
      "type": "n8n-nodes-base.if",
      "position": [
        464,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "69651e9f-d4d9-4417-9ec1-f28c19ed10f4",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json['Video ID'] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "6b62ac99-f5a7-4444-a164-574da357e6eb",
      "name": "Save to Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        688,
        112
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "appUJQfAXniGZzwL8",
          "cachedResultUrl": "https://airtable.com/appUJQfAXniGZzwL8",
          "cachedResultName": "YouTube Video IDS"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblgRW5IYgTz1bUxB",
          "cachedResultUrl": "https://airtable.com/appUJQfAXniGZzwL8/tblgRW5IYgTz1bUxB",
          "cachedResultName": "Table 1"
        },
        "columns": {
          "value": {
            "Video ID": "={{ $('Get New YouTube Video').item.json.id.split(\":\")[2]}}\n"
          },
          "schema": [
            {
              "id": "Video ID",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Video ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Video ID"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "943d060a-d587-4f75-94cc-d184076ea1a7",
      "name": "Format Video ID",
      "type": "n8n-nodes-base.set",
      "position": [
        896,
        112
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "7caccd3e-f605-40ce-b2fd-0e6f48c310a3",
              "name": "snippet.title",
              "type": "string",
              "value": "={{ $('Get New YouTube Video').item.json.title }}"
            },
            {
              "id": "4ca86560-0701-4d2e-845a-aeab3bb8be36",
              "name": "id.videoId",
              "type": "string",
              "value": "={{ $json.fields['Video ID'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1dbeb5be-1c36-48be-898e-8d0722947c95",
      "name": " Fetch Transcript",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1088,
        112
      ],
      "parameters": {
        "url": "=https://api.supadata.ai/v1/transcript?url=https://youtu.be/{{ $json.id.videoId }}",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3ee9cafb-76f2-4c0c-a382-8e7e2f34f85c",
      "name": "Prepare Transcript + Duration",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        112
      ],
      "parameters": {
        "jsCode": "const transcript = $json.content;\n\nconst lines = transcript.map(line => {\n  const seconds = Math.floor(line.offset / 1000);\n  const minutes = Math.floor(seconds / 60);\n  const remaining = seconds % 60;\n  const timestamp = `${String(minutes).padStart(2, '0')}:${String(remaining).padStart(2, '0')}`;\n  return `${timestamp} ${line.text}`;\n});\n\nconst formattedTranscript = lines.join('\\n');\n\n// Calculate video length\nconst last = transcript[transcript.length - 1];\nconst maxDurationSeconds = Math.floor((last.offset + last.duration) / 1000);\n\nreturn [{\n  json: {\n    formattedTranscript,\n    maxDurationSeconds\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "483ae4b0-e725-46be-a90f-6480fb8468a9",
      "name": "AI: Generate Chapters",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1712,
        112
      ],
      "parameters": {
        "text": "=Based on the transcript below, generate chapter timestamps with short titles.\n\n\nFormatted transcript: {{ $json.formattedTranscript }}\n\nFull video length: {{ $json.maxDurationSeconds }}\n\nOnly return the chapters in this format:\n\n00:00 Intro  \n01:10 Key Concept  \n02:45 Main Topic  \n04:30 Explanation  \n...\n\nDo not include any intro text, explanation, or extra commentary \u2014 only the list of chapters.\n\n#Rules\n1. The timestamps have to always start at 00:00 to work on YouTube. So for example:\n- Don't include timestamps beyond {{ $json.maxDurationSeconds }} seconds\n- Use clear, descriptive titles\n- Group together lines close in time\n\n00:00 Intro  \n01:10 Key Concept  \n02:45 Main Topic  \n04:30 Explanation  \n\nIf we're missing the first 00:00 it won't be valid",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "0ded7384-5220-476e-a79d-2304e4c825a4",
      "name": "Fetch Current Description",
      "type": "n8n-nodes-base.youTube",
      "position": [
        2304,
        464
      ],
      "parameters": {
        "options": {},
        "videoId": "={{ $('Save to Airtable').item.json.fields['Video ID'].trim() }}",
        "resource": "video",
        "operation": "get"
      },
      "credentials": {
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2785f37b-6761-4323-8bbc-323fa1b16d24",
      "name": "Update Description",
      "type": "n8n-nodes-base.youTube",
      "position": [
        2576,
        464
      ],
      "parameters": {
        "title": "={{ $('Get New YouTube Video').item.json.title }}",
        "videoId": "={{ $('Save to Airtable').item.json.fields['Video ID'].trim() }}",
        "resource": "video",
        "operation": "update",
        "categoryId": "27",
        "regionCode": "US",
        "updateFields": {
          "description": "={{ $json.snippet.description }}\n\n{{ $('AI: Generate Chapters').item.json.output }}"
        }
      },
      "credentials": {
        "youTubeOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Is New Video?": {
      "main": [
        [
          {
            "node": "Save to Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Airtable": {
      "main": [
        [
          {
            "node": "Is New Video?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Video ID": {
      "main": [
        [
          {
            "node": " Fetch Transcript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Airtable": {
      "main": [
        [
          {
            "node": "Format Video ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    " Fetch Transcript": {
      "main": [
        [
          {
            "node": "Prepare Transcript + Duration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI: Generate Chapters",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI: Generate Chapters": {
      "main": [
        [
          {
            "node": "Fetch Current Description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get New YouTube Video": {
      "main": [
        [
          {
            "node": "Check Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Current Description": {
      "main": [
        [
          {
            "node": "Update Description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Transcript + Duration": {
      "main": [
        [
          {
            "node": "AI: Generate Chapters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}