{
  "id": "gfwrZIQMBNjI9x3A",
  "name": "Automated SEO Indexing: Sitemap to GSC & Indexing API",
  "tags": [],
  "nodes": [
    {
      "id": "96e7f03e-e08d-4256-b6f5-e99946cb7d72",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        1024,
        688
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "304c6dc5-e1f7-4167-b9e5-361ba3d69026",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        1248,
        592
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ffc58500-4c70-49f6-87c5-8d9f6e547690",
              "name": "sitemap",
              "type": "string",
              "value": "https://gamelandvn.com/sitemap_index.xml"
            },
            {
              "id": "9f4adba7-e964-47d4-b420-34e9ee94e7d4",
              "name": "gsc_property",
              "type": "string",
              "value": "https://gamelandvn.com/"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fa4d8a14-76c5-4b18-8116-eae4544830bc",
      "name": "Fetch Sitemap XML",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1472,
        592
      ],
      "parameters": {
        "url": "={{ $json.url || $node[\"Configuration\"].json.sitemap }}",
        "options": {}
      },
      "typeVersion": 4.1
    },
    {
      "id": "b4b296be-37f3-4f5e-8c75-c250547b8f9b",
      "name": "Convert XML to JSON",
      "type": "n8n-nodes-base.xml",
      "position": [
        1696,
        512
      ],
      "parameters": {
        "options": {
          "trim": true,
          "normalize": true,
          "mergeAttrs": true,
          "ignoreAttrs": false,
          "normalizeTags": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b07b709f-25de-4ce8-a2f3-8a602953a30a",
      "name": "Parse Sitemap Structure",
      "type": "n8n-nodes-base.code",
      "position": [
        1920,
        512
      ],
      "parameters": {
        "jsCode": "const input = $input.first().json;\nlet output = [];\n\n// Sitemap Index\nif (input.sitemapindex && input.sitemapindex.sitemap) {\n    const sitemaps = Array.isArray(input.sitemapindex.sitemap) \n        ? input.sitemapindex.sitemap \n        : [input.sitemapindex.sitemap];\n    \n    output = sitemaps.map(s => ({\n        url: s.loc,\n        isSitemapIndex: true\n    }));\n} \n// Single sitemap\nelse if (input.urlset && input.urlset.url) {\n    const urls = Array.isArray(input.urlset.url) \n        ? input.urlset.url \n        : [input.urlset.url];\n    \n    output = urls.map(u => ({\n        urlData: u,\n        isSitemapIndex: false\n    }));\n}\n\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "e1044373-f078-4a7a-941f-3edc34c6fd8e",
      "name": "Is Sitemap Index?",
      "type": "n8n-nodes-base.if",
      "position": [
        2144,
        592
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "3b4d5079-327b-487e-a553-b595532ddf06",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isSitemapIndex }}",
              "rightValue": "true"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "efd97ee2-d810-4463-a14f-a974d158c37e",
      "name": "Format URL Data",
      "type": "n8n-nodes-base.set",
      "position": [
        2480,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "url-field",
              "name": "url",
              "type": "string",
              "value": "={{ $json.urlData.loc }}"
            },
            {
              "id": "lastmod-field",
              "name": "lastmod",
              "type": "string",
              "value": "={{ $json.urlData.lastmod || '' }}"
            },
            {
              "id": "current-date",
              "name": "currentDate",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.3
    },
    {
      "id": "0d9aa95e-a433-4670-892c-f605bec062ba",
      "name": "Filter: Recent URLs Only",
      "type": "n8n-nodes-base.filter",
      "position": [
        2704,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-lastmod",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.lastmod }}",
              "rightValue": ""
            },
            {
              "id": "check-date-range",
              "operator": {
                "type": "dateTime",
                "operation": "after",
                "singleValue": true
              },
              "leftValue": "={{ $json.lastmod }}",
              "rightValue": "={{ $now.minus({ days: 90 }).toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "4a8a07b1-4848-43bb-801e-aa45336a8646",
      "name": "Check Submission History",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        608
      ],
      "parameters": {
        "jsCode": "// Load previously submitted URLs from storage\nconst items = [];\n\nfor (const item of $input.all()) {\n  const url = item.json.url;\n  let shouldSubmit = true;\n  let reason = 'new_url';\n  \n  try {\n    // Check if URL was submitted before\n    const storageKey = `indexed:${Buffer.from(url).toString('base64').substring(0, 50)}`;\n    const result = await $execution.getWorkflowStaticData('global');\n    \n    if (result && result[storageKey]) {\n      const lastSubmitted = new Date(result[storageKey]);\n      const daysSinceSubmit = Math.floor((Date.now() - lastSubmitted.getTime()) / (1000 * 60 * 60 * 24));\n      \n      // Only resubmit if more than 7 days have passed\n      if (daysSinceSubmit < 7) {\n        shouldSubmit = false;\n        reason = `submitted_${daysSinceSubmit}_days_ago`;\n      } else {\n        reason = 'resubmit_after_7_days';\n      }\n    }\n  } catch (error) {\n    // If storage fails, default to submitting\n    reason = 'storage_error';\n  }\n  \n  items.push({\n    json: {\n      ...item.json,\n      shouldSubmit,\n      reason\n    }\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "a242b268-1079-43b9-b417-8f1542701422",
      "name": "GSC: Inspect URL Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3504,
        608
      ],
      "parameters": {
        "url": "https://searchconsole.googleapis.com/v1/urlInspection/index:inspect",
        "method": "POST",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 2000
            }
          }
        },
        "jsonBody": "={\n  \"inspectionUrl\": \"{{ $json.url }}\",\n  \"siteUrl\": \"{{ $('Configuration').item.json.gsc_property }}\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true
    },
    {
      "id": "c6127880-6072-4d00-aef4-3b645dea9901",
      "name": "Logic: Should Index?",
      "type": "n8n-nodes-base.code",
      "position": [
        3728,
        608
      ],
      "parameters": {
        "jsCode": "// Check if URL needs indexing based on Search Console response\nconst items = [];\n\nfor (const item of $input.all()) {\n  let needsIndexing = true;\n  let indexStatus = 'unknown';\n  \n  try {\n    const response = item.json;\n    \n    // Check coverage state\n    if (response.inspectionResult && response.inspectionResult.indexStatusResult) {\n      const coverageState = response.inspectionResult.indexStatusResult.coverageState;\n      indexStatus = coverageState;\n      \n      // If already indexed and no issues, skip\n      if (coverageState === 'Submitted and indexed' || coverageState === 'Indexed, not submitted in sitemap') {\n        needsIndexing = false;\n      }\n    }\n  } catch (error) {\n    // If check fails, default to indexing\n    indexStatus = 'check_failed';\n  }\n  \n  items.push({\n    json: {\n      url: item.json.url || item.json.inspectionResult?.inspectionUrl,\n      needsIndexing,\n      indexStatus,\n      originalData: item.json\n    }\n  });\n}\n\nreturn items;"
      },
      "typeVersion": 2
    },
    {
      "id": "232a6d6f-0abd-4b97-b15f-affaebfcff5a",
      "name": "Filter: Only Not Indexed",
      "type": "n8n-nodes-base.filter",
      "position": [
        3952,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-indexing-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.needsIndexing }}",
              "rightValue": true
            },
            {
              "id": "f8579e5d-e842-40cf-9bf5-cc94de7e05d5",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.originalData.inspectionResult.indexStatusResult.indexingState }}",
              "rightValue": "INDEXING_STATE_UNSPECIFIED"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "06a009bd-e21d-49ed-9d3b-52c7f17801d7",
      "name": "Batch Processing",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        4176,
        608
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "6fdc1d5f-b3a2-4719-99b1-776ae927b138",
      "name": "Delay (Rate Limiting)",
      "type": "n8n-nodes-base.wait",
      "position": [
        4624,
        608
      ],
      "parameters": {
        "unit": "seconds",
        "amount": 2
      },
      "typeVersion": 1
    },
    {
      "id": "ec8043b3-5afa-4701-af3b-b9f2056cbe79",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        1024,
        496
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "5740f5e3-64be-4535-aa46-0774ce91a4f3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1216,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 576,
        "content": "# SETUP & CONFIGURATION"
      },
      "typeVersion": 1
    },
    {
      "id": "146b8248-3146-4a7b-abd1-5ccd210b3702",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 624,
        "height": 576,
        "content": "# DATA FILTERING & LOGIC"
      },
      "typeVersion": 1
    },
    {
      "id": "ce12a6a2-9f47-4604-9fbe-e8ef8b59c85d",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3200,
        384
      ],
      "parameters": {
        "color": 7,
        "width": 1584,
        "height": 576,
        "content": "# API INTERACTION & INDEXING"
      },
      "typeVersion": 1
    },
    {
      "id": "93247ec5-f7ab-46e3-8c67-fe4326c4f228",
      "name": "Filter: Valid for Submission",
      "type": "n8n-nodes-base.filter",
      "position": [
        3280,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "should-submit-check",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.shouldSubmit }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "287fc7ca-6e67-4371-88b3-ac468f986bed",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 832,
        "height": 1072,
        "content": "## Overview\n\nThis workflow automates Google indexing by fetching URLs from your sitemap, checking their status in GSC, and submitting non-indexed pages to the Google Indexing API.\n\n## Setup steps\n\n**1. Configure Google Cloud Console**\n\nCreate Project: Go to the [Google Cloud Console](https://console.cloud.google.com/) and create a new project.\n\nEnable APIs: Search for and enable both the Google Search Console API and the Web Search Indexing API.\n\nCreate Service Account: Navigate to Credentials > Create Credentials > Service Account. Fill in the details and click Done.\n\nGenerate Key: Select your service account > Edit service account > Keys > Add Key > Create new key > JSON. Save the downloaded file securely.\n\n**2. Set Up Credentials in n8n**\n\nNew Credential: In n8n, go to Create credentials > Google Service Account API.\n\nInput Data: Paste the Service Account Email and Private Key from your downloaded JSON file. In JSON file, Service Account Email is client_email and Private Key is private_key.\n\nHTTP Request Setup: Enable Set up for use in HTTP Request node.\n\nScopes: Enter exactly: https://www.googleapis.com/auth/indexing https://www.googleapis.com/auth/webmasters.readonly into the Scopes field.\n\nGSC Permission: Add the Service Account email to your Google Search Console property as an Owner via Settings > Users and permissions > Add user.\n\n**3. Workflow Configuration**\n\nConfiguration Node: Open the Configuration node and enter your Sitemap URL and GSC Property URL. If your property type is URL prefix, the URL must end with a forward slash /. Example: https://hanthienhai.com/\n\nLink Credentials: Update the credentials in both the GSC: Inspect URL Status and GSC: Request Indexing nodes with the service account created in Step 2.\n\n**4. Schedule & Activate**\n\nSet Schedule: Adjust the Schedule Trigger node to your preferred execution frequency.\n\nActivate: Toggle the workflow to Active to start the automation.\n\n## Questions or Need Help?\n\nFor setup assistance, customization, or workflow support, feel free to contact me at admin@hanthienhai.com"
      },
      "typeVersion": 1
    },
    {
      "id": "9c1cae13-9ea1-4fd0-b631-b394f894047d",
      "name": "GSC: Request Indexing",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4400,
        528
      ],
      "parameters": {
        "url": "https://indexing.googleapis.com/v3/urlNotifications:publish",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"url\": \"{{ $json.originalData.inspectionResult.indexStatusResult.userCanonical }}\",\n  \"type\": \"URL_UPDATED\"\n}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "googleApi"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.1,
      "continueOnFail": true,
      "alwaysOutputData": true
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0a710726-0b64-42bd-8a88-630c34467c28",
  "connections": {
    "Configuration": {
      "main": [
        [
          {
            "node": "Fetch Sitemap XML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format URL Data": {
      "main": [
        [
          {
            "node": "Filter: Recent URLs Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Batch Processing": {
      "main": [
        [],
        [
          {
            "node": "GSC: Request Indexing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Sitemap XML": {
      "main": [
        [
          {
            "node": "Convert XML to JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Sitemap Index?": {
      "main": [
        [
          {
            "node": "Fetch Sitemap XML",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Format URL Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert XML to JSON": {
      "main": [
        [
          {
            "node": "Parse Sitemap Structure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Logic: Should Index?": {
      "main": [
        [
          {
            "node": "Filter: Only Not Indexed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delay (Rate Limiting)": {
      "main": [
        [
          {
            "node": "Batch Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Request Indexing": {
      "main": [
        [
          {
            "node": "Delay (Rate Limiting)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GSC: Inspect URL Status": {
      "main": [
        [
          {
            "node": "Logic: Should Index?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Sitemap Structure": {
      "main": [
        [
          {
            "node": "Is Sitemap Index?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Submission History": {
      "main": [
        [
          {
            "node": "Filter: Valid for Submission",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Only Not Indexed": {
      "main": [
        [
          {
            "node": "Batch Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Recent URLs Only": {
      "main": [
        [
          {
            "node": "Check Submission History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter: Valid for Submission": {
      "main": [
        [
          {
            "node": "GSC: Inspect URL Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}