This workflow corresponds to n8n.io template #17697 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request 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": "3iVShLMqduvzfBjT",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI agent that qualifies Polish public tenders (Baza Konkurencyjno\u015bci) into GO / NO-GO",
"tags": [
{
"id": "AOsofVk0eoycM6GI",
"name": "Poland",
"createdAt": "2026-07-25T22:19:19.387Z",
"updatedAt": "2026-07-25T22:19:19.387Z"
},
{
"id": "PjOd3TDnbXVp9sBX",
"name": "AI",
"createdAt": "2026-07-25T22:43:27.234Z",
"updatedAt": "2026-07-25T22:43:27.234Z"
},
{
"id": "uPH98RnOYWzeBPFw",
"name": "Sales",
"createdAt": "2026-07-25T22:43:27.261Z",
"updatedAt": "2026-07-25T22:43:27.261Z"
}
],
"nodes": [
{
"id": "f3a8385a-d0a8-4836-9102-a335e78e4a46",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
1760
],
"parameters": {
"width": 520,
"height": 900,
"content": "## AI agent that qualifies Polish public tenders (Baza Konkurencyjno\u015bci) into GO / NO-GO\n\n### How it works\n\nThis workflow checks Polish public tender announcements from Baza Konkurencyjno\u015bci and qualifies them as GO or NO-GO opportunities. It can be run manually or twice a day, fetches announcements, filters duplicates and irrelevant items, then uses an AI agent to review details and return structured verdicts. Qualified opportunities are formatted into a report and sent to the team via Telegram.\n\n### Setup steps\n\n- Configure the Settings node with the correct list API URL, detail API URL, public URL base, and tender-matching keywords.\n- Add credentials for the Anthropic chat model used by the Bid qualification agent.\n- Configure the tender_details HTTP tool so it can retrieve full tender details from the detail API.\n- Set up Telegram credentials and the target chat/channel in the Notify the team node.\n- Review the schedule trigger timing and activate the workflow when ready.\n\n### Customization\n\nAdjust the keywords, AI qualification prompt or scoring criteria, schedule frequency, and Telegram message format to match the organization\u2019s target tenders and review process."
},
"typeVersion": 1
},
{
"id": "1e77552f-2f48-4306-8044-efce12e3bccb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
2800,
1760
],
"parameters": {
"color": 7,
"width": 416,
"height": 496,
"content": "## Start and configure\n\nManual and scheduled triggers start the workflow, then the Settings node centralizes API endpoints, public URL base, and keyword criteria used downstream."
},
"typeVersion": 1
},
{
"id": "d774d93e-6e6c-4b34-8849-b6d32a2ce0bf",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
3248,
1840
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Fetch and filter tenders\n\nRetrieves the latest announcements from Baza Konkurencyjno\u015bci and performs a cheap local pre-filter and deduplication step before invoking the costly AI analysis."
},
"typeVersion": 1
},
{
"id": "8511ed15-3217-4436-83c1-5717803ae28a",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
3696,
1840
],
"parameters": {
"color": 7,
"width": 640,
"height": 560,
"content": "## Qualify with AI\n\nThe bid qualification agent evaluates candidate tenders using an Anthropic chat model, an HTTP tool for tender details, and a structured output parser to produce consistent GO / NO-GO verdicts."
},
"typeVersion": 1
},
{
"id": "835c2322-30a5-4769-97b6-c171195d1dd4",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
4368,
1840
],
"parameters": {
"color": 7,
"width": 448,
"height": 336,
"content": "## Report and notify\n\nBuilds a team-facing report from qualified tender results and sends relevant GO opportunities through Telegram, while keeping NO-GO results in the workflow output."
},
"typeVersion": 1
},
{
"id": "175224ba-fd6a-4f90-8549-3baee00cde2f",
"name": "Manual Execution Trigger",
"type": "n8n-nodes-base.manualTrigger",
"position": [
2848,
1936
],
"parameters": {},
"typeVersion": 1
},
{
"id": "70cdabf2-7215-40ac-b259-69262a24a62c",
"name": "Scheduled Trigger: Twice Daily",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2848,
2096
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8,15 * * 1-5"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "a0e3a7eb-ab88-4a9b-9cd5-c8e96257afdd",
"name": "Set API and Keywords",
"type": "n8n-nodes-base.set",
"position": [
3056,
2016
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "0",
"name": "listApi",
"type": "string",
"value": "https://bazakonkurencyjnosci.funduszeeuropejskie.gov.pl/api/announcements/search?page=1&limit=100&sort=default&status%5B0%5D=PUBLISHED"
},
{
"id": "1",
"name": "detailApi",
"type": "string",
"value": "https://bazakonkurencyjnosci.funduszeeuropejskie.gov.pl/api/announcements/"
},
{
"id": "2",
"name": "publicUrlBase",
"type": "string",
"value": "https://bazakonkurencyjnosci.funduszeeuropejskie.gov.pl/ogloszenia/"
},
{
"id": "3",
"name": "keywords",
"type": "string",
"value": "oprogramowan,aplikacj,system informatyczn,system klasy,system erp,system mes,system zarzadzania,system do zarzadzania,platform,portal,cyfryzac,informatyz,wdrozenie systemu informatyczn,uslugi programistyczn,prace programistyczn,dedykowan oprogramowan,baza danych,integracj systemow informatyczn"
},
{
"id": "4",
"name": "excludeKeywords",
"type": "string",
"value": "licencj,subskrypcj,odnowienie,dostawa sprzetu,dostawa urzadzen,zakup sprzetu,komputer,laptop,serwer,macierz,firewall,ngfw,switch,router,drukark,maszyn,obrabiark,laser,koparki,palet,przenosnik,regal,tasmoc,linia produkcyjna,stabilizacj gruntu,motion capture,kopii zapasowych,backup,roboty budowlane,przylacz,fotowoltaic,termomoderniz,pojazd,samochod,zbiornik,piec,kotl,aparat,sprzet ratownicz,odpad,szkolen,audyt,nadzor inwestorski,terapi,psycholog,superwizj,logoped,rehabilitac,opiek,catering,transport osob"
},
{
"id": "5",
"name": "regions",
"type": "string",
"value": ""
},
{
"id": "6",
"name": "minDaysToDeadline",
"type": "string",
"value": "5"
},
{
"id": "7",
"name": "maxPerRun",
"type": "string",
"value": "5"
},
{
"id": "8",
"name": "companyProfile",
"type": "string",
"value": "Two-person software house. We build custom web applications, internal business systems, production and booking software. Stack: Next.js, React, TypeScript, Python/FastAPI, PostgreSQL, Docker. We do NOT do: embedded firmware, hardware, construction work, hardware resale, off-the-shelf ERP licences."
},
{
"id": "9",
"name": "dealBreakers",
"type": "string",
"value": "Price carries more than 80% of the award criteria. The subject is off-the-shelf software or licences rather than a custom build. The scope is mostly outside our stack and partial offers are not allowed. Required references or track record we do not have. Deposit or security above what a small company can post."
}
]
}
},
"typeVersion": 3.4
},
{
"id": "9ea891ab-b9db-438d-b4f2-7d952e2f01e2",
"name": "Fetch Tender Announcements",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueRegularOutput",
"position": [
3296,
2016
],
"parameters": {
"url": "={{ $('Set API and Keywords').first().json.listApi }}",
"options": {
"timeout": 25000,
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"typeVersion": 4.2
},
{
"id": "58ef858c-81b9-4b43-ae78-7beba5da823c",
"name": "Local Pre-Filter Tenders",
"type": "n8n-nodes-base.code",
"position": [
3520,
2016
],
"parameters": {
"jsCode": "// Cheap local pre-filter. The AI agent is expensive \u2014 roughly 12k tokens per\n// announcement \u2014 so it must only ever see announcements that could plausibly matter.\n// Exclusions win over keywords: a tender for \"licences for a warehouse system\"\n// is a resale deal, not a build, no matter how many software words it contains.\nconst cfg = $('Set API and Keywords').first().json;\nconst store = $getWorkflowStaticData('global');\nstore.seen = store.seen || [];\n\nlet list = [];\nfor (const item of $input.all()) {\n const r = item.json;\n const d = r?.data || r;\n if (Array.isArray(d?.advertisements)) list.push(...d.advertisements);\n}\nif (!list.length) {\n throw new Error('No announcements in the response. Inspect the \"Fetch announcements\" node output.');\n}\n\nconst strip = s => String(s ?? '').toLowerCase()\n .replace(/\u0142/g,'l').replace(/\u0105/g,'a').replace(/\u0119/g,'e').replace(/\u015b/g,'s')\n .replace(/\u0107/g,'c').replace(/\u017c/g,'z').replace(/\u017a/g,'z').replace(/\u00f3/g,'o').replace(/\u0144/g,'n');\n\nconst KEEP = String(cfg.keywords || '').split(',').map(s => strip(s.trim())).filter(Boolean);\nconst DROP = String(cfg.excludeKeywords || '').split(',').map(s => strip(s.trim())).filter(Boolean);\nconst REGION = String(cfg.regions || '').split(',').map(s => strip(s.trim())).filter(Boolean);\nconst MIN_DAYS = Number(cfg.minDaysToDeadline || 5);\nconst MAX = Number(cfg.maxPerRun || 8);\n\nconst now = new Date();\nconst hits = [];\n\nfor (const a of list) {\n const id = String(a.id ?? '');\n if (!id || store.seen.includes(id)) continue;\n store.seen.push(id);\n if (a.is_mine === true) continue;\n\n const text = strip((a.title || '') + ' ' + (a.content || ''));\n const place = strip(a.fulfillment_place || '');\n\n if (DROP.some(w => text.includes(w))) continue;\n if (KEEP.length && !KEEP.some(w => text.includes(w))) continue;\n if (REGION.length && !REGION.some(w => place.includes(w))) continue;\n\n let days = null;\n if (a.submission_deadline) {\n const d = new Date(String(a.submission_deadline).replace(' ', 'T'));\n if (!isNaN(d)) days = Math.ceil((d - now) / 86400000);\n }\n if (days !== null && days < MIN_DAYS) continue;\n\n hits.push({ json: {\n id,\n days_to_deadline: days,\n title: a.title || '',\n summary: String(a.content || '').slice(0, 600),\n buyer: a.advertiser_name || '',\n place: a.fulfillment_place || '',\n deadline: a.submission_deadline || '',\n published: a.publication_date || '',\n url: (cfg.publicUrlBase || '') + id,\n }});\n}\n\nif (store.seen.length > 6000) store.seen = store.seen.slice(-4000);\nhits.sort((a, b) => (a.json.days_to_deadline ?? 999) - (b.json.days_to_deadline ?? 999));\nreturn hits.slice(0, MAX);"
},
"typeVersion": 2
},
{
"id": "eda07ed6-5b37-4cd0-9bcd-6e9d16d10167",
"name": "Qualify Tender Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"onError": "continueRegularOutput",
"position": [
3936,
2016
],
"parameters": {
"text": "=You are a bid qualification analyst. Decide whether the company you work for should spend time preparing an offer for this public tender.\n\n=== ANNOUNCEMENT ===\nID: {{ $json.id }}\nTitle: {{ $json.title }}\nBuyer: {{ $json.buyer }}\nPlace: {{ $json.place }}\nDeadline: {{ $json.deadline }} ({{ $json.days_to_deadline }} days left)\nSummary: {{ $json.summary }}\n\n=== YOUR COMPANY PROFILE ===\n{{ $('Set API and Keywords').first().json.companyProfile }}\n\n=== HARD DISQUALIFIERS \u2014 any one of these means NO_GO ===\n{{ $('Set API and Keywords').first().json.dealBreakers }}\n\n=== INSTRUCTIONS ===\nThe summary above is truncated and never contains the award criteria, the estimated value or the contact person. Use the `tender_details` tool with the announcement ID to fetch the full record whenever the decision depends on information you do not have \u2014 especially the award criteria, because a tender where price carries most of the weight is a price auction, not a fit.\n\nDo not invent facts. If something is genuinely unknown after using the tool, say so in `open_questions` rather than guessing.\n\nBe decisive. A borderline GO that wastes two days of work is worse than an honest NO_GO.",
"options": {
"maxIterations": 4
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "4455e706-5dd7-48ac-8f0d-d83d522b8c62",
"name": "Claude Language Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"notes": "Swap for OpenAI, Gemini or a local model \u2014 any chat model works.",
"position": [
3776,
2240
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "claude-sonnet-4-5-20250929",
"cachedResultName": "Claude Sonnet 4.5"
},
"options": {}
},
"typeVersion": 1.3
},
{
"id": "08e3643c-79a2-4a27-86c7-f22568abbbfc",
"name": "Fetch Tender Details",
"type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
"position": [
3936,
2240
],
"parameters": {
"url": "={{ $('Set API and Keywords').first().json.detailApi }}{id}",
"fields": "title,submission_deadline,partial_offer_allowed,orders,contact_persons",
"dataField": "data.advertisement",
"fieldsToInclude": "selected",
"toolDescription": "Fetch the full record of a tender announcement by its numeric ID. Returns the award criteria, estimated value, the real scope description, whether partial offers are allowed, and the contact person. Call it at most once per announcement, and only when the decision genuinely depends on the award criteria or the real scope.",
"optimizeResponse": true,
"placeholderDefinitions": {
"values": [
{
"name": "id",
"type": "string",
"description": "Numeric announcement ID"
}
]
}
},
"typeVersion": 1.1
},
{
"id": "4b8aa376-c8c9-4a0c-b729-6a0f8af970ec",
"name": "Parse Tender Verdict",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
4096,
2240
],
"parameters": {
"schemaType": "manual",
"inputSchema": "{\"type\": \"object\", \"properties\": {\"verdict\": {\"type\": \"string\", \"enum\": [\"GO\", \"NO_GO\", \"GO_IF\"]}, \"confidence\": {\"type\": \"number\"}, \"one_line_reason\": {\"type\": \"string\"}, \"price_weight_percent\": {\"type\": [\"number\", \"null\"]}, \"estimated_value\": {\"type\": [\"string\", \"null\"]}, \"contact\": {\"type\": [\"string\", \"null\"]}, \"condition_to_verify\": {\"type\": [\"string\", \"null\"]}, \"scope_in_our_wheelhouse\": {\"type\": \"string\"}, \"scope_outside_our_wheelhouse\": {\"type\": \"string\"}, \"open_questions\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}, \"risks\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"verdict\", \"one_line_reason\", \"scope_in_our_wheelhouse\", \"open_questions\"]}"
},
"typeVersion": 1.2
},
{
"id": "2a06dcfe-55e3-4146-a6ff-02480855468a",
"name": "Generate Tender Report",
"type": "n8n-nodes-base.code",
"position": [
4416,
2016
],
"parameters": {
"jsCode": "// One message per qualified tender. NO_GO results are kept in the output but not sent \u2014\n// they are useful for tuning the profile, not for interrupting anyone.\nconst cfg = $('Set API and Keywords').first().json;\nconst rows = $input.all().map(i => i.json?.output ?? i.json);\n\nconst worth = rows.filter(r => r && r.verdict && r.verdict !== 'NO_GO');\nif (!worth.length) return [];\n\nconst lines = [];\nlines.push(`${worth.length} tender(s) worth a look`);\nlines.push('');\n\nfor (const r of worth) {\n lines.push(`[${r.verdict}] ${r.one_line_reason}`);\n if (r.price_weight_percent != null) lines.push(`Price weight: ${r.price_weight_percent}%`);\n if (r.estimated_value) lines.push(`Estimated value: ${r.estimated_value}`);\n if (r.contact) lines.push(`Contact: ${r.contact}`);\n if (r.condition_to_verify) lines.push(`Verify first: ${r.condition_to_verify}`);\n if (r.scope_outside_our_wheelhouse) lines.push(`Outside our scope: ${r.scope_outside_our_wheelhouse}`);\n if (Array.isArray(r.open_questions) && r.open_questions.length) {\n lines.push('Ask the buyer:');\n r.open_questions.slice(0, 4).forEach(q => lines.push(' - ' + q));\n }\n lines.push('');\n}\n\nreturn [{ json: { text: lines.join('\\n').slice(0, 3900), count: worth.length, results: rows } }];"
},
"typeVersion": 2
},
{
"id": "a1095026-d3b4-4999-ba69-5a728702e1ef",
"name": "Send Telegram Notification",
"type": "n8n-nodes-base.telegram",
"notes": "Swap for Slack, e-mail or anything else \u2014 the payload is plain text.",
"onError": "continueRegularOutput",
"position": [
4672,
2016
],
"parameters": {
"text": "={{ $json.text }}",
"additionalFields": {
"disable_web_page_preview": true
}
},
"typeVersion": 1.2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "566f9a78-b099-49b2-af21-344a41b53078",
"nodeGroups": [],
"connections": {
"Fetch Tender Details": {
"ai_tool": [
[
{
"node": "Qualify Tender Agent",
"type": "ai_tool",
"index": 0
}
]
]
},
"Parse Tender Verdict": {
"ai_outputParser": [
[
{
"node": "Qualify Tender Agent",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Qualify Tender Agent": {
"main": [
[
{
"node": "Generate Tender Report",
"type": "main",
"index": 0
}
]
]
},
"Set API and Keywords": {
"main": [
[
{
"node": "Fetch Tender Announcements",
"type": "main",
"index": 0
}
]
]
},
"Claude Language Model": {
"ai_languageModel": [
[
{
"node": "Qualify Tender Agent",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Generate Tender Report": {
"main": [
[
{
"node": "Send Telegram Notification",
"type": "main",
"index": 0
}
]
]
},
"Local Pre-Filter Tenders": {
"main": [
[
{
"node": "Qualify Tender Agent",
"type": "main",
"index": 0
}
]
]
},
"Manual Execution Trigger": {
"main": [
[
{
"node": "Set API and Keywords",
"type": "main",
"index": 0
}
]
]
},
"Fetch Tender Announcements": {
"main": [
[
{
"node": "Local Pre-Filter Tenders",
"type": "main",
"index": 0
}
]
]
},
"Scheduled Trigger: Twice Daily": {
"main": [
[
{
"node": "Set API and Keywords",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow checks new announcements in Poland’s Baza Konkurencyjności register on a schedule, pre-filters them by keywords and deadlines, then uses an Anthropic chat model agent to fetch tender details on demand and return a structured GO/NO_GO verdict before notifying your…
Source: https://n8n.io/workflows/17697/ — 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.
This workflow is designed for marketers, content creators, agencies, and solo founders who want to publish long‑form posts with visuals on autopilot using n8n and AI agents.
Template Carnaval - time instagram. Uses toolWorkflow, lmChatOpenAi, memoryBufferWindow, agent. Event-driven trigger; 56 nodes.
Automated Research Report Generation with OpenAI, Wikipedia, Google Search, and Gmail/Telegram. Uses lmChatOpenAi, memoryBufferWindow, toolHttpRequest, agent. Event-driven trigger; 26 nodes.
This workflow automates the process of generating professional research reports for researchers, students, and professionals. It eliminates manual research and report formatting by aggregating data, g
Agent Data Analyst + Visualisation. Uses nocoDbTool, httpRequest, executeWorkflowTrigger, lmChatOpenAi. Event-driven trigger; 23 nodes.