{
  "name": "Create self-improving TikTok carousels with Gemini, fal.ai, OpenAI and Sheets",
  "nodes": [
    {
      "id": "4da2ba44-04b4-42c6-9e28-eeb5820a567f",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1248,
        -64
      ],
      "parameters": {
        "width": 480,
        "height": 896,
        "content": "## Auto-create self-improving TikTok carousels with Gemini, fal.ai & Upload-Post\n\n### How it works\n\n1. Runs daily (or manually) and loads product info and Upload-Post credentials from Set Config.\n2. Refreshes TikTok analytics of previous posts in the Google Sheets Performance Log via the Upload-Post API.\n3. Compiles post and profile analytics with the Agent Skill memory into a context for Gemini.\n4. Gemini plans a new 4-slide carousel; fal.ai renders the slides; OpenAI writes the caption.\n5. Upload-Post publishes the carousel to TikTok and logs the post to the sheet.\n6. OpenAI rewrites the Agent Skill with what it learned, so each run improves the next.\n\n### Setup steps\n\n- [ ] Copy the Google Sheet template (Performance Log + Agent Skill tabs) and paste its ID in Set Config\n- [ ] Add Google Sheets OAuth2 credentials to the four Sheets nodes\n- [ ] Add Google Gemini and OpenAI credentials\n- [ ] Add a fal.ai HTTP Header Auth credential (Authorization: Key <FAL_KEY>)\n- [ ] Create an Upload-Post account at https://www.upload-post.com, connect TikTok to a profile and paste user + API key in Set Config\n\n### Customization\n\nEdit product fields and the Strategy Agent prompt for your niche, change the fal.ai model or slide count, or add more platforms in the Upload to TikTok node."
      },
      "typeVersion": 1
    },
    {
      "id": "6d77fa31-8b35-448a-8de5-f3652a81d3be",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -688,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 512,
        "content": "## Trigger and configuration\n\nRuns daily or on demand. Set Config holds your product name/description and your Upload-Post username and API key, so every later step reads them from one place."
      },
      "typeVersion": 1
    },
    {
      "id": "afeb075f-58a5-45e3-a85f-32557fff6689",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -256,
        176
      ],
      "parameters": {
        "color": 7,
        "width": 1344,
        "height": 304,
        "content": "## Refresh analytics of past posts\n\nReads the Performance Log sheet, skips rows that already have fresh stats, fetches views/likes/comments/shares for each TikTok post from the Upload-Post analytics API and writes the engagement rate back to the sheet."
      },
      "typeVersion": 1
    },
    {
      "id": "7cdab8a2-1d7c-44da-9224-db3d1c6a51ab",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1120,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 320,
        "content": "## Build the learning context\n\nPulls profile-level TikTok analytics, reads the current Agent Skill memory and the refreshed log, and compiles everything into one context summary the agent can reason about."
      },
      "typeVersion": 1
    },
    {
      "id": "d386466b-8f29-49ce-83ec-684de91ffa71",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1840,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 576,
        "content": "## Strategy agent plans the carousel\n\nGemini uses the Think tool to analyse what worked, then returns a structured plan: hook/title, four slide image prompts, hashtags and an engagement question. Set Vars flattens the plan for the next steps."
      },
      "typeVersion": 1
    },
    {
      "id": "da25f7e5-306a-445b-befc-1f8c9223c420",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 784,
        "content": "## Render the four slides\n\nEach prompt is sent in parallel to fal.ai (nano-banana-2) to generate a 9:16 image; the four URLs are merged and aggregated into one item."
      },
      "typeVersion": 1
    },
    {
      "id": "df7f44cf-3342-4b67-a228-d45c37859047",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3248,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 688,
        "height": 320,
        "content": "## Caption, publish and log\n\nOpenAI writes a short TikTok caption, Upload-Post publishes the photo carousel to TikTok and the new post is appended to the Performance Log so it gets analytics on the next run."
      },
      "typeVersion": 1
    },
    {
      "id": "820b5060-b0dd-4d0a-bd95-10cfd4845fae",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3968,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 496,
        "height": 320,
        "content": "## Self-improve the Agent Skill\n\nOpenAI rewrites the Agent Skill text from the cumulative results and saves it back to Google Sheets, so the next run starts from what actually performed."
      },
      "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": "your-upload-post-username"
            },
            {
              "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": "YOUR_GOOGLE_SHEET_ID"
            }
          ]
        }
      },
      "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 }}"
        }
      },
      "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
            }
          }
        },
        "sendHeaders": true,
        "authentication": "none",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=ApiKey {{ $('Set Config').first().json.upload_post_api_key }}"
            }
          ]
        }
      },
      "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 }}"
        }
      },
      "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": [
        1168,
        304
      ],
      "parameters": {
        "url": "=https://api.upload-post.com/api/analytics/{{ $('Set Config').first().json.upload_post_user }}?platforms=tiktok",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "authentication": "none",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=ApiKey {{ $('Set Config').first().json.upload_post_api_key }}"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "0eef627e-b5ef-4144-b23b-ad184aa60200",
      "name": "Read Agent Skill",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1328,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Agent Skill"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "5af68d2e-5097-48e7-8314-b14d058da2d7",
      "name": "Read Fresh Data",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1520,
        304
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Performance Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Config').first().json.spreadsheet_id }}"
        }
      },
      "typeVersion": 4.5,
      "alwaysOutputData": true
    },
    {
      "id": "088aa2f3-0865-4720-ac1d-3906e55926ca",
      "name": "Prepare Context",
      "type": "n8n-nodes-base.code",
      "position": [
        1664,
        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": [
        2096,
        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": [
        1984,
        560
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "617342f1-a386-47e3-8aa4-7bd58d75d892",
      "name": "Gemini 2.5 Pro",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1888,
        560
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-3.1-pro-preview"
      },
      "typeVersion": 1
    },
    {
      "id": "24667f3d-8fbc-4129-8699-59af8a9c1132",
      "name": "Strategy Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1888,
        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": [
        2240,
        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": [
        2464,
        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"
      },
      "typeVersion": 4.2
    },
    {
      "id": "f7fef427-9734-4426-8cea-cab126811d62",
      "name": "Extract URL 1",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2656,
        64
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "2579c31e-17c0-4aa4-b964-0d065cf74668",
      "name": "Generate Image 2",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2464,
        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"
      },
      "typeVersion": 4.2
    },
    {
      "id": "3279ac16-2265-490e-aa57-6185e20c8431",
      "name": "Extract URL 2",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2656,
        224
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "1e28bcc4-7eda-45c1-aabd-d3f3258691dd",
      "name": "Generate Image 3",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2464,
        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"
      },
      "typeVersion": 4.2
    },
    {
      "id": "a9935c6f-54ea-4ce9-a2a2-7baebbed05ac",
      "name": "Extract URL 3",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2656,
        384
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "5d784b25-52b4-4390-b53f-8dea07a40f28",
      "name": "Generate Image 4",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2464,
        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"
      },
      "typeVersion": 4.2
    },
    {
      "id": "1fa984cd-ef7e-4b3a-b849-4dc4340b5ece",
      "name": "Extract URL 4",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        2656,
        544
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "images"
      },
      "typeVersion": 1
    },
    {
      "id": "b41373ce-c47f-4f4e-8453-337195544a9b",
      "name": "Merge Images",
      "type": "n8n-nodes-base.merge",
      "position": [
        2864,
        224
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3.1
    },
    {
      "id": "568262ab-b916-4112-85f5-c6b792e2a1d9",
      "name": "Aggregate Images",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        3072,
        256
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "6c53693c-13f5-4ea0-ad60-fd621882780a",
      "name": "Generate Description",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        3296,
        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."
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "074fcaeb-a280-4a6b-8e30-275976b0cc22",
      "name": "Upload to TikTok",
      "type": "n8n-nodes-upload-post.uploadPost",
      "position": [
        3632,
        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 }}"
      },
      "typeVersion": 1
    },
    {
      "id": "074de448-6e63-4521-a38b-869b636501ba",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3792,
        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 }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "5153b194-c65e-4d9e-a6e0-da1fd06ece9a",
      "name": "Update Skill",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        4016,
        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."
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "d595715b-88ac-4b9c-9946-ce8379a6728b",
      "name": "Write Skill",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        4320,
        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 }}"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "1a6eae6d-042c-48d2-a8f5-c425b4d6c8c8",
      "name": "When clicking ‘Execute workflow’",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -640,
        480
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "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 ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Set Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "active": false
}
