This workflow follows the HTTP Request → Notion 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 →
{
"updatedAt": "2026-05-21T17:18:04.694Z",
"createdAt": "2026-04-17T15:24:28.753Z",
"id": "",
"name": "ATS Pulls - Main",
"description": null,
"active": true,
"isArchived": false,
"nodes": [
{
"id": "17939af5-ff6a-4cd1-9ce7-be3387324511",
"name": "Manual Trigger",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
0,
0
],
"parameters": {}
},
{
"id": "c68b78a6-b5e2-4e46-b816-735851805e5e",
"name": "Notion: Query Watchlist",
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [
224,
96
],
"parameters": {
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"mode": "id",
"value": "YOUR_NOTION_WATCHLIST_DB_ID"
},
"returnAll": true,
"simple": true,
"filterType": "manual",
"matchType": "allFilters",
"filters": {
"conditions": [
{
"key": "Active Watchlist|checkbox",
"type": "checkbox",
"condition": "equals",
"checkboxValue": true
}
]
}
}
},
{
"id": "58cac5cd-aaa5-422b-ae78-a665c3a9d1c1",
"name": "Code: Build ATS requests",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
448,
96
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const items = $input.all();\nconst ATS_ENDPOINTS = {\n greenhouse: 'https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true',\n ashby: 'https://api.ashbyhq.com/posting-api/job-board/{slug}?includeCompensation=true',\n lever: 'https://api.lever.co/v0/postings/{slug}?mode=json'\n};\n\nconst result = [];\nfor (const item of items) {\n const props = item.json;\n // n8n Notion node v2.2 with simple=true transforms property names to snake_case\n // with a \"property_\" prefix. E.g. \"Active Watchlist\" -> \"property_active_watchlist\"\n // \"ATS Type\" -> \"property_ats_type\", \"Board Slug\" -> \"property_board_slug\", \"Name\" -> \"property_name\"\n const activeWatchlist = props['property_active_watchlist'];\n const atsType = (props['property_ats_type'] || '').toLowerCase().trim();\n const boardSlug = (props['property_board_slug'] || '').trim();\n const company = props['property_name'] || '';\n\n // Skip rows missing ATS Type or Board Slug (correct degradation)\n if (!activeWatchlist) continue;\n if (!atsType || !['greenhouse','ashby','lever'].includes(atsType)) continue;\n if (!boardSlug) continue;\n\n const urlTemplate = ATS_ENDPOINTS[atsType];\n const url = urlTemplate.replace('{slug}', boardSlug);\n\n result.push({ json: { company, ats_type: atsType, slug: boardSlug, url } });\n}\nreturn result;"
}
},
{
"id": "32bf434a-da3b-47a5-a6ec-6fb049fb31c4",
"name": "HTTP: Fetch ATS",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
672,
96
],
"parameters": {
"method": "GET",
"url": "={{ $json.url }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
},
"batching": {
"batch": {
"batchSize": 5,
"batchInterval": 1000
}
}
}
},
"onError": "continueRegularOutput"
},
{
"id": "db51e328-a8e2-415a-ab3e-f6d308b5f80d",
"name": "Code: Normalize ATS jobs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
896,
96
],
"parameters": {
"mode": "runOnceForAllItems",
"jsCode": "const turkishPattern = /\\b(turkish|t\u00fcrkisch)\\b/i;\nconst excludeTitlePattern = /marketing manager|brand manager|performance marketing|growth marketing|sales manager|key account|business development|pr manager|communications manager/i;\nconst requiresHighGermanPattern = /deutsch\\s*(c1|c2|muttersprache|muttersprachlich|verhandlungssicher|flie\u00dfend|fliessend)|german\\s*(c1|c2|native|fluent)|native\\s*german\\s*speaker|fluent\\s*in\\s*german|muttersprachlich\\s*deutsch/i;\nconst LOCATION_OK = /\\b(germany|deutschland|berlin|munich|m\u00fcnchen|hamburg|cologne|k\u00f6ln|frankfurt|d\u00fcsseldorf|stuttgart|dublin|london|amsterdam|paris|madrid|barcelona|lisbon|stockholm|copenhagen|helsinki|vienna|zurich|warsaw|emea|europe|eu\\b|remote)\\b/i;\nconst AMERICAS_ONLY = /\\b(americas only|us only|u\\.s\\. only|us-only|us based|us-based|canada only|latam|apac only|north america only)\\b/i;\n\nconst today = new Date().toISOString().slice(0, 10);\nconst items = $input.all();\nconst buildItems = $('Code: Build ATS requests').all();\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n const body = item.json;\n\n // Correlate each HTTP response to its source watchlist row by index.\n // The HTTP node preserves input order 1:1, so buildItems[i] is the paired source.\n // (Was: $('Code: Build ATS requests').item \u2014 broken in runOnceForAllItems mode;\n // resolved to a single static item and stamped every Greenhouse job with one company.)\n const buildItem = buildItems[i] || null;\n const ats_type = buildItem ? buildItem.json.ats_type : '';\n const companyName = buildItem ? buildItem.json.company : '';\n\n let jobs = [];\n\n if (ats_type === 'greenhouse') {\n const raw = body.jobs || [];\n for (const j of raw) {\n const title = j.title || null;\n const location = j.location?.name || '';\n const descRaw = typeof j.content === 'string' ? j.content : null;\n const description = descRaw ? descRaw.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim() : null;\n const haystack = `${title || ''} ${description || ''} ${location}`;\n jobs.push({\n source: 'greenhouse',\n external_id: j.id ? String(j.id) : null,\n url: j.absolute_url || null,\n title,\n company: companyName,\n location,\n salary: null,\n description,\n turkish_bonus: turkishPattern.test(haystack),\n date_seen: today\n });\n }\n } else if (ats_type === 'ashby') {\n const raw = body.jobs || [];\n for (const j of raw) {\n const title = j.title || null;\n const primaryLoc = j.location || '';\n const secondary = Array.isArray(j.secondaryLocations)\n ? j.secondaryLocations.map(s => s.locationName || s.location || '').filter(Boolean).join('; ')\n : '';\n const location = [primaryLoc, secondary].filter(Boolean).join('; ');\n const description = j.descriptionPlain || null;\n const haystack = `${title || ''} ${description || ''} ${location}`;\n jobs.push({\n source: 'ashby',\n external_id: j.id || null,\n url: j.jobUrl || j.applyUrl || null,\n title,\n company: companyName,\n location,\n salary: j.compensation?.compensationTierSummary || null,\n description,\n turkish_bonus: turkishPattern.test(haystack),\n date_seen: today\n });\n }\n } else if (ats_type === 'lever') {\n // Lever: body IS the array (no wrapper)\n const raw = Array.isArray(body) ? body : [];\n for (const j of raw) {\n const title = j.text || null;\n const location = j.categories?.location || '';\n const descRaw = j.descriptionPlain || (typeof j.description === 'string' ? j.description : null);\n const description = descRaw && !j.descriptionPlain\n ? descRaw.replace(/<[^>]+>/g, ' ').replace(/\\s+/g, ' ').trim()\n : descRaw;\n const salary = j.salaryRange?.text || null;\n const haystack = `${title || ''} ${description || ''} ${location}`;\n jobs.push({\n source: 'lever',\n external_id: j.id || null,\n url: j.hostedUrl || j.applyUrl || null,\n title,\n company: companyName,\n location,\n salary,\n description,\n turkish_bonus: turkishPattern.test(haystack),\n date_seen: today\n });\n }\n }\n\n // Apply filter\n for (const job of jobs) {\n if (!job.url) continue;\n if (excludeTitlePattern.test(job.title || '')) continue;\n if (job.turkish_bonus) { results.push({ json: job }); continue; }\n if (AMERICAS_ONLY.test(job.location || '')) continue;\n if (requiresHighGermanPattern.test(job.description || '')) continue;\n if (LOCATION_OK.test(job.location || '')) results.push({ json: job });\n }\n}\n\nreturn results;"
}
},
{
"id": "15084406-f32a-44b9-ab3b-7ba5aa4f45e4",
"name": "Postgres: Insert listings",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
1120,
96
],
"parameters": {
"query": "INSERT INTO listings (source, external_id, url, title, company, location, salary, description, turkish_bonus, date_seen)\nVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\nON CONFLICT (source, COALESCE(external_id, url)) DO UPDATE SET\n company = EXCLUDED.company,\n title = EXCLUDED.title,\n location = EXCLUDED.location,\n salary = EXCLUDED.salary,\n description = EXCLUDED.description,\n date_seen = EXCLUDED.date_seen,\n turkish_bonus = EXCLUDED.turkish_bonus;",
"options": {
"queryBatching": "independently",
"queryReplacement": "={{ [$json.source, $json.external_id, $json.url, $json.title, $json.company, $json.location, $json.salary, $json.description, $json.turkish_bonus, $json.date_seen] }}"
},
"resource": "database",
"operation": "executeQuery"
}
},
{
"id": "7560f7fd-9d29-48af-b529-1b5654f3bb57",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
0,
192
],
"parameters": {
"httpMethod": "POST",
"path": "job-ats-run",
"responseMode": "lastNode",
"options": {}
}
}
],
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Notion: Query Watchlist",
"type": "main",
"index": 0
}
]
]
},
"Notion: Query Watchlist": {
"main": [
[
{
"node": "Code: Build ATS requests",
"type": "main",
"index": 0
}
]
]
},
"Code: Build ATS requests": {
"main": [
[
{
"node": "HTTP: Fetch ATS",
"type": "main",
"index": 0
}
]
]
},
"HTTP: Fetch ATS": {
"main": [
[
{
"node": "Code: Normalize ATS jobs",
"type": "main",
"index": 0
}
]
]
},
"Code: Normalize ATS jobs": {
"main": [
[
{
"node": "Postgres: Insert listings",
"type": "main",
"index": 0
}
]
]
},
"Webhook Trigger": {
"main": [
[
{
"node": "Notion: Query Watchlist",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"timezone": "Europe/Berlin",
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner",
"availableInMCP": true,
"binaryMode": "separate"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
ATS Pulls - Main. Uses notion, httpRequest, postgres. Event-driven trigger; 7 nodes.
Source: https://github.com/ozlar34/job-match-radar/blob/main/workflows/04-ats-pulls-storage/ats-pulls-main.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Reagendamiento_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 89 nodes.
This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod
Sync your Google Contacts with your Notion database.
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.