AutomationFlowsData & Sheets › Find Low-competition Keyword Opportunities with Dataforseo

Find Low-competition Keyword Opportunities with Dataforseo

ByDahiana @mssporto on n8n.io

What it does: Discovers all keywords a domain ranks for and analyzes their difficulty, trends, and intent. Identifies opportunities based on actual ranking data rather than suggestions.

Cron / scheduled trigger★★★★☆ complexity18 nodesHTTP RequestGoogle Sheets
Data & Sheets Trigger: Cron / scheduled Nodes: 18 Complexity: ★★★★☆ Added:

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

This workflow follows the Google Sheets → HTTP Request 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
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "e6f42b9f-060d-4fee-9c8b-def8beba5ad3",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2304,
        4160
      ],
      "parameters": {
        "color": 4,
        "width": 336,
        "height": 256,
        "content": "## \ud83d\udca5 Run to find your n8n's server IP\n"
      },
      "typeVersion": 1
    },
    {
      "id": "31aa849e-e6ea-4595-b566-3233a470bb7e",
      "name": "Find n8n's IP",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2256,
        4256
      ],
      "parameters": {
        "url": "https://api.ipify.org?format=jso",
        "options": {}
      },
      "typeVersion": 4.3
    },
    {
      "id": "42879ff1-b096-419a-a65f-ef69ee02bb29",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2304,
        3440
      ],
      "parameters": {
        "width": 426,
        "height": 688,
        "content": "## \ud83d\udd0d  Low competition keyword finder\n\n**What it does:**\nFinds untapped keyword opportunities by analyzing keyword suggestions, checking their difficulty, and examining SERP competition. \n\n**How it works:**\n1. Reads seed keyword or domain from Google Sheets\n2. Gets relevant keyword ideas \n3. Checks ranking difficulty for each keyword \n4. Analyzes SERP results for top opportunities\n5. Filters for low-competition keywords (KD < 30)\n6. Writes results back to Google Sheets\n\n**Setup Requirements:**\n- DataForSEO account with API access\n- Google Sheets: duplicate [this template](https://docs.google.com/spreadsheets/d/13ioeuFckLX4qEesbJwQ4C04I0-TPppdMoJVKEAPXCSI/edit?usp=sharing). You can create your own.\n\nData Captured:\n\n- Keyword & Search Volume\n- Monthly/Quarterly/Yearly Trends\n- Keyword Difficulty (0-100)\n- Search Intent (main + foreign)\n- Average Backlinks\n- Last Updated Time\n- SE Type & Location/Language codes"
      },
      "typeVersion": 1
    },
    {
      "id": "161964df-e615-4999-b0f8-a74c591a2813",
      "name": "Read Seeds",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1488,
        3680
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": "1232412"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/XX/edit?usp=drivesdkXXXXXXXX"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "2563a5c1-ee84-4f65-b066-f84676d6a792",
      "name": "Format Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -336,
        3712
      ],
      "parameters": {
        "jsCode": "const keywordsData = $input.first().json;\nconst difficultyData = $input.last().json;\n\nconst keywords = keywordsData.tasks[0].result[0].items;\nconst difficulties = difficultyData.tasks[0].result[0].items;\n\nconst difficultyMap = {};\ndifficulties.forEach(item => {\n  difficultyMap[item.keyword] = item.keyword_difficulty;\n});\n\nconst rows = keywords.map(kw => ({\n  seed: keywordsData.tasks[0].data.target,\n  keywords: kw.keyword,\n  'Search Volume': kw.keyword_info.search_volume || 0,\n  'Trend Monthly': kw.keyword_info.search_volume_trend?.monthly ?? null,\n  'Trend Quarterly': kw.keyword_info.search_volume_trend?.quarterly ?? null,\n  'Trend Yearly': kw.keyword_info.search_volume_trend?.yearly ?? null,\n  'SE Type': kw.se_type,\n  'Main Intent': kw.search_intent_info?.main_intent || '',\n  'Foreign Intent': kw.search_intent_info?.foreign_intent?.join(', ') || '',\n  'Last Updated Time': kw.keyword_info.last_updated_time,\n  'Backlinks': kw.avg_backlinks_info?.backlinks || 0,\n  'Keyword Difficulty': difficultyMap[kw.keyword] ?? null\n}));\n\nreturn rows.map(row => ({ json: row }));"
      },
      "typeVersion": 2
    },
    {
      "id": "b0e0e5a1-d4df-4262-8b5c-366a40ac5d6f",
      "name": "Collect All Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        -96,
        3712
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "305d419d-efd5-41c9-8c6a-c1f39509e870",
      "name": "Write to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1056,
        3472
      ],
      "parameters": {
        "columns": {
          "value": {
            "SE Type": "={{ $json['SE Type'] }}",
            "keywords": "={{ $json.keywords }}",
            "Backlinks": "={{ $json.Backlinks }}",
            "Main Intent": "={{ $json['Main Intent'] }}",
            "Trend Yearly": "={{ $json['Trend Yearly'] }}",
            "Search Volume": "={{ $json['Search Volume'] }}",
            "Trend Monthly": "={{ $json['Trend Monthly'] }}",
            "Foreign Intent": "={{ $json['Foreign Intent'] }}",
            "Trend Quarterly": "={{ $json['Trend Quarterly'] }}",
            "Last Updated Time": "={{ $json['Last Updated Time'] }}",
            "Keyword Difficulty": "={{ $json['Keyword Difficulty'] }}"
          },
          "schema": [
            {
              "id": "keywords",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "keywords",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Search Volume",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Search Volume",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Trend Monthly",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Trend Monthly",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Trend Quarterly",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Trend Quarterly",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Trend Yearly",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Trend Yearly",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "SE Type",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "SE Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Main Intent",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Main Intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Foreign Intent",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Foreign Intent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Updated Time",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Last Updated Time",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Backlinks",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Backlinks",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Keyword Difficulty",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Keyword Difficulty",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "data",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "data",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "id",
          "value": 129579803
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "1RERSXASXDADS"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "1cd961c3-7823-4c00-b3a6-c99a98e68259",
      "name": "Loop Over Domains",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1280,
        3680
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "30d02fea-f40b-442b-8f61-670d2f5931f2",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1680,
        3680
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "months"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "407d3833-9585-48b8-855c-8389a23997be",
      "name": "Get Keyword Difficulty",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 2,
      "position": [
        -752,
        3904
      ],
      "parameters": {
        "url": "https://api.dataforseo.com/v3/dataforseo_labs/google/bulk_keyword_difficulty/live",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify([\n  {\n    \"keywords\": $json.tasks[0].result[0].items.map(i => i.keyword),\n    \"location_name\": $('Loop Over Domains').item.json.location_name,\n    \"language_name\": $('Loop Over Domains').item.json.language_name\n  }\n]) }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 2000
    },
    {
      "id": "21a50d5b-d650-49d0-88ba-e441b58a9f4d",
      "name": "Get Keywords",
      "type": "n8n-nodes-base.httpRequest",
      "maxTries": 2,
      "position": [
        -1056,
        3696
      ],
      "parameters": {
        "url": "https://api.dataforseo.com/v3/dataforseo_labs/google/keywords_for_site/live",
        "method": "POST",
        "options": {},
        "jsonBody": "=[{\n  \"target\": \"{{ $json.seed }}\",\n  \"location_name\": \"{{ $json.location_name }}\",\n  \"language_name\": \"{{ $json.language_name }}\",\n  \"limit\": {{ $json.limit }}\n}]",
        "sendBody": true,
        "specifyBody": "json"
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 2000
    },
    {
      "id": "a6edc925-46cb-4511-9383-c7212665c339",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -528,
        3712
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "1dbfb339-bddc-48a5-b070-e8eeaa54f462",
      "name": "Flatten Data",
      "type": "n8n-nodes-base.code",
      "position": [
        112,
        3712
      ],
      "parameters": {
        "jsCode": "const allRows = [];\n\nfor (const item of $input.all()) {\n  if (item.json.data && Array.isArray(item.json.data)) {\n    allRows.push(...item.json.data);\n  }\n}\n\nreturn allRows.map(row => ({ json: row }));"
      },
      "typeVersion": 2
    },
    {
      "id": "ec1a4714-ba6e-4345-a5c0-1b7fd12ee0c3",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1552,
        3504
      ],
      "parameters": {
        "color": 7,
        "content": "**Load Domain Seeds**\n\nReads domains from sheet and\nIncludes location, language, and limit parameters"
      },
      "typeVersion": 1
    },
    {
      "id": "4f225165-8fe6-42c9-a45c-f5df3d75e19f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        3856
      ],
      "parameters": {
        "color": 7,
        "content": "**Fetch Keywords**\n\nAPI: keywords_for_site\nReturns all keywords the domain ranks for\nLimited by \"limit\" parameter from sheet"
      },
      "typeVersion": 1
    },
    {
      "id": "3227a544-aa1b-4aef-9ab6-f274d336dc58",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -816,
        4064
      ],
      "parameters": {
        "color": 7,
        "content": "**Get Difficulty Scores**\n\nAPI: bulk_keyword_difficulty\nChecks ranking difficulty (0-100) for all keywords\nRuns in parallel with keyword fetch"
      },
      "typeVersion": 1
    },
    {
      "id": "56f921ba-01b4-4b7b-912f-c0a4641c1b97",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        3568
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 112,
        "content": "**Combine & Format**\n\nMerges keyword data with difficulty scores\nFormats for Google Sheets output\nIncludes trends, intent, backlinks"
      },
      "typeVersion": 1
    },
    {
      "id": "8ad8c2d6-585f-4bca-83da-3902e5433a22",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        3568
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 112,
        "content": "**Aggregate Results**\n\nCollects all formatted rows\nFlattens nested data structure\nPrepares final dataset for writing"
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Format Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Seeds": {
      "main": [
        [
          {
            "node": "Loop Over Domains",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Data": {
      "main": [
        [
          {
            "node": "Collect All Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flatten Data": {
      "main": [
        [
          {
            "node": "Loop Over Domains",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Keywords": {
      "main": [
        [
          {
            "node": "Get Keyword Difficulty",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Read Seeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Domains": {
      "main": [
        [
          {
            "node": "Write to Sheet",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Get Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect All Results": {
      "main": [
        [
          {
            "node": "Flatten Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Keyword Difficulty": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    }
  }
}

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

What it does: Discovers all keywords a domain ranks for and analyzes their difficulty, trends, and intent. Identifies opportunities based on actual ranking data rather than suggestions.

Source: https://n8n.io/workflows/11216/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This workflow automates video distribution to 9 social platforms simultaneously using Blotato's API. It includes both a scheduled publisher (checks Google Sheets for videos marked "Ready") and a subwo

Google Sheets, HTTP Request, Form Trigger +2
Data & Sheets

YogiAI. Uses googleSheets, googleSheetsTool, httpRequest, stopAndError. Scheduled trigger; 61 nodes.

Google Sheets, Google Sheets Tool, HTTP Request +1
Data & Sheets

This workflow monitors Google Calendar for events indicating that a customer will visit the company today or the next day, retrieves the required details, and sends reminder notifications to the relev

Google Calendar, Google Sheets, HTTP Request +1
Data & Sheets

ofn hook v0.24.0 beta. Uses start, httpRequest, functionItem, itemLists. Scheduled trigger; 42 nodes.

Start, HTTP Request, Function Item +3
Data & Sheets

Security teams, DevOps engineers, vulnerability analysts, and automation builders who want to eliminate repetitive Nessus scan parsing, AI-based risk triage, and manual reporting. Designed for orgs fo

Email Send, HTTP Request, Google Sheets +1