{
  "id": "aADQkjrNpTP527uI",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Track trending TikTok videos and save insights to Google Sheets",
  "tags": [],
  "nodes": [
    {
      "id": "aa97ad1b-c386-42e5-b515-cc3bacc8b2f8",
      "name": "Run manually",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -640,
        208
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "ffbac1c2-147c-488f-98f0-e54a6ef05eef",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -368,
        352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "openai_model",
              "type": "string",
              "value": "gpt-4o-mini"
            },
            {
              "id": "c2",
              "name": "days_limit",
              "type": "number",
              "value": 7
            },
            {
              "id": "c3",
              "name": "results_limit",
              "type": "number",
              "value": 10
            },
            {
              "id": "c4",
              "name": "output_language",
              "type": "string",
              "value": "English"
            },
            {
              "id": "c5",
              "name": "tiktok_actor",
              "type": "string",
              "value": "clockworks/tiktok-scraper"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1572e2b9-e107-45ae-b98f-f24b1f3e2388",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -640,
        432
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 6
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "ac34eba7-9ad5-46ff-a2b4-029822e2f263",
      "name": "Get Accounts",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -128,
        304
      ],
      "parameters": {
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "accounts"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "TikTok Trends Watcher"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "7271a902-da2d-4ac8-a37f-04b0a28bab59",
      "name": "Build Apify Input",
      "type": "n8n-nodes-base.code",
      "position": [
        80,
        304
      ],
      "parameters": {
        "jsCode": "const accounts = $input.all()\n  .map(i => (i.json.username || '').toString().trim().replace(/^@/, ''))\n  .filter(Boolean);\n\nconst resultsPerPage = $('Configuration').first().json.results_limit ?? 10;\n\nconst payload = {\n  profiles: accounts,\n  resultsPerPage: resultsPerPage,\n  profileScrapeSections: [\"videos\"],\n  profileSorting: \"latest\",\n  excludePinnedPosts: false,\n  shouldDownloadVideos: false,\n  proxyConfiguration: { useApifyProxy: true }\n};\n\nreturn [{ json: { payload, accounts } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "01cf7e01-e27f-4cc3-99f8-1b0e888eab00",
      "name": "Run TikTok Scraper",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        304
      ],
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/acts/' + $('Configuration').first().json.tiktok_actor.replace('/', '~') + '/run-sync-get-dataset-items' }}",
        "method": "POST",
        "options": {
          "timeout": 300000
        },
        "jsonBody": "={{ JSON.stringify($json.payload) }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "d3e0703d-674b-4cd1-9b66-8129895bb20a",
      "name": "Normalize TikTok Videos",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        304
      ],
      "parameters": {
        "jsCode": "const daysLimit = $('Configuration').first().json.days_limit ?? 7;\nconst cutoff = Date.now() - daysLimit * 24 * 60 * 60 * 1000;\n\nconst out = [];\nfor (const item of $input.all()) {\n  const v = item.json;\n  if (v.errorCode) continue;\n\n  const tsIso = v.createTimeISO\n    || (v.createTime ? new Date(v.createTime * 1000).toISOString() : null);\n  const ts = tsIso ? new Date(tsIso).getTime() : 0;\n  if (!ts || ts < cutoff) continue;\n\n  const hashtags = Array.isArray(v.hashtags)\n    ? v.hashtags.map(h => (typeof h === 'string' ? h : h.name)).filter(Boolean).join(', ')\n    : '';\n\n  const videoUrl = (Array.isArray(v.mediaUrls) && v.mediaUrls[0])\n    || (v.videoMeta && v.videoMeta.downloadAddr)\n    || '';\n  if (!videoUrl) continue;\n\n  out.push({ json: {\n    username : (v.authorMeta && v.authorMeta.name) || '',\n    post_url : v.webVideoUrl || '',\n    caption  : v.text || '',\n    hashtags : hashtags,\n    views    : v.playCount ?? 0,\n    likes    : v.diggCount ?? 0,\n    comments : v.commentCount ?? 0,\n    shares   : v.shareCount ?? 0,\n    saves    : v.collectCount ?? 0,\n    duration : (v.videoMeta && v.videoMeta.duration) ?? 0,\n    date     : tsIso,\n    video_url: videoUrl\n  }});\n}\nreturn out;"
      },
      "typeVersion": 2
    },
    {
      "id": "2f23822d-fd92-4830-b91a-603111a5004d",
      "name": "Download Video",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        800,
        304
      ],
      "parameters": {
        "url": "={{ $json.video_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "5df8a462-6e4d-4941-a932-8287292f0d7e",
      "name": "Transcribe Video",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1040,
        304
      ],
      "parameters": {
        "options": {},
        "resource": "audio",
        "operation": "transcribe"
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "f60d08bf-1ce8-4b0c-aed1-8e36bef7e3a4",
      "name": "Analyze Content",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1280,
        304
      ],
      "parameters": {
        "images": {
          "values": [
            {}
          ]
        },
        "modelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Configuration').first().json.openai_model }}"
        },
        "options": {
          "maxTokens": 2000,
          "temperature": 0.2
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a social media content analyst. You always reply with a single valid JSON object and nothing else."
            },
            {
              "content": "=Analyze this TikTok video using its transcription and caption.\n\nTranscription:\n{{ $json.text }}\n\nCaption:\n{{ $('Normalize TikTok Videos').item.json.caption }}\n\nHashtags:\n{{ $('Normalize TikTok Videos').item.json.hashtags }}\n\nTasks:\n1. \"hook\": the strongest attention-grabbing line from the first seconds (max 15 words).\n2. \"category\": one of Business, Marketing, Cooking, Interview, Education, Entertainment, Unknown.\n3. \"format\": one of Talking head, Voiceover, Tutorial, Skit, Unknown (infer it).\n4. \"translation\": the full transcription translated into {{ $('Configuration').first().json.output_language }}.\n5. \"translation_hook\": the hook translated into {{ $('Configuration').first().json.output_language }}.\n\nReturn ONLY a JSON object with exactly these keys: hook, category, format, translation, translation_hook."
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "801fc259-d950-40e4-8612-c7ef068584ba",
      "name": "Build Row",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        304
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const raw = $json;\n\nlet text = raw.output ?? raw.content ?? raw.text ?? raw.message ?? '';\nif (Array.isArray(raw.output)) {\n  text = raw.output?.[0]?.content?.[0]?.text ?? '';\n}\n\nlet analysis = {};\ntry {\n  const cleaned = String(text).replace(/^```json\\s*/i, '').replace(/```$/, '').trim();\n  analysis = JSON.parse(cleaned);\n} catch (e) {\n  analysis = {};\n}\n\nconst meta = $('Normalize TikTok Videos').item.json;\nconst transcription = $('Transcribe Video').item.json.text ?? '';\n\nreturn { json: {\n  username        : meta.username,\n  post_url        : meta.post_url,\n  caption         : meta.caption,\n  hashtags        : meta.hashtags,\n  views           : meta.views,\n  likes           : meta.likes,\n  comments        : meta.comments,\n  shares          : meta.shares,\n  saves           : meta.saves,\n  duration        : meta.duration,\n  date            : meta.date,\n  hook            : analysis.hook ?? '',\n  transcription   : transcription,\n  category        : analysis.category ?? '',\n  format          : analysis.format ?? '',\n  translation_hook: analysis.translation_hook ?? '',\n  translation     : analysis.translation ?? '',\n  parsed_at       : new Date().toISOString()\n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "bd870278-bb69-4f39-9307-7a440a664553",
      "name": "Save Insights to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1760,
        304
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "insights"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "TikTok Trends Watcher"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c8a8b842-1f77-42ec-b54f-81ff85c9407e",
      "name": "Sticky Note f9973bb8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1376,
        -288
      ],
      "parameters": {
        "color": 3,
        "width": 640,
        "height": 1680,
        "content": "# Track trending TikTok videos and save insights to Google Sheets\n# \ud83d\udce5  [Open full documentation on Notion](https://automatisation.notion.site/Track-trending-TikTok-videos-and-save-insights-to-Google-Sheets-Course-39a3d6550fd981b489b2f881df718078)\n\n## How it works\n1. Reads the TikTok accounts to monitor from a **Google Sheet** (`accounts` tab).\n2. Calls the **Apify** TikTok scraper (via HTTP) to pull each creator's latest videos.\n3. Keeps only videos posted within your time window.\n4. **Transcribes** each video with **OpenAI Whisper**, then asks **OpenAI (GPT)** for a hook, a category, a format and a translation.\n5. Writes every result to the **Google Sheet** (`insights` tab), one row per video.\n\n## Setup\n1. Create a Google Sheet with two tabs: **accounts** (one column: `username`) and **insights**.\n2. Add credentials and attach them: **Google Sheets** (Get Accounts + Save Insights), **OpenAI** (Transcribe + Analyze), **Apify token** as HTTP Bearer Auth (Run TikTok Scraper).\n3. In **Get Accounts** and **Save Insights**, pick your spreadsheet and the right tab.\n4. Open **Configuration** and set your variables.\n5. Run once manually with 2\u20133 accounts, then let the schedule run daily.\n\n## Requirements\n- An **n8n** instance (self-hosted or cloud)\n- A **Google account** (Google Sheets)\n- An **OpenAI API key** (transcription + text)\n- An **Apify** account + API token\n\n## Customization\n- Edit the category / format rules in the *Analyze Content* prompt.\n- Change `days_limit`, `results_limit`, `openai_model`, `output_language` in *Configuration*.\n- Swap `tiktok_actor` for another Apify TikTok actor (align the payload in *Build Apify Input*).\n\n---\n\nNeed help customizing?\nContact me for consulting and support : [Linkedin](https://www.linkedin.com/in/doctor-firass/)\n\n# MY NEW YOUTUBE CHANNEL\n\ud83d\udc49 [Subscribe to my new YouTube channel](https://www.youtube.com/@DrFiras_AI). Here I'll share videos and Shorts with practical tutorials and FREE templates for n8n.\n\n[![The AI Doctor](https://www.dr-firas.com/the-ai-doctor.png)](https://www.youtube.com/@DrFiras_AI)"
      },
      "typeVersion": 1
    },
    {
      "id": "8ec208be-323f-4911-882d-42bad4be338f",
      "name": "Sticky Note 7bd6a499",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        112
      ],
      "parameters": {
        "color": 7,
        "height": 406,
        "content": "## \u2699\ufe0f Configuration\nSet everything here in one place: `openai_model`, `days_limit`, `results_limit`, `output_language`, `tiktok_actor`."
      },
      "typeVersion": 1
    },
    {
      "id": "253ca3c3-a46b-4167-a969-6b1f41fb2ffc",
      "name": "Sticky Note 60b26d9a",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -160,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 410,
        "content": "## 1. Read accounts & launch the scraper\nReads the `accounts` tab, builds one Apify payload, calls the TikTok scraper (Apify run-sync API)."
      },
      "typeVersion": 1
    },
    {
      "id": "eca8ed7b-e406-4398-bd0d-c603de2e8788",
      "name": "Sticky Note 392484d5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 220,
        "height": 410,
        "content": "## 2. Keep recent videos\nFlatten the TikTok data and keep only videos inside `days_limit`."
      },
      "typeVersion": 1
    },
    {
      "id": "98d11765-6d47-40f7-b1be-2d543e6b1d82",
      "name": "Sticky Note af9e1486",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 700,
        "height": 410,
        "content": "## 3. Transcribe & analyze with OpenAI\nDownload each video, transcribe with Whisper, extract hook / category / format / translation with GPT."
      },
      "typeVersion": 1
    },
    {
      "id": "aa1986a0-d9e9-4c1d-9b2a-9f8cc22cffe1",
      "name": "Sticky Note 2f0a9200",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        112
      ],
      "parameters": {
        "color": 7,
        "width": 460,
        "height": 410,
        "content": "## 4. Save to Google Sheets\nUpsert one row per video (matched on `post_url`)."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "73f84389-adee-4a20-9253-5150248748da",
  "nodeGroups": [],
  "connections": {
    "Build Row": {
      "main": [
        [
          {
            "node": "Save Insights to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Accounts": {
      "main": [
        [
          {
            "node": "Build Apify Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run manually": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "Get Accounts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Video": {
      "main": [
        [
          {
            "node": "Transcribe Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Content": {
      "main": [
        [
          {
            "node": "Build Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Transcribe Video": {
      "main": [
        [
          {
            "node": "Analyze Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Apify Input": {
      "main": [
        [
          {
            "node": "Run TikTok Scraper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run TikTok Scraper": {
      "main": [
        [
          {
            "node": "Normalize TikTok Videos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize TikTok Videos": {
      "main": [
        [
          {
            "node": "Download Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}