This workflow corresponds to n8n.io template #17009 — we link there as the canonical source.
This workflow follows the Google Sheets → 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 →
{
"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[](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
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApihttpHeaderAuthopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs daily (or manually) to fetch recent videos from a list of TikTok accounts via Apify, transcribes each video with OpenAI, generates content insights and translations, and then appends or updates one row per video in Google Sheets. Runs on a daily schedule (or…
Source: https://n8n.io/workflows/17009/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is Part 2 of the HR Client Acquisition system and builds on the lead discovery pipeline from the previous workflow:
This workflow runs on a schedule to scrape job listings via Apify (Google Jobs and Career Site API), scores each role against your base resume using OpenAI, generates tailored cover letter and optiona
Product - SERP Analysis (Serper + Firecrawl). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 40 nodes.
Product - SERP Analysis (Serper & Crawl4AI). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 39 nodes.
Product - SERP Analysis (SerpAPI + Crawl4AI). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 38 nodes.