AutomationFlowsAI & RAG › AI Candidate Screening Pipeline: Linkedin to Telegram with Gemini & Apify

AI Candidate Screening Pipeline: Linkedin to Telegram with Gemini & Apify

ByDean Pike @deanjp on n8n.io

LinkedIn URL → Scrape → Match → Screen → Decide, all automated

Event trigger★★★★★ complexityAI-powered55 nodesGoogle DriveAgentGoogle Drive ToolOutput Parser StructuredTelegram TriggerHTTP RequestTelegramGoogle Gemini Chat
AI & RAG Trigger: Event Nodes: 55 Complexity: ★★★★★ AI nodes: yes Added:
AI Candidate Screening Pipeline: Linkedin to Telegram with Gemini & Apify — n8n workflow card showing Google Drive, Agent, Google Drive Tool integration

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

This workflow follows the Agent → Google Drive 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
{
  "id": "FQVkQc57GUYlurVw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "First-Round Telegram and LinkedIn Fast Track AI Recruiter Assistant",
  "tags": [
    {
      "id": "94yO3JL7wpOZjk3A",
      "name": "AI Chatbot",
      "createdAt": "2025-10-22T11:21:35.499Z",
      "updatedAt": "2025-10-22T11:21:35.499Z"
    },
    {
      "id": "PEnmqZaiZ8C9mjTU",
      "name": "AI HR Automation",
      "createdAt": "2025-09-14T13:05:40.150Z",
      "updatedAt": "2025-09-14T13:05:40.150Z"
    },
    {
      "id": "zuRVEfrAKpTf5tpR",
      "name": "Telegram",
      "createdAt": "2025-08-15T02:14:41.041Z",
      "updatedAt": "2025-08-15T02:14:41.041Z"
    }
  ],
  "nodes": [
    {
      "id": "9914e5d9-f6dd-4a18-b6c5-02f995ef011f",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        48,
        -80
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "1bca066f-1627-4351-9a0a-178c359cfb50",
      "name": "Download Selected JD",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -304,
        -256
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.output.jd_match.jd_file_id }}"
        },
        "options": {
          "googleFileConversion": {
            "conversion": {
              "docsToFormat": "application/pdf"
            }
          }
        },
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "e39f1a98-9266-4023-a819-878f817d2d98",
      "name": "JD Matching Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "onError": "continueRegularOutput",
      "position": [
        -1936,
        -272
      ],
      "parameters": {
        "text": "=## Candidate's Telegram Message (Prority JD matching method)\n\"{{ $('Receive Telegram Msg to Recruiter Bot').item.json.message.text }}\"\n\n## CANDIDATE LINKEDIN PROFILE (Fallback JD matching method if candidate's Telegram message does not state which role they're applying for)\n{{ JSON.stringify($json, null, 2) }}\n\n## AVAILABLE JOB DESCRIPTIONS:\nUse the Google Drvie tool to see the list of our current job description files and file IDs. They are each aptly named with a job title in the file name.\n\nAnalyze the Telegram message context first, and secondly the candidate's LinkedIn profile data above as a fallback method.\n- Select the SINGLE most appropriate job description if there is one that clearly relates to candidate's Telegram message. \nIF THE CANDIDATE'S TELEGRAM MESSAGE MENTIONS A ROLE THAT IS IDENTICAL OR CLOSELY RELATES TO A JD YOU SEE IN THE LIST PROVIDED, YOU MUST SELECT THAT JD.\n- OR, as the fallback method, select up to a maximum of 3 job descriptions that are a best match to the candidate's LinkedIn profile.\n\n\n## YOUR RESPONSE FORMAT\nReturn your response in this exact JSON format:\n\nFor a Telegram message match:\n {\n    \"match_type\": \"telegram_msg_match\",\n    \"jd_match\": {\n      \"jd_filename\": \"Marketing Director JD\",\n      \"jd_file_id\": \"1xxxxxxxxxxxxxxxxxxxxxx\",\n      \"confidence\": \"high\"\n    }\n  }\n\nFor a LinkedIn profile match (up to a maximum of 3 best-match JDs):\n{\n  \"match_type\": \"linkedin_profile_match\", \n  \"jd_match\": [\n    {\n      \"jd_filename\": \"Marketing Director JD\",\n      \"jd_file_id\": \"2xxxxxxxxxxxxxxxxxxxxxx\"\n    },\n    {\n      \"jd_filename\": \"COO_JD.pdf\", \n      \"jd_file_id\": \"3xxxxxxxxxxxxxxxxxxxxxx\"\n    },\n    {\n      \"jd_filename\": \"Sales Enablement Lead - job description.pdf\",\n      \"jd_file_id\": \"4xxxxxxxxxxxxxxxxxxxxxx\"\n    }\n  ]\n}",
        "options": {
          "systemMessage": "=You are an expert HR tech recruiter. Your task is to match a candidate with the most appropriate job description from as list of job descriptions I provide to you. \n\nAs a priority, you should first try to match a single JD based on the contents of the candidate's Telegram message where possible. \n\nIf the contents of their message do not clearly state a JD or specific role, then you should match up to 3 JDs based on the content of their LinkedIn profile as a fallback method - but you do not have to match always 3: it can be less JDs if there is just 1 or 2 JDs that are only the best fit.\n\n## COMPANY DESCRIPTION\nOur company specialises in providing AI and Automation workflow solutions for small businesses, and we use tools such as n8n, Zapier, OpenAI, Claude Code, Airtable, and more."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "executeOnce": false,
      "typeVersion": 2.2
    },
    {
      "id": "14c93c36-cdca-4650-846c-77eb9139d6b1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2112,
        -384
      ],
      "parameters": {
        "color": 2,
        "width": 1536,
        "height": 624,
        "content": "## Job Description (Vacany) Matching with Candidate's LinkedIn Profile"
      },
      "typeVersion": 1
    },
    {
      "id": "5ab30ebc-fea5-46e1-a0aa-bf29046213a1",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2112,
        272
      ],
      "parameters": {
        "width": 1504,
        "height": 592,
        "content": "## LinkedIn Profile Analysis and Feedback"
      },
      "typeVersion": 1
    },
    {
      "id": "b686e251-6ed4-4984-bbc4-283d52bed4e7",
      "name": "Detailed JD Matching Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -304,
        -48
      ],
      "parameters": {
        "text": "=Compare this candidate's full LinkedIn profile against the detailed job descriptions provided. There will be a maximum of 3 separate job descriptions that were previously identified as being a best match for this candidate's profile.\n\nSelect the SINGLE best JD match by simply responding with the file name exactly like the input of each file name provided to you.\n\n\n## Candidate's LinkedIn profile\n{{ JSON.stringify($('Set Key LinkedIn Profile Data').item.json.linkedinProfileData, null, 2) }}\n\n\n\n## Pre-matched Job Descriptions\n\nJob Description 1: {{ $('Loop Over Items').all()[0].json.jd_filename }}\n{{ $('Loop Over Items').all()[0].json.text }}\n\n\n{% if $('Loop Over Items').all()[1] %}\nJob Description 2: {{ $('Loop Over Items').all()[1].json.jd_filename }}\n{{ $('Loop Over Items').all()[1].json.text }}\n{% endif %}\n\n\n{% if $('Loop Over Items').all()[2] %}\nJob Description 3: {{ $('Loop Over Items').all()[2].json.jd_filename }}\n{{ $('Loop Over Items').all()[2].json.text }}\n{% endif %}\n\n\n--\n\nSelect the best match and respond in this JSON format:\n{\n  \"selected_jd\": {\n    \"jd_filename\": \"exact_filename_here\"\n  }\n}",
        "options": {
          "systemMessage": "=You are an expert HR tech recruiter. Your task is to match a candidate's profile based on their LinkedIn profile provided, with the best-fit job description provided from a maximum of 3 job descriptions.\n\n## COMPANY DESCRIPTION\nOur company specialises in providing AI and Automation workflow solutions for small businesses, and we use tools such as n8n, Zapier, OpenAI, Claude Code, Airtable, and more."
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "5487ce4d-0026-455f-9220-e3634e5b6f79",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -1072,
        -144
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "7ea5ec43-29b8-4815-96b1-8fad17f88cf8",
      "name": "Download Selected JD1",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -896,
        0
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.jd_file_id }}"
        },
        "options": {
          "googleFileConversion": {
            "conversion": {
              "docsToFormat": "application/pdf"
            }
          }
        },
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "a09b357e-9e1a-4beb-9a5b-f2f4339955e1",
      "name": "Extract from File1",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -720,
        0
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "c5584a89-5766-4a40-ade5-c801c689f5ae",
      "name": "Access JD Files",
      "type": "n8n-nodes-base.googleDriveTool",
      "position": [
        -1840,
        -64
      ],
      "parameters": {
        "filter": {
          "folderId": {
            "__rl": true,
            "mode": "list",
            "value": "1UWI0TanlIGOec_d3S2HJzDymT-51BxHm",
            "cachedResultUrl": "https://drive.google.com/drive/folders/1UWI0TanlIGOec_d3S2HJzDymT-51BxHm",
            "cachedResultName": "Job Descriptions"
          }
        },
        "options": {},
        "resource": "fileFolder"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "5568dfe0-ef24-4335-8b78-4d189d0cc8a2",
      "name": "Transform for Multiple JDs",
      "type": "n8n-nodes-base.code",
      "position": [
        -1296,
        -176
      ],
      "parameters": {
        "jsCode": "// Transform structure at input.output if it exists, or process for telegram_msg_match\n  if ($json.output.match_type === \"linkedin_profile_match\") {\n    // For linkedin_profile_match, map the array - FIX: use jd_match not linkedin_profile_match\n    return $json.output.jd_match.map(jd => ({\n      jd_filename: jd.jd_filename,\n      jd_file_id: jd.jd_file_id\n    }));\n  } else {\n    // For telegram_msg_match, return single item - FIX: use jd_match not telegram_msg_match\n    return [{\n      jd_filename: $json.output.jd_match.jd_filename,\n      jd_file_id: $json.output.jd_match.jd_file_id\n    }];\n  }"
      },
      "typeVersion": 2
    },
    {
      "id": "a6a3f28e-183e-460b-a587-3a89fafa1ca8",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -576,
        -384
      ],
      "parameters": {
        "color": 2,
        "width": 1344,
        "height": 1248,
        "content": ""
      },
      "typeVersion": 1
    },
    {
      "id": "949885c6-2c82-4be6-92e7-7349faca7d89",
      "name": "Match Selected JD Name with Full Text",
      "type": "n8n-nodes-base.code",
      "position": [
        48,
        208
      ],
      "parameters": {
        "jsCode": "// Get the selected filename from the AI agent output\nconst selectedFilename = $json.output.selected_jd.jd_filename;\n\n// Get all the JD data from the loop output\nconst allJDs = $('Loop Over Items').all();\n\n// Find the matching JD by filename\nconst selectedJD = allJDs.find(jd => jd.json.jd_filename === selectedFilename);\n\nif (!selectedJD) {\n  throw new Error(`No JD found with filename: ${selectedFilename}`);\n}\n\n// Return just the selected JD data\nreturn [{\n  selected_jd_filename: selectedJD.json.jd_filename,\n  selected_jd_file_id: selectedJD.json.jd_file_id,\n  selected_jd_text: selectedJD.json.text\n}];"
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "b36268e4-98f0-4ac2-9e2a-708f5ac0b7eb",
      "name": "Recruiter Scoring Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -1968,
        448
      ],
      "parameters": {
        "text": "=Candidate's LinkedIn Profile:\n{{ JSON.stringify($('Set Key LinkedIn Profile Data').item.json.linkedinProfileData, null, 2) }}\n",
        "options": {
          "systemMessage": "=# Overview\nYou are an expert sales and technical recruiter specializing in AI, automation, and software roles. You have been given a job description and a candidate's LinkedIn profile. Your task is to analyze the LinkedIn profile data in relation to the job description and provide a detailed screening report.\n\nFocus specifically on how well the candidate matches the core requirements and ideal profile outlined in the job description. Evaluate both technical skill alignment and business-context understanding. Use reasoning grounded in the actual content of the resume and job post \u2013 avoid making assumptions.\n\n## Output\nYour output should follow this exact format:\n\nJob Description Matched:\nA simple direct copy of {{ $json.selected_jd_filename }}\n\nCandidate Strengths:\nList the top strengths or relevant qualifications the candidate brings to the table. Be specific.\n\nCandidate Weaknesses:\nList areas where the candidate is lacking or mismatched based on the job description.\n\nRisk Factor:\n- Assign a risk score (Low / Medium / High)\n- Explain the worst-case scenario if this candidate is hired.\n\nReward Factor:\n- Assign a reward score (Low / Medium / High)\n- Describe the best-case scenario \u2013 what value could this candidate unlock?\n- Does the candidate appear to be a short-term or long-term fit?\n\nOverall Fit Rating (0\u201310):\nAssign a number between 0 (terrible match) and 10 (perfect match). Do not give decimals.\n\nJustification for Rating:\nExplain clearly why this candidate received that score. Reference specific LinkedIn profile content and how it aligns or doesn't with the job description.\n\n\n## Job Description\n\nFilename: {{ $json.selected_jd_filename }}\n\nJD content:\n{{ $json.selected_jd_text }}\n"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2.2
    },
    {
      "id": "6db7839e-719d-44c7-b3b5-17798299933a",
      "name": "Structured Output Parser-1",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -1664,
        -64
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n    \"type\": \"object\",\n    \"properties\": {\n      \"match_type\": {\n        \"type\": \"string\",\n        \"enum\": [\"telegram_msg_match\", \"linkedin_profile_match\"]\n      },\n      \"jd_match\": {\n        \"oneOf\": [\n          {\n            \"type\": \"object\",\n            \"properties\": {\n              \"jd_filename\": {\"type\": \"string\"},\n              \"jd_file_id\": {\"type\": \"string\"},\n              \"confidence\": {\n                \"type\": \"string\",\n                \"enum\": [\"high\", \"medium\", \"low\"]\n              }\n            },\n            \"required\": [\"jd_filename\", \"jd_file_id\", \"confidence\"]\n          },\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"object\",\n              \"properties\": {\n                \"jd_filename\": {\"type\": \"string\"},\n                \"jd_file_id\": {\"type\": \"string\"}\n              },\n              \"required\": [\"jd_filename\", \"jd_file_id\"]\n            },\n            \"minItems\": 1,\n            \"maxItems\": 3\n          }\n        ]\n      }\n    },\n    \"required\": [\"match_type\", \"jd_match\"]\n  }"
      },
      "typeVersion": 1.3
    },
    {
      "id": "a86fc69f-8052-4474-9d76-ee4988d2f461",
      "name": "Structured Output Parser-3",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -1792,
        688
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"name\": \"linkedin_screening_evaluation\",\n  \"description\": \"Analyzes a candidate's linkedin profile against a job description and output strengths, weaknesses, risk/reward assessment, and an overall fit score.\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"candidate_strengths\": {\n      \"type\": \"array\",\n      \"description\": \"A list of specific strengths or qualifications that match the job description. Do not include any special characters.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"candidate_weaknesses\": {\n      \"type\": \"array\",\n      \"description\": \"A list of areas where the candidate falls short or lacks alignment with the job requirements. Do not include any special characters.\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"risk_factor\": {\n      \"type\": \"object\",\n      \"description\": \"An evaluation of the potential risks of hiring this candidate.\",\n      \"properties\": {\n        \"score\": {\n          \"type\": \"string\",\n          \"enum\": [\"Low\", \"Medium\", \"High\"],\n          \"description\": \"The risk level of hiring this candidate.\"\n        },\n        \"explanation\": {\n          \"type\": \"string\",\n          \"description\": \"A brief explanation of the worst-case scenario if the candidate is hired.\"\n        }\n      },\n      \"required\": [\"score\", \"explanation\"]\n    },\n    \"reward_factor\": {\n      \"type\": \"object\",\n      \"description\": \"An evaluation of the potential upside of hiring this candidate.\",\n      \"properties\": {\n        \"score\": {\n          \"type\": \"string\",\n          \"enum\": [\"Low\", \"Medium\", \"High\"],\n          \"description\": \"The reward level of hiring this candidate.\"\n        },\n        \"explanation\": {\n          \"type\": \"string\",\n          \"description\": \"A description of the best-case scenario and whether the candidate is a short-term or long-term fit.\"\n        }\n      },\n      \"required\": [\"score\", \"explanation\"]\n    },\n    \"selected_jd_filename\": {\n      \"type\": \"string\",\n      \"description\": \"The name of the job description file you are comparing the candidate's LinkedIn profile with.\"\n    },\n    \"overall_fit_rating\": {\n      \"type\": \"integer\",\n      \"description\": \"A rating from 0 to 10 indicating how well the candidate matches the job description.\",\n      \"minimum\": 0,\n      \"maximum\": 10\n    },\n    \"justification_for_rating\": {\n      \"type\": \"string\",\n      \"description\": \"A summary explaining why the candidate received the specific fit rating, referencing their LinkedIn profile and job description alignment.\"\n    }\n  },\n  \"required\": [\n    \"candidate_strengths\",\n    \"candidate_weaknesses\",\n    \"risk_factor\",\n    \"reward_factor\",\n    \"overall_fit_rating\",\n    \"justification_for_rating\",\n    \"selected_jd_filename\"\n  ]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "3e195903-7d10-47e3-91b2-17574b8dcacb",
      "name": "Structured Output Parser-2",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -144,
        160
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"selected_jd\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"jd_filename\": {\n          \"type\": \"string\",\n          \"description\": \"Exact filename of the selected job description\"\n        }\n      },\n      \"required\": [\"jd_filename\"]\n    }\n  },\n  \"required\": [\"selected_jd\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "083be177-e9dd-4469-b501-f76b8c498a11",
      "name": "Receive Telegram Msg to Recruiter Bot",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        -4064,
        -256
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9fab2352-6fe9-4f12-b86a-c6623f716a03",
      "name": "Extract LinkedIn Profile Information",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2592,
        0
      ],
      "parameters": {
        "url": "https://api.apify.com/v2/acts/dev_fusion~linkedin-profile-scraper/runs?token=YOUR_TOKEN_HERE",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n    \"profileUrls\": [\n        \"{{ $json.possibleProfileUrl }}\"\n    ]\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "148a5324-f33f-4848-a8d0-2568ece924a3",
      "name": "LinkedIn Profile Ready?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3104,
        256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "06eeb15d-c309-4e6a-a790-8c4daf800dc4",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.data.status }}",
              "rightValue": "SUCCEEDED"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0d3015d5-4f7c-4352-b233-4666733bb816",
      "name": "Wait for LinkedIn Profile",
      "type": "n8n-nodes-base.wait",
      "position": [
        -3568,
        528
      ],
      "parameters": {
        "amount": 15
      },
      "typeVersion": 1.1
    },
    {
      "id": "97eea42e-0e47-4494-bceb-c0f172456fc6",
      "name": "Reply with Confirmation Msg",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -2816,
        -288
      ],
      "parameters": {
        "text": "=Hi {{ $('Receive Telegram Msg to Recruiter Bot').item.json.message.chat.first_name }} - Thanks for sharing your LinkedIn profile. We'll review your profile very shortly and let you know our thoughts plus any next steps.\n\nTalent @ YOUR COMPANY NAME",
        "chatId": "={{ $('Receive Telegram Msg to Recruiter Bot').item.json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ad85be28-c5c8-4a52-98b4-772317740fc0",
      "name": "Increment Loop Counter",
      "type": "n8n-nodes-base.set",
      "position": [
        -3328,
        528
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "75664d80-2196-45b8-ad86-d57e40a3a07c",
              "name": "loopCount",
              "type": "number",
              "value": "={{ ($json.loopCount || 0) + 1 }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "4792c59c-a665-426f-990d-b44155792783",
      "name": "Initialize Loop Counter to Poll for Completion",
      "type": "n8n-nodes-base.set",
      "position": [
        -3936,
        256
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e1b85dbe-05b1-4886-bb4b-7dd62aa64e3a",
              "name": "loopCount",
              "type": "number",
              "value": 0
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7f0e5f13-db43-46ce-a9f2-d5608e0f07d9",
      "name": "Restore Loop Counter",
      "type": "n8n-nodes-base.code",
      "position": [
        -3408,
        256
      ],
      "parameters": {
        "jsCode": "// Get loopCount from the node that loops back\n  let loopCount = 0;\n  try {\n    loopCount = $('Checked 10x for LinkedIn Profile Data?').item.json.loopCount || 0;\n  } catch (e) {\n    // First iteration - use Initialize\n    loopCount = $('Initialize Loop Counter to Poll for Completion').item.json.loopCount || 0;\n  }\n\n  return {\n    json: {\n      ...$json,      // HTTP response\n      loopCount: loopCount  // Add loopCount back\n    }\n  };"
      },
      "typeVersion": 2
    },
    {
      "id": "3e963459-50fd-4326-a2e0-45890e1c10bc",
      "name": "Checked 10x for LinkedIn Profile Data?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3088,
        528
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "9e701358-0ccd-48dc-a175-3e7cbfdebbcd",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.loopCount }}",
              "rightValue": 10
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "fa9b7243-97ff-41a8-a5fc-50ef75e72ad3",
      "name": "Check LinkedIn Profile Extraction Status",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -3696,
        256
      ],
      "parameters": {
        "url": "=https://api.apify.com/v2/acts/{{ $('Extract LinkedIn Profile Information').item.json.data.actId }}/runs/last?token=YOUR_APIFY_API_TOKEN",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "9f93b9f6-b381-4947-8034-dbf37e5ea8d0",
      "name": "Get Fully Extracted LinkedIn Profile Data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2832,
        240
      ],
      "parameters": {
        "url": "=https://api.apify.com/v2/acts/{{ $json.data.actId }}/runs/last/dataset/items?token=YOUR_APIFY_API_TOKEN",
        "options": {}
      },
      "executeOnce": true,
      "typeVersion": 4.2
    },
    {
      "id": "4c5e2b97-4c1c-40db-9d4f-89c51c03c939",
      "name": "Reply with Error/Try Again Msg",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3584,
        -64
      ],
      "parameters": {
        "text": "=Hi {{ $json.message.chat.first_name }} - thanks for getting in touch - we're unable to verify the LinkedIn profile URL you shared. Can you double check it and try sending us your LinkedIn profile URL again?\n\nNote that the format should look like: https://www.linkedin.com/in/williamhgates\n\nTalent @ YOUR COMPANY NAME",
        "chatId": "={{ $json.message.chat.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "9075ffb4-1688-41ed-9e58-f21dd6223dd9",
      "name": "Set Key LinkedIn Profile Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -2384,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9745b346-6484-4b22-9fe6-fccd0446c60d",
              "name": "linkedinProfileData",
              "type": "object",
              "value": "={{ $json }}"
            },
            {
              "id": "a29a37a6-bef8-4c24-ac9d-f4a2f5528bba",
              "name": "dateShared",
              "type": "string",
              "value": "={{ $now.toUTC().toFormat('yyyy-LL-dd HH:mm') }}"
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 3.4
    },
    {
      "id": "3b89d395-f178-4163-8709-96e117c996ac",
      "name": "Set JD Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -560,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1376530-e0d7-4ce5-abb7-bd5839a34ecb",
              "name": "jd_filename",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.jd_filename }}"
            },
            {
              "id": "85efbcf5-b293-4058-aef6-8a6dfbed1c0a",
              "name": "jd_file_id",
              "type": "string",
              "value": "={{ $('Loop Over Items').item.json.jd_file_id }}"
            },
            {
              "id": "63d88a73-7144-458f-812b-f53ccd8fda49",
              "name": "text",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "21723055-422d-4103-b65a-4811baa34300",
      "name": "Set Selected JD Format",
      "type": "n8n-nodes-base.set",
      "position": [
        528,
        448
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "271dc7a2-e8c3-40f7-9e7c-04c4463490f0",
              "name": "selected_jd_filename",
              "type": "string",
              "value": "={{ $('JD Match w/Telegram Msg?').item.json.output.email_match.jd_filename }}"
            },
            {
              "id": "2e987315-7b9b-47ca-a2ca-400978944b7a",
              "name": "selected_jd_file_id",
              "type": "string",
              "value": "={{ $('JD Match w/Telegram Msg?').item.json.output.email_match.jd_file_id }}"
            },
            {
              "id": "f9d90d18-8193-46d0-a9b2-a3fe4edcd7e5",
              "name": "selected_jd_text",
              "type": "string",
              "value": "={{ $json.text }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "87cebfda-e8b8-4e5d-9236-a1db16114fc5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4192,
        -384
      ],
      "parameters": {
        "color": 6,
        "width": 2048,
        "height": 1248,
        "content": "## Get LinkedIn Profile via Telegram"
      },
      "typeVersion": 1
    },
    {
      "id": "8903e8c4-5ae9-4d61-a426-7ed450a96c43",
      "name": "Send Msg to Internal Talent Group",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -2592,
        -288
      ],
      "parameters": {
        "text": "={{\n  (() => {\n    // Get the Telegram trigger payload safely (works even if it's pinned)\n    const trig = $('Receive Telegram Msg to Recruiter Bot').first().json || {};\n    const msg  = trig.message || trig.update?.message || {};\n\n    // Prefer \"from.username\"; fall back to \"chat.username\"\n    const username = msg.from?.username ?? msg.chat?.username ?? '';\n    const first    = msg.chat?.first_name ?? msg.from?.first_name ?? '';\n    const said     = msg.text ?? '';\n\n    // HTML escape for Parse Mode = HTML\n    const esc = s => (s ?? '').toString().replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n\n    return (\n`FYI - I've started the review of a new candidate's LinkedIn profile based on their message to me directly.\n\nName: ${esc(first)}\nTelegram Username: <code>${esc(username)}</code>\n\nHere's what they said:\n\"${esc(said)}\"\n\nI'll let you know here once the review is complete.`\n    );\n  })()\n}}",
        "chatId": "YOUR_TELEGRAM_INTERNAL_GROUP_CHAT_ID",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "0db07b5d-cdce-4020-8d78-77ce2508491b",
      "name": "Send Review Completed Msg to Talent Group",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -880,
        432
      ],
      "parameters": {
        "text": "={{\n  (() => {\n    const esc = s => (s ?? '').toString().replace(/[&<>]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c]));\n    const first = esc($json[\"First Name\"]);\n    const last  = esc($json[\"Last Name\"]);\n    const user  = esc($json[\"Telegram Username\"]);\n    const date  = esc($json[\"Date\"]);\n    const jd    = esc($json[\"JD Match\"]);\n    const fit   = esc($json[\"Overall Fit\"]);\n    const sub   = esc($json[\"Submission ID\"]);\n    const url   = \"https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit?usp=sharing\";\n\n    return `We've completed the AI candidate screening for <b>${first} ${last}</b> (Telegram username: <code>${user}</code>), who shared their LinkedIn profile with us on ${date}.` +\n           `\\n\\nWe matched this person with the JD: <b>${jd}</b>, and rated their overall fit for this role as <b>${fit}</b>/10.` +\n           `\\n\\nFull details of our analysis of the candidate can be found under submission ID <code>${sub}</code> in this <a href=\"${url}\">Google Sheet</a>.`;\n  })()\n}}",
        "chatId": "=YOUR_TELEGRAM_INTERNAL_GROUP_CHAT_ID",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "de46c695-e870-444e-903d-0efa2d60a94e",
      "name": "Gemini 2.5 Pro-3",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -2000,
        688
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "77100aff-9497-4915-b803-b55733a0ecb3",
      "name": "Gemini 2.5 Pro-2",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -336,
        160
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "e5af9a04-fa2a-45da-9758-a66b013114e9",
      "name": "Gemini 2.5 Pro-1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        -2000,
        -64
      ],
      "parameters": {
        "options": {},
        "modelName": "models/gemini-2.5-pro"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2773b269-bddd-4ff2-8eb4-b626ee712698",
      "name": "Gather and Set Final Data",
      "type": "n8n-nodes-base.set",
      "position": [
        -1376,
        432
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c4820f4c-f4ff-4cd8-9f62-ee9a959a6efd",
              "name": "submission_ID",
              "type": "string",
              "value": "={{\n  (\n    ( $json?.linkedinProfileData?.lastName ?? 'unknown' ) + ''\n  )\n  .toLowerCase()\n  .replace(/[^a-z0-9]+/g, '')  // sanitize\n  + '_' +\n  new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0,14) // UTC YYYYMMDDHHmmss\n}}"
            },
            {
              "id": "51631060-f04b-4fc9-af2f-31133dad0eec",
              "name": "telegram_username",
              "type": "string",
              "value": "={{ $('Receive Telegram Msg to Recruiter Bot').first().json?.message?.chat?.username }}"
            },
            {
              "id": "1c3c43ee-89df-4dd9-8b42-138f68a52274",
              "name": "email",
              "type": "string",
              "value": "={{ $json.linkedinProfileData.email }}"
            },
            {
              "id": "94829fe4-ac08-4ad6-9ff2-0b3791aadb0b",
              "name": "first_name",
              "type": "string",
              "value": "={{ $json.linkedinProfileData.firstName }}"
            },
            {
              "id": "72dab19c-c074-4a63-ada4-b146648aeaba",
              "name": "last_name",
              "type": "string",
              "value": "={{ $json.linkedinProfileData.lastName }}"
            },
            {
              "id": "7e08a276-e356-4f5f-bf3d-9c259216dd00",
              "name": "linkedin_profile_url",
              "type": "string",
              "value": "={{ $json.linkedinProfileData.linkedinUrl }}"
            },
            {
              "id": "5fbe3149-a1f4-49a2-a6ff-6d97ee411703",
              "name": "candidate_strengths",
              "type": "string",
              "value": "={{ $json.output.candidate_strengths.join(\"\\n\\n\") }}"
            },
            {
              "id": "b0e86b60-1797-4c94-bf81-9a2746d41472",
              "name": "candidate_weaknesses",
              "type": "string",
              "value": "={{ $json.output.candidate_weaknesses.join(\"\\n\\n\") }}"
            },
            {
              "id": "dfd2141c-1e30-4587-9815-46f44b13db41",
              "name": "risk_factor",
              "type": "string",
              "value": "={{ $json.output.risk_factor.score }}\n\n{{ $json.output.risk_factor.explanation }}"
            },
            {
              "id": "738ce192-3f62-42b6-8330-9f0021685491",
              "name": "=reward_factor",
              "type": "string",
              "value": "={{ $json.output.reward_factor.score }}\n\n{{ $json.output.reward_factor.explanation }}"
            },
            {
              "id": "c77fc627-e03c-49d0-8a17-05da4166e566",
              "name": "selected_jd_filename",
              "type": "string",
              "value": "={{ $json.output.selected_jd_filename }}"
            },
            {
              "id": "0fda6a01-7100-4f3f-8387-4192f725754c",
              "name": "overall_fit_rating",
              "type": "string",
              "value": "={{ $json.output.overall_fit_rating }}"
            },
            {
              "id": "1304faef-8725-49e4-97d1-9475b5abd40a",
              "name": "justification_for_rating",
              "type": "string",
              "value": "={{ $json.output.justification_for_rating }}"
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 3.4
    },
    {
      "id": "c38679d4-d14d-4b39-894b-1aaee12d8e78",
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        -1600,
        432
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition"
      },
      "typeVersion": 3.2
    },
    {
      "id": "637c9812-3f39-419d-8210-b60b025b92c8",
      "name": "Add Candidate Analysis in GSheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -1136,
        432
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $now.setZone('UTC').format('yyyy-MM-dd HH:mm') }}",
            "JD Match": "={{ $json.selected_jd_filename }}",
            "Last Name": "={{ $json.last_name }}",
            "Strengths": "={{ $json.candidate_strengths }}",
            "First Name": "={{ $json.first_name }}",
            "Weaknesses": "={{ $json.candidate_weaknesses }}",
            "Overall Fit": "={{ $json.overall_fit_rating }}",
            "Risk Factor": "={{ $json.risk_factor }}",
            "Justification": "={{ $json.justification_for_rating }}",
            "Reward Factor": "={{ $json.reward_factor }}",
            "Submission ID": "={{ $json.submission_ID }}",
            "Email (if known)": "={{ $json.email }}",
            "Telegram Username": "={{ $json.telegram_username }}",
            "LinkedIn Profile URL": "={{ $json.linkedin_profile_url }}"
          },
          "schema": [
            {
              "id": "Submission ID",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Submission ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LinkedIn Profile URL",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "LinkedIn Profile URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "First Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "First Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Last Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email (if known)",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Email (if known)",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Telegram Username",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Telegram Username",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Strengths",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Strengths",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Weaknesses",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Weaknesses",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Risk Factor",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Risk Factor",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Reward Factor",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Reward Factor",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "JD Match",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "JD Match",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Overall Fit",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Overall Fit",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Justification",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Justification",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit#gid=0",
          "cachedResultName": "LinkedIn Profile AI Candidate Screening"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit?usp=drivesdk",
          "cachedResultName": "LinkedIn Profile AI Candidate Screening"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c3be847a-df8b-4947-ac45-b0b7b7356f1d",
      "name": "Start Msg Sent + Valid LinkedIn Profile URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3840,
        -256
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a1a72753-9507-46bd-b14e-2179fcf176b1",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.message.text }}",
              "rightValue": "/start"
            },
            {
              "id": "b6681835-d6d7-4645-81bb-069885672a5b",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $json.message.text }}",
              "rightValue": "linkedin.com/in/"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "90e80421-d74a-40a5-a632-656177579795",
      "name": "Spam Check: Sent  <4 LinkedIn Profiles?",
      "type": "n8n-nodes-base.if",
      "position": [
        -3152,
        -272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "62ba6eb7-dd7e-4965-88c6-febd5fda4b83",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $items(\"Get All Rows Matching Telegram Username\").length }}",
              "rightValue": 3
            }
          ]
        }
      },
      "executeOnce": false,
      "typeVersion": 2.2
    },
    {
      "id": "0687fa9f-2c46-4858-a4a1-4e1783204719",
      "name": "Reply - Too Many LinkedIn URLs Sent Msg",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -3152,
        -48
      ],
      "parameters": {
        "text": "=Hi {{ $('Receive Telegram Msg to Recruiter Bot').item.json.message.chat.first_name }} - Apologies - we've detected that you have sent us more than 3 LinkedIn profile URLs either recently or in the past.\n\nWe put this limit in place to help prevent spam.\n\nPlease get in touch with us directly if you have an additional genuine job application you'd like to submit: talent@yourcompany.com\n\nTalent @ YOUR COMPANY NAME",
        "chatId": "={{ $('Receive Telegram Msg to Recruiter Bot').item.json.message.from.id }}",
        "additionalFields": {
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d132d927-781d-4bd2-aca1-ff77d4e0f134",
      "name": "Get All Rows Matching Telegram Username",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -3584,
        -272
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.message.chat.username }}",
              "lookupColumn": "Telegram Username"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit#gid=0",
          "cachedResultName": "LinkedIn Profile AI Candidate Screening"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit?usp=drivesdk",
          "cachedResultName": "LinkedIn Profile AI Candidate Screening"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 4.7
    },
    {
      "id": "543325a6-3cf2-42f5-a64b-95be550296c6",
      "name": "Count Rows Matching Telegram Username",
      "type": "n8n-nodes-base.summarize",
      "position": [
        -3360,
        -272
      ],
      "parameters": {
        "options": {},
        "fieldsToSummarize": {
          "values": [
            {
              "field": "Telegram Username"
            }
          ]
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "6c09a56b-05c8-4e0d-bc7e-dc485dcdb744",
      "name": "Grab Clean LinkedIn URL",
      "type": "n8n-nodes-base.code",
      "position": [
        -2816,
        -48
      ],
      "parameters": {
        "jsCode": "// Function node: extract canonical /in/<slug> profile URL\nconst trig = $('Receive Telegram Msg to Recruiter Bot').first().json || {};\nconst msg  = trig.message || trig.update?.message || trig.channel_post || {};\nconst txt  = ((msg.text ?? msg.caption ?? '') + '');\n\n// 1) Collect candidates from Telegram entities (most reliable)\nlet candidates = [];\nconst entities = msg.entities ?? msg.caption_entities ?? [];\nfor (const e of entities) {\n  if (e.type === 'url') {\n    const start = e.offset ?? 0;\n    const end   = start + (e.length ?? 0);\n    candidates.push(txt.slice(start, end));\n  } else if (e.type === 'text_link' && e.url) {\n    candidates.push(e.url);\n  }\n}\n// 2) Fallback: scan the text\nif (!candidates.length) {\n  candidates = [...txt.matchAll(/https?:\\/\\/[^\\s<>()]+/gi)].map(m => m[0]);\n}\n\n// 3) Sanitize + extract /in/<slug> (supports /m/in/ and /mwlite/in/)\nconst sanitize = s =>\n  (s || '')\n    .replace(/[\\p{Z}\\p{C}]+/gu, '')   // remove whitespace & control chars (incl. \\n, NBSP, ZWSP)\n    .replace(/[)\\].,;!?]+$/, '');     // drop trailing punctuation\n\nconst IN_PROFILE = /linkedin\\.com\\/(?:mwlite\\/|m\\/)?in\\/([A-Za-z0-9._%-]+)/i;\n\nfor (const raw0 of candidates) {\n  const raw = sanitize(raw0);\n  const m = raw.match(IN_PROFILE);\n  if (!m) continue;\n\n  const slug = decodeURIComponent(m[1]).replace(/\\/+$/, '');\n  if (!slug) continue;\n\n  return [{\n    json: {\n      isLinkedIn: true,\n      slug,\n      possibleProfileUrl: `https://www.linkedin.com/in/${slug}`,\n      sourceUrl: raw\n    }\n  }];\n}\n\n// Nothing matched\nreturn [{ json: { isLinkedIn: false } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "8ed28114-ce30-4aab-9847-334a152caac8",
      "name": "JD Match w/Telegram Msg?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1504,
        -272
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "dde6fee9-e053-49de-aacf-b63f33fabec3",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.output.match_type }}",
              "rightValue": "telegram_msg_match"
            }
          ]
        }
      },
      "executeOnce": true,
      "typeVersion": 2.2
    },
    {
      "id": "dfcbb685-4a0b-4592-bbdc-a21edc17e7d8",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3376,
        -1328
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 912,
        "content": "## Sample Outputs\n### [Google Sheets - LinkedIn AI Candidate Screening - sample](https://docs.google.com/spreadsheets/d/19ZzSG-MyFgdvruWhTozuimYBG-QSvaPhHmqMJqdmJpM/edit?usp=sharing)\n\n\n### Telegram messages between AI recruiter bot and job applicant\n\n![](https://i.postimg.cc/7L1rTsv8/telegram5.jpg)\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "9f0beee4-1ee4-4071-8105-caf6a597edd3",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -4192,
        -1152
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 736,
        "content": "## Troubleshooting\n  * **Telegram bot not responding**: Ensure bot token is correct in \"Receive Telegram Msg to Recruiter Bot\" node, and users have sent `/start` to your bot at least once\n  * **\"LinkedIn profile URL invalid\" error**: Check that candidates are sending full URLs in format `https://www.linkedin.com/in/username` (not shortened links or text without URL)\n  * **Apify extraction failing**: Verify Apify API token is correctly set in all three HTTP Request nodes (\"Extract LinkedIn        \n  Profile Information\", \"Check LinkedIn Profile Extraction Status\", \"Get Fully Extracted LinkedIn Profile Data\")\n  * **LinkedIn extraction timeout**: Increase polling attempts in \"Checked 10x for LinkedIn Profile Data?\" node (currently 10) or increase wait time in \"Wait for LinkedIn Profile\" node (currently 15 seconds)\n  * **Spam check blocking valid users**: Check \"Get All Rows Matching Telegram Username\" node is pointing to correct Google Sheet, and adjust limit in \"Spam Check: Sent <4 LinkedIn Profiles?\" node if needed\n  * **JD matching returns no results**: Check \"Access JD Files\" node folder ID points to your Job Descriptions folder, and JD files are named clearly (e.g., \"Marketing Director JD.pdf\")\n  * **JD matching is not relevant for my company**: Update the \"Company Description\" in the System Messages in all three AI agent nodes (\"JD Matching Agent\", \"Detailed JD Matching Agent\", \"Recruiter Scoring Agent\")\n  * **\"Can't find matching JD\"**: Ensure candidate's Telegram message mentions role name OR their LinkedIn profile clearly indicates relevant experience for available JDs\n  * **Google Sheets errors**: Verify sheet name is \"LinkedIn Profile AI Candidate Screening\" and column headers exactly match workflow expectations (Submission ID, Date, LinkedIn Profile URL, First Name, Last Name, etc.)\n  * **Telegram group notifications not appearing**: Verify chat ID is correct in \"Send Msg to Internal Talent Group\" and \"Send Review Completed Msg to Talent Group\" nodes (use negative number for group chats, e.g., `-4954246611`)\n  * **Missing candidate data in Google Sheets**: LinkedIn profile may be incomplete - verify Apify successfully extracted data by checking \"Get Fully Extracted LinkedIn Profile Data\" node output\n  * **Loop counter not working**: Check \"Restore Loop Counter\" code node references correct node names (\"Checked 10x for LinkedIn Profile Data?\" and \"Initialize Loop Counter to Poll for Completion\")\n  * **401/403 API errors**: Re-authorize all OAuth2 credentials (Google Drive, Google Sheets) and verify Apify and Telegram API tokens are valid\n  * **AI analysis quality issues**: Edit system prompts in \"JD Matching Agent\", \"Detailed JD Matching Agent\", and \"Recruiter Scoring Agent\" nodes to refine screening criteria and provide more context about your hiring needs\n  * **Gemini API rate limit errors**: Check your usage at [Google AI Studio](https://aistudio.google.com/) and consider upgrading to paid tier if exceeding free tier limits (see [rate limits documentation](https://ai.google.dev/gemini-api/docs/rate-limits))"
      },
      "typeVersion": 1
    },
    {
      "id": "2090e260-0749-4ae7-a74a-2d9773416e37",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -5264,
        -1152
      ],
      "parameters": {
        "color": 7,
        "width": 1040,
        "height": 2544,
        "content": "# **First-Round Telegram + LinkedIn AI Recruiter Assistant**\n  *LinkedIn URL \u2192 Scrape \u2192 Match \u2192 Screen \u2192 Decide, all automated*\n\n  This workflow automatically processes candidate LinkedIn profiles shared via Telegram, intelligently matches them to job descriptions, performs AI-powered screening analysis, and sends actionable summaries to your team in Telegram.\n\n  ### Good to know\n  - Handles LinkedIn profile scraping via Apify API (extracts full profile data including experience, education, skills)\n  - Built-in spam prevention: limits users to 3 LinkedIn profile submissions\n  - Two-stage JD matching: prioritizes role mentioned in candidate's Telegram message, falls back to LinkedIn profile analysis if needed\n  - Uses Google Gemini API for AI screening (generous free tier and rate limits, typically enough to avoid paying for API requests - check latest pricing at [Google AI Pricing](https://ai.google.dev/pricing) and [rate limits documentation](https://ai.google.dev/gemini-api/docs/rate-limits))\n  - Automatic polling mechanism checks Apify extraction status up to 10 times (15-second intervals)\n  - Complete audit trail logged in Google Sheets with unique submission IDs\n\n  ## Who's it for\n  Hiring teams and recruiters who want to streamline first-round screening for candidates who share LinkedIn profiles directly. Perfect for companies accepting applications via messaging platforms (Telegram, WhatsApp, etc.), especially useful fortech-savvy audiences and remote/global hiring.\n\n  ## How it works\n  1. Telegram bot receives message containing LinkedIn profile URL from candidate\n  2. Validates URL format and checks spam prevention (max 3 submissions per Telegram username)\n  3. Sends confirmation message to candidate and notifies internal talent team via Telegram group\n  4. Extracts clean LinkedIn URL and initiates Apify scraping job\n  5. Polls Apify API up to 10 times (15-second intervals) until profile extraction completes\n  6. AI agent matches candidate to best-fit job description by analyzing Telegram message context first (if candidate mentioned a role), or LinkedIn profile content as fallback (selects up to 3 potential JD matches)\n  7. If multiple JDs matched, second AI agent selects the single best fit based on detailed profile analysis\n  8. AI recruiter agent analyzes LinkedIn profile against selected JD and generates structured screening report (strengths,\n  weaknesses, risk/rewar

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

LinkedIn URL → Scrape → Match → Screen → Decide, all automated

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

More AI & RAG workflows → · Browse all categories →

Related workflows

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

AI & RAG

This workflow creates a multi-talented AI assistant named Simran that interacts with users via Telegram. It can handle text and voice messages, understand the user's intent, and perform various tasks.

MongoDB, Chain Llm, Google Gemini Chat +11
AI & RAG

Generate AI viral videos with NanoBanana & VEO3, shared on socials via Blotato 2. Uses @blotato/n8n-nodes-blotato, googleSheets, lmChatOpenAi, toolThink. Event-driven trigger; 94 nodes.

@Blotato/N8N Nodes Blotato, Google Sheets, OpenAI Chat +9
AI & RAG

This project is a template for building a complete academic virtual assistant using n8n. It connects to Telegram, answers frequently asked questions by querying MongoDB, keeps the community informed a

Telegram, MongoDB, Telegram Trigger +6
AI & RAG

Creators, marketers, and brands that want to turn a single product photo into premium motion clips, then optionally publish to Instagram/TikTok/YouTube via LATE. No editing skills required.

Telegram, Agent Tool, Telegram Trigger +5
AI & RAG

Product to Social Video (xCodeWraith Edition). Uses telegram, agentTool, telegramTrigger, httpRequest. Event-driven trigger; 83 nodes.

Telegram, Agent Tool, Telegram Trigger +5