AutomationFlowsAI & RAG › Generate SEO Tour Blog Posts with Claude, Dataforseo, Google Docs, and Slack

Generate SEO Tour Blog Posts with Claude, Dataforseo, Google Docs, and Slack

ByAnas Ashfaq @anas-elandz on n8n.io

This workflow collects tour details via an n8n form, analyzes your website’s brand voice with Anthropic Claude, researches real SEO keywords and Google “People Also Ask” questions with DataForSEO, then writes a structured long-form article, saves it as a Google Doc, and posts…

Event trigger★★★★★ complexityAI-powered33 nodesForm TriggerAgentAnthropic ChatHTTP Request ToolHTTP RequestSlack
AI & RAG Trigger: Event Nodes: 33 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Agent → Form Trigger 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": "1Z6WMtiqL10cEobk",
  "meta": {
    "aiBuilderAssisted": true
  },
  "name": "AI SEO Blog Post Generator for Tour Operators (Claude + DataForSEO)",
  "tags": [],
  "nodes": [
    {
      "id": "08eb3c74-271e-442b-8f91-e97607a67c71",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2816,
        832
      ],
      "parameters": {
        "width": 480,
        "height": 704,
        "content": "## AI SEO Blog Post Generator for Tour Operators (Claude + DataForSEO)\n\n### How it works\n\n1. Triggered by form input, initiates SEO blog post generation.\n2. Gathers brand configuration and voice via AI.\n3. Generates and parses keywords using external API services.\n4. Uses keyword data to fetch SERP results and extract insights.\n5. Compiles insights into blog sections and assembles the full article.\n6. Uploads the final article to Google Docs and notifies via Slack.\n\n### Setup steps\n\n- [ ] Configure credentials for DataForSEO API.\n- [ ] Set up Google Drive API access for document upload.\n- [ ] Connect Slack API for notifications.\n\n### Customization\n\nAdjust the brand tone and style rules in the Brand Config node."
      },
      "typeVersion": 1
    },
    {
      "id": "ca6ee44b-8699-4bfa-afac-ab640a20f947",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3376,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 480,
        "content": "## Trigger and configuration setup\n\nInitiates the workflow and applies brand configurations."
      },
      "typeVersion": 1
    },
    {
      "id": "7ec4f699-2e1a-46fb-af2a-e4258d8927dd",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4208,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 272,
        "content": "## Process brand and keyword data\n\nParses brand analysis and builds keyword requests."
      },
      "typeVersion": 1
    },
    {
      "id": "ce4a7e94-e0cd-45cd-9d0d-cf08910aea7d",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5104,
        832
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Fetch and parse keyword volumes\n\nObtains keyword volume data from API and processes it."
      },
      "typeVersion": 1
    },
    {
      "id": "71af1299-5af3-43dc-9cf6-37c98ce8782c",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        5552,
        832
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Aggregate and build SERP requests\n\nAggregates keywords and prepares fetch requests for SERP data."
      },
      "typeVersion": 1
    },
    {
      "id": "e328c4c7-4a72-4323-ad51-92cedd9e2f8b",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6000,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 272,
        "content": "## Fetch and parse SERP data\n\nFetches SERP results and parses the data."
      },
      "typeVersion": 1
    },
    {
      "id": "f3c42077-fc7d-4c96-abc4-6d5bf78b3483",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6448,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 864,
        "height": 272,
        "content": "## Structure blog sections\n\nBuilds and writes individual sections based on parsed data."
      },
      "typeVersion": 1
    },
    {
      "id": "47ca8049-b464-46a2-a4c1-41c66fd6dcf0",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7344,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 272,
        "content": "## Assemble and compile article\n\nAggregates sections into a complete article."
      },
      "typeVersion": 1
    },
    {
      "id": "21fe3500-ac7a-457b-aa09-0937b251fc6c",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7792,
        848
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 272,
        "content": "## Upload and notify\n\nUploads the article to Google Docs and sends a Slack notification."
      },
      "typeVersion": 1
    },
    {
      "id": "5b04342f-6847-4727-9aa0-317cc846deb4",
      "name": "When Form Submitted",
      "type": "n8n-nodes-base.formTrigger",
      "position": [
        3424,
        960
      ],
      "parameters": {
        "options": {},
        "formTitle": "Generate an AI-Citation-Ready Blog Post for Your Tour",
        "formFields": {
          "values": [
            {
              "fieldName": "service_name",
              "fieldLabel": "Tour or Experience Name",
              "placeholder": "e.g. 7-Day Kenya Safari",
              "requiredField": true
            },
            {
              "fieldName": "description",
              "fieldLabel": "Trip overview (one sentence)",
              "placeholder": "e.g. A guided small-group wildlife safari through Maasai Mara and Amboseli",
              "requiredField": true
            },
            {
              "fieldName": "location",
              "fieldLabel": "Destination",
              "placeholder": "e.g. Nairobi, Kenya",
              "requiredField": true
            },
            {
              "fieldName": "website_url",
              "fieldLabel": "Your Website URL",
              "placeholder": "e.g. https://yourtourcompany.com",
              "requiredField": true
            },
            {
              "fieldName": "duration",
              "fieldLabel": "Duration",
              "placeholder": "e.g. 7 days / 6 nights"
            },
            {
              "fieldName": "price",
              "fieldLabel": "Price per person",
              "placeholder": "e.g. From EUR 1,450 per person"
            },
            {
              "fieldName": "group_size",
              "fieldLabel": "Group size",
              "placeholder": "e.g. Max 8 travellers"
            },
            {
              "fieldName": "best_time",
              "fieldLabel": "Best time to travel (optional)",
              "placeholder": "e.g. July to October for the Great Migration"
            },
            {
              "fieldName": "inclusions",
              "fieldType": "textarea",
              "fieldLabel": "What is included",
              "placeholder": "Airport transfers, accommodation, all game drives, park fees, meals from Day 2..."
            },
            {
              "fieldName": "itinerary",
              "fieldType": "textarea",
              "fieldLabel": "Day-by-day itinerary highlights",
              "placeholder": "Day 1: Arrive Nairobi, transfer to lodge. Day 2: Morning game drive in Maasai Mara..."
            },
            {
              "fieldName": "seo_country",
              "fieldLabel": "Target country for SEO",
              "defaultValue": "United States"
            },
            {
              "fieldName": "language",
              "fieldLabel": "Language",
              "defaultValue": "English"
            }
          ]
        },
        "formDescription": "Fill in your tour details. The workflow reads your website, researches keywords, finds real traveller questions, and writes a structured blog article optimised for Google and AI search engines."
      },
      "typeVersion": 2.5
    },
    {
      "id": "fa192910-834f-4373-8ccc-477611dd4263",
      "name": "Set Branding Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        3648,
        960
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg1",
              "name": "slack_channel_id",
              "type": "string",
              "value": "C07D3C8P57X"
            },
            {
              "id": "a1",
              "name": "data_integrity_rule",
              "type": "string",
              "value": "ONLY use facts from the form. If a detail is missing, write an honest placeholder like \"contact us for the full details\" \u2014 never invent inclusions, hotel names, transport types, distances, itinerary stops, or prices."
            },
            {
              "id": "a2",
              "name": "default_tone",
              "type": "string",
              "value": "Knowledgeable and direct. Writes like someone who has been on the trip and knows the destination well. No marketing hype or travel clich\u00e9s."
            },
            {
              "id": "a3",
              "name": "default_style_rules",
              "type": "string",
              "value": "Answer every H2 question in the first sentence of that section. Short paragraphs (3-4 sentences max). Bullets for lists of 3 or more items. Vary sentence length. Write to the traveller using \"you\"."
            },
            {
              "id": "a4",
              "name": "default_banned_phrases",
              "type": "string",
              "value": "unforgettable, breathtaking, once-in-a-lifetime, world-class, seamless, bucket list, magical, game-changing, wanderlust, paradise, hidden gem, truly, ultimate, remarkable, extraordinary, bespoke, curated, immersive"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "41f3272d-11d9-4fb6-95fb-957ee2332ad6",
      "name": "Brand Analysis Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        3872,
        960
      ],
      "parameters": {
        "text": "={{ \"Analyze the brand voice and ideal traveller profile for this tour operator: \" + $json.website_url + \"\\n\\nTour: \" + $json.service_name + \"\\nDescription: \" + $json.description + \"\\nDestination: \" + $json.location }}",
        "options": {
          "maxIterations": 15,
          "systemMessage": "You are a travel marketing strategist. Analyze a tour operator website to extract their real brand voice, writing style, and ideal traveller profile.\n\nYou have the \"Fetch Webpage\" tool. Follow these steps:\n1. Fetch the homepage URL from the user message.\n2. Try fetching /sitemap.xml appended to the domain. If it loads, extract 8-10 blog or content page URLs.\n3. If sitemap fails, extract internal links from the homepage navigation and body.\n4. Pick 5 diverse pages to read. Prioritise: blog posts, tour pages, About page. Skip: /contact, /login, /cart, /checkout, /privacy, /terms, /cookie, /booking.\n5. Fetch each selected page.\n6. Synthesise your findings from what you actually read.\n\nBase the analysis entirely on what you read. Do not guess or invent. If a page fails, try a different one.\n\nReturn ONLY valid JSON with no markdown or preamble:\n{\n  \"brand_name\": \"company name from the website\",\n  \"tone\": \"2-3 sentences describing the actual writing tone observed: formal or casual, expert or accessible, adventure-focused or luxury-focused\",\n  \"style_rules\": \"3-5 concrete patterns observed: sentence structure, vocabulary level, use of statistics or storytelling, paragraph length\",\n  \"banned_phrases\": \"comma-separated list of overused phrases or travel cliches found on this site that should be avoided\",\n  \"icp\": {\n    \"who\": \"one sentence: describe the typical traveller this operator targets\",\n    \"pain_points\": [\"pain 1\", \"pain 2\", \"pain 3\"],\n    \"goals\": [\"goal 1\", \"goal 2\", \"goal 3\"],\n    \"tone_for_them\": \"how this brand speaks to their travellers\"\n  }\n}"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "0395f0f1-6c3e-4551-a866-0958982ac034",
      "name": "Claude Sonnet Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        3888,
        1184
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "claude-sonnet-4-5-20250929",
          "cachedResultName": "Claude Sonnet 4.5"
        },
        "options": {
          "temperature": 0.3,
          "maxTokensToSample": 2000
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "9fed5cad-4e7c-407c-8eb1-1cb3b4d65cfa",
      "name": "Fetch Web Content",
      "type": "n8n-nodes-base.httpRequestTool",
      "position": [
        4016,
        1184
      ],
      "parameters": {
        "url": "={{ $fromAI(\"url\", \"The full URL of the webpage to fetch and read\") }}",
        "options": {
          "timeout": 15000,
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          },
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "maxLength": 4000,
        "onlyContent": true,
        "responseType": "html",
        "elementsToOmit": "nav, footer, header, script, style, iframe, .cookie-notice, .cookie-banner",
        "optimizeResponse": true,
        "truncateResponse": true
      },
      "typeVersion": 4.4
    },
    {
      "id": "014f50cd-a9a6-4922-9378-7b38663f90a2",
      "name": "Parse Analysis Output",
      "type": "n8n-nodes-base.code",
      "position": [
        4256,
        960
      ],
      "parameters": {
        "jsCode": "var agentOutput = String($input.first().json.output || '');\nvar defaults = $('Set Branding Parameters').first().json;\nvar brand = {};\ntry { var m = agentOutput.match(/\\{[\\s\\S]*\\}/); brand = JSON.parse(m ? m[0] : agentOutput); } catch(e) { brand = {}; }\nvar icp = brand.icp || { who: 'adventure travellers and holiday-makers', pain_points: [], goals: [] };\nreturn [{ json: {\n  brand_name: String(brand.brand_name || ''),\n  tone: String(brand.tone || defaults.default_tone),\n  style_rules: String(brand.style_rules || defaults.default_style_rules),\n  banned_phrases: String(brand.banned_phrases || defaults.default_banned_phrases),\n  icp_str: JSON.stringify(icp),\n  data_integrity_rule: String(defaults.data_integrity_rule)\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "e9105a82-13da-44b5-8c28-4314d6da08ad",
      "name": "Prepare Keyword Query",
      "type": "n8n-nodes-base.code",
      "position": [
        4480,
        960
      ],
      "parameters": {
        "jsCode": "const form = $('When Form Submitted').first().json;\nconst brand = $input.first().json;\nconst icpWho = brand.icp_str ? (JSON.parse(brand.icp_str).who || '') : '';\nconst prompt = 'You are an SEO keyword researcher specialising in travel and tour operators.\\n\\nGenerate 8 seed keywords for a blog article about this tour:\\nTour name: ' + form.service_name + '\\nDestination: ' + form.location + '\\nDescription: ' + form.description + '\\nTarget traveller: ' + icpWho + '\\n\\nRules:\\n- Include the exact tour name as one keyword\\n- At least 2 keywords with the destination name\\n- At least 2 intent keywords (e.g. \"best\", \"cost\", \"how to book\", \"worth it\")\\n- At least 1 comparison keyword (e.g. \"vs\", \"alternative\", \"review\")\\n- 2-5 words per keyword\\n- Output ONLY valid JSON: {\"seed_keywords\": [\"kw1\", \"kw2\"]}';\nconst requestBodyStr = JSON.stringify({ model: 'claude-opus-4-5', max_tokens: 400, messages: [{ role: 'user', content: prompt }] });\nreturn [{ json: { requestBodyStr } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ffb32139-a3ab-4d21-9473-67d1ac50d27b",
      "name": "Post for Seed Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4704,
        960
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "body": "={{ $json.requestBodyStr }}",
        "method": "POST",
        "options": {
          "timeout": 60000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "nodeCredentialType": "anthropicApi"
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "1ff83623-bf41-4ae5-b39d-3af492f483ba",
      "name": "Extract Seed Keywords",
      "type": "n8n-nodes-base.code",
      "position": [
        4928,
        960
      ],
      "parameters": {
        "jsCode": "const resp = $input.first().json;\nconst blocks = (resp.content || []).filter(b => b.type === 'text');\nconst text = blocks.length > 0 ? blocks[blocks.length - 1].text : '{}';\nlet keywords = [];\ntry { const parsed = JSON.parse(text); keywords = parsed.seed_keywords || []; } catch(e) { const m = text.match(/\"seed_keywords\"\\s*:\\s*(\\[[\\s\\S]*?\\])/); if (m) { try { keywords = JSON.parse(m[1]); } catch(e2) {} } }\nconst form = $('When Form Submitted').first().json;\nif (keywords.length === 0) keywords = [form.service_name];\nconst country = String(form.seo_country || 'United States');\nconst lang = String(form.language || 'English');\nreturn keywords.slice(0, 8).map(function(kw) {\n  const bodyArr = [{ keyword: kw, location_name: country, language_name: lang, include_seed_keyword: true, filters: [['keyword_info.search_volume', '>=', 10]], order_by: [{ 'keyword_info.search_volume': 'desc' }], limit: 5 }];\n  return { json: { keyword: String(kw), seo_country: country, language: lang, bodyStr: JSON.stringify(bodyArr) } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "1ca2114e-3a30-45f4-b1e9-fc9474f017b2",
      "name": "Post Keyword Volumes",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5152,
        960
      ],
      "parameters": {
        "url": "https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live",
        "body": "={{ $json.bodyStr }}",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "c7792485-9620-4f00-b418-d50035c6653b",
      "name": "Process Keyword Data",
      "type": "n8n-nodes-base.code",
      "position": [
        5376,
        960
      ],
      "parameters": {
        "jsCode": "const inputItems = $input.all();\nconst seedItems = $('Extract Seed Keywords').all();\nconst result = [];\nfor (var i = 0; i < inputItems.length; i++) {\n  var resp = inputItems[i].json;\n  var tasks = resp.tasks || [];\n  var output = [];\n  var seen = {};\n  for (var t = 0; t < tasks.length; t++) {\n    var taskResults = tasks[t].result || [];\n    for (var r = 0; r < taskResults.length; r++) {\n      var items = taskResults[r].items || [];\n      for (var k = 0; k < items.length; k++) {\n        var kwItem = items[k];\n        var kw = kwItem.keyword;\n        if (!kw || seen[kw.toLowerCase()]) continue;\n        seen[kw.toLowerCase()] = true;\n        output.push({ keyword: String(kw), search_volume: Number((kwItem.keyword_info && kwItem.keyword_info.search_volume) || 0), competition: String((kwItem.keyword_info && kwItem.keyword_info.competition_level) || 'unknown'), intent: String((kwItem.search_intent_info && kwItem.search_intent_info.main_intent) || 'informational') });\n      }\n    }\n  }\n  var seedKw = (seedItems[i] && seedItems[i].json && seedItems[i].json.keyword) ? String(seedItems[i].json.keyword) : 'unknown';\n  if (output.length === 0) output.push({ keyword: seedKw, search_volume: 0, competition: 'unknown', intent: 'informational' });\n  for (var j = 0; j < Math.min(output.length, 3); j++) result.push({ json: { keyword: output[j].keyword, search_volume: output[j].search_volume, competition: output[j].competition, intent: output[j].intent } });\n}\nreturn result;"
      },
      "typeVersion": 2
    },
    {
      "id": "71739ca0-e47e-4c11-b68e-300125fb724d",
      "name": "Aggregate Keyword Insights",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        5600,
        960
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "keywords"
      },
      "typeVersion": 1
    },
    {
      "id": "540cf703-ca3e-463a-9092-cf6f2b85bc14",
      "name": "Construct SERP Query",
      "type": "n8n-nodes-base.code",
      "position": [
        5824,
        960
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst keywords = data.keywords || [];\nconst sorted = keywords.slice().sort((a, b) => (b.search_volume || 0) - (a.search_volume || 0));\nconst form = $('When Form Submitted').first().json;\nconst primaryKeyword = (sorted[0] && sorted[0].keyword) ? String(sorted[0].keyword) : String(form.service_name);\nconst secondaryKws = sorted.slice(1, 6).map(k => k.keyword).filter(Boolean);\nconst secondaryKeywordsStr = secondaryKws.join(', ');\nconst country = String(form.seo_country || 'United States');\nconst lang = String(form.language || 'English');\nconst serpBodyArr = [{ keyword: primaryKeyword, location_name: country, language_name: lang, depth: 20, people_also_ask_click_depth: 2 }];\nreturn [{ json: { primary_keyword: primaryKeyword, secondary_keywords_str: secondaryKeywordsStr, seo_country: country, language: lang, serpBodyStr: JSON.stringify(serpBodyArr) } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "b1f0d7d7-6c65-4d30-8815-2ff71e240e04",
      "name": "Post SERP and PAA Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6048,
        960
      ],
      "parameters": {
        "url": "https://api.dataforseo.com/v3/serp/google/organic/live/advanced",
        "body": "={{ $json.serpBodyStr }}",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "authentication": "genericCredentialType",
        "rawContentType": "application/json",
        "genericAuthType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "7da969b3-df1c-4f01-b744-26f740df5ce5",
      "name": "Analyze SERP Output",
      "type": "n8n-nodes-base.code",
      "position": [
        6272,
        960
      ],
      "parameters": {
        "jsCode": "const tasks = $input.first().json.tasks;\nconst items = (tasks && tasks[0] && tasks[0].result && tasks[0].result[0] && tasks[0].result[0].items) ? tasks[0].result[0].items : [];\nvar paaQuestions = [];\nvar organicTitles = [];\nfor (var i = 0; i < items.length; i++) {\n  var item = items[i];\n  if (item.type === 'organic' && item.title) organicTitles.push(String(item.title));\n  if (item.type === 'people_also_ask' && item.items) { for (var j = 0; j < item.items.length; j++) { if (item.items[j].title) paaQuestions.push(String(item.items[j].title)); } }\n}\nvar serpReq = $('Construct SERP Query').first().json;\nvar uniquePaa = [];\nvar seen = {};\nfor (var p = 0; p < paaQuestions.length; p++) { if (!seen[paaQuestions[p]]) { seen[paaQuestions[p]] = true; uniquePaa.push(paaQuestions[p]); } }\nreturn [{ json: {\n  paa_questions_str: JSON.stringify(uniquePaa.slice(0, 10)),\n  competitor_titles_str: JSON.stringify(organicTitles.slice(0, 5)),\n  primary_keyword: String(serpReq.primary_keyword),\n  secondary_keywords_str: String(serpReq.secondary_keywords_str || '')\n} }];"
      },
      "typeVersion": 2
    },
    {
      "id": "96f95108-f9d1-45a6-a345-67d155e5de7d",
      "name": "Organize Content Sections",
      "type": "n8n-nodes-base.code",
      "position": [
        6496,
        960
      ],
      "parameters": {
        "jsCode": "var sections = [\n  { section_id: 1, section_name: 'Title + Meta', h2_topic: 'none', prompt: 'Write an SEO title and meta description for a tour operator blog article. Title: include the tour name and destination, max 60 chars, make it informational not salesy (e.g. \"7-Day Kenya Safari: Costs, Itinerary and What to Expect\"). Meta: 150-160 chars, answer-first, include destination and a secondary keyword naturally, end with a soft CTA. Return JSON: { title, meta_description, slug }.' },\n  { section_id: 2, section_name: 'Introduction', h2_topic: 'none \u2014 no H2 in this section, content goes directly under the H1', prompt: 'Write a 3-paragraph HTML introduction. Target 200-250 words. No H2 tag.\\nPara 1 (60-80 words): What is this tour, where does it go, how long is it, what makes it different. Include the primary keyword in the first sentence.\\nPara 2 (80-100 words): Who typically travels on this tour and why they choose it. Reference the destination. Embed 1-2 secondary keywords naturally.\\nPara 3 (60-70 words): What this article answers \u2014 list the topics covered (pricing, itinerary, inclusions, best time, who it suits). Return JSON: { html }.' },\n  { section_id: 3, section_name: 'Tour Inclusions', h2_topic: 'what is included in the tour \u2014 ONLY this topic, nothing else', prompt: 'H2 TOPIC: what this tour includes. Scan the PAA questions for the one most closely about inclusions or what is in the package. Use it as the H2 if it fits. If no good match, write: \"What Does [Tour Name] Include?\" \u2014 do NOT use a question about cost, itinerary, or who the tour is for.\\nTarget 150-200 words. First sentence answers the H2 directly.\\nIf the inclusions field has items, list them as HTML bullets (max 8 items). If empty, write: \"The full inclusion list is available on request.\" Never invent bullets.\\nReturn JSON: { html }.' },\n  { section_id: 4, section_name: 'Day-by-Day Itinerary', h2_topic: 'the daily programme or itinerary \u2014 ONLY this topic, nothing else', prompt: 'H2 TOPIC: the day-by-day itinerary. Scan PAA for the question closest to \"what is the itinerary\" or \"day by day\". Use it as the H2 if it fits. If no good match, write: \"What Is the Day-by-Day Itinerary for [Tour Name]?\"\\nTarget 200-300 words.\\nIf the itinerary field has day-by-day content: write each day as an H3 formatted \"Day 1: [Short Title]\" with 2-4 sentences. Each day: 40-60 words.\\nIf the itinerary field is empty, write: \"The full day-by-day programme is provided in your booking confirmation. Contact us to receive the detailed itinerary.\"\\nDo NOT invent activities, locations, hotel names, or transport details.\\nReturn JSON: { html }.' },\n  { section_id: 5, section_name: 'Pricing', h2_topic: 'tour cost or price \u2014 ONLY this topic, nothing else', prompt: 'H2 TOPIC: how much the tour costs. Scan PAA for the question closest to \"how much does it cost\" or \"what is the price\". Use it as the H2 if it fits. If no good match, write: \"How Much Does [Tour Name] Cost Per Person?\"\\nTarget 120-180 words. First sentence states the price from the form. Then explain 2-3 factors that affect price using only form data. If price not specified, write: \"Pricing is available on request and varies by group size and travel dates.\" Return JSON: { html }.' },\n  { section_id: 6, section_name: 'Who Is This Tour For', h2_topic: 'the ideal traveller \u2014 who this tour suits \u2014 ONLY this topic, nothing else', prompt: 'H2 TOPIC: who this tour is designed for. Scan PAA for the question closest to \"who is this for\" or \"is this suitable for\". Use it as the H2 if it fits. If no good match, write: \"Who Is [Tour Name] Designed For?\"\\nTarget 150-200 words. Draw from the ICP data and the tour description.\\nWrite two short lists:\\n- \"This tour suits you if\" \u2014 3 bullets\\n- \"This tour may not suit you if\" \u2014 2 bullets\\nReturn JSON: { html }.' },\n  { section_id: 7, section_name: 'Best Time to Go', h2_topic: 'when to travel or best time of year \u2014 ONLY this topic, nothing else', prompt: 'H2 TOPIC: the best time of year to take this tour. Scan PAA for the question closest to \"best time to visit\" or \"when to go\". Use it as the H2 if it fits. If no good match, write: \"When Is the Best Time to Go on [Tour Name]?\"\\nTarget 120-160 words. First sentence: direct answer (best months or season). Use the best_time field if provided. If not, write factual seasonal advice for the destination. Return JSON: { html }.' },\n  { section_id: 8, section_name: 'FAQ', h2_topic: 'frequently asked questions \u2014 use remaining PAA questions as H3 headings', prompt: 'H2: \"Frequently Asked Questions About [Tour Name]\". Then use 5-6 PAA questions as H3 headings \u2014 pick ones NOT already used as H2s in sections 3-7.\\nEach answer: 2-3 sentences (50-80 words). If unknown, write \"Contact us for details.\"\\nReturn JSON: { html, faq_items } where faq_items is [ { question, answer } ].' }\n];\nreturn sections.map(function(s) {\n  return { json: { section_id: s.section_id, section_name: String(s.section_name), h2_topic: String(s.h2_topic), section_prompt: String(s.prompt) } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "6d52ab0d-b50c-4680-a1cc-d5b5368ba161",
      "name": "Formulate Section Request",
      "type": "n8n-nodes-base.code",
      "position": [
        6720,
        960
      ],
      "parameters": {
        "jsCode": "var form = $('When Form Submitted').first().json;\nvar bv = $('Parse Analysis Output').first().json;\nvar serp = $('Analyze SERP Output').first().json;\nvar icp = bv.icp_str || '{}';\nvar paaQuestions = serp.paa_questions_str || '[]';\nvar competitorTitles = serp.competitor_titles_str || '[]';\nvar secondaryKws = serp.secondary_keywords_str || '';\n\nvar systemPrompt = 'You are a professional travel writer and content strategist for a tour operator. Write like someone who knows this destination and this type of traveller well.\\n\\n'\n  + 'Tour operator brand voice: ' + bv.tone + '\\n'\n  + 'Style rules: ' + bv.style_rules + '\\n'\n  + 'Banned words and cliches: ' + bv.banned_phrases + '\\n'\n  + 'Ideal traveller profile: ' + icp\n  + '\\n\\nKEYWORD STRATEGY:\\n'\n  + 'Primary keyword: embed in the first sentence of the introduction, in at least one H2, and in the conclusion or FAQ.\\n'\n  + 'Secondary keywords: ' + (secondaryKws || 'none') + ' \u2014 weave these into sections where they fit naturally. Do not force them. Aim to use each at least once across the full article.\\n'\n  + '\\nH2 UNIQUENESS RULE \u2014 CRITICAL:\\n'\n  + 'Each section has a unique assigned H2 topic (shown in the task). You MUST write an H2 that matches that topic exactly. Never write an H2 that overlaps with another section topic.\\n'\n  + '\\nSEO + GEO CONTENT RULES:\\n'\n  + '1. ANSWER-FIRST: every section answers the H2 question in the first sentence.\\n'\n  + '2. Each section must be 120-300 words. Thin sections under 80 words weaken SEO.\\n'\n  + '3. ENTITY RICHNESS: use specific named entities from form data \u2014 destination names, tour name, price with currency, duration, group size, wildlife or cultural highlights.\\n'\n  + '4. Use concrete numbers: \"7 days / 6 nights\", \"from EUR 1,450 per person\", \"maximum 8 travellers\".\\n'\n  + '\\nHUMAN WRITING RULES \u2014 NEVER BREAK:\\n'\n  + '1. No em dashes. Replace with a period, comma, or rewrite.\\n'\n  + '2. No exclamation marks in body paragraphs.\\n'\n  + '3. No AI openers: Think about, Imagine, Dive into, Discover, In this guide, Picture yourself.\\n'\n  + '4. No travel cliches: unforgettable, breathtaking, once-in-a-lifetime, world-class, magical, bucket list, hidden gem.\\n'\n  + '5. No filler words: basically, essentially, ultimately, seamlessly, truly, really.\\n'\n  + '6. Vary sentence length \u2014 mix short (6-8 words) with longer (18-22 words) sentences.\\n'\n  + '7. Write directly to the traveller using \"you\".\\n'\n  + '\\nDATA ACCURACY \u2014 NEVER BREAK:\\n'\n  + '1. ' + bv.data_integrity_rule + '\\n'\n  + '2. Verify every bullet exists in form data before writing it.\\n'\n  + '3. Never invent: hotel names, lodge names, transport types, distances, park fees, or itinerary stops.\\n'\n  + '\\nOutput ONLY valid JSON. No markdown fences. No preamble.';\n\nvar userBase = 'TOUR DATA (only facts you may use):\\n'\n  + 'Tour name: ' + form.service_name + '\\n'\n  + 'Trip overview: ' + form.description + '\\n'\n  + 'Destination: ' + form.location + '\\n'\n  + 'Duration: ' + (form.duration || 'not specified') + '\\n'\n  + 'Price: ' + (form.price || 'not specified') + '\\n'\n  + 'Group size: ' + (form.group_size || 'not specified') + '\\n'\n  + 'Best time to travel: ' + (form.best_time || 'not specified') + '\\n'\n  + 'What is included: ' + (form.inclusions || 'not specified \u2014 do not invent') + '\\n'\n  + 'Day-by-day itinerary: ' + (form.itinerary || 'not specified \u2014 do not invent') + '\\n'\n  + '\\nSEO CONTEXT:\\n'\n  + 'Primary keyword: ' + serp.primary_keyword + '\\n'\n  + 'Secondary keywords: ' + (secondaryKws || 'none') + '\\n'\n  + 'People Also Ask questions: ' + paaQuestions + '\\n'\n  + 'Competitor titles (context only, do not copy): ' + competitorTitles;\n\nreturn $input.all().map(function(item) {\n  var h2Rule = '\\n\\nH2 ASSIGNED TOPIC FOR THIS SECTION: ' + item.json.h2_topic + '\\nWrite your H2 to match this topic exactly. Do not write an H2 about any other topic.';\n  var userPrompt = userBase + h2Rule + '\\n\\nTASK: ' + item.json.section_prompt;\n  var reqObj = { model: 'claude-opus-4-5', max_tokens: 2000, system: systemPrompt, messages: [{ role: 'user', content: userPrompt }] };\n  return { json: { requestBodyStr: JSON.stringify(reqObj), section_id: item.json.section_id, section_name: item.json.section_name } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "e9fe6396-39b4-4132-9058-cb5721b35bf7",
      "name": "Post to Generate Section",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        6944,
        960
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "body": "={{ $json.requestBodyStr }}",
        "method": "POST",
        "options": {
          "timeout": 90000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "rawContentType": "application/json",
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "nodeCredentialType": "anthropicApi"
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "1a49042f-8607-4621-b7f0-d169b7fed920",
      "name": "Decode Section Response",
      "type": "n8n-nodes-base.code",
      "position": [
        7168,
        960
      ],
      "parameters": {
        "jsCode": "var writeItems = $input.all();\nvar buildItems = $('Formulate Section Request').all();\nreturn writeItems.map(function(item, i) {\n  var resp = item.json;\n  var blocks = (resp.content || []).filter(function(b) { return b.type === 'text'; });\n  var text = blocks.length > 0 ? blocks[blocks.length - 1].text : '{}';\n  var parsed = {};\n  try { var m = text.match(/\\{[\\s\\S]*\\}/); parsed = JSON.parse(m ? m[0] : text); } catch(e) { parsed = { html: String(text) }; }\n  var buildItem = buildItems[i] ? buildItems[i].json : { section_id: 0, section_name: 'Unknown' };\n  return { json: { section_id: Number(buildItem.section_id), section_name: String(buildItem.section_name), content_str: JSON.stringify(parsed) } };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "e4ecb821-b609-472d-93e6-e446646339f9",
      "name": "Combine Section Outputs",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        7392,
        960
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "sections"
      },
      "typeVersion": 1
    },
    {
      "id": "bced3c11-22f5-4461-baff-54cb3e0787cb",
      "name": "Assemble Full Article",
      "type": "n8n-nodes-base.code",
      "position": [
        7616,
        960
      ],
      "parameters": {
        "jsCode": "var allData = $input.first().json;\nvar sections = (allData.sections || []).slice().sort(function(a, b) { return (a.section_id || 0) - (b.section_id || 0); });\nvar form = $('When Form Submitted').first().json;\nvar serpData = $('Analyze SERP Output').first().json;\nvar brandData = $('Parse Analysis Output').first().json;\nvar titleContent = {};\nvar faqContent = {};\nfor (var i = 0; i < sections.length; i++) {\n  var s = sections[i];\n  var c = {};\n  try { c = JSON.parse(s.content_str || '{}'); } catch(e) { c = {}; }\n  if (s.section_id === 1) titleContent = c;\n  if (s.section_id === 8) faqContent = c;\n}\nvar title = String(titleContent.title || form.service_name);\nvar metaDesc = String(titleContent.meta_description || '');\nvar slug = String(titleContent.slug || '');\nvar primaryKw = String(serpData.primary_keyword || form.service_name);\nvar secondaryKws = String(serpData.secondary_keywords_str || '');\nvar brandName = String(brandData.brand_name || '');\nvar bodyHtml = '';\nfor (var j = 0; j < sections.length; j++) {\n  if (sections[j].section_id !== 1) {\n    var cnt = {};\n    try { cnt = JSON.parse(sections[j].content_str || '{}'); } catch(e) { cnt = {}; }\n    if (cnt.html) bodyHtml += cnt.html + '\\n';\n  }\n}\nvar faqItems = faqContent.faq_items || [];\nvar schemaJson = '';\nif (faqItems.length > 0) {\n  var schema = { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faqItems.map(function(f) { return { '@type': 'Question', name: f.question, acceptedAnswer: { '@type': 'Answer', text: f.answer } }; }) };\n  schemaJson = JSON.stringify(schema, null, 2);\n}\nvar docsHtml = '<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>' + title + '</title></head><body>';\ndocsHtml += '<h1>' + title + '</h1>';\ndocsHtml += '<table><tr><td><strong>Tour</strong></td><td>' + form.service_name + '</td></tr>';\ndocsHtml += '<tr><td><strong>Destination</strong></td><td>' + form.location + '</td></tr>';\ndocsHtml += '<tr><td><strong>Primary keyword</strong></td><td>' + primaryKw + '</td></tr>';\ndocsHtml += '<tr><td><strong>Secondary keywords</strong></td><td>' + secondaryKws + '</td></tr>';\ndocsHtml += '<tr><td><strong>Meta description</strong></td><td>' + metaDesc + '</td></tr>';\ndocsHtml += '<tr><td><strong>Slug</strong></td><td>' + slug + '</td></tr></table><hr>';\ndocsHtml += bodyHtml;\nif (schemaJson) { docsHtml += '<hr><h2>FAQPage JSON-LD Schema</h2><p>Paste this into your page head for AI Overview eligibility.</p><pre>' + schemaJson.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</pre>'; }\ndocsHtml += '</body></html>';\nvar slackMsg = '*New tour article ready* :pencil:\\n*Tour:* ' + form.service_name + '\\n*Destination:* ' + form.location + '\\n*Primary keyword:* ' + primaryKw + '\\n*Secondary keywords:* ' + (secondaryKws || 'none') + '\\n*Brand:* ' + (brandName || form.website_url) + '\\n*FAQ items:* ' + faqItems.length;\nreturn [{ json: { docs_html: docsHtml, article_title: title, meta_description: metaDesc, primary_keyword: primaryKw, secondary_keywords: secondaryKws, slug: slug, slack_message_base: slackMsg } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4c2ca158-1673-480e-9bf0-a92fe291fdf1",
      "name": "Prepare for Drive Upload",
      "type": "n8n-nodes-base.code",
      "position": [
        7840,
        960
      ],
      "parameters": {
        "jsCode": "var item = $input.first().json;\nvar title = String(item.article_title || 'Article');\nvar htmlContent = String(item.docs_html || '');\nvar slackBase = String(item.slack_message_base || '');\nvar boundary = 'n8nblog2024';\nvar metadata = JSON.stringify({ name: title, mimeType: 'application/vnd.google-apps.document' });\nvar parts = ['--' + boundary, 'Content-Type: application/json; charset=UTF-8', '', metadata, '--' + boundary, 'Content-Type: text/html; charset=UTF-8', '', htmlContent, '--' + boundary + '--'];\nvar body = parts.join('\\r\\n');\nreturn [{ json: { multipart_body: body, article_title: title, slack_message_base: slackBase } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "e83b6f10-db40-4214-8946-5a077f918eb2",
      "name": "Upload to Google Drive",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        8064,
        960
      ],
      "parameters": {
        "url": "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,mimeType",
        "body": "={{ $json.multipart_body }}",
        "method": "POST",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendBody": true,
        "contentType": "raw",
        "authentication": "predefinedCredentialType",
        "rawContentType": "multipart/related; boundary=n8nblog2024",
        "nodeCredentialType": "googleDriveOAuth2Api"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "17bcd346-c10d-49c6-a886-fa4e35a46a6d",
      "name": "Post Notification to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        8288,
        960
      ],
      "parameters": {
        "text": "={{ $('Assemble Full Article').first().json.slack_message_base + \"\\n*Google Doc:* \" + ($json.id ? \"https://docs.google.com/document/d/\" + $json.id + \"/edit\" : \"Error \u2014 check Google Drive credential\") }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Set Branding Parameters').first().json.slack_channel_id }}"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.4
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "8a65bde7-3a00-496d-92c2-57f7dd1acd3d",
  "connections": {
    "Fetch Web Content": {
      "ai_tool": [
        [
          {
            "node": "Brand Analysis Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Analyze SERP Output": {
      "main": [
        [
          {
            "node": "Organize Content Sections",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet Model": {
      "ai_languageModel": [
        [
          {
            "node": "Brand Analysis Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "When Form Submitted": {
      "main": [
        [
          {
            "node": "Set Branding Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Brand Analysis Agent": {
      "main": [
        [
          {
            "node": "Parse Analysis Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Construct SERP Query": {
      "main": [
        [
          {
            "node": "Post SERP and PAA Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Keyword Volumes": {
      "main": [
        [
          {
            "node": "Process Keyword Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Keyword Data": {
      "main": [
        [
          {
            "node": "Aggregate Keyword Insights",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Full Article": {
      "main": [
        [
          {
            "node": "Prepare for Drive Upload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Seed Keywords": {
      "main": [
        [
          {
            "node": "Post Keyword Volumes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Analysis Output": {
      "main": [
        [
          {
            "node": "Prepare Keyword Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Keyword Query": {
      "main": [
        [
          {
            "node": "Post for Seed Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post for Seed Keywords": {
      "main": [
        [
          {
            "node": "Extract Seed Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload to Google Drive": {
      "main": [
        [
          {
            "node": "Post Notification to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Section Outputs": {
      "main": [
        [
          {
            "node": "Assemble Full Article",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decode Section Response": {
      "main": [
        [
          {
            "node": "Combine Section Outputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Branding Parameters": {
      "main": [
        [
          {
            "node": "Brand Analysis Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post to Generate Section": {
      "main": [
        [
          {
            "node": "Decode Section Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare for Drive Upload": {
      "main": [
        [
          {
            "node": "Upload to Google Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Formulate Section Request": {
      "main": [
        [
          {
            "node": "Post to Generate Section",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Organize Content Sections": {
      "main": [
        [
          {
            "node": "Formulate Section Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post SERP and PAA Request": {
      "main": [
        [
          {
            "node": "Analyze SERP Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Keyword Insights": {
      "main": [
        [
          {
            "node": "Construct SERP Query",
            "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 collects tour details via an n8n form, analyzes your website’s brand voice with Anthropic Claude, researches real SEO keywords and Google “People Also Ask” questions with DataForSEO, then writes a structured long-form article, saves it as a Google Doc, and posts…

Source: https://n8n.io/workflows/16297/ — 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 n8n workflow provides a secure, enterprise-grade response system for AWS IAM access key compromises with built-in form submission and human approval mechanisms. When an AWS access key is suspecte

HTTP Request, Agent, Slack +3
AI & RAG

The AI-Powered Shopify SEO Content Automation is an enterprise-grade workflow that transforms product content creation for e-commerce stores. This sophisticated multi-agent system integrates GPT-4o, C

Perplexity Tool, Memory Buffer Window, Agent +15
AI & RAG

Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 91 nodes.

Form Trigger, Chain Llm, Output Parser Structured +8
AI & RAG

Content - Newsletter Agent. Uses formTrigger, chainLlm, outputParserStructured, httpRequest. Event-driven trigger; 87 nodes.

Form Trigger, Chain Llm, Output Parser Structured +7
AI & RAG

This workflow contains community nodes that are only compatible with the self-hosted version of n8n.

Form Trigger, Google Sheets, HTTP Request +3