This workflow corresponds to n8n.io template #4278 — we link there as the canonical source.
This workflow follows the Airtable → Chainllm 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 →
{
"id": "ExmRkYWlTAPhA2IQ",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "HN Jobs update",
"tags": [],
"nodes": [
{
"id": "2e510877-4fd3-4e46-a1ad-b44187226b78",
"name": "Split Out",
"type": "n8n-nodes-base.splitOut",
"position": [
340,
400
],
"parameters": {
"options": {},
"fieldToSplitOut": "hits"
},
"typeVersion": 1
},
{
"id": "b5a9c1f3-f167-4646-8f11-97f4be32ff93",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
2780,
600
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"company\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Name of the hiring company\"\n },\n \"title\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Job title/role being advertised\"\n },\n \"location\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Work location including remote/hybrid status\"\n },\n \"type\": {\n \"type\": [\n \"string\",\n null\n ],\n \"enum\": [\n \"FULL_TIME\",\n \"PART_TIME\",\n \"CONTRACT\",\n \"INTERNSHIP\",\n \"FREELANCE\",\n null\n ],\n \"description\": \"Employment type (Full-time, Contract, etc)\"\n },\n \"work_location\": {\n \"type\": [\n \"string\",\n null\n ],\n \"enum\": [\n \"REMOTE\",\n \"HYBRID\",\n \"ON_SITE\",\n null\n ],\n \"description\": \"Work arrangement type\"\n },\n \"salary\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Compensation details if provided\"\n },\n \"description\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Main job description text including requirements and team info\"\n },\n \"apply_url\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Direct application/job posting URL\"\n },\n \"company_url\": {\n \"type\": [\n \"string\",\n null\n ],\n \"description\": \"Company website or careers page\"\n }\n }\n}\n"
},
"typeVersion": 1.2
},
{
"id": "80012214-5045-4c54-a5e1-cf7b76a611c3",
"name": "Search for Who is hiring posts",
"type": "n8n-nodes-base.httpRequest",
"position": [
140,
400
],
"parameters": {
"url": "https://uj5wyc0l7x-dsn.algolia.net/1/indexes/Item_dev_sort_date/query",
"method": "POST",
"options": {},
"jsonBody": "{\n \"query\": \"\\\"Ask HN: Who is hiring\\\"\",\n \"analyticsTags\": [\n \"web\"\n ],\n \"page\": 0,\n \"hitsPerPage\": 30,\n \"minWordSizefor1Typo\": 4,\n \"minWordSizefor2Typos\": 8,\n \"advancedSyntax\": true,\n \"ignorePlurals\": false,\n \"clickAnalytics\": true,\n \"minProximity\": 7,\n \"numericFilters\": [],\n \"tagFilters\": [\n [\n \"story\"\n ],\n []\n ],\n \"typoTolerance\": \"min\",\n \"queryType\": \"prefixNone\",\n \"restrictSearchableAttributes\": [\n \"title\",\n \"comment_text\",\n \"url\",\n \"story_text\",\n \"author\"\n ],\n \"getRankingInfo\": true\n}",
"sendBody": true,
"sendQuery": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"queryParameters": {
"parameters": [
{
"name": "x-algolia-agent",
"value": "Algolia for JavaScript (4.13.1); Browser (lite)"
},
{
"name": "x-algolia-application-id",
"value": "UJ5WYC0L7X"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Accept-Language",
"value": "en-GB,en-US;q=0.9,en;q=0.8"
},
{
"name": "Connection",
"value": "keep-alive"
},
{
"name": "DNT",
"value": "1"
},
{
"name": "Origin",
"value": "https://hn.algolia.com"
},
{
"name": "Referer",
"value": "https://hn.algolia.com/"
},
{
"name": "Sec-Fetch-Dest",
"value": "empty"
},
{
"name": "Sec-Fetch-Mode",
"value": "cors"
},
{
"name": "Sec-Fetch-Site",
"value": "cross-site"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/0.0.0.0 Safari/537.36"
},
{
"name": "sec-ch-ua",
"value": "\"Chromium\";v=\"133\", \"Not(A:Brand\";v=\"99\""
},
{
"name": "sec-ch-ua-mobile",
"value": "?0"
},
{
"name": "sec-ch-ua-platform",
"value": "\"macOS\""
}
]
}
},
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "58a1f06b-0e41-4f73-9a47-5134806e4249",
"name": "Get relevant data",
"type": "n8n-nodes-base.set",
"position": [
640,
400
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "73dd2325-faa7-4650-bd78-5fc97cc202de",
"name": "title",
"type": "string",
"value": "={{ $json.title }}"
},
{
"id": "44918eac-4510-440e-9ac0-bf14d2b2f3af",
"name": "createdAt",
"type": "string",
"value": "={{ $json.created_at }}"
},
{
"id": "00eb6f09-2c22-411c-949c-886b2d95b6eb",
"name": "updatedAt",
"type": "string",
"value": "={{ $json.updated_at }}"
},
{
"id": "2b4f9da6-f60e-46e0-ba9d-3242fa955a55",
"name": "storyId",
"type": "string",
"value": "={{ $json.story_id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d69eb7f3-383f-4e67-a0e7-b5930cfc8b26",
"name": "Get latest post",
"type": "n8n-nodes-base.filter",
"position": [
920,
400
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d7dd7175-2a50-45aa-bd3e-4c248c9193c4",
"operator": {
"type": "dateTime",
"operation": "after"
},
"leftValue": "={{ $json.createdAt }}",
"rightValue": "={{$now.minus({days: 30})}} "
}
]
}
},
"typeVersion": 2.2
},
{
"id": "737cce8c-2ef0-4421-bccf-d9bdfa308385",
"name": "Split out children (jobs)",
"type": "n8n-nodes-base.splitOut",
"position": [
1400,
400
],
"parameters": {
"options": {},
"fieldToSplitOut": "kids"
},
"typeVersion": 1
},
{
"id": "13e36405-f049-4d3d-a17c-f14b42817ba1",
"name": "Trun into structured data",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
2540,
400
],
"parameters": {
"text": "={{ $json.cleaned_text }}",
"messages": {
"messageValues": [
{
"message": "Extract the JSON data"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.5
},
{
"id": "40d699ab-bf6d-402b-8192-c485a3525d27",
"name": "Extract text",
"type": "n8n-nodes-base.set",
"position": [
1800,
400
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "6affa370-56ce-4ad8-8534-8f753fdf07fc",
"name": "text",
"type": "string",
"value": "={{ $json.text }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a20afe14-6457-4aa3-9135-75733c704723",
"name": "Clean text",
"type": "n8n-nodes-base.code",
"position": [
2000,
400
],
"parameters": {
"jsCode": "// In a Function node in n8n\nconst inputData = $input.all();\n\nfunction cleanAllPosts(data) {\n return data.map(item => {\n try {\n // Check if item exists and has the expected structure\n if (!item || typeof item !== 'object') {\n return { cleaned_text: '', error: 'Invalid item structure' };\n }\n\n // Get the text, with multiple fallbacks\n let text = '';\n if (typeof item === 'string') {\n text = item;\n } else if (item.json && item.json.text) {\n text = item.json.text;\n } else if (typeof item.json === 'string') {\n text = item.json;\n } else {\n text = JSON.stringify(item);\n }\n\n // Make sure text is a string\n text = String(text);\n \n // Perform the cleaning operations\n try {\n text = text.replace(///g, '/');\n text = text.replace(/'/g, \"'\");\n text = text.replace(/&\\w+;/g, ' ');\n text = text.replace(/<[^>]*>/g, '');\n text = text.replace(/\\|\\s*/g, '| ');\n text = text.replace(/\\s+/g, ' ');\n text = text.replace(/\\s*(https?:\\/\\/[^\\s]+)\\s*/g, '\\n$1\\n');\n text = text.replace(/\\n{3,}/g, '\\n\\n');\n text = text.trim();\n } catch (cleaningError) {\n console.log('Error during text cleaning:', cleaningError);\n // Return original text if cleaning fails\n return { cleaned_text: text, warning: 'Partial cleaning applied' };\n }\n\n return { cleaned_text: text };\n \n } catch (error) {\n console.log('Error processing item:', error);\n return { \n cleaned_text: '', \n error: `Processing error: ${error.message}`,\n original: item\n };\n }\n }).filter(result => result.cleaned_text || result.error); \n}\n\ntry {\n return cleanAllPosts(inputData);\n} catch (error) {\n console.log('Fatal error:', error);\n return [{ \n cleaned_text: '', \n error: `Fatal error: ${error.message}`,\n input: inputData \n }];\n}\n"
},
"typeVersion": 2
},
{
"id": "8ba5a5b4-0d03-46f8-90a1-7550913b77dd",
"name": "Limit for testing (optional)",
"type": "n8n-nodes-base.limit",
"position": [
2220,
400
],
"parameters": {
"maxItems": 5
},
"typeVersion": 1
},
{
"id": "d5535a17-79f1-4dc2-941a-ea4dd6a109a8",
"name": "Write results to airtable",
"type": "n8n-nodes-base.airtable",
"position": [
3200,
400
],
"parameters": {
"base": {
"__rl": true,
"mode": "list",
"value": "app3Gb4cEMflRuqzy",
"cachedResultUrl": "https://airtable.com/app3Gb4cEMflRuqzy",
"cachedResultName": "HN Who is hiring?"
},
"table": {
"__rl": true,
"mode": "list",
"value": "tbl4JrmWPcxiUuCiX",
"cachedResultUrl": "https://airtable.com/app3Gb4cEMflRuqzy/tbl4JrmWPcxiUuCiX",
"cachedResultName": "Table 1"
},
"columns": {
"value": {
"Type": "={{ $json.output.type }}",
"Title": "={{ $json.output.title }}",
"Salary": "={{ $json.output.salary }}",
"Company": "={{ $json.output.company }}",
"Location": "={{ $json.output.location }}",
"Apply_url": "={{ $json.output.apply_url }}",
"Description": "={{ $json.output.description }}",
"company_url": "={{ $json.output.company_url }}",
"work_location": "={{ $json.output.work_location }}"
},
"schema": [
{
"id": "Title",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Company",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Company",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Location",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Type",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "work_location",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "work_location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Description",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Salary",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Salary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Apply_url",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "Apply_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_url",
"type": "string",
"display": true,
"removed": false,
"readOnly": false,
"required": false,
"displayName": "company_url",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "create"
},
"credentials": {
"airtableTokenApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"id": "e51caae1-c8d1-4954-91a5-d0dfde442346",
"name": "HI API: Get the individual job post",
"type": "n8n-nodes-base.httpRequest",
"position": [
1600,
400
],
"parameters": {
"url": "=https://hacker-news.firebaseio.com/v0/item/{{ $json.kids }}.json?print=pretty",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "880d31e0-41fa-434d-a3d0-ac850ede0b59",
"name": "HN API: Get Main Post",
"type": "n8n-nodes-base.httpRequest",
"position": [
1200,
400
],
"parameters": {
"url": "=https://hacker-news.firebaseio.com/v0/item/{{ $json.storyId }}.json?print=pretty",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "e1bc30b5-b792-4f1e-8d38-ead61178c5fd",
"name": "Code",
"type": "n8n-nodes-base.code",
"position": [
2900,
400
],
"parameters": {
"jsCode": "function convertSalaryRangeToNumber(salaryStr) {\n if (!salaryStr) return null;\n\n // Remove $ and spaces, convert to lowercase\n salaryStr = salaryStr.replace(/\\$/g, '').replace(/\\s/g, '').toLowerCase();\n\n // Replace 'k' with '000'\n salaryStr = salaryStr.replace(/k/g, '000');\n\n // Split by dash, en dash, em dash\n const parts = salaryStr.split(/[-\u2013\u2014]/);\n\n // Extract numbers\n const numbers = parts.map(part => {\n // Remove non-digit/non-dot characters\n const cleaned = part.replace(/[^\\d.]/g, '');\n return cleaned ? parseFloat(cleaned) : NaN;\n }).filter(num => !isNaN(num));\n\n if (numbers.length === 0) return null;\n if (numbers.length === 1) return numbers[0];\n if (numbers.length === 2) return (numbers[0] + numbers[1]) / 2;\n\n // fallback if more than 2 numbers (rare)\n return numbers[0];\n}\n\n// Example usage inside n8n Function node\n// Assume input data is in items, and salary is in item.json.salary\nfor (let item of items) {\n const salaryStr = item.json.salary;\n item.json.salary_numeric = convertSalaryRangeToNumber(salaryStr);\n}\n\nreturn items;\n"
},
"typeVersion": 2
},
{
"id": "294d6c73-8946-4ee7-8d60-11b72ecb3964",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
2600,
620
],
"parameters": {
"options": {},
"modelName": "models/gemini-2.0-flash"
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "6a6c4536-a57a-4e58-a76f-81eef755fa19",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-20,
400
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 2
}
]
}
},
"typeVersion": 1.2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "e5356e09-7a30-467a-a848-507087afb818",
"connections": {
"Code": {
"main": [
[
{
"node": "Write results to airtable",
"type": "main",
"index": 0
}
]
]
},
"Split Out": {
"main": [
[
{
"node": "Get relevant data",
"type": "main",
"index": 0
}
]
]
},
"Clean text": {
"main": [
[
{
"node": "Limit for testing (optional)",
"type": "main",
"index": 0
}
]
]
},
"Extract text": {
"main": [
[
{
"node": "Clean text",
"type": "main",
"index": 0
}
]
]
},
"Get latest post": {
"main": [
[
{
"node": "HN API: Get Main Post",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Search for Who is hiring posts",
"type": "main",
"index": 0
}
]
]
},
"Get relevant data": {
"main": [
[
{
"node": "Get latest post",
"type": "main",
"index": 0
}
]
]
},
"HN API: Get Main Post": {
"main": [
[
{
"node": "Split out children (jobs)",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "Trun into structured data",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "Trun into structured data",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Split out children (jobs)": {
"main": [
[
{
"node": "HI API: Get the individual job post",
"type": "main",
"index": 0
}
]
]
},
"Trun into structured data": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Limit for testing (optional)": {
"main": [
[
{
"node": "Trun into structured data",
"type": "main",
"index": 0
}
]
]
},
"Search for Who is hiring posts": {
"main": [
[
{
"node": "Split Out",
"type": "main",
"index": 0
}
]
]
},
"HI API: Get the individual job post": {
"main": [
[
{
"node": "Extract text",
"type": "main",
"index": 0
}
]
]
}
}
}
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.
airtableTokenApigooglePalmApihttpHeaderAuth
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically fetches the latest "Ask HN: Who is hiring?" posts from Hacker News, extracts individual job listings, cleans the raw text, converts them into structured job listings using Google Gemini AI, and saves them into Airtable.
Source: https://n8n.io/workflows/4278/ — 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.
Automatically scan major financial newswires for biotech catalyst events, score them with AI sentiment analysis, and surface ranked trade candidates — all without manual monitoring.
It transforms a single text prompt into a fully scripted, visually rich video with AI-generated images and voiceovers, then distributes it across multiple social media platforms. Content Creators & Yo
kisisel asistan. Uses toolWorkflow, toolHttpRequest, toolCalculator, toolThink. Scheduled trigger; 43 nodes.
Effortlessly generate, review, and publish SEO-optimized blog posts to WordPress using AI and automation.
This n8n workflow automatically monitors YouTube channels, transcribes new videos, and generates AI-powered summaries with relevance scoring. It pulls channel URLs from a Google Sheet, fetches recent