AutomationFlowsWeb Scraping › Upwork Pre-bid Triage · Pro

Upwork Pre-bid Triage · Pro

Upwork Pre-Bid Triage · PRO. Uses httpRequest. Event-driven trigger; 23 nodes.

Event trigger★★★★☆ complexity23 nodesHTTP Request
Web Scraping Trigger: Event Nodes: 23 Complexity: ★★★★☆ Added:

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 →

Download .json
{
  "name": "Upwork Pre-Bid Triage \u00b7 PRO",
  "nodes": [
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-000000000001",
      "name": "\u25b6 Run now",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -368,
        624
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "id": "aa000000-0000-4000-8000-000000000002",
      "name": "\ud83d\udd52 Every hour",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -368,
        832
      ],
      "typeVersion": 1.2
    },
    {
      "parameters": {
        "jsCode": "// keyword set to sweep \u2014 edit freely\nreturn [\n  { json: { q: 'AI automation' } },\n  { json: { q: 'GoHighLevel' } },\n  { json: { q: 'n8n' } },\n  { json: { q: 'make.com automation' } }\n];"
      },
      "id": "aa000000-0000-4000-8000-000000000003",
      "name": "\u2460 Keywords",
      "type": "n8n-nodes-base.code",
      "position": [
        48,
        688
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "url": "http://localhost:4178/api/search",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $json.q }}"
            },
            {
              "name": "limit",
              "value": "6"
            }
          ]
        },
        "options": {}
      },
      "id": "aa000000-0000-4000-8000-000000000004",
      "name": "\u2461 Search (per keyword)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        400,
        688
      ],
      "typeVersion": 4.2
    },
    {
      "parameters": {
        "jsCode": "// flatten all keyword results + dedupe by job id\nconst seen = new Set(); const out = [];\nfor (const it of $input.all()) {\n  for (const j of (it.json.jobs || [])) {\n    if (seen.has(j.id)) continue; seen.add(j.id); out.push({ json: j });\n  }\n}\nreturn out;"
      },
      "id": "aa000000-0000-4000-8000-000000000005",
      "name": "\u2462 Flatten + dedupe",
      "type": "n8n-nodes-base.code",
      "position": [
        768,
        688
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "url": "http://localhost:4178/api/job",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "id",
              "value": "={{ $json.id }}"
            }
          ]
        },
        "options": {}
      },
      "id": "aa000000-0000-4000-8000-000000000006",
      "name": "\u2463 Enrich (deep intel)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1120,
        688
      ],
      "typeVersion": 4.2
    },
    {
      "parameters": {
        "jsCode": "// zip search + deep intel, score, tag, add trust gap\nconst search = $('\u2462 Flatten + dedupe').all();\nconst deep = $input.all();\nconst num = v => (v==null||v===''||isNaN(+v)) ? null : +v;\nconst out = [];\nfor (let i=0;i<deep.length;i++){\n  const s = (search[i]||{}).json || {};\n  const j = (deep[i].json.job) || {};\n  const bid = j.bid_stats || {}; const cr = j.client_record || {};\n  const wh = j.work_history || {}; const priv = [...(wh.closed||[]),...(wh.open||[])].map(r=>num(r.feedback_to_client_score)).filter(x=>x!=null&&x>0);\n  const privAvg = priv.length ? +(priv.reduce((a,b)=>a+b,0)/priv.length).toFixed(2) : null;\n  const spend = num(cr.spend_total); const prop = num(s.proposal_count)||0;\n  const hired = (j.job_activity&&j.job_activity.totalHired)||0;\n  const score = Math.round(100*(0.34*(spend!=null?Math.min(1,Math.log10(spend+1)/6):0.2)+0.28*(1-Math.min(1,prop/50))+0.20*(hired>0?0.2:1)+0.18*(bid.avg!=null?Math.min(1,bid.avg/120):0.4)));\n  const tag = score>=72?'HOT':score>=55?'WATCH':'SKIP';\n  const feedback = num(cr.feedback_score);\n  out.push({ json: { score, tag, title: s.title||j.title, type: s.job_type, proposals: prop, bid_avg: bid.avg, bid_min: bid.min, bid_max: bid.max, client_spend: spend, client_country:(s.client||{}).country, verified:(s.client||{}).verification_status==='VERIFIED', connects_cost: num(j.connects_cost), feedback, private_avg: privAvg, trust_gap:(feedback!=null&&privAvg!=null)?+(feedback-privAvg).toFixed(2):null, id: s.id } });\n}\nout.sort((a,b)=>b.json.score-a.json.score);\nreturn out;"
      },
      "id": "aa000000-0000-4000-8000-000000000007",
      "name": "\u2464 Score & tag",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        688
      ],
      "typeVersion": 2
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 1
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.tag }}",
                    "rightValue": "HOT"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 1
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.tag }}",
                    "rightValue": "WATCH"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "loose",
                  "version": 1
                },
                "combinator": "and",
                "conditions": [
                  {
                    "operator": {
                      "type": "number",
                      "operation": "gte"
                    },
                    "leftValue": "={{ $json.trust_gap }}",
                    "rightValue": 0.5
                  }
                ]
              }
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "aa000000-0000-4000-8000-000000000008",
      "name": "\u2465 Route by signal",
      "type": "n8n-nodes-base.switch",
      "position": [
        1840,
        688
      ],
      "typeVersion": 3
    },
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-000000000009",
      "name": "\ud83c\udfaf Top pick",
      "type": "n8n-nodes-base.limit",
      "position": [
        2208,
        368
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:4178/api/analyze",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "id",
              "value": "={{ $json.id }}"
            }
          ]
        },
        "options": {
          "timeout": 240000
        }
      },
      "id": "aa000000-0000-4000-8000-00000000000a",
      "name": "\u26a1 Analyze (LLM)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2560,
        368
      ],
      "typeVersion": 4.2,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://localhost:4178/api/draft",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "id",
              "value": "={{ $('\ud83c\udfaf Top pick').first().json.id }}"
            }
          ]
        },
        "options": {
          "timeout": 240000
        }
      },
      "id": "aa000000-0000-4000-8000-00000000000b",
      "name": "\u270d Draft proposal (LLM)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2928,
        368
      ],
      "typeVersion": 4.2,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "// bundle the top pick with its LLM analysis + drafted proposal\nconst job = $('\ud83c\udfaf Top pick').first().json;\nconst analysis = ($('\u26a1 Analyze (LLM)').first().json||{}).analysis || '(analysis pending)';\nconst draft = ($('\u270d Draft proposal (LLM)').first().json||{}).draft || '(draft pending)';\nreturn [{ json: { ...job, llm_analysis: analysis, llm_draft: draft } }];"
      },
      "id": "aa000000-0000-4000-8000-00000000000c",
      "name": "\ud83d\udccb Assemble intel + proposal",
      "type": "n8n-nodes-base.code",
      "position": [
        3280,
        368
      ],
      "typeVersion": 2
    },
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-00000000000d",
      "name": "\ud83d\udc40 Watchlist",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2208,
        688
      ],
      "typeVersion": 1
    },
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-00000000000e",
      "name": "\ud83d\udea9 Risky client alert",
      "type": "n8n-nodes-base.noOp",
      "position": [
        2208,
        912
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "jsCode": "// build a human digest from the full scored set\nconst jobs = $('\u2464 Score & tag').all().map(i=>i.json);\nconst money=v=>v==null?'\u2014':Math.abs(v)>=1e3?'$'+(v/1e3).toFixed(0)+'K':'$'+v;\nconst hot=jobs.filter(j=>j.tag==='HOT'), watch=jobs.filter(j=>j.tag==='WATCH'), risk=jobs.filter(j=>j.trust_gap!=null&&j.trust_gap>=0.5);\nconst L=['# Pre-Bid Digest','',`Scanned **${jobs.length}** gigs \u00b7 ${hot.length} HOT \u00b7 ${watch.length} WATCH \u00b7 ${risk.length} trust-risk`,'','## Open first'];\nhot.slice(0,5).forEach(j=>L.push(`- **[${j.score}] ${j.title}** \u2014 ${j.client_country||''} \u00b7 spend ${money(j.client_spend)} \u00b7 ${j.proposals} proposals \u00b7 bid ${j.bid_avg!=null?('$'+Math.round(j.bid_avg)):'\u2014'}`));\nif(risk.length){L.push('','## \u26a0 Trust-gap flags');risk.forEach(j=>L.push(`- ${j.title} \u2014 public ${j.feedback} vs private ${j.private_avg} (gap +${j.trust_gap})`));}\nreturn [{ json: { digest: L.join('\\n'), scanned: jobs.length, hot: hot.length, watch: watch.length, risk: risk.length } }];"
      },
      "id": "aa000000-0000-4000-8000-00000000000f",
      "name": "\ud83d\udcca Build digest",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        1120
      ],
      "typeVersion": 2
    },
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-000000000010",
      "name": "\ud83d\udce4 Digest ready",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1840,
        1120
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "url": "http://localhost:4178/api/dashboard",
        "options": {}
      },
      "id": "aa000000-0000-4000-8000-000000000011",
      "name": "\ud83d\udcc8 My dashboard",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        48,
        1120
      ],
      "typeVersion": 4.2
    },
    {
      "parameters": {},
      "id": "aa000000-0000-4000-8000-000000000012",
      "name": "\ud83d\udca0 Connects snapshot",
      "type": "n8n-nodes-base.noOp",
      "position": [
        400,
        1120
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## \u2460 INGEST \u2014 multi-keyword sweep\nFan out across keywords \u2192 dedupe by job id. All calls go through the **MCP bridge** (OAuth + refresh handled).",
        "height": 140,
        "width": 1060,
        "color": 4
      },
      "id": "aa000000-0000-4000-8000-000000000021",
      "name": "note-ingest",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        32,
        480
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## \u2461 ENRICH + SCORE\nPull deep intel per gig (competitor bids, client spend, **private ratings**), compute an opportunity score + trust gap.",
        "height": 140,
        "width": 620,
        "color": 5
      },
      "id": "aa000000-0000-4000-8000-000000000022",
      "name": "note-score",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1120,
        480
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## \u2462 ROUTE\nSwitch each gig \u2192 HOT \u00b7 WATCH \u00b7 Trust-risk.",
        "height": 140,
        "width": 340,
        "color": 3
      },
      "id": "aa000000-0000-4000-8000-000000000023",
      "name": "note-route",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        288
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## \u2463 LLM AGENT (Codex, free)\nOn the top pick: **\u26a1 analyze** (fit / win-odds / suggested bid / red flags) + **\u270d draft a proposal**. Wire an approval before any submit.",
        "height": 140,
        "width": 1440,
        "color": 2
      },
      "id": "aa000000-0000-4000-8000-000000000024",
      "name": "note-llm",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2208,
        192
      ],
      "typeVersion": 1
    },
    {
      "parameters": {
        "content": "## \u2464 DIGEST + ALERTS\nHuman-readable digest of the batch + trust-gap flags. Add Slack / email here.",
        "height": 140,
        "width": 620,
        "color": 6
      },
      "id": "aa000000-0000-4000-8000-000000000025",
      "name": "note-digest",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1488,
        928
      ],
      "typeVersion": 1
    }
  ],
  "connections": {
    "\u25b6 Run now": {
      "main": [
        [
          {
            "node": "\u2460 Keywords",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83d\udcc8 My dashboard",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2460 Keywords": {
      "main": [
        [
          {
            "node": "\u2461 Search (per keyword)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udfaf Top pick": {
      "main": [
        [
          {
            "node": "\u26a1 Analyze (LLM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2464 Score & tag": {
      "main": [
        [
          {
            "node": "\u2465 Route by signal",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83d\udcca Build digest",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd52 Every hour": {
      "main": [
        [
          {
            "node": "\u2460 Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u26a1 Analyze (LLM)": {
      "main": [
        [
          {
            "node": "\u270d Draft proposal (LLM)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcc8 My dashboard": {
      "main": [
        [
          {
            "node": "\ud83d\udca0 Connects snapshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcca Build digest": {
      "main": [
        [
          {
            "node": "\ud83d\udce4 Digest ready",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2465 Route by signal": {
      "main": [
        [
          {
            "node": "\ud83c\udfaf Top pick",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83d\udc40 Watchlist",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83d\udea9 Risky client alert",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "\u2462 Flatten + dedupe": {
      "main": [
        [
          {
            "node": "\u2463 Enrich (deep intel)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2463 Enrich (deep intel)": {
      "main": [
        [
          {
            "node": "\u2464 Score & tag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2461 Search (per keyword)": {
      "main": [
        [
          {
            "node": "\u2462 Flatten + dedupe",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u270d Draft proposal (LLM)": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Assemble intel + proposal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  },
  "nodeGroups": []
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Upwork Pre-Bid Triage · PRO. Uses httpRequest. Event-driven trigger; 23 nodes.

Source: https://github.com/Majboor/upwork-cli/blob/main/examples/n8n/upwork-MCP-automation.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

This workflow listens for an “Approved” label on a Trello card, reads the AI draft bookkeeping JSON from card comments, and posts the corresponding transaction to Xero. It then adds a Xero deep link b

Trello Trigger, HTTP Request
Web Scraping

02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.

Execute Workflow Trigger, HTTP Request, Sea Table
Web Scraping

This workflow allows you to import any workflow from a file or another n8n instance and map the credentials easily. A multi-form setup guides you through the entire process At the beginning you have t

Execute Command, Read Write File, HTTP Request +3
Web Scraping

[n8n] Advanced URL Parsing and Shortening Workflow - Switchy.io Integration. Uses splitInBatches, stickyNote, httpRequest, html. Event-driven trigger; 56 nodes.

HTTP Request, GitHub, Stop And Error +1
Web Scraping

[](https://youtu.be/c7yCZhmMjtI)

HTTP Request, GitHub, Stop And Error +1