{
  "id": "sdhO9f92RhFTuMTs",
  "name": "Job Listing Tracking Decodo (Template)",
  "tags": [],
  "nodes": [
    {
      "id": "a4ceb35e-fbf7-4298-a0b2-6fc0a3b6b6e3",
      "name": "Google Gemini Chat Model (Agent)",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        560,
        400
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-1.5-pro"
      },
      "typeVersion": 1
    },
    {
      "id": "23f47c29-22e7-46b3-abec-7ea82300ad3f",
      "name": "Google Gemini Chat Model (Summarizer)",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -48,
        384
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "434b83d1-d234-4891-bd6f-3226bb908671",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1664,
        176
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                6
              ]
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9a8afe02-250b-4e18-992b-6b85456fc0e3",
      "name": "Set Search Config1",
      "type": "n8n-nodes-base.set",
      "position": [
        -1440,
        176
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"topic\": \"INSERT_TOPIC_HERE\",\n  \"regions\": [\"INSERT_REGION_HERE\"],\n  \"platforms\": [\"linkedin.com\"],\n  \"search_terms\": [\"hiring\", \"vacancy\", \"job opening\", \"career\"]\n}\n"
      },
      "typeVersion": 3.4
    },
    {
      "id": "24cef7ec-e5fa-4707-a83e-8e2282a0714c",
      "name": "Google Search Jobs1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        -1168,
        176
      ],
      "parameters": {
        "geo": "={{ $json.regions[0] }}",
        "query": "={{$json.topic}} {{$json.search_terms}} site:{{ $json.platforms[0] }} {{ $json.regions[0] }} after:2025-10-01\n",
        "headless": false,
        "operation": "google_search"
      },
      "typeVersion": 1
    },
    {
      "id": "884af97e-1f75-4b86-8256-fcafefb2e630",
      "name": "Parse Search Results1",
      "type": "n8n-nodes-base.code",
      "position": [
        -912,
        176
      ],
      "parameters": {
        "jsCode": "// Safely get the organic results array from Decodo Google Search\n\nconst root = $json.results?.[0]?.content?.results;\n\n// Some Decodo versions nest it as results.organic, others as organic directly.\n// This handles both shapes.\nconst organic = root?.results?.organic || root?.organic || [];\n\nif (!Array.isArray(organic) || organic.length === 0) {\n  return []; // nothing to process\n}\n\n// Map each search result to its own item\nreturn organic.map(item => ({\n  json: {\n    url: item.url || \"\",\n    title: item.title || \"\",\n    desc: item.desc || \"\",\n    pos: item.pos || item.pos_overall || null,\n    url_shown: item.url_shown || \"\",\n    favicon_text: item.favicon_text || \"\",\n    // keep any context you want to reuse later:\n    region: $json.region || \"\",\n    platform: $json.platform || \"\"\n  }\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "f5a63c2c-0236-452f-a222-5636c8340eee",
      "name": "Loop Job URLs1",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -576,
        176
      ],
      "parameters": {
        "options": {},
        "batchSize": "=1"
      },
      "typeVersion": 3
    },
    {
      "id": "f4a6f5f4-711a-4c22-8afb-e4db83c77548",
      "name": "Scrape Page HTML1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        -272,
        256
      ],
      "parameters": {
        "url": "={{ $json.url }}"
      },
      "typeVersion": 1
    },
    {
      "id": "87392b6e-31c7-4104-9d83-6e935afb8956",
      "name": "Clean HTML Text1",
      "type": "n8n-nodes-base.code",
      "position": [
        -192,
        496
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\nlet html = \"\";\n\ntry {\n  html = item.results[0].content || \"\";\n} catch (e) {\n  html = \"\";\n}\n\nlet cleaned = html\n  .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n  .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n  .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, \"\")\n  .replace(/<\\/?[^>]+>/g, \" \")\n  .replace(/&nbsp;/gi, \" \")\n  .replace(/&amp;/gi, \"&\")\n  .replace(/&quot;/gi, '\"')\n  .replace(/&#39;/gi, \"'\")\n  .replace(/&lt;/gi, \"<\")\n  .replace(/&gt;/gi, \">\")\n  .replace(/\\s+/g, \" \")\n  .trim();\n\nreturn {\n  json: {\n    text_clean: cleaned\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bc76fed8-9777-4fc3-9080-0ca8f4a10046",
      "name": "AI Summarizer1",
      "type": "@n8n/n8n-nodes-langchain.chainSummarization",
      "position": [
        -64,
        160
      ],
      "parameters": {
        "options": {
          "summarizationMethodAndPrompts": {
            "values": {
              "prompt": "=You will receive raw cleaned text {{ $json.text_clean }} from a job-posting webpage (LinkedIn or Indeed). \nYour task is to produce a short, dense summary of the job posting focusing ONLY on \nthe information needed for job-listing tracking.\n\nDo NOT rewrite the entire page. \nDo NOT include irrelevant text, branding, disclaimers, repetitive paragraphs, or legal content.\n\nExtract only the following elements IF they appear in the text:\n\n- Company name\n- Job title\n- Location (city + country if possible)\n- Employment type (Full-time, Part-time, Contract, Remote, Hybrid, Internship)\n- Key responsibilities (very short, 2\u20133 bullets max)\n- Required skills or technologies (condense into a simple list)\n- Preferred qualifications (if present)\n- Posted date or time reference (e.g., \u201cposted 4 days ago\u201d)\n- Any noted salary or benefits (if present)\n\nReturn your result as a **short human-readable text summary**, not JSON.\n\nExample style:\n\u201cCompany: X  \nRole: Senior Data Scientist  \nLocation: Berlin, Germany  \nEmployment: Full-time  \nResponsibilities: \u2014 ; \u2014 ; \u2014  \nSkills: Python, TensorFlow, SQL  \nPreferred: MSc/PhD  \nPosted: 2 days ago\u201d\n\nIf a field is missing in the original content, skip it silently. \nKeep the entire summary under 8\u201310 lines. \nAvoid extra explanations.\n",
              "summarizationMethod": "stuff"
            }
          }
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "2f0cdf6c-fc57-45f3-bd94-a6209d118040",
      "name": "Aggregate Summaries1",
      "type": "n8n-nodes-base.code",
      "position": [
        272,
        160
      ],
      "parameters": {
        "jsCode": "// Collect all items coming from Summarization/Extraction\nconst items = $input.all();\n\n// Extract each summary from each item\nconst summaries = items.map(item => item.json);\n\n// Build a single output item\nreturn [\n  {\n    json: {\n      summaries\n    }\n  }\n];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "bab23a2e-af2c-4ba0-b708-72d999b4b4ca",
      "name": "Job Matcher Agent1",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        560,
        160
      ],
      "parameters": {
        "text": "=You are a personal AI job-matching analyst.\n\nINPUT YOU WILL RECEIVE:\n1. The user\u2019s full CV text.\n2. The job listings scraped from one region and one platform \n   (LinkedIn OR Indeed) using Decodo.\n   Each job listing contains:\n   - job_title\n   - company_name\n   - location\n   - posted_date\n   - platform\n   - job_description\n   - skills_required\n   - experience_level\n   - url\n\n\nThe region and platform are fixed for this report. \nDo not mention any other region or platform.\n\n-----------------------------------\nSTEP 1 \u2014 Understand the Candidate\n-----------------------------------\nAnalyze the CV deeply:\n- Identify the candidate\u2019s strongest skills, technologies, tools, and domains.\n- Determine the type of roles the candidate fits (automation engineer, AI engineer, network engineer, etc.).\n- Determine seniority level and career strengths.\n\n-----------------------------------\nSTEP 2 \u2014 Match Jobs to the Candidate\n-----------------------------------\nFor each scraped job:\n\n1. Determine if the job is relevant to the candidate\u2019s career path.\n2. Compare job requirements with the CV.\n3. Compute a **Match Percentage (0\u2013100%)** based on:\n   - Skill overlap\n   - Experience relevance\n   - Seniority fit\n   - Domain fit\n4. Exclude jobs below 50% match.\n5. For each included job, prepare:\n   - Job Title\n   - Company Name\n   - Location\n   - Platform (LinkedIn or Indeed)\n   - Match Percentage\n   - Short explanation of why it matches\n   - Posted Date (use posted_date provided \u2014 do NOT guess)\n   - URL\n\n-----------------------------------\nSTEP 3 \u2014 Generate The Final Weekly Report\n-----------------------------------\nWrite ONE single message structured as follows:\n\nA) Candidate Overview\n   - 1\u20132 sentences summarizing the candidate\u2019s strengths and professional identity.\n\nB) Matching Jobs (all from ONE region and ONE platform)\n   - List all relevant jobs ordered by Match Percentage (highest \u2192 lowest):\n     - Job Title \u2013 Company Name\n       Match: XX%\n       Posted: <posted_date from job listing>\n       Why it matches:\n         \u2022 <reason 1>\n         \u2022 <reason 2>\n       Link: <url>\n \nC) Key Insights for the Candidate\n   - 3\u20135 short insights:\n     \u2022 What role types seem to fit them best.\n     \u2022 What skills appear most often in matched jobs.\n     \u2022 Whether the region looks promising.\n     \u2022 Any skill gaps that could increase their match %.\n\n-----------------------------------\nRULES\n-----------------------------------\n- The report must be plain text (no markdown, no JSON).\n- Do NOT mention today\u2019s date.\n- Do NOT fabricate dates \u2014 only use posted_date from the job listing.\n- Only mention the region and platform that appear in the input.\n- Do NOT invent job listings or details.\n- Do NOT output sections with zero matches.\n- Maintain a professional, supportive tone.\n\n\nuser_cv : \"{{ $json.cv_text }}\"",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "a6e31cfb-9b44-45f5-8751-9133d8147cd5",
      "name": "Send Email Report1",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1008,
        160
      ],
      "parameters": {
        "sendTo": "user@example.com",
        "message": "={{ $json.output }}",
        "options": {
          "appendAttribution": false
        },
        "subject": "=Weekly Job Listing Report \u2013 LinkedIn/Indeed Hiring Trends ({{ new Date().toLocaleDateString() }})",
        "emailType": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "4f7e7fd7-adaf-4cb9-9a2e-554f7d3d05aa",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1488,
        96
      ],
      "parameters": {
        "color": 4,
        "width": 704,
        "height": 256,
        "content": "Groupt A: \ndefines target search parameters and uses the Decodo API (requires credentials) to search for job listings. It then filters out invalid entries to generate a clean list of URLs for processing."
      },
      "typeVersion": 1
    },
    {
      "id": "78cabaf6-22fa-4f44-86b5-fa78b74ddcec",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        64
      ],
      "parameters": {
        "color": 5,
        "width": 640,
        "height": 560,
        "content": "Group B:\ngoes through each job URL to scrape the full page HTML using Decodo, ensuring proxies handle any blocking issues. It then strips out all tags and scripts to produce clean, plain text for analysis."
      },
      "typeVersion": 1
    },
    {
      "id": "9f3f1439-0de9-4a23-bfd7-21ed81ca5909",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        16
      ],
      "parameters": {
        "color": 3,
        "width": 1200,
        "height": 336,
        "content": "Group C: \nuses AI and takes all the raw job postings converts them into key details. It then collects all the individual summaries and aggregates them into a single list for the final analysis and prepare them to be emailed."
      },
      "typeVersion": 1
    },
    {
      "id": "7938eb14-a48d-4f60-b4be-466cb17be9bd",
      "name": "Sticky Note15",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1728,
        -400
      ],
      "parameters": {
        "width": 1008,
        "height": 528,
        "content": "## How to Set Up **Decodo** Credentials in n8n\n\n### Part 1: Get Your Decodo Authentication Token\n\n* **Activate Your Plan: The Decodo node requires a Web Scraping API Advanced plan. You can begin with a free trial, which is available on the Decodo dashboard.**\n* **Locate Your Token: Once your plan is active, navigate to the Web Scraping API page within your Decodo account.**\n* **Copy Your Token: Find and copy the authentication token generated for you on that page.**\n\n### Part 2: Add the Token to n8n\n\n* **Open n8n Credentials: Inside n8n, open the credentials window.**\n\n* **Create a New Credential: Select the option to create a new credential.**\n\n* **Find the Decodo Type: Search for and select the \"Decodo Credentials API\".**\n\n* **Paste and Save: Enter your authentication token (that you copied from the Decodo dashboard) into the field and save it.**\n\n\n### For detailed instructions : github.com/Decodo/n8n-nodes-decodo/tree/main"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "83aefaae-3872-4a03-9909-45a81c2ba7f4",
  "connections": {
    "AI Summarizer1": {
      "main": [
        [
          {
            "node": "Aggregate Summaries1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Job URLs1": {
      "main": [
        [
          {
            "node": "AI Summarizer1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Scrape Page HTML1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean HTML Text1": {
      "main": [
        [
          {
            "node": "Loop Job URLs1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Set Search Config1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Page HTML1": {
      "main": [
        [
          {
            "node": "Clean HTML Text1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Job Matcher Agent1": {
      "main": [
        [
          {
            "node": "Send Email Report1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Search Config1": {
      "main": [
        [
          {
            "node": "Google Search Jobs1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Search Jobs1": {
      "main": [
        [
          {
            "node": "Parse Search Results1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate Summaries1": {
      "main": [
        [
          {
            "node": "Job Matcher Agent1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Search Results1": {
      "main": [
        [
          {
            "node": "Loop Job URLs1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model (Agent)": {
      "ai_languageModel": [
        [
          {
            "node": "Job Matcher Agent1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model (Summarizer)": {
      "ai_languageModel": [
        [
          {
            "node": "AI Summarizer1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}