{
  "id": "NsSvrI5J3SWwYgKE",
  "name": "Daily Job Postings Fetcher by Query (JSearch \u2192 Google Sheets \u2192 Telegram)",
  "tags": [],
  "nodes": [
    {
      "id": "eac919e3-dd1c-4d6b-af7b-aa058f0b8bee",
      "name": "Run Daily at 22:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1856,
        608
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 22
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ff1ac477-1069-4269-8c8c-f64206683ddc",
      "name": "Fetch Jobs from JSearch API",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches job results",
      "position": [
        -1184,
        912
      ],
      "parameters": {
        "url": "=https://jsearch.p.rapidapi.com/search?query={{ $json.query }}&page=1&num_pages=10&date_posted=week",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.2
    },
    {
      "id": "155d612f-91d0-4469-aad0-3812fc3ab045",
      "name": "Split API Results",
      "type": "n8n-nodes-base.splitOut",
      "notes": "Splits job items",
      "position": [
        -976,
        912
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {},
        "fieldToSplitOut": "data"
      },
      "notesInFlow": true,
      "typeVersion": 1
    },
    {
      "id": "d4ad60d5-c586-4075-9780-a742cb644115",
      "name": "Load Existing Job IDs",
      "type": "n8n-nodes-base.googleSheets",
      "notes": "Loads saved job IDs",
      "position": [
        -928,
        592
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "[YOUR_GOOGLE_SHEET_HERE]"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "[YOUR_GOOGLE_SHEET_HERE]"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "abb11618-7250-4e47-85d1-eedb8058bea6",
      "name": "Combine API Results with Existing IDs",
      "type": "n8n-nodes-base.merge",
      "notes": "Merges items for check",
      "position": [
        -704,
        592
      ],
      "parameters": {
        "mode": "chooseBranch",
        "useDataOfInput": 2
      },
      "notesInFlow": true,
      "typeVersion": 3.2
    },
    {
      "id": "00cbeb08-38df-42d7-9cdb-bdcb46343069",
      "name": "Filter Out Existing Jobs",
      "type": "n8n-nodes-base.code",
      "notes": "Skips already saved jobs",
      "position": [
        -480,
        592
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get all existing IDs from Google Sheets\nconst existingIds = $items('Load Existing Job IDs').map(i => i.json.ID);\n\n// Current job\nconst job = $input.item.json;\nconst jobId = job.data?.job_id;\n\n// If ID already exists \u2192 skip this item\nif (existingIds.includes(jobId)) {\n\treturn null; // <-- correct way to skip\n}\n\n// Otherwise \u2192 pass it forward\nreturn $input.item;"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "1e405143-b08f-4f8c-9f19-f9e48697cb69",
      "name": "Count New Jobs",
      "type": "n8n-nodes-base.code",
      "notes": "Counts new job items",
      "position": [
        -256,
        592
      ],
      "parameters": {
        "jsCode": "// Get all incoming items to this node\nconst allItems = $input.all();\n// Count them\nconst count = allItems.length;\n\nreturn [{ json: { count } }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "756b2f27-e413-47f9-a251-75439e39f474",
      "name": "Loop Over New Jobs",
      "type": "n8n-nodes-base.splitInBatches",
      "notes": "Loops through new jobs",
      "position": [
        0,
        400
      ],
      "parameters": {
        "options": {}
      },
      "notesInFlow": true,
      "typeVersion": 3
    },
    {
      "id": "bad27543-e41e-407f-8fc9-aa1a2f6e7ac7",
      "name": "Delay Before Writing to Sheet",
      "type": "n8n-nodes-base.wait",
      "notes": "Google API limits",
      "position": [
        176,
        400
      ],
      "parameters": {
        "amount": 2
      },
      "notesInFlow": true,
      "typeVersion": 1.1
    },
    {
      "id": "1381d96a-9e1c-49cb-a6ef-0c56206c2cf8",
      "name": "Save Job to Google Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "notes": "Writes job to sheet",
      "position": [
        352,
        400
      ],
      "parameters": {
        "columns": {
          "value": {
            "ID": "={{ $('Loop Over New Jobs').item.json.data.job_id }}",
            "Type": "={{ $('Loop Over New Jobs').item.json.data.job_employment_type }}",
            "Remote": "={{ $('Loop Over New Jobs').item.json.data.job_is_remote }}",
            "Salary": "={{ $('Loop Over New Jobs').item.json.data.job_salary }}",
            "Source": "={{ $('Loop Over New Jobs').item.json.data.job_publisher }}",
            "Status": "New",
            "Maximum": "={{ $('Loop Over New Jobs').item.json.data.job_max_salary }}",
            "Minimum": "={{ $('Loop Over New Jobs').item.json.data.job_min_salary }}",
            "Location": "={{ $('Loop Over New Jobs').item.json.data.job_location }}",
            "Job Title": "={{ $('Loop Over New Jobs').item.json.data.job_title }}",
            "Apply Link": "==HYPERLINK(\"{{ $('Loop Over New Jobs').item.json.data.job_apply_link }}\",\"Apply now\")",
            "Fetched At": "={{ $now }}",
            "Google Link": "==HYPERLINK(\"{{ $('Loop Over New Jobs').item.json.data.job_google_link }}\",\"View in Google\")",
            "Company Name": "={{ $('Loop Over New Jobs').item.json.data.employer_name }}",
            "Direct Apply": "={{ $('Loop Over New Jobs').item.json.data.job_apply_is_direct }}",
            "Company Website": "={{ $('Loop Over New Jobs').item.json.data.employer_website }}",
            "Date Posted (UTC)": "={{ new Date($('Loop Over New Jobs').item.json.data.job_posted_at_datetime_utc).toLocaleString('en-GB', { timeZone: 'Asia/Yerevan' }) }}"
          },
          "schema": [
            {
              "id": "Status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Job Title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Job Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Company Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Company Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Apply Link",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Apply Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Company Website",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Company Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Source",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Source",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Type",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Direct Apply",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Direct Apply",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Remote",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Remote",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date Posted (UTC)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date Posted (UTC)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Fetched At",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Fetched At",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Location",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Location",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Google Link",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Google Link",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Salary",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Salary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Minimum",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Minimum",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Maximum",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Maximum",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "[YOUR_GOOGLE_SHEET_HERE]"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "[YOUR_GOOGLE_SHEET_HERE]"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 4.7
    },
    {
      "id": "3856c183-c121-4a81-9a30-0e99c2f1ef87",
      "name": "Combine Count and Save Results",
      "type": "n8n-nodes-base.merge",
      "notes": "Merges count and flow",
      "position": [
        176,
        592
      ],
      "parameters": {
        "mode": "chooseBranch",
        "useDataOfInput": 2
      },
      "typeVersion": 3.2
    },
    {
      "id": "d7c89547-3671-4852-8327-703d60507f06",
      "name": "Send Telegram Report",
      "type": "n8n-nodes-base.telegram",
      "notes": "Sends job summary",
      "position": [
        352,
        592
      ],
      "parameters": {
        "text": "={{ $json.count }} jobs fetched.  | <a href=\"https://docs.google.com/spreadsheets/d/1MlCTZvfqmKmIovTUTvr7b_2RY1q3ye9NXO9NMA48w1A/edit?gid=0#gid=0\">View</a>\n#Jobs_Wags",
        "chatId": "[YOUR_CHAT_ID_HERE]",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "notesInFlow": true,
      "typeVersion": 1.2
    },
    {
      "id": "8398eca6-b09a-4724-a132-c81ff2ac1529",
      "name": "Set Search Parameters",
      "type": "n8n-nodes-base.set",
      "notes": "Edit the query here",
      "position": [
        -1632,
        912
      ],
      "parameters": {
        "mode": "raw",
        "options": {},
        "jsonOutput": "{\n  \"search_query\": \"[YOUR_JOB_NAME_HERE]\"\n}\n"
      },
      "notesInFlow": true,
      "typeVersion": 3.4
    },
    {
      "id": "7195e276-08c1-4ca6-af25-b7fdfc354307",
      "name": "Build Search Query",
      "type": "n8n-nodes-base.code",
      "notes": "Encodes the search query",
      "position": [
        -1408,
        912
      ],
      "parameters": {
        "jsCode": "const input = $json.search_query || \"\";\nconst query = encodeURIComponent(input);\n\nreturn [{ json: { query } }];"
      },
      "notesInFlow": true,
      "typeVersion": 2
    },
    {
      "id": "fb9b986b-25e6-494b-9a57-826e52898f58",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        800
      ],
      "parameters": {
        "color": 7,
        "width": 832,
        "height": 288,
        "content": "### Query Building and Querying Section\n* Defines search text, encodes it\n* Fetches job results from the API\n* Splits them into individual items"
      },
      "typeVersion": 1
    },
    {
      "id": "19b0bb8b-ec8b-4190-9069-8c44ad6b0dd7",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        464
      ],
      "parameters": {
        "color": 7,
        "width": 880,
        "height": 304,
        "content": "### Existing-ID Filtering Section\n*  Loads existing job IDs from the sheet\n* Matches API results against them\n* Removes duplicates\n* Counts how many new jobs remain for processing"
      },
      "typeVersion": 1
    },
    {
      "id": "97696dc3-73e1-4c2e-bfb5-1fde2c85508c",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        240
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 528,
        "content": "### Saving & Reporting Section\n* Iterates through new jobs\n* Applies a small delay to respect Google API limits\n* Writes each job to the sheet\n* Combines results\n* Sends a Telegram summary"
      },
      "typeVersion": 1
    },
    {
      "id": "6247f510-2f32-4915-929d-8192351e0675",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1904,
        -16
      ],
      "parameters": {
        "width": 736,
        "height": 608,
        "content": "## How it works\nThis workflow runs once per day and automatically collects new job postings from the JSearch API.\nIt builds a search query, fetches recent job listings, and splits the API response into individual jobs.\nBefore saving anything, it loads existing job IDs from a Google Sheet and filters out duplicates, ensuring each job is stored only once.\nNew jobs are then written to the sheet with structured fields (title, company, salary, links, location, etc.).\nTo avoid Google API rate limits, each write is slightly delayed.\nAt the end of the run, the workflow sends a Telegram message with a summary showing the number of new jobs added and a direct link to the sheet.\nThe goal is to provide a clean, automated daily job tracker that eliminates manual checks and duplicate entries.\n\n## Setup steps\n### Create a Google Sheet with matching headers\n(Status, ID, Job Title, Company Name, Apply Link, Company Website, Source, Type, Direct Apply, Remote, Date Posted (UTC), Fetched At, Location, Google Link, Salary, Minimum, Maximum)\n### Add credentials:\n\u2022 RapidAPI key for JSearch.\n\u2022 Google Sheets OAuth.\n\u2022 Telegram Bot token.\n### Update placeholders:\n\u2022 [YOUR_JOB_NAME_HERE] in Set Search Parameters.\n\u2022 [YOUR_GOOGLE_SHEET_HERE] (Document ID + Sheet).\n\u2022 [YOUR_CHAT_ID_HERE] for Telegram.\n### Enable the workflow and adjust the schedule time if needed."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e631f69c-586d-4877-bc74-f9a673f61e62",
  "connections": {
    "Count New Jobs": {
      "main": [
        [
          {
            "node": "Combine Count and Save Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Split API Results": {
      "main": [
        [
          {
            "node": "Combine API Results with Existing IDs",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Build Search Query": {
      "main": [
        [
          {
            "node": "Fetch Jobs from JSearch API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over New Jobs": {
      "main": [
        [
          {
            "node": "Combine Count and Save Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Delay Before Writing to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run Daily at 22:00": {
      "main": [
        [
          {
            "node": "Load Existing Job IDs",
            "type": "main",
            "index": 0
          },
          {
            "node": "Set Search Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Existing Job IDs": {
      "main": [
        [
          {
            "node": "Combine API Results with Existing IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Search Parameters": {
      "main": [
        [
          {
            "node": "Build Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Out Existing Jobs": {
      "main": [
        [
          {
            "node": "Count New Jobs",
            "type": "main",
            "index": 0
          },
          {
            "node": "Loop Over New Jobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Job to Google Sheet": {
      "main": [
        [
          {
            "node": "Loop Over New Jobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Jobs from JSearch API": {
      "main": [
        [
          {
            "node": "Split API Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Delay Before Writing to Sheet": {
      "main": [
        [
          {
            "node": "Save Job to Google Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Count and Save Results": {
      "main": [
        [
          {
            "node": "Send Telegram Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine API Results with Existing IDs": {
      "main": [
        [
          {
            "node": "Filter Out Existing Jobs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}