{
  "id": "SqKRcjj5T57lXv0Z",
  "name": "Audit any website SEO with AI and Google PageSpeed using Groq and Google Sheets",
  "tags": [
    {
      "id": "7MBrqY6qk4mY8vJP",
      "name": "ai-content",
      "createdAt": "2026-06-14T15:55:44.934Z",
      "updatedAt": "2026-06-14T15:55:44.934Z"
    },
    {
      "id": "kchX80UgGVQMYFTq",
      "name": "AI Automation",
      "createdAt": "2026-06-14T13:36:36.357Z",
      "updatedAt": "2026-06-14T13:36:36.357Z"
    }
  ],
  "nodes": [
    {
      "id": "a1cf1849-cfa2-402b-96ff-d46a4e09c3a6",
      "name": "Receive URL for SEO audit",
      "type": "n8n-nodes-base.webhook",
      "position": [
        752,
        368
      ],
      "parameters": {
        "path": "seo-auditor",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "d6217242-8ff4-4537-9733-94595a193fca",
      "name": "Download target page HTML",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        992,
        368
      ],
      "parameters": {
        "url": "={{ $json.body.url }}",
        "options": {
          "timeout": 15000,
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e8a891a0-0872-4e0e-a23b-7800e4674295",
      "name": "Page downloaded successfully?",
      "type": "n8n-nodes-base.if",
      "position": [
        1200,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-html",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              },
              "leftValue": "={{ $json.data }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "0146abf3-b53d-4395-8da6-bd36fed8de4a",
      "name": "Extract SEO Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1472,
        272
      ],
      "parameters": {
        "jsCode": "const html = $input.first().json.data;\nconst url = $('Webhook Trigger').first().json.body.url;\n\n// Extract title\nconst titleMatch = html.match(/<title[^>]*>(.*?)<\\/title>/i);\nconst title = titleMatch ? titleMatch[1].trim() : '';\n\n// Extract meta description\nconst metaDescMatch = html.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"'](.*?)[\"'][^>]*>/i)\n  || html.match(/<meta[^>]*content=[\"'](.*?)[\"'][^>]*name=[\"']description[\"'][^>]*>/i);\nconst metaDescription = metaDescMatch ? metaDescMatch[1].trim() : '';\n\n// Extract meta keywords\nconst metaKeyMatch = html.match(/<meta[^>]*name=[\"']keywords[\"'][^>]*content=[\"'](.*?)[\"'][^>]*>/i);\nconst metaKeywords = metaKeyMatch ? metaKeyMatch[1].trim() : '';\n\n// Extract all headings\nconst h1s = (html.match(/<h1[^>]*>(.*?)<\\/h1>/gi) || []).map(h => h.replace(/<[^>]+>/g, '').trim());\nconst h2s = (html.match(/<h2[^>]*>(.*?)<\\/h2>/gi) || []).map(h => h.replace(/<[^>]+>/g, '').trim());\nconst h3s = (html.match(/<h3[^>]*>(.*?)<\\/h3>/gi) || []).map(h => h.replace(/<[^>]+>/g, '').trim());\n\n// Extract images and alt text\nconst images = html.match(/<img[^>]+>/gi) || [];\nconst totalImages = images.length;\nconst imagesWithAlt = images.filter(img => /alt=[\"'][^\"']+[\"']/i.test(img)).length;\nconst imagesWithoutAlt = totalImages - imagesWithAlt;\n\n// Extract internal and external links\nconst allLinks = html.match(/<a[^>]*href=[\"'](.*?)[\"'][^>]*>/gi) || [];\nconst domain = new URL(url).hostname;\nlet internalLinks = 0;\nlet externalLinks = 0;\nfor (const link of allLinks) {\n  const hrefMatch = link.match(/href=[\"'](.*?)[\"']/i);\n  if (hrefMatch) {\n    const href = hrefMatch[1];\n    if (href.startsWith('/') || href.includes(domain)) internalLinks++;\n    else if (href.startsWith('http')) externalLinks++;\n  }\n}\n\n// Check canonical\nconst canonicalMatch = html.match(/<link[^>]*rel=[\"']canonical[\"'][^>]*href=[\"'](.*?)[\"'][^>]*>/i);\nconst canonical = canonicalMatch ? canonicalMatch[1] : '';\n\n// Check viewport meta (mobile-friendly)\nconst hasViewport = /<meta[^>]*name=[\"']viewport[\"']/i.test(html);\n\n// Check Open Graph tags\nconst hasOgTitle = /<meta[^>]*property=[\"']og:title[\"']/i.test(html);\nconst hasOgDesc = /<meta[^>]*property=[\"']og:description[\"']/i.test(html);\nconst hasOgImage = /<meta[^>]*property=[\"']og:image[\"']/i.test(html);\n\n// Check Twitter Card\nconst hasTwitterCard = /<meta[^>]*name=[\"']twitter:card[\"']/i.test(html);\n\n// Check Schema/JSON-LD\nconst schemaMatches = html.match(/<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>(.*?)<\\/script>/gis) || [];\nconst hasSchema = schemaMatches.length > 0;\n\n// Check robots meta\nconst robotsMatch = html.match(/<meta[^>]*name=[\"']robots[\"'][^>]*content=[\"'](.*?)[\"'][^>]*>/i);\nconst robotsMeta = robotsMatch ? robotsMatch[1] : 'not set';\n\n// Word count (strip all HTML)\nconst textContent = html.replace(/<script[^>]*>.*?<\\/script>/gis, '').replace(/<style[^>]*>.*?<\\/style>/gis, '').replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim();\nconst wordCount = textContent.split(' ').filter(w => w.length > 0).length;\n\n// Check for lang attribute\nconst langMatch = html.match(/<html[^>]*lang=[\"'](.*?)[\"']/i);\nconst htmlLang = langMatch ? langMatch[1] : 'not set';\n\nreturn [{\n  json: {\n    url,\n    title,\n    titleLength: title.length,\n    metaDescription,\n    metaDescLength: metaDescription.length,\n    metaKeywords,\n    h1s,\n    h1Count: h1s.length,\n    h2s,\n    h2Count: h2s.length,\n    h3s,\n    h3Count: h3s.length,\n    totalImages,\n    imagesWithAlt,\n    imagesWithoutAlt,\n    internalLinks,\n    externalLinks,\n    canonical,\n    hasViewport,\n    hasOgTitle,\n    hasOgDesc,\n    hasOgImage,\n    hasTwitterCard,\n    hasSchema,\n    schemaCount: schemaMatches.length,\n    robotsMeta,\n    wordCount,\n    htmlLang\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "b077cc79-4891-42af-b0e5-7e70b14194e5",
      "name": "PageSpeed Check",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1488,
        816
      ],
      "parameters": {
        "url": "=https://www.googleapis.com/pagespeed/v5/runPagespeed?url={{ encodeURIComponent($('Webhook Trigger').first().json.body.url) }}&strategy=mobile&category=performance&category=accessibility&category=seo&category=best-practices",
        "options": {
          "timeout": 30000
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e7fbf8de-6fda-4c2a-9e44-5f03a2bffaa7",
      "name": "Merge Results",
      "type": "n8n-nodes-base.merge",
      "position": [
        1840,
        368
      ],
      "parameters": {
        "mode": "combine",
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "164b4428-8a27-412a-8b6c-0f970442b898",
      "name": "SEO Analyzer",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2032,
        368
      ],
      "parameters": {
        "text": "=Analyze this website SEO data and provide a comprehensive audit.\n\nURL: {{ $json.url }}\n\n## On-Page Data:\n- Title: \"{{ $json.title }}\" ({{ $json.titleLength }} chars)\n- Meta Description: \"{{ $json.metaDescription }}\" ({{ $json.metaDescLength }} chars)\n- Meta Keywords: {{ $json.metaKeywords || 'None' }}\n- H1 tags ({{ $json.h1Count }}): {{ $json.h1s }}\n- H2 tags ({{ $json.h2Count }}): {{ $json.h2s }}\n- H3 tags ({{ $json.h3Count }}): {{ $json.h3s }}\n- Word count: {{ $json.wordCount }}\n- Images total: {{ $json.totalImages }}, with alt: {{ $json.imagesWithAlt }}, without alt: {{ $json.imagesWithoutAlt }}\n- Internal links: {{ $json.internalLinks }}, External links: {{ $json.externalLinks }}\n- Canonical: {{ $json.canonical || 'Not set' }}\n- Viewport (mobile): {{ $json.hasViewport }}\n- Open Graph: title={{ $json.hasOgTitle }}, desc={{ $json.hasOgDesc }}, image={{ $json.hasOgImage }}\n- Twitter Card: {{ $json.hasTwitterCard }}\n- Schema markup: {{ $json.hasSchema }} ({{ $json.schemaCount }} blocks)\n- Robots meta: {{ $json.robotsMeta }}\n- HTML lang: {{ $json.htmlLang }}\n\nRespond ONLY with a valid JSON object (no markdown, no backticks) using this exact structure:\n{\n  \"overall_score\": <number 0-100>,\n  \"categories\": {\n    \"title_tag\": {\"score\": <0-10>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"meta_description\": {\"score\": <0-10>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"headings\": {\"score\": <0-15>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"content\": {\"score\": <0-15>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"images\": {\"score\": <0-10>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"links\": {\"score\": <0-10>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"technical\": {\"score\": <0-15>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"social_tags\": {\"score\": <0-10>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"},\n    \"schema\": {\"score\": <0-5>, \"status\": \"pass|warning|fail\", \"finding\": \"<what was found>\", \"fix\": \"<specific fix>\"}\n  },\n  \"top_3_fixes\": [\"<most impactful fix first>\", \"<second>\", \"<third>\"],\n  \"summary\": \"<2-3 sentence overall assessment>\"\n}",
        "options": {
          "systemMessage": "You are an expert SEO auditor. Analyze the provided on-page SEO data and score each category. Be specific about what is wrong and how to fix it. Your scoring must be strict and honest - do not inflate scores. A page with no meta description gets 0/10 for that category, not 5/10. Respond ONLY with valid JSON, no markdown formatting, no backticks, no explanation text."
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "b21adff0-4407-41a2-a627-177510d2fa0b",
      "name": "Groq Llama 3.3 70B",
      "type": "@n8n/n8n-nodes-langchain.lmChatGroq",
      "position": [
        2032,
        608
      ],
      "parameters": {
        "model": "llama-3.3-70b-versatile",
        "options": {
          "temperature": 0.2
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8ce7f6f9-7ddb-4233-89a0-e5f257d3ac7f",
      "name": "Parse Analysis",
      "type": "n8n-nodes-base.code",
      "position": [
        2320,
        368
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.output || $input.first().json.text || '';\n\ntry {\n  // Try to extract JSON from the response\n  let jsonStr = raw;\n  \n  // Remove markdown code blocks if present\n  const codeBlockMatch = raw.match(/```(?:json)?\\s*([\\s\\S]*?)```/);\n  if (codeBlockMatch) jsonStr = codeBlockMatch[1];\n  \n  // Find JSON object boundaries\n  const startIdx = jsonStr.indexOf('{');\n  const endIdx = jsonStr.lastIndexOf('}');\n  if (startIdx !== -1 && endIdx !== -1) {\n    jsonStr = jsonStr.substring(startIdx, endIdx + 1);\n  }\n  \n  const analysis = JSON.parse(jsonStr);\n  \n  return [{\n    json: {\n      parseSuccess: true,\n      url: $('Extract SEO Data').first().json.url,\n      overall_score: analysis.overall_score || 0,\n      categories: analysis.categories || {},\n      top_3_fixes: analysis.top_3_fixes || [],\n      summary: analysis.summary || '',\n      timestamp: new Date().toISOString()\n    }\n  }];\n} catch (e) {\n  return [{\n    json: {\n      parseSuccess: false,\n      url: $('Extract SEO Data').first().json.url,\n      error: 'Failed to parse AI analysis: ' + e.message,\n      raw_response: raw.substring(0, 500),\n      timestamp: new Date().toISOString()\n    }\n  }];\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "18551658-7ef3-4774-8bde-4aa8fe9df438",
      "name": "AI analysis parsed correctly?",
      "type": "n8n-nodes-base.if",
      "position": [
        2544,
        368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-parse",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.parseSuccess }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9ae7e259-aa59-448a-bc66-5039f7291189",
      "name": "Save to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2784,
        272
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "6e44812c-3d3d-44b9-9ed2-58bd49adec7b",
      "name": "Return audit report",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3024,
        272
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: true, url: $json.url, score: $json.overall_score, summary: $json.summary, top_fixes: $json.top_3_fixes, categories: $json.categories }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "60c99d10-4c8c-4118-a143-843a10f85f07",
      "name": "Return error response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        2784,
        592
      ],
      "parameters": {
        "options": {
          "responseCode": 400
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ success: false, error: $json.error || 'Page could not be fetched or analyzed', url: $('Webhook Trigger').first().json.body.url }) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "02073c8d-9ade-4443-a38e-dc38146892be",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        -16
      ],
      "parameters": {
        "color": "#EEA0A0",
        "width": 492,
        "height": 764,
        "content": "## Audit any website SEO with AI and Google PageSpeed using Groq and Google Sheets\n\n### How it works\n\nThis workflow performs a comprehensive SEO audit on any URL. Send a POST request with a URL, and the workflow fetches the page, extracts 20+ on-page SEO factors from the raw HTML, checks Google PageSpeed scores for Core Web Vitals, and sends everything to Groq Llama 3.3 70B for expert analysis. The AI scores each SEO category, identifies the top 3 fixes by impact, and returns a structured audit report. Results are logged to Google Sheets and returned via webhook response.\n\n### What gets checked\n\n- Title tag length and keyword presence\n- Meta description quality and length\n- Heading hierarchy (H1, H2, H3 count and structure)\n- Image alt text coverage\n- Internal and external link counts\n- Canonical tag, robots meta, viewport meta\n- Open Graph and Twitter Card tags\n- Schema markup (JSON-LD) detection\n- Word count and content depth\n- Google PageSpeed performance, accessibility, SEO, and best practices scores"
      },
      "typeVersion": 1
    },
    {
      "id": "5ee08ec5-f837-4bf4-903e-0e64901d28ba",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        672,
        160
      ],
      "parameters": {
        "color": 2,
        "width": 664,
        "height": 372,
        "content": "## Receive URL and fetch page\n\nWebhook accepts a POST request with a JSON body containing the target URL. The HTTP Request node fetches the full HTML of the page. The IF node checks that the fetch returned valid HTML before proceeding."
      },
      "typeVersion": 1
    },
    {
      "id": "2227bf7b-068f-4b67-8939-727bf8fbbaea",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        -32
      ],
      "parameters": {
        "color": 4,
        "width": 360,
        "height": 432,
        "content": "## Extract SEO data from HTML\n\nA Code node parses the raw HTML using regex to extract 20+ SEO factors: title tag, meta description, all heading tags, image alt text coverage, internal and external link counts, canonical URL, viewport meta, Open Graph tags, Twitter Card, Schema markup, robots directive, word count, and language attribute. No external libraries needed."
      },
      "typeVersion": 1
    },
    {
      "id": "682afd8b-3285-426b-9f7c-a3c83b1e2797",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1360,
        544
      ],
      "parameters": {
        "color": 6,
        "width": 360,
        "height": 428,
        "content": "## Google PageSpeed check\n\nHits the free Google PageSpeed Insights API (no API key required) to get performance, accessibility, SEO, and best practices scores for mobile. Returns Core Web Vitals including Largest Contentful Paint, First Input Delay, and Cumulative Layout Shift. Runs in parallel with the HTML extraction."
      },
      "typeVersion": 1
    },
    {
      "id": "de69ea16-642c-407c-96b5-d11d4d188c18",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        64
      ],
      "parameters": {
        "color": 3,
        "width": 716,
        "height": 688,
        "content": "## AI SEO analysis and scoring\n\nThe Merge node combines the HTML extraction results with PageSpeed data. The SEO Analyzer agent receives all data and scores each category with specific findings and fix recommendations. Groq Llama 3.3 70B produces a structured JSON audit with an overall score out of 100, category breakdowns, and the top 3 highest-impact fixes. Parse Analysis extracts the JSON from the AI response."
      },
      "typeVersion": 1
    },
    {
      "id": "c6b90639-a2cc-43b1-b8f9-5eca6b6dc1ca",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2528,
        32
      ],
      "parameters": {
        "width": 684,
        "height": 740,
        "content": "## Validate, log, and respond\n\nThe IF node checks if the AI response parsed correctly. Successful audits are logged to Google Sheets with all scores, findings, and timestamp, then returned as a structured JSON response to the webhook caller. Failed parses return an error response with the raw AI output for debugging."
      },
      "typeVersion": 1
    },
    {
      "id": "cdae876b-f99a-4780-9f8a-2107fa0e386c",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3264,
        -64
      ],
      "parameters": {
        "color": 6,
        "width": 476,
        "height": 980,
        "content": "## Setup Guide\n\n### Step 1 - Groq API Key (required)\n1. Go to console.groq.com and sign up for free\n2. Navigate to API Keys and create a new key\n3. In n8n: Credentials - Add - Groq API - paste key - Save\n4. Select this credential in the Groq Llama 3.3 70B node\n\n### Step 2 - Google Sheets (required for logging)\n1. In n8n: Credentials - Add - Google Sheets OAuth2\n2. Create a spreadsheet with columns: url, overall_score, summary, top_3_fixes, categories, timestamp\n3. Open the Save to Sheets node and select your spreadsheet and sheet tab\n\n### Step 3 - Test the workflow\n1. Click Test Workflow in n8n\n2. Send a POST request to the webhook URL:\n   curl -X POST YOUR_WEBHOOK_URL -H \"Content-Type: application/json\" -d '{\"url\": \"https://example.com\"}'\n3. Wait 15-30 seconds for PageSpeed and AI analysis\n4. Check the webhook response and Google Sheets for results\n\n### Notes\n- Google PageSpeed API is completely free with no API key required\n- The HTML extraction uses regex parsing, no external libraries needed\n- Groq free tier allows 30 requests per minute which is plenty for audits"
      },
      "typeVersion": 1
    },
    {
      "id": "0629f831-362a-46e4-85c7-fbdebd4c40e3",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3776,
        0
      ],
      "parameters": {
        "color": 5,
        "width": 476,
        "height": 880,
        "content": "## Customization\n\n### Add email reporting\nInsert a Gmail node after Save to Sheets to email the audit report to the client automatically. Format the categories as an HTML table for a professional look.\n\n### Audit multiple pages\nAdd a Code node after the Webhook that extracts all internal links from the first page, then use a Split In Batches node to audit the top 10 pages. This turns a single-page audit into a full site audit.\n\n### Add DataForSEO keyword data\nInsert a DataForSEO node to pull keyword rankings, search volume, and competitor data alongside the on-page audit. DataForSEO has a native n8n node and costs $0.0006 per query.\n\n### Schedule recurring audits\nReplace the Receive URL for SEO audit with a Cron trigger and a Google Sheets input of URLs to audit. Run weekly or monthly, compare scores over time, and alert when scores drop.\n\n### Change the LLM\nSwap Groq for OpenAI or Anthropic by replacing the LLM sub-node. The prompt and parsing remain the same."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "905c9bca-e400-44f5-b1e3-e5a0fb8842df",
  "nodeGroups": [],
  "connections": {
    "SEO Analyzer": {
      "main": [
        [
          {
            "node": "Parse Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Results": {
      "main": [
        [
          {
            "node": "SEO Analyzer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Analysis": {
      "main": [
        [
          {
            "node": "AI analysis parsed correctly?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Sheets": {
      "main": [
        [
          {
            "node": "Return audit report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PageSpeed Check": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract SEO Data": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Groq Llama 3.3 70B": {
      "ai_languageModel": [
        [
          {
            "node": "SEO Analyzer",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Download target page HTML": {
      "main": [
        [
          {
            "node": "Page downloaded successfully?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive URL for SEO audit": {
      "main": [
        [
          {
            "node": "Download target page HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI analysis parsed correctly?": {
      "main": [
        [
          {
            "node": "Save to Sheets",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return error response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Page downloaded successfully?": {
      "main": [
        [
          {
            "node": "Extract SEO Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "PageSpeed Check",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Return error response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}