{
  "id": "TiSJltTgLzEOzr17",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Verify resume claims against GitHub and LinkedIn and score candidate authenticity",
  "tags": [],
  "nodes": [
    {
      "id": "40144a16-3dcd-459c-88ff-08c93d4030de",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        -336
      ],
      "parameters": {
        "width": 1000,
        "height": 960,
        "content": "## Verify resume claims against GitHub and LinkedIn and score candidate authenticity\n\nThis workflow automatically validates candidate resume claims by cross-referencing public data sources, portfolios, and skill benchmarks \u2014 then generates a structured authenticity score report.\n\n### Who's it for\n\u2022 Recruiters screening high-volume applicants\n\u2022 HR teams conducting pre-interview verification\n\u2022 Hiring managers validating technical skill claims\n\u2022 Staffing agencies performing candidate due diligence\n\n### How it works / What it does\n1. Accepts resume data via webhook or scheduled batch\n2. Parses and extracts all verifiable claims\n3. Cross-references GitHub, LinkedIn, education & employment sources\n4. Benchmarks technical skills against known standards\n5. Calculates weighted authenticity scores per claim category\n6. Generates discrepancy report and confidence levels\n7. Logs full verification report to Google Sheets\n\n### How to set up\n1. Import this workflow\n2. Set up credentials (GitHub API, LinkedIn API, OpenAI/Anthropic, Google Sheets)\n3. Configure scoring weights and threshold values in the Set node\n4. Activate workflow\n\n### Requirements\n\u2022 Webhook endpoint or scheduled trigger\n\u2022 GitHub API token\n\u2022 LinkedIn API access (or RapidAPI LinkedIn proxy)\n\u2022 Google Sheets (for output logging)\n\u2022 OpenAI / Anthropic API\n\n### How to customize the workflow\n\u2022 Adjust scoring weights in the Set Scoring Config node\n\u2022 Add/remove verification sources in the HTTP Request nodes\n\u2022 Modify Python claim extractor keywords\n\u2022 Update Google Sheet columns to match your ATS schema"
      },
      "typeVersion": 1
    },
    {
      "id": "163c77a8-847b-4ebf-8c81-60d00b9a44be",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1856,
        -96
      ],
      "parameters": {
        "color": 4,
        "width": 780,
        "height": 520,
        "content": "## 1. Trigger & Resume Intake"
      },
      "typeVersion": 1
    },
    {
      "id": "80d0f75a-2f3c-454f-974d-f13e96a1ff3e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2688,
        -176
      ],
      "parameters": {
        "color": 4,
        "width": 820,
        "height": 580,
        "content": "## 2. Claim Extraction & Parsing"
      },
      "typeVersion": 1
    },
    {
      "id": "a94d1a69-c66c-4684-b50f-8a4061cb1bfd",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        -224
      ],
      "parameters": {
        "color": 4,
        "width": 1140,
        "height": 880,
        "content": "## 3. Cross-Reference Verification"
      },
      "typeVersion": 1
    },
    {
      "id": "79f320c4-bc1f-43ee-abf7-f4f8d7d3bd75",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4848,
        -176
      ],
      "parameters": {
        "color": 4,
        "width": 772,
        "height": 728,
        "content": "## 4. Scoring & Report Generation"
      },
      "typeVersion": 1
    },
    {
      "id": "45e86f4d-5b35-4645-bd94-65709e85e37f",
      "name": "Webhook - Resume Submission",
      "type": "n8n-nodes-base.webhook",
      "position": [
        2016,
        32
      ],
      "parameters": {
        "path": "resume-verify-inbound",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 1.1
    },
    {
      "id": "2f6ce57e-0e7b-4aab-b59e-40ea7e20ecb9",
      "name": "Schedule - Daily Batch Verify",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        2016,
        224
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "2ab30679-fe28-403b-bb49-67d46a8b83d7",
      "name": "Prepare Candidate Context",
      "type": "n8n-nodes-base.set",
      "position": [
        2256,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "candidateId",
              "type": "string",
              "value": "={{ $json.candidateId || $json.body?.candidateId || ('CAND-' + Date.now()) }}"
            },
            {
              "name": "candidateName",
              "type": "string",
              "value": "={{ $json.name || $json.body?.name || 'Unknown' }}"
            },
            {
              "name": "resumeText",
              "type": "string",
              "value": "={{ $json.resumeText || $json.body?.resumeText || '' }}"
            },
            {
              "name": "linkedinUrl",
              "type": "string",
              "value": "={{ $json.linkedinUrl || $json.body?.linkedinUrl || '' }}"
            },
            {
              "name": "githubUsername",
              "type": "string",
              "value": "={{ $json.githubUsername || $json.body?.githubUsername || '' }}"
            },
            {
              "name": "portfolioUrl",
              "type": "string",
              "value": "={{ $json.portfolioUrl || $json.body?.portfolioUrl || '' }}"
            },
            {
              "name": "submittedAt",
              "type": "string",
              "value": "={{ new Date().toISOString() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "1a904ae1-351c-4642-8774-3b277ee70454",
      "name": "Set Scoring Config",
      "type": "n8n-nodes-base.set",
      "position": [
        2480,
        128
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "name": "scoreWeights",
              "type": "object",
              "value": "={{ { employment: 0.35, education: 0.25, skills: 0.25, github: 0.10, certifications: 0.05 } }}"
            },
            {
              "name": "confidenceThresholds",
              "type": "object",
              "value": "={{ { high: 0.80, medium: 0.55, low: 0.30 } }}"
            },
            {
              "name": "flagThreshold",
              "type": "number",
              "value": "={{ 0.40 }}"
            },
            {
              "name": "jobKeywords",
              "type": "array",
              "value": "={{ ['engineer', 'developer', 'manager', 'analyst', 'architect', 'lead', 'senior', 'specialist', 'consultant', 'director'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "baf95db8-2749-415c-8883-b4ccbbbd1ec6",
      "name": "Python - Extract Resume Claims",
      "type": "n8n-nodes-base.code",
      "position": [
        2832,
        128
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "pythonNative",
        "pythonCode": "# ============================================================\n# EXTRACT & PARSE VERIFIABLE CLAIMS FROM RESUME TEXT\n# ============================================================\n\nitem = _input.item.json\n\nresume = (item.get('resumeText', '') or '').strip()\n\nimport re\n\n# ============================================================\n# 1. EMPLOYMENT HISTORY EXTRACTION\n# ============================================================\n\nemployment_patterns = [\n    r'(?P<title>[A-Z][\\w\\s]+?(?:Engineer|Developer|Manager|Analyst|Architect|Lead|Director|Specialist|Consultant|Designer|Scientist)[\\w\\s]*?)\\s*(?:at|@|,|-)\\s*(?P<company>[A-Z][\\w\\s&.,]+?)\\s*(?:from|\\()?\\s*(?P<start>(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?|\\d{4})[\\w\\s,]*)\\s*(?:\u2013|-|to|\u2014)\\s*(?P<end>(?:Present|Current|Now|Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?|\\d{4})[\\w\\s,]*)',\n]\n\nemployment_claims = []\nseen_employment = set()\n\nfor pat in employment_patterns:\n    for m in re.finditer(pat, resume, re.IGNORECASE):\n        title   = (m.group('title')   or '').strip()\n        company = (m.group('company') or '').strip()\n        start   = (m.group('start')   or '').strip()\n        end     = (m.group('end')     or '').strip()\n\n        dedup_key = f\"{title.lower()}|{company.lower()}\"\n        if dedup_key in seen_employment:\n            continue\n        seen_employment.add(dedup_key)\n\n        employment_claims.append({\n            'type':    'employment',\n            'title':   title,\n            'company': company,\n            'start':   start,\n            'end':     end\n        })\n\n# ============================================================\n# 2. EDUCATION EXTRACTION\n# ============================================================\n\nedu_pattern = r'(?P<degree>Bachelor(?:\\'?s)?|Master(?:\\'?s)?|PhD|Ph\\.D|B\\.?S\\.?|M\\.?S\\.?|MBA|B\\.?E\\.?|M\\.?E\\.?|Associate(?:\\'?s)?|Doctorate)[\\w\\s.]*?(?:in|of)?\\s*(?P<field>[\\w\\s]+?)\\s*(?:from|at|,|-)\\s*(?P<institution>[A-Z][\\w\\s]+?(?:University|College|Institute|School|Academy)[\\w\\s]*?)(?=\\n|,|\\.|$)'\n\nedu_claims = []\nseen_edu = set()\n\nfor m in re.finditer(edu_pattern, resume, re.IGNORECASE):\n    degree      = (m.group('degree')      or '').strip()\n    field       = (m.group('field')       or '').strip()\n    institution = (m.group('institution') or '').strip()\n\n    dedup_key = f\"{degree.lower()}|{institution.lower()}\"\n    if dedup_key in seen_edu:\n        continue\n    seen_edu.add(dedup_key)\n\n    edu_claims.append({\n        'type':        'education',\n        'degree':      degree,\n        'field':       field,\n        'institution': institution\n    })\n\n# ============================================================\n# 3. SKILLS EXTRACTION\n# ============================================================\n\nskill_section_pattern = r'(?:Technical\\s+)?(?:Skills?|Technologies?|Tech\\s+Stack|Tools?|Expertise|Proficiencies?|Competencies?)[:\\s\\n]+([^\\n#]{10,300})'\n\nskill_list  = []\nseen_skills = set()\n\nfor section_match in re.finditer(skill_section_pattern, resume, re.IGNORECASE):\n    raw_section = section_match.group(1) or ''\n    raw_skills  = re.split(r'[,\\n|\u2022\u00b7/]', raw_section)\n\n    for s in raw_skills:\n        cleaned = s.strip().strip('*-\u2013').strip()\n        if 2 < len(cleaned) < 40 and cleaned.lower() not in seen_skills:\n            seen_skills.add(cleaned.lower())\n            skill_list.append(cleaned)\n\n        if len(skill_list) >= 30:\n            break\n\nskill_claims = [{'type': 'skill', 'name': s} for s in skill_list]\n\n# ============================================================\n# 4. CERTIFICATION EXTRACTION\n# ============================================================\n\ncert_patterns = [\n    r'(?:Certified|Certificate|Certification\\s+in|Certified\\s+in)[:\\s]+([A-Z][\\w\\s]+?(?:AWS|GCP|Azure|PMP|CPA|CFA|CISSP|CompTIA|Scrum|Google|Microsoft|Oracle|Salesforce|PMI|ITIL|CEH|CISM|CISA)[\\w\\s]*?)(?=\\n|,|\\.|$)',\n    r'(?:AWS|GCP|Azure|Google|Microsoft|Oracle|Salesforce)\\s+(?:Certified\\s+)?[\\w\\s]+?(?:Associate|Professional|Expert|Developer|Architect|Engineer|Practitioner)(?=\\n|,|\\.|$)',\n]\n\ncert_claims  = []\nseen_certs   = set()\n\nfor pat in cert_patterns:\n    for m in re.finditer(pat, resume, re.IGNORECASE):\n        cert_name = (m.group(1) if m.lastindex and m.lastindex >= 1 else m.group(0)).strip()\n        if cert_name.lower() not in seen_certs:\n            seen_certs.add(cert_name.lower())\n            cert_claims.append({'type': 'certification', 'name': cert_name})\n\n# ============================================================\n# 5. YEAR RANGE SANITY CHECK\n# ============================================================\n\nyear_pattern  = r'\\b(19[8-9]\\d|20[0-2]\\d)\\b'\nyears_found   = [int(y) for y in re.findall(year_pattern, resume)]\ntimeline_flag = False\ntimeline_note = ''\n\nif len(years_found) >= 2:\n    span = max(years_found) - min(years_found)\n    if span > 40:\n        timeline_flag = True\n        timeline_note = f'Suspicious year span detected: {min(years_found)} to {max(years_found)}'\n\n# ============================================================\n# 6. ASSEMBLE OUTPUT\n# ============================================================\n\nall_claims = employment_claims + edu_claims + skill_claims + cert_claims\n\nreturn {\n    'json': {\n        **item,\n        'parsedClaims': all_claims,\n        'claimCounts': {\n            'employment':     len(employment_claims),\n            'education':      len(edu_claims),\n            'skills':         len(skill_claims),\n            'certifications': len(cert_claims),\n            'total':          len(all_claims)\n        },\n        'timelineFlag': timeline_flag,\n        'timelineNote': timeline_note,\n        'resumeLength': len(resume)\n    }\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "36564e60-7005-4b4d-8346-5d3db3b28b9a",
      "name": "Filter - Has Valid Claims",
      "type": "n8n-nodes-base.filter",
      "position": [
        3088,
        128
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "conditions": [
            {
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.claimCounts?.total }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "8d3f86c1-d478-4050-8b02-4472f577056d",
      "name": "GitHub - Fetch Profile",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3616,
        0
      ],
      "parameters": {
        "url": "=https://api.github.com/users/{{ $json.githubUsername }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_GITHUB_TOKEN"
            },
            {
              "name": "Accept",
              "value": "application/vnd.github.v3+json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "60981c35-680f-43ff-829a-66e16e25ac25",
      "name": "GitHub - Fetch Repositories",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3616,
        176
      ],
      "parameters": {
        "url": "=https://api.github.com/users/{{ $json.githubUsername }}/repos?per_page=30&sort=updated",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_GITHUB_TOKEN"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "09676d79-e7d0-462f-b30a-32892c671efb",
      "name": "LinkedIn - Fetch Public Profile",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3616,
        352
      ],
      "parameters": {
        "url": "=https://api.rapidapi.com/linkedin/profile?url={{ $json.linkedinUrl }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-RapidAPI-Key",
              "value": "YOUR_RAPIDAPI_KEY"
            },
            {
              "name": "X-RapidAPI-Host",
              "value": "linkedin-profiles-api.p.rapidapi.com"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "329372c4-2a23-454e-8f5d-6838dfbfe17e",
      "name": "JS - Aggregate External Evidence",
      "type": "n8n-nodes-base.code",
      "position": [
        3936,
        176
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Aggregate all external source data\nconst githubProfile = item.githubProfile || {};\nconst githubRepos = item.githubRepos || [];\nconst linkedinData = item.linkedinData || {};\n\n// GitHub-derived evidence\nconst githubEvidence = {\n  profileExists: !!githubProfile.login,\n  publicRepos: githubProfile.public_repos || 0,\n  followers: githubProfile.followers || 0,\n  accountAgeDays: githubProfile.created_at\n    ? Math.floor((Date.now() - new Date(githubProfile.created_at)) / 86400000)\n    : 0,\n  topLanguages: [...new Set(\n    (githubRepos || [])\n      .map(r => r.language)\n      .filter(Boolean)\n  )].slice(0, 10),\n  repoNames: (githubRepos || []).map(r => r.name),\n  stargazersTotal: (githubRepos || []).reduce((s, r) => s + (r.stargazers_count || 0), 0)\n};\n\n// LinkedIn-derived evidence\nconst linkedinEvidence = {\n  profileExists: !!linkedinData.firstName,\n  displayName: `${linkedinData.firstName || ''} ${linkedinData.lastName || ''}`.trim(),\n  headline: linkedinData.headline || '',\n  positions: linkedinData.positions?.values || [],\n  educations: linkedinData.educations?.values || [],\n  skills: (linkedinData.skills?.values || []).map(s => s.skill?.name).filter(Boolean)\n};\n\nreturn {\n  json: {\n    ...item,\n    githubEvidence,\n    linkedinEvidence\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e1cc8527-c205-4c9c-a4ab-ec263d2c1c08",
      "name": "AI - Cross-Reference & Verify Claims",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        4160,
        176
      ],
      "parameters": {
        "text": "=You are an expert resume verification AI. Analyze the candidate's resume claims against external evidence and produce a structured JSON verification report.\n\n## Candidate Information\nName: {{ $json.candidateName }}\nCandidate ID: {{ $json.candidateId }}\n\n## Resume Claims (parsed)\n{{ JSON.stringify($json.parsedClaims, null, 2) }}\n\n## GitHub Evidence\n{{ JSON.stringify($json.githubEvidence, null, 2) }}\n\n## LinkedIn Evidence\n{{ JSON.stringify($json.linkedinEvidence, null, 2) }}\n\n## Your Task\nFor each parsed claim, determine:\n1. Verification status: VERIFIED / UNVERIFIED / FLAGGED / INCONCLUSIVE\n2. Confidence score: 0.0 to 1.0\n3. Evidence found or discrepancy noted\n4. Specific flag reason (if FLAGGED)\n\nAlso assess:\n- Do the skills claimed match the languages/repos found on GitHub?\n- Does employment history align with LinkedIn positions?\n- Are the claimed years of experience consistent across sources?\n- Are there any date overlaps or impossible timelines?\n\nRespond ONLY with valid JSON in this exact structure:\n{\n  \"verifiedClaims\": [\n    {\n      \"claimType\": \"skill|employment|education|certification\",\n      \"claimValue\": \"string describing the claim\",\n      \"status\": \"VERIFIED|UNVERIFIED|FLAGGED|INCONCLUSIVE\",\n      \"confidenceScore\": 0.0,\n      \"evidenceSummary\": \"brief description\",\n      \"flagReason\": \"null or reason string\"\n    }\n  ],\n  \"discrepancies\": [\n    {\n      \"type\": \"date_overlap|skill_mismatch|employer_unverifiable|credential_gap\",\n      \"description\": \"string\",\n      \"severity\": \"HIGH|MEDIUM|LOW\"\n    }\n  ],\n  \"overallNotes\": \"string\"\n}",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "5930c09c-c1fb-44f0-8d21-7b94258501c7",
      "name": "JS - Calculate Authenticity Score",
      "type": "n8n-nodes-base.code",
      "position": [
        4496,
        176
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\n// Parse AI response\nlet aiResult = {};\ntry {\n  const raw = item.output || item.response || item.text || '{}';\n  const clean = raw.replace(/```json|```/g, '').trim();\n  aiResult = JSON.parse(clean);\n} catch(e) {\n  aiResult = { verifiedClaims: [], discrepancies: [], overallNotes: 'Parse error: ' + e.message };\n}\n\nconst claims = aiResult.verifiedClaims || [];\nconst discrepancies = aiResult.discrepancies || [];\nconst weights = item.scoreWeights || { employment: 0.35, education: 0.25, skills: 0.25, github: 0.10, certifications: 0.05 };\nconst flagThreshold = item.flagThreshold || 0.40;\n\n// Group claims by type\nconst grouped = {};\nclaims.forEach(c => {\n  const t = c.claimType || 'other';\n  if (!grouped[t]) grouped[t] = [];\n  grouped[t].push(c);\n});\n\n// Calculate category scores\nconst categoryScores = {};\nObject.entries(grouped).forEach(([type, claimsArr]) => {\n  const avg = claimsArr.reduce((s, c) => s + (c.confidenceScore || 0), 0) / claimsArr.length;\n  const verified = claimsArr.filter(c => c.status === 'VERIFIED').length;\n  const flagged = claimsArr.filter(c => c.status === 'FLAGGED').length;\n  categoryScores[type] = {\n    averageConfidence: +avg.toFixed(3),\n    verifiedCount: verified,\n    flaggedCount: flagged,\n    totalCount: claimsArr.length,\n    verificationRate: +(verified / claimsArr.length).toFixed(3)\n  };\n});\n\n// GitHub bonus scoring\nconst gh = item.githubEvidence || {};\nconst githubScore = gh.profileExists\n  ? Math.min(1.0, 0.4 + (gh.publicRepos > 10 ? 0.3 : gh.publicRepos * 0.03) + (gh.accountAgeDays > 365 ? 0.3 : gh.accountAgeDays / 365 * 0.3))\n  : 0.0;\n\n// Weighted overall authenticity score\nconst skillScore = categoryScores['skill']?.averageConfidence || 0;\nconst employmentScore = categoryScores['employment']?.averageConfidence || 0;\nconst educationScore = categoryScores['education']?.averageConfidence || 0;\nconst certScore = categoryScores['certification']?.averageConfidence || 0;\n\nconst overallScore = +(\n  (employmentScore * weights.employment) +\n  (educationScore * weights.education) +\n  (skillScore * weights.skills) +\n  (githubScore * weights.github) +\n  (certScore * weights.certifications)\n).toFixed(3);\n\n// Determine overall verdict\nlet verdict = 'LOW_CONFIDENCE';\nif (overallScore >= 0.80) verdict = 'HIGHLY_AUTHENTIC';\nelse if (overallScore >= 0.60) verdict = 'LIKELY_AUTHENTIC';\nelse if (overallScore >= 0.40) verdict = 'PARTIALLY_VERIFIED';\nelse if (overallScore >= 0.20) verdict = 'SUSPICIOUS';\n\nconst highSeverityFlags = discrepancies.filter(d => d.severity === 'HIGH').length;\nif (highSeverityFlags >= 2) verdict = 'FLAGGED_FOR_REVIEW';\n\n// Final report\nconst report = {\n  candidateId: item.candidateId,\n  candidateName: item.candidateName,\n  verifiedAt: new Date().toISOString(),\n  overallAuthenticityScore: overallScore,\n  verdict,\n  categoryScores,\n  githubScore: +githubScore.toFixed(3),\n  discrepancies,\n  flaggedClaimsCount: claims.filter(c => c.status === 'FLAGGED').length,\n  verifiedClaimsCount: claims.filter(c => c.status === 'VERIFIED').length,\n  totalClaimsAnalyzed: claims.length,\n  verifiedClaims: claims,\n  overallNotes: aiResult.overallNotes || '',\n  linkedinUrl: item.linkedinUrl,\n  githubUsername: item.githubUsername,\n  status: 'Completed'\n};\n\nreturn { json: { ...item, report } };"
      },
      "typeVersion": 2
    },
    {
      "id": "17b3b6fa-3f90-4cde-b382-f68a17130e5a",
      "name": "Update Google Sheet - Verification Log",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5024,
        64
      ],
      "parameters": {
        "url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/ResumeVerifications!A1:append?valueInputOption=USER_ENTERED",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"values\": [[\n    \"{{ $json.report.verifiedAt }}\",\n    \"{{ $json.report.candidateId }}\",\n    \"{{ $json.report.candidateName }}\",\n    \"{{ $json.report.overallAuthenticityScore }}\",\n    \"{{ $json.report.verdict }}\",\n    \"{{ $json.report.verifiedClaimsCount }}\",\n    \"{{ $json.report.flaggedClaimsCount }}\",\n    \"{{ $json.report.totalClaimsAnalyzed }}\",\n    \"{{ $json.report.discrepancies.length }}\",\n    \"{{ $json.report.githubScore }}\",\n    \"{{ $json.githubUsername }}\",\n    \"{{ $json.linkedinUrl }}\",\n    \"{{ $json.report.overallNotes }}\"\n  ]]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_GOOGLE_OAUTH_TOKEN"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8c4f522b-8b6f-4666-8e1f-80607d313889",
      "name": "Send Verification Report Email",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        5024,
        288
      ],
      "parameters": {
        "url": "https://api.sendgrid.com/v3/mail/send",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"personalizations\": [{\"to\": [{\"email\": \"recruiter@yourcompany.com\"}]}],\n  \"from\": {\"email\": \"verify-bot@yourcompany.com\", \"name\": \"Resume Verifier\"},\n  \"subject\": \"Resume Verification: {{ $json.report.candidateName }} \u2014 Score: {{ $json.report.overallAuthenticityScore }} ({{ $json.report.verdict }})\",\n  \"content\": [{\n    \"type\": \"text/plain\",\n    \"value\": \"Verification complete for {{ $json.report.candidateName }}\\nCandidate ID: {{ $json.report.candidateId }}\\nAuthenticity Score: {{ $json.report.overallAuthenticityScore }}/1.0\\nVerdict: {{ $json.report.verdict }}\\nClaims Verified: {{ $json.report.verifiedClaimsCount }} of {{ $json.report.totalClaimsAnalyzed }}\\nFlags Raised: {{ $json.report.flaggedClaimsCount }}\\nDiscrepancies: {{ $json.report.discrepancies.length }}\\nNotes: {{ $json.report.overallNotes }}\\n\\nFull report logged to Google Sheets.\"\n  }]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_SENDGRID_API_KEY"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "1c0a37bd-9d86-440a-97e0-8281828f8d70",
      "name": "Webhook Response - Return Report",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        5280,
        176
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json.report) }}"
      },
      "typeVersion": 1.1
    },
    {
      "id": "21b74c49-3db5-4243-b647-667b18700524",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        4128,
        384
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "9e1b90f1-f41f-45db-8b4b-9441f25cdf76",
      "name": "Wait - Rate Limit Buffer",
      "type": "n8n-nodes-base.wait",
      "position": [
        3296,
        128
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "736c1d7b-01a3-4374-b367-a0b35cf84487",
  "connections": {
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI - Cross-Reference & Verify Claims",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Set Scoring Config": {
      "main": [
        [
          {
            "node": "Python - Extract Resume Claims",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub - Fetch Profile": {
      "main": [
        [
          {
            "node": "JS - Aggregate External Evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait - Rate Limit Buffer": {
      "main": [
        [
          {
            "node": "GitHub - Fetch Profile",
            "type": "main",
            "index": 0
          },
          {
            "node": "GitHub - Fetch Repositories",
            "type": "main",
            "index": 0
          },
          {
            "node": "LinkedIn - Fetch Public Profile",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter - Has Valid Claims": {
      "main": [
        [
          {
            "node": "Wait - Rate Limit Buffer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Candidate Context": {
      "main": [
        [
          {
            "node": "Set Scoring Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub - Fetch Repositories": {
      "main": [
        [
          {
            "node": "JS - Aggregate External Evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Resume Submission": {
      "main": [
        [
          {
            "node": "Prepare Candidate Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule - Daily Batch Verify": {
      "main": [
        [
          {
            "node": "Prepare Candidate Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Python - Extract Resume Claims": {
      "main": [
        [
          {
            "node": "Filter - Has Valid Claims",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LinkedIn - Fetch Public Profile": {
      "main": [
        [
          {
            "node": "JS - Aggregate External Evidence",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Aggregate External Evidence": {
      "main": [
        [
          {
            "node": "AI - Cross-Reference & Verify Claims",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS - Calculate Authenticity Score": {
      "main": [
        [
          {
            "node": "Update Google Sheet - Verification Log",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Verification Report Email",
            "type": "main",
            "index": 0
          },
          {
            "node": "Webhook Response - Return Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI - Cross-Reference & Verify Claims": {
      "main": [
        [
          {
            "node": "JS - Calculate Authenticity Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}