AI Job Scout
Module 11 · ai · intermediate
learner.json
In n8n, copy this file and paste it onto the canvas, or use Import from File.
{
"name": "11 - AI Job Scout",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 4
}
]
}
},
"id": "m11-schedule",
"name": "Every 4 Hours",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
0,
0
]
},
{
"parameters": {
"path": "jobs/scout",
"responseMode": "responseNode",
"options": {}
},
"id": "m11-webhook",
"name": "Scout API",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
200
],
"webhookId": "course-11-scout"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "s1",
"name": "keywords",
"value": "={{ ($json.query?.keywords ?? \"n8n,zapier,make.com,automation engineer,workflow automation,ai automation,ai agent,llm,integration engineer,integrations engineer,no-code,low-code\").split(\",\").map(k => k.trim().toLowerCase()).filter(Boolean) }}",
"type": "array"
},
{
"id": "s2",
"name": "min_score",
"value": "={{ Number($json.query?.min_score ?? 60) }}",
"type": "number"
},
{
"id": "s3",
"name": "max_jobs",
"value": "={{ Math.min(Number($json.query?.limit ?? 10), 25) }}",
"type": "number"
},
{
"id": "s4",
"name": "dry_run",
"value": "={{ $json.query?.dry_run === 'true' }}",
"type": "boolean"
},
{
"id": "s5",
"name": "profile",
"value": "Full-stack developer (TypeScript, Node.js, React, Python) specializing in n8n automations, AI agents and RAG (OpenAI), API integrations, Google Workspace and Supabase. Based in the Philippines (UTC+8), works fully remote, fluent English. Prefers freelance/contract work or remote roles open to Asia-based candidates.",
"type": "string"
}
]
},
"options": {}
},
"id": "m11-settings",
"name": "Settings (edit profile)",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
220,
100
]
},
{
"parameters": {
"url": "https://remoteok.com/api",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (n8n job scout)"
}
]
},
"options": {
"timeout": 20000
}
},
"id": "m11-rok",
"name": "Fetch RemoteOK",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
440,
0
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000,
"onError": "continueRegularOutput"
},
{
"parameters": {
"url": "https://weworkremotely.com/categories/remote-programming-jobs.rss",
"options": {}
},
"id": "m11-wwr",
"name": "Fetch We Work Remotely",
"type": "n8n-nodes-base.rssFeedRead",
"typeVersion": 1.2,
"position": [
440,
200
],
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000,
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "// RemoteOK returns an array; n8n splits it into items. The first item is a legal notice.\nconst strip = (html) => String(html ?? '').replace(/<[^>]+>/g, ' ').replace(/&[a-z#0-9]+;/gi, ' ').replace(/\\s+/g, ' ').trim();\nreturn $input.all()\n .filter(item => item.json.position)\n .map(({ json: j }) => ({\n json: {\n source: 'remoteok',\n title: j.position,\n company: j.company ?? '',\n url: j.url,\n tags: (j.tags ?? []).map(t => String(t).toLowerCase()),\n location: j.location ?? '',\n salary: j.salary_min ? `$${j.salary_min}-${j.salary_max}` : '',\n posted_at: j.date,\n description: strip(j.description).slice(0, 1500),\n },\n }));"
},
"id": "m11-norm-rok",
"name": "Normalize RemoteOK",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
0
]
},
{
"parameters": {
"jsCode": "// WWR titles look like \"Company: Job Title\".\nreturn $input.all().filter(item => item.json.title).map(({ json: j }) => {\n const [company, ...rest] = String(j.title ?? '').split(': ');\n return {\n json: {\n source: 'weworkremotely',\n title: rest.length ? rest.join(': ') : company,\n company: rest.length ? company : '',\n url: j.link,\n tags: (j.categories ?? []).map(t => String(t).toLowerCase()),\n location: '',\n salary: '',\n posted_at: j.isoDate ?? j.pubDate,\n description: String(j.contentSnippet ?? j.content ?? '').replace(/\\s+/g, ' ').trim().slice(0, 1500),\n },\n };\n});"
},
"id": "m11-norm-wwr",
"name": "Normalize WWR",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
660,
200
]
},
{
"parameters": {},
"id": "m11-merge",
"name": "All Jobs",
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
880,
100
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "k1",
"leftValue": "={{ $('Settings (edit profile)').first().json.keywords.some(k => new RegExp('(^|[^a-z0-9])' + k.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + 's?($|[^a-z0-9])').test([$json.title, $json.tags.join(' '), $json.description].join(' ').toLowerCase())) }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m11-keywords",
"name": "Matches Keywords",
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
1100,
100
]
},
{
"parameters": {
"sortFieldsUi": {
"sortField": [
{
"fieldName": "posted_at",
"order": "descending"
}
]
},
"options": {}
},
"id": "m11-newest",
"name": "Newest First",
"type": "n8n-nodes-base.sort",
"typeVersion": 1,
"position": [
1320,
100
]
},
{
"parameters": {
"maxItems": "={{ $('Settings (edit profile)').first().json.max_jobs }}"
},
"id": "m11-limit",
"name": "Top N",
"type": "n8n-nodes-base.limit",
"typeVersion": 1,
"position": [
1540,
100
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "d1",
"leftValue": "={{ $('Settings (edit profile)').first().json.dry_run }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m11-dry",
"name": "Dry Run?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1760,
100
]
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "jobs",
"options": {}
},
"id": "m11-agg-dry",
"name": "Collect Matches",
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
1980,
-60
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { dry_run: true, count: $json.jobs.length, jobs: $json.jobs.map(j => ({ source: j.source, title: j.title, company: j.company, url: j.url, posted_at: j.posted_at })) } }}",
"options": {}
},
"id": "m11-respond-dry",
"name": "Respond Matches",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
2200,
-60
]
},
{
"parameters": {
"text": "=CANDIDATE PROFILE:\n{{ $('Settings (edit profile)').first().json.profile }}\n\nJOB:\nTitle: {{ $json.title }}\nCompany: {{ $json.company }}\nTags: {{ $json.tags.join(', ') }}\nLocation: {{ $json.location || 'not stated' }}\nSalary: {{ $json.salary || 'not stated' }}\nDescription: {{ $json.description }}",
"attributes": {
"attributes": [
{
"name": "score",
"type": "number",
"description": "0-100: how well this job fits the candidate. 80+ = strong fit worth applying today, 60-79 = possible, below 60 = poor fit. Penalize hard if location/timezone rules out the candidate.",
"required": true
},
{
"name": "fit_reasons",
"description": "One sentence: the strongest reasons it fits (or doesn't)",
"required": true
},
{
"name": "red_flags",
"description": "Short comma-separated list of concerns (location limits, seniority mismatch, unpaid trial, vague scope), or 'none'",
"required": true
},
{
"name": "pitch_angle",
"description": "One sentence the candidate could open their application with, referencing a specific need from the job",
"required": true
}
]
},
"options": {
"systemPromptTemplate": "You are a blunt, practical career advisor for freelance automation developers. Score jobs strictly against the candidate profile. Never invent facts about the job."
}
},
"id": "m11-score",
"name": "Score Job",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"typeVersion": 1.2,
"position": [
1980,
160
]
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-5-mini",
"cachedResultName": "gpt-5-mini"
},
"options": {}
},
"id": "m11-model",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
1980,
380
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "j1",
"name": "title",
"value": "={{ $('Top N').item.json.title }}",
"type": "string"
},
{
"id": "j2",
"name": "company",
"value": "={{ $('Top N').item.json.company }}",
"type": "string"
},
{
"id": "j3",
"name": "url",
"value": "={{ $('Top N').item.json.url }}",
"type": "string"
},
{
"id": "j4",
"name": "source",
"value": "={{ $('Top N').item.json.source }}",
"type": "string"
},
{
"id": "j5",
"name": "posted_at",
"value": "={{ $('Top N').item.json.posted_at }}",
"type": "string"
},
{
"id": "j6",
"name": "score",
"value": "={{ Math.max(0, Math.min(100, Math.round(Number($json.output.score) || 0))) }}",
"type": "number"
},
{
"id": "j7",
"name": "fit_reasons",
"value": "={{ $json.output.fit_reasons }}",
"type": "string"
},
{
"id": "j8",
"name": "red_flags",
"value": "={{ $json.output.red_flags }}",
"type": "string"
},
{
"id": "j9",
"name": "pitch_angle",
"value": "={{ $json.output.pitch_angle }}",
"type": "string"
}
]
},
"options": {}
},
"id": "m11-scored",
"name": "Scored Job",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2200,
160
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "g1",
"leftValue": "={{ $json.score }}",
"rightValue": "={{ $('Settings (edit profile)').first().json.min_score }}",
"operator": {
"type": "number",
"operation": "gte"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m11-good",
"name": "Good Fit Only",
"type": "n8n-nodes-base.filter",
"typeVersion": 2.2,
"position": [
2420,
160
],
"alwaysOutputData": true
},
{
"parameters": {
"aggregate": "aggregateAllItemData",
"destinationFieldName": "jobs",
"options": {}
},
"id": "m11-agg",
"name": "Collect Good Fits",
"type": "n8n-nodes-base.aggregate",
"typeVersion": 1,
"position": [
2640,
160
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "r1",
"name": "jobs",
"value": "={{ $json.jobs.filter(j => j.title).sort((a, b) => b.score - a.score) }}",
"type": "array"
},
{
"id": "r2",
"name": "count",
"value": "={{ $json.jobs.filter(j => j.title).length }}",
"type": "number"
},
{
"id": "r3",
"name": "message",
"value": "={{ $json.jobs.filter(j => j.title).sort((a, b) => b.score - a.score).map(j => `${j.score} · ${j.title} @ ${j.company}\\n${j.pitch_angle}\\n${j.url}`).join('\\n\\n') || 'No good fits this time.' }}",
"type": "string"
}
]
},
"options": {}
},
"id": "m11-report",
"name": "Build Report",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2860,
160
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"conditions": [
{
"id": "a1",
"leftValue": "={{ $('Scout API').isExecuted }}",
"rightValue": "",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "m11-api",
"name": "Called via API?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
3080,
160
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}",
"options": {}
},
"id": "m11-respond",
"name": "Respond Report",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
3300,
80
]
},
{
"parameters": {},
"id": "m11-deliver",
"name": "Send Alert (Exercise 1)",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
3300,
260
]
}
],
"connections": {
"Every 4 Hours": {
"main": [
[
{
"node": "Settings (edit profile)",
"type": "main",
"index": 0
}
]
]
},
"Scout API": {
"main": [
[
{
"node": "Settings (edit profile)",
"type": "main",
"index": 0
}
]
]
},
"Settings (edit profile)": {
"main": [
[
{
"node": "Fetch RemoteOK",
"type": "main",
"index": 0
},
{
"node": "Fetch We Work Remotely",
"type": "main",
"index": 0
}
]
]
},
"Fetch RemoteOK": {
"main": [
[
{
"node": "Normalize RemoteOK",
"type": "main",
"index": 0
}
]
]
},
"Fetch We Work Remotely": {
"main": [
[
{
"node": "Normalize WWR",
"type": "main",
"index": 0
}
]
]
},
"Normalize RemoteOK": {
"main": [
[
{
"node": "All Jobs",
"type": "main",
"index": 0
}
]
]
},
"Normalize WWR": {
"main": [
[
{
"node": "All Jobs",
"type": "main",
"index": 1
}
]
]
},
"All Jobs": {
"main": [
[
{
"node": "Matches Keywords",
"type": "main",
"index": 0
}
]
]
},
"Matches Keywords": {
"main": [
[
{
"node": "Newest First",
"type": "main",
"index": 0
}
]
]
},
"Newest First": {
"main": [
[
{
"node": "Top N",
"type": "main",
"index": 0
}
]
]
},
"Top N": {
"main": [
[
{
"node": "Dry Run?",
"type": "main",
"index": 0
}
]
]
},
"Dry Run?": {
"main": [
[
{
"node": "Collect Matches",
"type": "main",
"index": 0
}
],
[
{
"node": "Score Job",
"type": "main",
"index": 0
}
]
]
},
"Collect Matches": {
"main": [
[
{
"node": "Respond Matches",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Score Job",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Score Job": {
"main": [
[
{
"node": "Scored Job",
"type": "main",
"index": 0
}
]
]
},
"Scored Job": {
"main": [
[
{
"node": "Good Fit Only",
"type": "main",
"index": 0
}
]
]
},
"Good Fit Only": {
"main": [
[
{
"node": "Collect Good Fits",
"type": "main",
"index": 0
}
]
]
},
"Collect Good Fits": {
"main": [
[
{
"node": "Build Report",
"type": "main",
"index": 0
}
]
]
},
"Build Report": {
"main": [
[
{
"node": "Called via API?",
"type": "main",
"index": 0
}
]
]
},
"Called via API?": {
"main": [
[
{
"node": "Respond Report",
"type": "main",
"index": 0
}
],
[
{
"node": "Send Alert (Exercise 1)",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"settings": {
"executionOrder": "v1"
}
}