{
  "name": "Audit a site for AI search readiness weekly and open prioritized fix tickets with Apify, OpenAI and Google Sheets",
  "nodes": [
    {
      "parameters": {
        "content": "## Audit your site for AI search readiness, then open the tickets to fix it\n\nAnswer engines read a page differently from a crawler. They want a direct answer near the top, headings phrased as questions, structured data that says what the page is, and text that survives being quoted out of context. Most sites were never built for that, and the score tells you nothing about what to do on Monday.\n\nThis workflow audits the site every week, compares each page against the last audit, and writes the engineering ticket for the pages that need one. The deliverable is a prioritized backlog with steps in it, not a dashboard.\n\n### Who it's for\nSEO freelancers and agencies who have to hand a developer something actionable, and in-house SEOs who own a site and want to catch a regression the week it happens rather than at the next quarterly audit.\n\n### How it works\n1. **Schedule** runs it every Monday at 7am.\n2. **Set your site** holds the URL, how many pages to audit, and the two thresholds that decide what gets a ticket.\n3. **Apify** runs the [SEO, GEO and AEO Audit](https://apify.com/fayoussef/seo-geo-aeo-audit?fpr=youssef) Actor, which renders each page and scores it on three pillars: classic SEO, GEO (how ready it is for AI engines) and AEO (how answerable it is). It returns every failed check with a recommendation.\n4. **Last week's scores are read back** from your Google Sheet, which is what makes this a comparison rather than a snapshot.\n5. **A Code node joins the two runs** per URL and decides what needs a ticket: a page that fell by `dropPoints`, a page under your `minScore` floor, or any page with a critical issue. Everything else is left alone on purpose.\n6. **Every page is written to the scoreboard tab**, so next week has something to compare against and you have a history to show a client.\n7. **OpenAI writes one ticket per page**: a title, a priority, why it matters for an answer engine in one sentence, and 3 to 6 numbered steps built only from the checks that actually failed.\n8. Tickets are appended to a Tickets tab and sent to Telegram, one message each.\n\n### How to set up\n1. Create a free [Apify account](https://apify.com/?fpr=youssef) and connect it in the **Apify** node.\n2. Connect an OpenAI account in the **OpenAI model** node.\n3. Create a Google Sheet with two empty tabs, one for the scoreboard and one for the tickets, and put their names in `scoreTab` and `ticketTab`. Both write their own headers on the first run.\n4. Create a Telegram bot with @BotFather, connect it, and paste your chat id into **Set your site**. Message @userinfobot to find your chat id.\n5. Put your site in `websiteUrl` and run it once by hand. The first run has nothing to compare against, so every page is treated as a first audit and only the floor and critical rules apply.\n\n### Requirements\n- Apify account (the free plan audits a few pages per run)\n- An OpenAI account, Google Sheets and a Telegram bot\n\n### How to customize\n- `dropPoints` is the fall that counts as a regression. 5 is sensitive, 10 is quiet.\n- `minScore` is the floor below which a page always gets a ticket, regardless of whether it moved.\n- `maxTickets` caps what one run can put in front of a developer, which matters most on the first run when every page is new.\n- Point the Telegram node at Slack, or swap `Save the ticket` for a Linear, Jira or Notion node: the ticket fields are already shaped like an issue.\n- Swap the **OpenAI model** node for Anthropic, Gemini or any other chat model: nothing else changes.",
        "height": 1860,
        "width": 520,
        "color": 1
      },
      "id": "ao33c501-0000-4000-8000-000000000001",
      "name": "Sticky Note - About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 1. The site, and what deserves a ticket\n`websiteUrl`: the site to audit. The Actor starts here and follows internal links.\n\n`maxPages`: how many pages per run. Start at 10. The audit renders each page, so this is the setting that decides what a run costs.\n\n`dropPoints`: how far a page's overall score must fall since the last audit before it counts as a regression. 5 catches small slips, 10 only catches real ones.\n\n`minScore`: the floor. Any page under this gets a ticket even if it has not moved, which is what surfaces pages that were always bad rather than newly broken.\n\n`maxTickets`: the most tickets one run may write. The first run has no history, so without this it can ticket every page at once.\n\n`scoreTab` and `ticketTab`: two empty tabs in your sheet. They write their own headers.",
        "height": 760,
        "width": 520,
        "color": 7
      },
      "id": "ao33c501-0000-4000-8000-000000000002",
      "name": "Sticky Note - Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 2. Audit now, then read the last audit\nThe Actor renders every page and scores three pillars: classic **SEO**, **GEO** for how ready the page is to be used by ChatGPT, Perplexity and Gemini, and **AEO** for how answerable it is. It returns each failed check with a recommendation attached, which is what the ticket is built from.\n\nThis is the part n8n cannot do on its own. Rendering a page, following its links and running dozens of checks per page is not an HTTP request, and there is no API that answers \"is this page ready for an answer engine\".\n\n**Read the last audit** then pulls last week's scores back out of your sheet. That node has `alwaysOutputData` turned on, so the very first run, when the tab is still empty, carries on instead of stopping.",
        "height": 760,
        "width": 840,
        "color": 7
      },
      "id": "ao33c501-0000-4000-8000-000000000003",
      "name": "Sticky Note - Step 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        360,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 3. Compare, then decide what is worth a developer's time\nThe Code node joins this run to the last one by URL and labels every page: `first audit`, `dropped`, `improved` or `unchanged`. For a dropped page it records how many points it lost.\n\nThen it decides. A page gets a ticket if it fell by `dropPoints`, or sits under `minScore`, or carries a critical issue. Everything else is deliberately left alone: a backlog that lists every page is the thing this workflow exists to avoid.\n\n**Every** page is written to the scoreboard tab regardless, because that is next week's comparison and the history you show a client. The pages that need work are sorted to the front.",
        "height": 760,
        "width": 880,
        "color": 7
      },
      "id": "ao33c501-0000-4000-8000-000000000004",
      "name": "Sticky Note - Step 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1240,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 4. Write the ticket, not the score\nOnly pages that need a fix reach this lane, capped by `maxTickets`.\n\nThe model is given the page, its three scores, the change since last week and the full list of failed checks with their recommendations. It returns a ticket: an imperative title, a priority (P1 for critical or regressed, P2 for below the floor, P3 otherwise), one sentence on why an answer engine cares, 3 to 6 numbered steps, and an effort estimate.\n\nIt is told that every step must correspond to a check that actually failed, and that fewer honest steps is correct. It is also told not to promise ranking or traffic outcomes, because it cannot know them.\n\nRead the steps before you pass them on: the recommendations come from the audit, but the wording around them is written by a model.",
        "height": 760,
        "width": 880,
        "color": 7
      },
      "id": "ao33c501-0000-4000-8000-000000000005",
      "name": "Sticky Note - Step 4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2160,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 5. A backlog, in two places\n`Save the ticket` appends one row per ticket to the `ticketTab` tab: the date, priority, title, URL, why it matters, the steps, the effort estimate and the scores that triggered it.\n\nThat tab is the deliverable. It is already shaped like an issue tracker, so if you work in Linear, Jira or Notion, swap this one node and the fields map across without touching anything else.\n\nTelegram gets the same ticket as a message per page, so the person on call sees the P1s without opening a sheet. Point it at Slack instead if that is where your team lives.",
        "height": 760,
        "width": 620,
        "color": 7
      },
      "id": "ao33c501-0000-4000-8000-000000000006",
      "name": "Sticky Note - Step 5",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        3080,
        -240
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "ao33c501-0000-4000-8000-000000000010",
      "name": "Every Monday at 7am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -120,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ao33c501-0000-4000-8000-000000000101",
              "name": "websiteUrl",
              "value": "PASTE_THE_SITE_TO_AUDIT_HERE",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000102",
              "name": "maxPages",
              "value": 10,
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000103",
              "name": "dropPoints",
              "value": 5,
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000104",
              "name": "minScore",
              "value": 70,
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000105",
              "name": "maxTickets",
              "value": 5,
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000106",
              "name": "scoreTab",
              "value": "Scoreboard",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000107",
              "name": "ticketTab",
              "value": "Tickets",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000108",
              "name": "googleSheetUrl",
              "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000109",
              "name": "telegramChatId",
              "value": "PASTE_YOUR_TELEGRAM_CHAT_ID_HERE",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000011",
      "name": "Set your site",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        120,
        120
      ]
    },
    {
      "parameters": {
        "operation": "Run actor and get dataset",
        "actorSource": "store",
        "actorId": {
          "__rl": true,
          "value": "rufzbCa6yiJQbfHcw",
          "mode": "id"
        },
        "customBody": "={{ (() => {\n  const s = $json;\n  return JSON.stringify({\n    websiteUrl: s.websiteUrl,\n    maxPages: s.maxPages,\n    includeHtmlReport: false\n  });\n})() }}"
      },
      "id": "ao33c501-0000-4000-8000-000000000012",
      "name": "Audit the site",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        460,
        120
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "pages",
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000013",
      "name": "Collect the audited pages",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        720,
        120
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.googleSheetUrl }}",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.scoreTab }}",
          "mode": "name"
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000014",
      "name": "Read the last audit",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        980,
        120
      ],
      "alwaysOutputData": true
    },
    {
      "parameters": {
        "jsCode": "const s = $('Set your site').first().json;\nconst pages = $('Collect the audited pages').first().json.pages || [];\nconst prev = {};\nfor (const item of $input.all()) {\n  const r = item.json || {};\n  const u = String(r['URL'] || '').trim();\n  if (u) prev[u] = r;\n}\nconst drop = Number(s.dropPoints);\nconst floor = Number(s.minScore);\nconst out = [];\nfor (const p of pages) {\n  const was = prev[p.url];\n  const beforeRaw = was === undefined ? null : Number(was['Overall']);\n  const before = beforeRaw === null || Number.isNaN(beforeRaw) ? null : beforeRaw;\n  const delta = before === null ? null : Number(p.overallScore) - before;\n  const critical = Number(p.criticalCount || 0);\n  let status;\n  if (delta === null) status = 'first audit';\n  else if (delta <= -drop) status = 'dropped';\n  else if (delta >= drop) status = 'improved';\n  else status = 'unchanged';\n  const needsFix = status === 'dropped' || Number(p.overallScore) < floor || critical > 0;\n  let reason;\n  if (status === 'dropped') reason = 'fell ' + Math.abs(delta) + ' points since the last audit';\n  else if (critical > 0) reason = critical + ' critical issue' + (critical === 1 ? '' : 's') + ' on the page';\n  else if (Number(p.overallScore) < floor) reason = 'scores ' + p.overallScore + ', under your floor of ' + floor;\n  else reason = 'nothing to fix';\n  const issues = (p.issues || []).slice(0, 12).map(i =>\n    '- [' + String(i.severity || 'issue') + ' / ' + String(i.pillar || '') + '] ' + String(i.message || '')\n    + (i.recommendation ? '\\n    recommended fix: ' + String(i.recommendation) : ''));\n  out.push({ json: {\n    url: p.url,\n    pageTitle: p.pageTitle || '',\n    pageType: p.pageType || '',\n    grade: p.grade || '',\n    overall: Number(p.overallScore),\n    seo: Number(p.seoScore),\n    geo: Number(p.geoScore),\n    aeo: Number(p.aeoScore),\n    issuesFound: Number(p.issuesFound || 0),\n    critical: critical,\n    before: before === null ? '' : before,\n    change: delta === null ? '' : (delta > 0 ? '+' + delta : String(delta)),\n    status: status,\n    needsFix: needsFix,\n    reason: reason,\n    issueList: issues.join('\\n'),\n    schemaTypes: (p.schemaTypes || []).join(', ') || 'none',\n    questionHeadings: Number(p.questionHeadings || 0),\n    wordCount: Number(p.wordCount || 0)\n  } });\n}\nout.sort((a, b) => (Number(b.json.needsFix) - Number(a.json.needsFix))\n  || (b.json.critical - a.json.critical) || (a.json.overall - b.json.overall));\nreturn out;"
      },
      "id": "ao33c501-0000-4000-8000-000000000015",
      "name": "Decide what needs fixing",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1340,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ao33c501-0000-4000-8000-000000000201",
              "name": "Checked",
              "value": "={{ $now.format('yyyy-MM-dd') }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000202",
              "name": "URL",
              "value": "={{ $json.url }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000203",
              "name": "Page",
              "value": "={{ $json.pageTitle }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000204",
              "name": "Grade",
              "value": "={{ $json.grade }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000205",
              "name": "Overall",
              "value": "={{ $json.overall }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000206",
              "name": "SEO",
              "value": "={{ $json.seo }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000207",
              "name": "GEO",
              "value": "={{ $json.geo }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000208",
              "name": "AEO",
              "value": "={{ $json.aeo }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000209",
              "name": "Issues",
              "value": "={{ $json.issuesFound }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000210",
              "name": "Critical",
              "value": "={{ $json.critical }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000211",
              "name": "Change",
              "value": "={{ $json.change }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000212",
              "name": "Status",
              "value": "={{ $json.status }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000016",
      "name": "Format the scoreboard row",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        1600,
        20
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.googleSheetUrl }}",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.scoreTab }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000017",
      "name": "Save this audit to the scoreboard",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1860,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "ao33c501-0000-4000-8000-000000000301",
              "leftValue": "={{ $json.needsFix === true }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000018",
      "name": "Keep what needs a fix",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1600,
        240
      ]
    },
    {
      "parameters": {
        "maxItems": "={{ $('Set your site').first().json.maxTickets }}"
      },
      "id": "ao33c501-0000-4000-8000-000000000019",
      "name": "Cap the tickets",
      "type": "n8n-nodes-base.limit",
      "typeVersion": 1,
      "position": [
        2260,
        240
      ]
    },
    {
      "parameters": {
        "text": "=PAGE\nURL: {{ $json.url }}\nTitle tag: {{ $json.pageTitle || 'missing' }}\nPage type: {{ $json.pageType }}\n\nSCORES THIS AUDIT (0 to 100)\nOverall: {{ $json.overall }} (grade {{ $json.grade }}){{ $json.before === '' ? ', first audit of this page' : ', last audit ' + $json.before + ' (' + $json.change + ')' }}\nClassic SEO: {{ $json.seo }}\nGEO, how ready it is for AI engines: {{ $json.geo }}\nAEO, how answerable it is: {{ $json.aeo }}\n\nWHY IT IS ON THE LIST\n{{ $json.reason }}\nCritical issues: {{ $json.critical }} of {{ $json.issuesFound }} failed checks\nStructured data found: {{ $json.schemaTypes }}\nQuestion-phrased headings: {{ $json.questionHeadings }}\nVisible words: {{ $json.wordCount }}\n\nFAILED CHECKS (the only problems you may write about)\n{{ $json.issueList || 'none reported' }}",
        "messages": {
          "messageValues": [
            {
              "message": "You write one engineering ticket for one web page, for a developer who has not seen the audit and does not work in SEO. The ticket has to be actionable without the audit tool open next to it.\n\nRules:\n- `title`: imperative, at most 10 words, naming both the change and which page it is on, because the developer reads a list of these. 'Add FAQ schema and answer headings to the pricing page', not 'SEO improvements' and not 'Add FAQ schema' on its own.\n- `priority`: decide it mechanically from the two lines below the heading 'WHY IT IS ON THE LIST'. If that text says the page fell by some number of points, or if critical issues is 1 or more, the priority is P1. Otherwise, if the text says the page is under the owner's floor, it is P2. Otherwise P3. Do not weigh it up yourself, and do not soften a P1 because the fixes look small.\n- `whyItMatters`: one sentence saying what an answer engine or a search engine currently does with this page because of these issues, and what it would do instead. Describe behaviour, not metrics: never mention the score, the grade, or the letters SEO, GEO or AEO. 'Perplexity has no marked-up answer to quote, so it summarises the page in its own words or skips it' is right. 'This will improve the AEO score' is wrong, and so is any claim about rankings or traffic.\n- `steps`: 3 to 6 numbered lines, one action each, in the order a developer would do them. Each step must correspond to a failed check listed below. Do not invent an issue that is not in the list, do not recommend work the audit did not find, and do not pad the list to reach a count. Fewer honest steps is correct.\n- `effort`: S for under an hour, M for a day, L for more.\n- No marketing language and no promises about rankings or traffic numbers: you do not know what the change will produce, only what it fixes. Banned words, in any form: leverage, enhance, optimise, streamline, robust, seamless, best practices, holistic, synergy.\n- Write plain ASCII punctuation. Use a plain hyphen for ranges, as in '40 to 60 words' or '40-60 words', never a long dash.\n\nAnswer with the JSON object the schema asks for and nothing else: no prose before or after it, no code fences, and no labelled lines such as 'Title:'."
            }
          ]
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "id": "ao33c501-0000-4000-8000-000000000020",
      "name": "Write the fix ticket",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.5,
      "position": [
        2520,
        240
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "temperature": 0.3
        }
      },
      "id": "ao33c501-0000-4000-8000-000000000021",
      "name": "OpenAI model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        2520,
        400
      ]
    },
    {
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"title\": { \"type\": \"string\" },\n    \"priority\": { \"type\": \"string\" },\n    \"whyItMatters\": { \"type\": \"string\" },\n    \"steps\": { \"type\": \"string\" },\n    \"effort\": { \"type\": \"string\" }\n  },\n  \"required\": [\"title\", \"priority\", \"whyItMatters\", \"steps\", \"effort\"]\n}"
      },
      "id": "ao33c501-0000-4000-8000-000000000022",
      "name": "Ticket format",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "typeVersion": 1.2,
      "position": [
        2780,
        400
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ao33c501-0000-4000-8000-000000000401",
              "name": "Opened",
              "value": "={{ $now.format('yyyy-MM-dd') }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000402",
              "name": "Priority",
              "value": "={{ $json.output.priority }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000403",
              "name": "Ticket",
              "value": "={{ $json.output.title }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000404",
              "name": "URL",
              "value": "={{ $('Decide what needs fixing').item.json.url }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000405",
              "name": "Why it matters",
              "value": "={{ $json.output.whyItMatters }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000406",
              "name": "Steps",
              "value": "={{ $json.output.steps }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000407",
              "name": "Effort",
              "value": "={{ $json.output.effort }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000408",
              "name": "Why it is on the list",
              "value": "={{ $('Decide what needs fixing').item.json.reason }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000409",
              "name": "Overall",
              "value": "={{ $('Decide what needs fixing').item.json.overall }}",
              "type": "number"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000410",
              "name": "Change",
              "value": "={{ $('Decide what needs fixing').item.json.change }}",
              "type": "string"
            },
            {
              "id": "ao33c501-0000-4000-8000-000000000411",
              "name": "Critical issues",
              "value": "={{ $('Decide what needs fixing').item.json.critical }}",
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000023",
      "name": "Format the ticket row",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3180,
        240
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.googleSheetUrl }}",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "={{ $('Set your site').first().json.ticketTab }}",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "ao33c501-0000-4000-8000-000000000024",
      "name": "Save the ticket",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        3440,
        140
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Set your site').first().json.telegramChatId }}",
        "text": "={{ (() => {\nconst esc = s => String(s ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');\nconst t = $json;\nconst p = $('Decide what needs fixing').item.json;\nconst scores = 'overall ' + p.overall + '/100 (' + p.grade + ')'\n  + (p.change === '' ? ', first audit' : ', was ' + p.before + ' (' + p.change + ')');\nreturn '\\ud83e\\uddfe <b>' + esc(t['Priority']) + ' \\u00b7 ' + esc(t['Ticket']) + '</b>\\n'\n  + '\\ud83d\\udd17 ' + esc(String(p.url).slice(0, 120)) + '\\n'\n  + '\\ud83d\\udcca ' + esc(scores) + ' \\u00b7 SEO ' + p.seo + ' \\u00b7 GEO ' + p.geo + ' \\u00b7 AEO ' + p.aeo + '\\n'\n  + '\\u26a0 ' + esc(p.reason) + '\\n\\n'\n  + '<i>' + esc(t['Why it matters']) + '</i>\\n\\n'\n  + '<b>Steps</b> (' + esc(t['Effort']) + ')\\n<pre>' + esc(String(t['Steps']).trim()) + '</pre>';\n})() }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "id": "ao33c501-0000-4000-8000-000000000025",
      "name": "Send the ticket to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        3440,
        340
      ]
    }
  ],
  "connections": {
    "Every Monday at 7am": {
      "main": [
        [
          {
            "node": "Set your site",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set your site": {
      "main": [
        [
          {
            "node": "Audit the site",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Audit the site": {
      "main": [
        [
          {
            "node": "Collect the audited pages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect the audited pages": {
      "main": [
        [
          {
            "node": "Read the last audit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the last audit": {
      "main": [
        [
          {
            "node": "Decide what needs fixing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decide what needs fixing": {
      "main": [
        [
          {
            "node": "Format the scoreboard row",
            "type": "main",
            "index": 0
          },
          {
            "node": "Keep what needs a fix",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format the scoreboard row": {
      "main": [
        [
          {
            "node": "Save this audit to the scoreboard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep what needs a fix": {
      "main": [
        [
          {
            "node": "Cap the tickets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cap the tickets": {
      "main": [
        [
          {
            "node": "Write the fix ticket",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Write the fix ticket": {
      "main": [
        [
          {
            "node": "Format the ticket row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format the ticket row": {
      "main": [
        [
          {
            "node": "Save the ticket",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send the ticket to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI model": {
      "ai_languageModel": [
        [
          {
            "node": "Write the fix ticket",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Ticket format": {
      "ai_outputParser": [
        [
          {
            "node": "Write the fix ticket",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
