{ "id": "Tender8v2Ks9Qm1a", "name": "Monitor official EU tenders from TED and route only new notices", "nodes": [ { "parameters": { "content": "## Monitor official EU procurement notices\n\nUse this template to turn a focused TED search into a scheduled stream of only new public-procurement notices. It is designed for bid teams, suppliers, market-intelligence analysts, and downstream qualification workflows that need a real source instead of sample tender data.\n\n### How it works\n1. The schedule starts the workflow every six hours.\n2. **Set tender search** groups keywords, CPV codes, buyer countries, state namespace, lookback, and output limit.\n3. **Find new EU tenders** runs EU Tender Radar through Apify's verified n8n node. The Actor queries the official TED Search API v3, creates a quiet first baseline, and suppresses previously seen publication numbers.\n4. **Prepare tender alerts** keeps normalized tender notices and exposes stable delivery IDs plus direct TED links for downstream routing.\n\n### Setup\n1. Install the verified `@apify/n8n-nodes-apify` node if your n8n instance does not already expose Apify.\n2. Add an Apify API credential to **Find new EU tenders**.\n3. Replace the example keywords, CPV codes, countries, and namespace in **Set tender search**.\n4. Test once to create the silent baseline, then activate the schedule. Later runs emit unseen notices only.\n\n### Customization\nConnect the final node to email, Slack, a CRM, a database, or a human-approved bid-qualification flow. Deduplicate retries with `deliveryId`.", "height": 760, "width": 1700 }, "id": "tender-overview-sticky", "name": "Workflow overview", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -840, -1160 ] }, { "parameters": { "rule": { "interval": [ { "field": "hours", "hoursInterval": 6 } ] } }, "id": "schedule-tender-check", "name": "Every six hours", "type": "n8n-nodes-base.scheduleTrigger", "typeVersion": 1.2, "position": [ -200, 0 ] }, { "parameters": { "assignments": { "assignments": [ { "id": "tender-keywords", "name": "keywordsCsv", "value": "laboratory reagents,diagnostics", "type": "string" }, { "id": "tender-cpv-codes", "name": "cpvCodesCsv", "value": "33696500", "type": "string" }, { "id": "tender-countries", "name": "buyerCountriesCsv", "value": "FRA,DEU,ESP", "type": "string" }, { "id": "tender-namespace", "name": "stateNamespace", "value": "eu-tender-laboratory-supplies", "type": "string" }, { "id": "tender-first-run", "name": "firstRunMode", "value": "baseline_only", "type": "string" }, { "id": "tender-lookback", "name": "lookbackDays", "value": 30, "type": "number" }, { "id": "tender-max-results", "name": "maxResults", "value": 100, "type": "number" } ] }, "options": {} }, "id": "set-tender-search", "name": "Set tender search", "type": "n8n-nodes-base.set", "typeVersion": 3.4, "position": [ 40, 0 ] }, { "parameters": { "memory": 1024, "actorId": "xerophylous_mercy_0wn~eu-tender-radar", "resource": "Actors", "operation": "Run actor and get dataset", "customBody": "={{ JSON.stringify({ keywords: $json.keywordsCsv.split(',').map((value) => value.trim()).filter(Boolean), keywordMode: 'any', cpvCodes: $json.cpvCodesCsv.split(',').map((value) => value.trim()).filter(Boolean), buyerCountries: $json.buyerCountriesCsv.split(',').map((value) => value.trim().toUpperCase()).filter(Boolean), lookbackDays: $json.lookbackDays, scope: 'ACTIVE', onlyNew: true, firstRunMode: $json.firstRunMode, maxResults: $json.maxResults, stateStoreName: 'eu-tender-radar-state', stateNamespace: $json.stateNamespace }) }}", "timeout": 120, "maxTotalChargeUsd": 1, "build": "" }, "id": "find-new-eu-tenders", "name": "Find new EU tenders", "type": "@apify/n8n-nodes-apify.apify", "typeVersion": 1, "position": [ 320, 0 ] }, { "parameters": { "jsCode": "return items\n .filter((item) => item.json.recordType === 'tender_notice')\n .map((item) => ({\n json: {\n alertTitle: item.json.title,\n publicationNumber: item.json.publicationNumber,\n buyerName: item.json.buyerName,\n buyerCountries: item.json.buyerCountries ?? [],\n publicationDate: item.json.publicationDate,\n submissionDeadline: item.json.submissionDeadline,\n cpvCodes: item.json.cpvCodes ?? [],\n noticeType: item.json.noticeType,\n tedUrl: item.json.tedUrl,\n deliveryId: item.json.deliveryId,\n source: item.json.source\n },\n pairedItem: item.pairedItem\n }));" }, "id": "prepare-tender-alerts", "name": "Prepare tender alerts", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 600, 0 ] }, { "parameters": { "content": "### 1. Schedule and search\nEdit the comma-separated keywords, CPV codes, countries, and unique namespace before activation.", "height": 240, "width": 500, "color": 7 }, "id": "tender-configure-sticky", "name": "Schedule and search note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ -300, -320 ] }, { "parameters": { "content": "### 2. Run official-source monitor\nAdd an Apify credential. The Actor queries TED, owns the baseline, and returns only unseen notices.", "height": 240, "width": 260, "color": 7 }, "id": "tender-monitor-sticky", "name": "Tender monitor note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 280, -320 ] }, { "parameters": { "content": "### 3. Route new notices\nThe Code node emits compact tender records. Connect your review, alerting, or CRM destination here.", "height": 240, "width": 260, "color": 7 }, "id": "tender-route-sticky", "name": "Tender route note", "type": "n8n-nodes-base.stickyNote", "typeVersion": 1, "position": [ 580, -320 ] } ], "connections": { "Every six hours": { "main": [ [ { "node": "Set tender search", "type": "main", "index": 0 } ] ] }, "Set tender search": { "main": [ [ { "node": "Find new EU tenders", "type": "main", "index": 0 } ] ] }, "Find new EU tenders": { "main": [ [ { "node": "Prepare tender alerts", "type": "main", "index": 0 } ] ] } }, "active": false, "settings": { "executionOrder": "v1" }, "versionId": "00000000-0000-4000-8000-000000000002", "meta": { "templateCredsSetupCompleted": false }, "tags": [] }