This workflow corresponds to n8n.io template #12179 — we link there as the canonical source.
This workflow follows the Error Trigger → Google Sheets 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": "m5K7dzRL7Uo1x9DG",
"name": "Upwork Job Scraper with AI Scoring - Creator Hub",
"tags": [],
"nodes": [
{
"id": "5a376579-20a2-4731-8e93-25a9d05c852b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
32,
384
],
"parameters": {
"width": 450,
"height": 744,
"content": "## Upwork Job Scraper with AI Scoring\n\nThis workflow automates the process of manual lead sourcing on Upwork. Instead of constantly refreshing the feed, this system identifies high-potential jobs, scores them against your specific expertise, and drafts a personalized proposal.\n\n### How it works\nThe workflow runs on a 6-hour schedule, triggering an Apify actor to scrape the latest job listings based on your keywords. It uses a Google Sheet to cross-reference Job IDs, ensuring you never waste credits processing a lead you've already seen. GPT-4 then evaluates the job description, client history, and budget to assign a Fit Score (0-100). If a job passes your quality threshold (default is 60), AI generates a custom proposal draft, logs the lead to your spreadsheet, and sends a summary to your Telegram bot.\n\n### Setup steps\n1. **Google Sheets:** Create a new spreadsheet with a column header exactly named `Job ID` in the first tab.\n2. **Apify:** Set up an Upwork Scraper Actor and retrieve your Actor ID and API Token.\n3. **Environment Variables:** In n8n, set `GOOGLE_SHEETS_DOC_ID`, `APIFY_ACTOR_ID`, and `TELEGRAM_CHAT_ID`.\n4. **Credentials:** Connect OpenAI (GPT-4 access), Google Sheets, Apify, and Telegram.\n\n### Pro Tip\nThe Generate Proposal node uses GPT-4o-mini to save costs. Update the system prompt with your portfolio links before the first run."
},
"typeVersion": 1
},
{
"id": "f8406a75-544a-4f23-bd77-1c04f795df1e",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
512,
384
],
"parameters": {
"color": 7,
"width": 550,
"height": 302,
"content": "## Scrape Jobs\nSchedule trigger kicks off Apify to fetch latest Upwork listings.\nBudget ranges (hourly: $25-150, fixed: $200-50k) are customizable."
},
"typeVersion": 1
},
{
"id": "e2beaa02-d05e-459b-9aed-3164b9087b6b",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
384
],
"parameters": {
"color": 7,
"width": 450,
"height": 302,
"content": "## Deduplicate\nCross-references Google Sheet to skip already-processed jobs"
},
"typeVersion": 1
},
{
"id": "321958ec-fe0f-4211-b9ae-ecadfef62c8c",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
384
],
"parameters": {
"color": 7,
"width": 850,
"height": 420,
"content": "## Perform AI Scoring\nGPT-4 evaluates fit, client quality, and budget. Filters jobs scoring 60+."
},
"typeVersion": 1
},
{
"id": "492b9319-aa17-44d3-84f3-da4bddc0bcb7",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2496,
384
],
"parameters": {
"color": 7,
"width": 886,
"height": 420,
"content": "## Generate Proposals\nDrafts a custom proposal based on job description. Review the prompt before first use."
},
"typeVersion": 1
},
{
"id": "56ef68e3-eabd-4db9-882a-6c9d177e91e3",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
3424,
384
],
"parameters": {
"color": 7,
"width": 430,
"height": 246,
"content": "## Log & Notify\nSaves results to Google Sheet and sends Telegram summary"
},
"typeVersion": 1
},
{
"id": "e15d6673-338f-4948-9ae0-f35deb7db331",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
2736,
928
],
"parameters": {
"color": 3,
"width": 474,
"height": 270,
"content": "## Handle Errors\nCatches failures, logs errors, and sends alert to prevent missed opportunities"
},
"typeVersion": 1
},
{
"id": "e657cd7b-ecce-4cae-90ed-3ab55f29f259",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
544,
496
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 6
}
]
}
},
"typeVersion": 1.2
},
{
"id": "ea4f25c8-46cb-47ac-a0ec-1c9828d28c6c",
"name": "Run Apify Scraper",
"type": "n8n-nodes-base.httpRequest",
"position": [
720,
496
],
"parameters": {
"url": "=https://api.apify.com/v2/acts/{{ $env.APIFY_ACTOR_ID }}/runs?waitForFinish=300",
"method": "POST",
"options": {
"timeout": 300000
},
"jsonBody": "{\n \"budget.hourlyRate.min\": \"25\",\n \"budget.hourlyRate.max\": \"150\",\n \"budget.fixedPrice.min\": \"200\",\n \"budget.fixedPrice.max\": \"50000\",\n \"jobCategories\": [\"Web Development\", \"AI Apps & Integration\"],\n \"limit\": 100\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "836540af-d357-49b6-81b9-bd5158720b25",
"name": "Get Dataset Items",
"type": "n8n-nodes-base.httpRequest",
"position": [
912,
496
],
"parameters": {
"url": "=https://api.apify.com/v2/datasets/{{ $json.data.defaultDatasetId }}/items?clean=true",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth"
},
"typeVersion": 4.2
},
{
"id": "f584de90-f5cd-4f26-a8fb-db4fa6129fe0",
"name": "Read Existing Job IDs",
"type": "n8n-nodes-base.googleSheets",
"position": [
1184,
496
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Upwork Jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEETS_DOC_ID }}"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "12145b15-ef30-43a5-90a7-dc6a2e76be36",
"name": "Filter Duplicates",
"type": "n8n-nodes-base.code",
"position": [
1392,
496
],
"parameters": {
"jsCode": "const scraped = $('Get Dataset Items').all();\nconst existing = $('Read Existing Job IDs').all();\nconst ids = new Set(existing.map(r => r.json['Job ID']).filter(Boolean));\nconst newJobs = scraped.filter(j => !ids.has(j.json.uid));\nif (!newJobs.length) return [{ json: { _noNewJobs: true } }];\nreturn newJobs;"
},
"typeVersion": 2
},
{
"id": "87ee7eae-d29c-449f-8ae8-473b8ffa9311",
"name": "Has New Jobs?",
"type": "n8n-nodes-base.if",
"position": [
1632,
496
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "string",
"operation": "notEmpty"
},
"leftValue": "={{ $json.uid }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2
},
{
"id": "0d4df8d1-6d64-436d-9032-95e29a082b84",
"name": "Normalize Fields",
"type": "n8n-nodes-base.code",
"position": [
1824,
480
],
"parameters": {
"jsCode": "const items = $input.all();\nreturn items.map(item => {\n const j = item.json;\n let budget = j.budget?.hourlyRate?.min ? `$${j.budget.hourlyRate.min}-${j.budget.hourlyRate.max}/hr` : (j.budget?.fixedBudget ? `$${j.budget.fixedBudget}` : 'N/A');\n return { json: { jobId: j.uid, title: j.title, description: j.description, budget, url: j.externalLink, skills: (j.skills||[]).join(', '), postedAt: j.createdAt, clientVerified: j.client?.paymentVerified ? 'Yes' : 'No', clientSpent: j.client?.totalSpent || 0 }};\n});"
},
"typeVersion": 2
},
{
"id": "5d25151e-eb98-48fd-8fc0-7bd666519ae9",
"name": "AI Scoring",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
2000,
480
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o"
},
"options": {},
"responses": {
"values": [
{}
]
},
"builtInTools": {}
},
"typeVersion": 2
},
{
"id": "86a815ec-d204-446a-8f6c-79819f33e240",
"name": "Parse AI Score",
"type": "n8n-nodes-base.code",
"position": [
2304,
480
],
"parameters": {
"jsCode": "const items = $input.all();\nconst orig = $('Normalize Fields').all();\nreturn items.map((item, i) => {\n let p = {};\n try { p = JSON.parse((item.json.message?.content || '{}').replace(/```json?|```/g, '').trim()); } catch(e) {}\n return { json: { ...orig[i]?.json, score: p.score || 0, decision: p.decision || 'SKIP', reasoning: p.reasoning || '' }};\n});"
},
"typeVersion": 2
},
{
"id": "4539cc9c-eb73-42e3-af9a-d0d6228705d9",
"name": "Filter Score >= 60",
"type": "n8n-nodes-base.filter",
"position": [
2560,
480
],
"parameters": {
"options": {},
"conditions": {
"options": {
"typeValidation": "strict"
},
"conditions": [
{
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.score }}",
"rightValue": 60
}
]
}
},
"typeVersion": 2
},
{
"id": "82d96646-645f-4998-ac00-4f6075b3eb09",
"name": "Loop Over Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
2752,
480
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "4efa4a9c-40e5-404f-8378-84bdf63771d8",
"name": "Generate Proposal",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
2928,
608
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini"
},
"options": {},
"responses": {
"values": [
{}
]
},
"builtInTools": {}
},
"typeVersion": 2
},
{
"id": "502f6c69-f0b4-4f9b-84a4-f27adbb7bd19",
"name": "Log to Google Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
3216,
608
],
"parameters": {
"columns": {
"mappingMode": "autoMapInputData"
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Upwork Jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "={{ $env.GOOGLE_SHEETS_DOC_ID }}"
}
},
"typeVersion": 4.5
},
{
"id": "9b5cfd82-2180-4fef-bfec-0555f316478d",
"name": "Loop Complete",
"type": "n8n-nodes-base.noOp",
"position": [
2944,
464
],
"parameters": {},
"typeVersion": 1
},
{
"id": "04083b94-e4ed-4a60-89ff-ec9a932c5011",
"name": "Compute Metrics",
"type": "n8n-nodes-base.code",
"position": [
3472,
464
],
"parameters": {
"jsCode": "let scraped = 0, passed = 0;\ntry { scraped = $('Get Dataset Items').all().length; } catch(e) {}\ntry { passed = $('Parse AI Score').all().filter(j => j.json.score >= 60).length; } catch(e) {}\nreturn [{ json: { timestamp: new Date().toISOString(), scraped, passed }}];"
},
"typeVersion": 2
},
{
"id": "11f2ef02-457d-49e3-b7b5-deaf38a19ced",
"name": "Send Summary",
"type": "n8n-nodes-base.telegram",
"position": [
3648,
464
],
"parameters": {
"text": "=\u2705 Upwork Scraper Done\nScraped: {{ $json.scraped }}\nPassed: {{ $json.passed }}",
"chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
"additionalFields": {}
},
"typeVersion": 1.2
},
{
"id": "c79556bb-509d-4ae3-a49a-c0fc4a977f43",
"name": "No New Jobs",
"type": "n8n-nodes-base.noOp",
"position": [
1824,
656
],
"parameters": {},
"typeVersion": 1
},
{
"id": "9b0e8b7f-b466-472b-9adf-9b6a9efb7ba8",
"name": "Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"position": [
2784,
1024
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f70c6e2f-e1a7-4051-b766-c91df40b05e0",
"name": "Send Error Alert",
"type": "n8n-nodes-base.telegram",
"position": [
2992,
1024
],
"parameters": {
"text": "=\ud83d\udea8 Error: {{ $json.error?.message || 'Unknown' }}",
"chatId": "={{ $env.TELEGRAM_CHAT_ID }}",
"additionalFields": {}
},
"typeVersion": 1.2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "065df9fc-d961-4ff8-8a89-7b9386e8a33f",
"connections": {
"AI Scoring": {
"main": [
[
{
"node": "Parse AI Score",
"type": "main",
"index": 0
}
]
]
},
"Error Trigger": {
"main": [
[
{
"node": "Send Error Alert",
"type": "main",
"index": 0
}
]
]
},
"Has New Jobs?": {
"main": [
[
{
"node": "Normalize Fields",
"type": "main",
"index": 0
}
],
[
{
"node": "No New Jobs",
"type": "main",
"index": 0
}
]
]
},
"Loop Complete": {
"main": [
[
{
"node": "Compute Metrics",
"type": "main",
"index": 0
}
]
]
},
"Parse AI Score": {
"main": [
[
{
"node": "Filter Score >= 60",
"type": "main",
"index": 0
}
]
]
},
"Compute Metrics": {
"main": [
[
{
"node": "Send Summary",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Items": {
"main": [
[
{
"node": "Loop Complete",
"type": "main",
"index": 0
}
],
[
{
"node": "Generate Proposal",
"type": "main",
"index": 0
}
]
]
},
"Normalize Fields": {
"main": [
[
{
"node": "AI Scoring",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Run Apify Scraper",
"type": "main",
"index": 0
}
]
]
},
"Filter Duplicates": {
"main": [
[
{
"node": "Has New Jobs?",
"type": "main",
"index": 0
}
]
]
},
"Generate Proposal": {
"main": [
[
{
"node": "Log to Google Sheet",
"type": "main",
"index": 0
}
]
]
},
"Get Dataset Items": {
"main": [
[
{
"node": "Read Existing Job IDs",
"type": "main",
"index": 0
}
]
]
},
"Run Apify Scraper": {
"main": [
[
{
"node": "Get Dataset Items",
"type": "main",
"index": 0
}
]
]
},
"Filter Score >= 60": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Log to Google Sheet": {
"main": [
[
{
"node": "Loop Over Items",
"type": "main",
"index": 0
}
]
]
},
"Read Existing Job IDs": {
"main": [
[
{
"node": "Filter Duplicates",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Schedule Trigger runs every 6 hours (customizable) Apify Scraper fetches Upwork jobs matching your criteria Deduplication filters out jobs you've already seen AI Scoring (GPT-4) evaluates fit, client quality, budget (0-100 score) Filter keeps only jobs scoring 60+ Proposal…
Source: https://n8n.io/workflows/12179/ — 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.
Turn any Amazon India product URL into a fully-edited 10-second lifestyle video and auto-publish it to Instagram, Facebook, X (Twitter), LinkedIn, YouTube, and Threads — with platform-optimized captio
Automate price monitoring for e-commerce competitors—ideal for retailers, analysts, and pricing teams. Scrapes competitor sites, extracts pricing/stock data via AI, detects changes, and sends instant
This workflow is designed for entrepreneurs, sales teams, marketers, and agencies who want to automate lead discovery and build qualified business contact lists — without manual searching or copying d
This workflow creates an end-to-end Instagram content pipeline that automatically discovers trending content from competitor channels, extracts valuable insights, and generates new high-quality script
What if your team could skim the best of LinkedIn in 2 minutes instead of scrolling for hours? This workflow transforms raw LinkedIn posts into a bite-sized Slack digest — summarized, grouped, and del