AutomationFlowsAI & RAG › Create Tiktok Carousel Posts with Upload Post, Google Sheets, Gemini, Gpt-4.1

Create Tiktok Carousel Posts with Upload Post, Google Sheets, Gemini, Gpt-4.1

Byamanullah @rana on n8n.io

This workflow runs daily to pull TikTok post and profile analytics from Upload Post, updates a Google Sheets performance log, uses Google Gemini and OpenAI to generate a new carousel concept and caption, creates four images via fal.ai, publishes to TikTok, and updates a…

Cron / scheduled trigger★★★★★ complexityAI-powered39 nodesGoogle SheetsHTTP RequestOutput Parser StructuredTool ThinkGoogle Gemini ChatAgentOpenAIN8N Nodes Upload Post
AI & RAG Trigger: Cron / scheduled Nodes: 39 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Google Sheets 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": "GqeKzQgvF2M1wvNz",
  "name": "Larry TikTok Growth Engine",
  "tags": [],
  "nodes": [
    {
      "id": "2890ca23-9ed7-4e6e-b5e6-1af43567894b",
      "name": "Note Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        -368
      ],
      "parameters": {
        "color": 5,
        "width": 360,
        "height": 428,
        "content": "## Larry TikTok Growth Engine\n\n**Self-improving AI with persistent skill memory**\n\nFully sequential flow - no deadlocks.\n\n### Flow:\n1. Read sheet + collect per-post stats\n2. Read profile analytics + agent skill\n3. LLM analyzes all data (uses accumulated skill)\n4. Generates 4 slides with TEXT\n5. Publishes to TikTok\n6. Logs + updates skill file with new learnings\n"
      },
      "typeVersion": 1
    },
    {
      "id": "3c8470e3-5d8e-44b8-a788-1c63167ec314",
      "name": "Note P1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -464,
        112
      ],
      "parameters": {
        "color": 4,
        "width": 1720,
        "height": 672,
        "content": "## Phase 1: Collect Stats "
      },
      "typeVersion": 1
    },
    {
      "id": "2a9d3171-ad6b-4a19-af58-d31b0f5804c9",
      "name": "Note P2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        112
      ],
      "parameters": {
        "color": 3,
        "width": 900,
        "height": 656,
        "content": "## Phase 2: Read Context + Generate"
      },
      "typeVersion": 1
    },
    {
      "id": "23090b08-7556-43c2-bb36-731d5e45a4ad",
      "name": "Note P3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2192,
        0
      ],
      "parameters": {
        "color": 6,
        "width": 708,
        "height": 772,
        "content": "## Phase 3: Images"
      },
      "typeVersion": 1
    },
    {
      "id": "2b502824-1a6b-4221-a7f9-d28387f8ab4c",
      "name": "Note P4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2896,
        0
      ],
      "parameters": {
        "color": 2,
        "width": 1396,
        "height": 656,
        "content": "## Phase 4: Publish + Skill Update"
      },
      "typeVersion": 1
    },
    {
      "id": "d36e661e-5e1e-4c87-bbe1-c8a89ab2d2ae",
      "name": "Daily Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -640,
        304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4dbb4d96-5b0d-4687-8f27-9cc7a62bfcf8",
      "name": "Set Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -448,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "1809cc51-6da2-4aef-9946-89517d372cbc",
              "name": "product_name",
              "type": "string",
              "value": "Upload Post"
            },
            {
              "id": "79675585-aaf7-46ef-b2e3-687ec3316671",
              "name": "product_description",
              "type": "string",
              "value": "Upload Post is a SaaS that lets you publish photos, videos, and text to TikTok, Instagram, YouTube, X, LinkedIn, Facebook, Threads, Pinterest, and Reddit with a single API call. One click, all platforms."
            },
            {
              "id": "334f4cf1-f3a5-4313-845f-8bd494bf6472",
              "name": "upload_post_user",
              "type": "string",
              "value": "automated-tests"
            },
            {
              "id": "7c29629e-7b8c-49c2-ae2b-211810d01337",
              "name": "upload_post_api_key",
              "type": "string",
              "value": "PASTE_YOUR_UPLOAD_POST_API_KEY_HERE"
            },
            {
              "id": "ac725e41-af66-40e2-b4db-1d5e97c57865",
              "name": "spreadsheet_id",
              "type": "string",
              "value": "1o2NZ5yXQbrrvmUR_gICVcq2Y4svnRsGa5LBsxK4PqhE"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "016aac30-4c12-4277-a247-61310171e452",
      "name": "Read Performance Log",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        -208,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "5d9a3622-7422-436c-b98f-b0966fd37566",
      "name": "Filter Posts for Stats",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        304
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n  const d = item.json;\n  if (d.request_id && d.request_id.length > 5 && d.request_id !== 'pending') {\n    const hasStats = d.views && String(d.views).length > 0 && Number(d.views) > 0;\n    const postDate = d.post_date ? new Date(d.post_date) : null;\n    const isRecent = postDate ? (Date.now() - postDate.getTime()) < 7*24*60*60*1000 : true;\n    if (!hasStats || isRecent) {\n      results.push({ json: { request_id: d.request_id, _skip: false } });\n    }\n  }\n}\n\n// ALWAYS output at least 1 item so the chain never stalls\nif (results.length === 0) {\n  return [{ json: { request_id: 'skip', _skip: true } }];\n}\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c55a136a-8311-452d-910c-ef0ba2d404e5",
      "name": "Get Post Analytics",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        272,
        304
      ],
      "parameters": {
        "url": "=https://api.upload-post.com/api/uploadposts/post-analytics/{{ $json.request_id }}?platform=tiktok",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3e0b27fd-b6be-407d-a46f-1a5ef4c07f45",
      "name": "Extract Stats",
      "type": "n8n-nodes-base.code",
      "position": [
        512,
        304
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nconst filterItems = $('Filter Posts for Stats').all();\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n  const filterItem = filterItems[i]?.json || {};\n  const a = items[i].json;\n\n  // Skip if this was a pass-through item\n  if (filterItem._skip) {\n    results.push({ json: { request_id: 'skip', _skip: true, views: '0', likes: '0', comments: '0', shares: '0', engagement_rate: '0%' } });\n    continue;\n  }\n\n  let views = 0, likes = 0, comments = 0, shares = 0;\n  const sources = [a?.platforms?.tiktok?.post_metrics, a?.tiktok?.post_metrics, a?.post_metrics, a];\n  for (const m of sources) {\n    if (m && (m.views || m.view_count || m.play_count)) {\n      views = m.views || m.view_count || m.play_count || 0;\n      likes = m.likes || m.like_count || m.digg_count || 0;\n      comments = m.comments || m.comment_count || 0;\n      shares = m.shares || m.share_count || 0;\n      break;\n    }\n  }\n  const eng = views > 0 ? (((likes+comments+shares)/views)*100).toFixed(2)+'%' : '0%';\n  results.push({ json: { request_id: filterItem.request_id, _skip: false, views: String(views), likes: String(likes), comments: String(comments), shares: String(shares), engagement_rate: eng } });\n}\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "22463928-dab1-4fb4-9098-9701e8de6efe",
      "name": "Update Stats",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        720,
        304
      ],
      "parameters": {
        "columns": {
          "value": {
            "likes": "={{ $json.likes }}",
            "views": "={{ $json.views }}",
            "shares": "={{ $json.shares }}",
            "comments": "={{ $json.comments }}",
            "request_id": "={{ $json.request_id }}",
            "engagement_rate": "={{ $json.engagement_rate }}"
          },
          "schema": [
            {
              "id": "request_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "request_id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "views",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "views",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "likes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "likes",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "comments",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "comments",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "shares",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "shares",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "engagement_rate",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "engagement_rate",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "request_id"
          ]
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "9d9083ac-6cb6-4667-b87c-1293dafc61f8",
      "name": "Aggregate",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        944,
        304
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "89284274-5477-4607-81fc-4b1eee0d827c",
      "name": "Get Profile Analytics",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1120,
        304
      ],
      "parameters": {
        "url": "=https://api.upload-post.com/api/analytics/{{ $('Set Config').first().json.upload_post_user }}?platforms=tiktok",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0eef627e-b5ef-4144-b23b-ad184aa60200",
      "name": "Read Agent Skill",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1280,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Agent Skill"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "5af68d2e-5097-48e7-8314-b14d058da2d7",
      "name": "Read Fresh Data",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1472,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "088aa2f3-0865-4720-ac1d-3906e55926ca",
      "name": "Prepare Context",
      "type": "n8n-nodes-base.code",
      "position": [
        1616,
        304
      ],
      "parameters": {
        "jsCode": "const config = $('Set Config').first().json;\nconst sheetItems = $('Read Fresh Data').all();\n\n// Agent Skill\nlet agentSkill = \"No learnings yet. First run!\";\ntry { const s = $('Read Agent Skill').all(); if (s?.[0]?.json?.skill_content) agentSkill = s[0].json.skill_content; } catch(e) {}\n\n// Profile Analytics\nlet profile = \"Not available.\";\ntry {\n  const ad = $('Get Profile Analytics').first().json;\n  if (ad && !ad.error) {\n    const tk = ad.tiktok || ad;\n    profile = `Followers: ${tk.followers||'?'} | Impressions: ${tk.impressions||'?'} | Likes: ${tk.likes||'?'}`;\n    if (tk.reach_timeseries?.length > 0) {\n      profile += '\\nLast 7d views: ' + tk.reach_timeseries.slice(-7).map(d => `${d.date||'?'}:${d.value||d.views||'?'}`).join(', ');\n    }\n  }\n} catch(e) {}\n\n// Post History\nlet posts = \"\"; let total = 0;\nconst hasData = sheetItems?.length > 0 && sheetItems[0].json?.post_date;\nif (hasData) {\n  const p = sheetItems.map(i => i.json); total = p.length;\n  posts = p.map((x,i) => `Post ${i+1} (${x.post_date}): \"${x.title}\" | Views:${x.views||'?'} Likes:${x.likes||'?'} Comments:${x.comments||'?'} Shares:${x.shares||'?'} Eng:${x.engagement_rate||'?'} | Strategy: ${x.strategy_notes||'?'}`).join('\\n');\n  const wv = p.filter(x => Number(x.views) > 0);\n  if (wv.length > 0) {\n    const avg = k => (wv.reduce((s,x) => s+Number(x[k]||0),0)/wv.length).toFixed(0);\n    posts += `\\n\\nAVG: Views ${avg('views')} | Likes ${avg('likes')} | Comments ${avg('comments')}`;\n    const best = [...wv].sort((a,b) => Number(b.views)-Number(a.views))[0];\n    posts += `\\nBEST: \"${best.title}\" (${best.views} views) Strategy: ${best.strategy_notes}`;\n  }\n} else { posts = \"FIRST POST - No history. Establish baseline!\"; }\n\nreturn { json: { agent_skill: agentSkill, profile_analytics: profile, performance_summary: posts, total_posts: total, product_name: config.product_name, product_description: config.product_description }};"
      },
      "typeVersion": 2
    },
    {
      "id": "af4eb90b-cfd9-4678-9fe7-39ff9afc7fe2",
      "name": "Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        2032,
        560
      ],
      "parameters": {
        "jsonSchemaExample": "{\"title\": \"Stop posting one by one\", \"strategy_notes\": \"Testing question hooks - skill says 3x more views\", \"prompt1\": \"9:16 bold white text 'STILL POSTING ONE BY ONE?' on dark gradient, modern TikTok style\", \"prompt2\": \"9:16 clean slide 'ONE CLICK = ALL PLATFORMS' with social icons circle, gradient bg\", \"prompt3\": \"9:16 slide 'SAVE 4+ HOURS WEEKLY' warm lifestyle bg, creator at desk\", \"prompt4\": \"9:16 bold CTA 'TRY UPLOAD POST FREE' vibrant gradient, arrow down\", \"hashtags\": \"#socialmedia #contentcreator #automation #tiktokgrowth #fyp #viral #saas #marketing\", \"engagement_hook\": \"How many platforms do you post to?\"}"
      },
      "typeVersion": 1.2
    },
    {
      "id": "263aa0a9-3cc7-4e37-a75b-b0fc286624f6",
      "name": "Think Tool",
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "position": [
        1920,
        560
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "617342f1-a386-47e3-8aa4-7bd58d75d892",
      "name": "Gemini 2.5 Pro",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1824,
        560
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-3.1-pro-preview"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "24667f3d-8fbc-4129-8699-59af8a9c1132",
      "name": "Strategy Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1824,
        304
      ],
      "parameters": {
        "text": "=== PRODUCT ===\n{{ $('Prepare Context').first().json.product_name }}: {{ $('Prepare Context').first().json.product_description }}\n\n=== TIKTOK PROFILE ===\n{{ $('Prepare Context').first().json.profile_analytics }}\n\n=== POST HISTORY ===\n{{ $('Prepare Context').first().json.performance_summary }}\n\n=== TASK ===\nCreate an optimized TikTok carousel using your skill + this data.\n\nReturn ONLY valid JSON:\n{\n  \"title\": \"<TikTok caption with hook, max 150 chars>\",\n  \"strategy_notes\": \"<What and WHY based on learnings, max 200 chars>\",\n  \"prompt1\": \"<Slide 1: HOOK - visual + bold text to display, 9:16>\",\n  \"prompt2\": \"<Slide 2: FEATURE - visual + text about product, 9:16>\",\n  \"prompt3\": \"<Slide 3: BENEFIT - visual + text, 9:16>\",\n  \"prompt4\": \"<Slide 4: CTA - visual + call-to-action text, 9:16>\",\n  \"hashtags\": \"<8-12 hashtags>\",\n  \"engagement_hook\": \"<Question for comments>\"\n}",
        "options": {
          "systemMessage": "You are Larry, an elite TikTok growth strategist with persistent memory.\n\n=== YOUR ACCUMULATED SKILL (learnings from ALL past runs) ===\n{{ $('Prepare Context').first().json.agent_skill }}\n=== END SKILL ===\n\nCreate 4-image TikTok carousel slideshows (9:16 vertical) to promote a SaaS product.\n\nCRITICAL - Each image MUST contain visible designed TEXT:\n- Slide 1: Powerful HOOK text (big, bold, scroll-stopping)\n- Slide 2: Key feature/benefit with text overlay\n- Slide 3: Another angle/use case/proof with text\n- Slide 4: Strong CTA text\n\nApply your skill learnings. Build on winners. Avoid losers.\nOutput strictly valid compact JSON only."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 1.9
    },
    {
      "id": "ee904cb1-a478-42dd-bccc-e6b5399ca032",
      "name": "Set Vars",
      "type": "n8n-nodes-base.set",
      "position": [
        2176,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c8f58cec-81df-4d3a-9ec7-e5f360d4a42c",
              "name": "title",
              "type": "string",
              "value": "={{ $json.output.title }}"
            },
            {
              "id": "fe06b575-9f23-49a0-b5bc-e52ada16dd79",
              "name": "strategy_notes",
              "type": "string",
              "value": "={{ $json.output.strategy_notes }}"
            },
            {
              "id": "161e1e7a-dbc9-404b-804f-e2db57eec887",
              "name": "prompt1",
              "type": "string",
              "value": "={{ $json.output.prompt1 }}"
            },
            {
              "id": "709d9b21-c21e-4761-bf4f-8db530275058",
              "name": "prompt2",
              "type": "string",
              "value": "={{ $json.output.prompt2 }}"
            },
            {
              "id": "ee55fc5a-142d-4dc7-b34e-e608845ff933",
              "name": "prompt3",
              "type": "string",
              "value": "={{ $json.output.prompt3 }}"
            },
            {
              "id": "6940ebd9-539e-4a6f-85c7-c0c2d7fe408a",
              "name": "prompt4",
              "type": "string",
              "value": "={{ $json.output.prompt4 }}"
            },
            {
              "id": "4d17e727-2c75-4e11-a508-715f92a0c264",
              "name": "hashtags",
              "type": "string",
              "value": "={{ $json.output.hashtags }}"
            },
            {
              "id": "e599377a-5324-40f3-b464-13e5d6aebf9e",
              "name": "engagement_hook",
              "type": "string",
              "value": "={{ $json.output.engagement_hook }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "490414ec-1ebe-4d31-8870-9e92f0a621eb",
      "name": "Generate Image 1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2368,
        64
      ],
      "parameters": {
        "url": "https://fal.run/fal-ai/nano-banana-2",
        "body": "={{ JSON.stringify({ prompt: $('Set Vars').first().json.prompt1, num_images: 1, aspect_ratio: '9:16', output_format: 'png' }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f7fef427-9734-4426-8cea-cab126811d62",
      "name": "Extract URL 1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2560,
        64
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "2579c31e-17c0-4aa4-b964-0d065cf74668",
      "name": "Generate Image 2",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2368,
        224
      ],
      "parameters": {
        "url": "https://fal.run/fal-ai/nano-banana-2",
        "body": "={{ JSON.stringify({ prompt: $('Set Vars').first().json.prompt2, num_images: 1, aspect_ratio: '9:16', output_format: 'png' }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "3279ac16-2265-490e-aa57-6185e20c8431",
      "name": "Extract URL 2",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2560,
        224
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "1e28bcc4-7eda-45c1-aabd-d3f3258691dd",
      "name": "Generate Image 3",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2368,
        384
      ],
      "parameters": {
        "url": "https://fal.run/fal-ai/nano-banana-2",
        "body": "={{ JSON.stringify({ prompt: $('Set Vars').first().json.prompt3, num_images: 1, aspect_ratio: '9:16', output_format: 'png' }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a9935c6f-54ea-4ce9-a2a2-7baebbed05ac",
      "name": "Extract URL 3",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2560,
        384
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "5d784b25-52b4-4390-b53f-8dea07a40f28",
      "name": "Generate Image 4",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2368,
        544
      ],
      "parameters": {
        "url": "https://fal.run/fal-ai/nano-banana-2",
        "body": "={{ JSON.stringify({ prompt: $('Set Vars').first().json.prompt4, num_images: 1, aspect_ratio: '9:16', output_format: 'png' }) }}",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "1fa984cd-ef7e-4b3a-b849-4dc4340b5ece",
      "name": "Extract URL 4",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2560,
        544
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "b41373ce-c47f-4f4e-8453-337195544a9b",
      "name": "Merge Images",
      "type": "n8n-nodes-base.merge",
      "position": [
        2768,
        224
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3.1
    },
    {
      "id": "568262ab-b916-4112-85f5-c6b792e2a1d9",
      "name": "Aggregate Images",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2976,
        256
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "6c53693c-13f5-4ea0-ad60-fd621882780a",
      "name": "Generate Description",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3136,
        256
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1",
          "cachedResultName": "GPT-4.1"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You write viral TikTok carousel descriptions. Short, punchy, emojis ok."
            },
            {
              "content": "=Write TikTok description for {{ $('Set Config').first().json.product_name }}.\nHook: {{ $('Set Vars').first().json.title }}\nHashtags: {{ $('Set Vars').first().json.hashtags }}\nQuestion: {{ $('Set Vars').first().json.engagement_hook }}\n\n1.Hook 2.Value 3.CTA 4.Hashtags 5.Question\nMax 300 chars. ONLY the description."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "074fcaeb-a280-4a6b-8e30-275976b0cc22",
      "name": "Upload to TikTok",
      "type": "n8n-nodes-upload-post.uploadPost",
      "position": [
        3408,
        256
      ],
      "parameters": {
        "user": "__manual_user__",
        "title": "={{ $json.message.content }}",
        "photos": "={{ $('Extract URL 1').first().json.url }},{{ $('Extract URL 2').first().json.url }},{{ $('Extract URL 3').first().json.url }},{{ $('Extract URL 4').first().json.url }}",
        "platform": [
          "tiktok"
        ],
        "userManual": "={{ $('Set Config').first().json.upload_post_user }}",
        "tiktokTitle": "={{ $('Set Vars').first().json.title }}",
        "tiktokAutoAddMusic": true,
        "tiktokPhotoDescription": "={{ $json.message.content }}"
      },
      "credentials": {
        "uploadPostApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "074de448-6e63-4521-a38b-869b636501ba",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3568,
        256
      ],
      "parameters": {
        "columns": {
          "value": {
            "likes": "",
            "title": "={{ $('Set Vars').first().json.title }}",
            "views": "",
            "shares": "",
            "comments": "",
            "post_url": "={{ $json.post_url || '' }}",
            "post_date": "={{ $now.format('yyyy-MM-dd HH:mm') }}",
            "request_id": "={{ $json.request_id || '' }}",
            "description": "={{ $('Generate Description').first().json.message.content }}",
            "strategy_notes": "={{ $('Set Vars').first().json.strategy_notes }}",
            "engagement_rate": "",
            "prompts_summary": "={{ $('Set Vars').first().json.prompt1.substring(0,100) }}...",
            "platform_post_id": "={{ $json.platform_post_id || '' }}"
          },
          "schema": [
            {
              "id": "post_date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "post_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "request_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "request_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "post_url",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "post_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "platform_post_id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "platform_post_id",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "strategy_notes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "strategy_notes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "prompts_summary",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "prompts_summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "views",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "views",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "likes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "likes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "comments",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "comments",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "shares",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "shares",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "engagement_rate",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "engagement_rate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5153b194-c65e-4d9e-a6e0-da1fd06ece9a",
      "name": "Update Skill",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3744,
        256
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1",
          "cachedResultName": "GPT-4.1"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You maintain a concise skill file for an AI content creator. Analyze data, write actionable learnings."
            },
            {
              "content": "=== CURRENT SKILL ===\n{{ $('Prepare Context').first().json.agent_skill }}\n\n=== LATEST POST ===\nTitle: {{ $('Set Vars').first().json.title }}\nStrategy: {{ $('Set Vars').first().json.strategy_notes }}\n\n=== ALL DATA ===\n{{ $('Prepare Context').first().json.performance_summary }}\nProfile: {{ $('Prepare Context').first().json.profile_analytics }}\n\nWrite an UPDATED skill file (max 2000 chars). Sections:\n## What Works (data-backed)\n## What Doesn't Work\n## Key Metrics (averages, best post, trend)\n## Strategy Playbook (top 3 to try next)\n## Product Angles (best ways to promote)\n\nBe specific with numbers. Update existing insights, don't just append. Output ONLY the skill content."
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "d595715b-88ac-4b9c-9946-ce8379a6728b",
      "name": "Write Skill",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4048,
        256
      ],
      "parameters": {
        "columns": {
          "value": {
            "id": "skill",
            "last_updated": "={{ $now.format('yyyy-MM-dd HH:mm') }}",
            "skill_content": "={{ $json.message.content }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "skill_content",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "skill_content",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            },
            {
              "id": "last_updated",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "last_updated",
              "defaultMatch": false,
              "canBeUsedToMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ]
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Agent Skill"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "1a6eae6d-042c-48d2-a8f5-c425b4d6c8c8",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -640,
        528
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "8f742164-ac55-480c-842d-99ad108455f9",
      "name": "Note Setup1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1392,
        112
      ],
      "parameters": {
        "color": 5,
        "width": 744,
        "height": 1100,
        "content": "## \ud83d\udee0\ufe0f Setup Guide\n\n  ### 1. Set Config Node\n  Edit the **Set Config** node with your values:\n  - `product_name` \u2014 Your product name\n  - `product_description` \u2014 Short description of your product\n  - `upload_post_user` \u2014 Your Upload Post profile username\n  - `upload_post_api_key` \u2014 Your Upload Post API key (from\n  upload-post.com dashboard)\n  - `spreadsheet_id` \u2014 Google Sheet ID (already set)\n\n  ### 2. Google Sheet\n  The sheet needs 2 tabs:\n  - **Performance Log** \u2014 columns: `post_date, request_id,\n  post_url, platform_post_id, title, description, strategy_notes,\n  prompts_summary, views, likes, comments, shares, engagement_rate`\n  - **Agent Skill** \u2014 columns: `id, skill_content, last_updated`\n - Copy to your drive this template and put the id : https://docs.google.com/spreadsheets/d/1jk1GvBd6b4DopVH1VXkIsY0yPoCmE-DsA9Kun0ZAfQU/edit?usp=sharing \n\n  ### 3. Credentials Required\n  - **Google Sheets OAuth2** \u2014 for reading/writing the sheet\n  - **Google PaLM (Gemini)** \u2014 for the Strategy Agent (Gemini 2.5\n  Pro)\n  - **fal.ai (Header Auth)** \u2014 for image generation\n  - **OpenAI** \u2014 for descriptions + skill updates (GPT-4.1)\n  - **Upload Post** \u2014 for publishing to TikTok\n  - **Upload Post (Header Auth)** \u2014 same API key, for analytics\n  endpoints\n\n  ### 4. How It Works\n  Every run:\n  1. Reads all posts from the sheet\n  2. Fetches fresh TikTok stats for each post via Upload Post API\n  3. Updates stats in the sheet (views, likes, etc.)\n  4. Reads the Agent Skill file (accumulated learnings)\n  5. Gemini 2.5 Pro creates an optimized carousel strategy based on\n   ALL past data + skill\n  6. Generates 4 images (9:16) with text via fal.ai\n  7. GPT-4.1 writes the TikTok description\n  8. Publishes to TikTok via Upload Post\n  9. Logs the new post to the sheet\n  10. GPT-4.1 analyzes all data and updates the skill file\n\n  ### 5. First Run\n  On the first run, the sheet is empty so the agent has no history.\n   It will create a baseline post. From the 2nd run onwards, it\n  starts learning and improving."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "1ce6e012-d178-4970-8785-008a6d6d5634",
  "connections": {
    "Set Vars": {
      "main": [
        [
          {
            "node": "Generate Image 1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Image 2",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Image 3",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Image 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Get Profile Analytics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Config": {
      "main": [
        [
          {
            "node": "Read Performance Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Think Tool": {
      "ai_tool": [
        [
          {
            "node": "Strategy Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Merge Images": {
      "main": [
        [
          {
            "node": "Aggregate Images",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Skill": {
      "main": [
        [
          {
            "node": "Write Skill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Stats": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Stats": {
      "main": [
        [
          {
            "node": "Update Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URL 1": {
      "main": [
        [
          {
            "node": "Merge Images",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URL 2": {
      "main": [
        [
          {
            "node": "Merge Images",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract URL 3": {
      "main": [
        [
          {
            "node": "Merge Images",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Extract URL 4": {
      "main": [
        [
          {
            "node": "Merge Images",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Log to Sheets": {
      "main": [
        [
          {
            "node": "Update Skill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Strategy Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Daily Schedule": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini 2.5 Pro": {
      "ai_languageModel": [
        [
          {
            "node": "Strategy Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Strategy Agent": {
      "main": [
        [
          {
            "node": "Set Vars",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Context": {
      "main": [
        [
          {
            "node": "Strategy Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Fresh Data": {
      "main": [
        [
          {
            "node": "Prepare Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Images": {
      "main": [
        [
          {
            "node": "Generate Description",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image 1": {
      "main": [
        [
          {
            "node": "Extract URL 1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image 2": {
      "main": [
        [
          {
            "node": "Extract URL 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image 3": {
      "main": [
        [
          {
            "node": "Extract URL 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Image 4": {
      "main": [
        [
          {
            "node": "Extract URL 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Agent Skill": {
      "main": [
        [
          {
            "node": "Read Fresh Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to TikTok": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Post Analytics": {
      "main": [
        [
          {
            "node": "Extract Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Description": {
      "main": [
        [
          {
            "node": "Upload to TikTok",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Performance Log": {
      "main": [
        [
          {
            "node": "Filter Posts for Stats",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Profile Analytics": {
      "main": [
        [
          {
            "node": "Read Agent Skill",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Posts for Stats": {
      "main": [
        [
          {
            "node": "Get Post Analytics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Set Config",
            "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.

Pro

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

About this workflow

This workflow runs daily to pull TikTok post and profile analytics from Upload Post, updates a Google Sheets performance log, uses Google Gemini and OpenAI to generate a new carousel concept and caption, creates four images via fal.ai, publishes to TikTok, and updates a…

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow automatically creates short-form AI videos using Sora 2 Cameos, powered by n8n and AI agents.

@Apify/N8N Nodes Apify, Google Sheets, Google Gemini +6
AI & RAG

Automatically generate viral short-form health videos using AI and publish them to social platforms with n8n and Veo 3. This workflow collects viral ideas, analyzes engagement patterns, generates AI v

@Apify/N8N Nodes Apify, Google Sheets, Google Gemini +6
AI & RAG

Overview

Google Gemini Chat, Tool Think, Output Parser Structured +6
AI & RAG

Veo3_POV_Vlogs. Uses googleSheets, toolThink, agent, outputParserStructured. Scheduled trigger; 18 nodes.

Google Sheets, Tool Think, Agent +4
AI & RAG

🎯 Create viral TikToks, Shorts, Reels, podcasts, and ASMR videos in minutes — all on autopilot.

OpenAI, HTTP Request, Form Trigger +7