# EU TED Public Procurement & Tenders Scraper (MCP-Ready) (`creatortsv/eu-ted-procurement-tenders-mcp`) Actor

Extract live European Union public procurement notices, government tenders, contracting authorities, CPV codes, and contract values via the official EU TED Search API v3 with eForms UBL XML fallback.

- **URL**: https://apify.com/creatortsv/eu-ted-procurement-tenders-mcp.md
- **Developed by:** [Vladimir Tsaplin](https://apify.com/creatortsv) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## EU TED Public Procurement & Tenders Scraper — Self-Healing Apify Actor & MCP Server (`eu-ted-procurement-tenders-mcp`)

```json
{
  "id": "ted:15980-2026",
  "publication_number": "15980-2026",
  "publication_date": "2026-01-12",
  "title": "Ireland - Security software development services - Provision of Security Operations Centre and Cyber Defence services to the Rehab Group",
  "buyer_name": "The Rehab Group",
  "country_code": "IRL",
  "cpv_codes": ["72212730", "72212732", "72611000"],
  "notice_type": "cn-standard",
  "total_value": 500000.0,
  "ted_url": "https://ted.europa.eu/en/notice/-/detail/15980-2026",
  "pdf_url": "https://ted.europa.eu/en/notice/15980-2026/pdf",
  "ppe_event": "ted_notice_extracted",
  "billable": true,
  "network_mode": "live_eu_ted_v3_api",
  "extracted_at": "2026-10-28T19:15:00Z"
}
```

**Keywords**: `EU TED Tenders`, `European Public Procurement`, `CPV Code Tender Monitor`, `B2B Government Contracts`, `MCP Server`, `eu-ted-api-v3`, `eforms-ubl-xml`

***

### Why B2B Bid Teams, GovTech Vendors & RevOps Use This Actor (ROI vs. DIY)

Public sector procurement across the 27 EU Member States and EEA exceeds EUR 2 trillion annually on **Tenders Electronic Daily (TED)**. Following the European Commission's migration to the **TED Search API v3 (`api.ted.europa.eu/v3/notices/search`)** and **eForms UBL XML (Regulation 2019/1780)**, legacy HTML scrapers and v2 integrations fail or return unnormalized multilingual dictionaries. This Actor converts complex CPV, ISO country, and keyword filters into clean, English-prioritized B2B contract records ready for CRM ingestion or AI agent tool-calling via the Apify MCP Server.

| Approach | Setup & Maintenance Effort | eForms & Multilingual Normalization | MCP / AI Agent Ready | Cost Structure |
|---|---|---|---|---|
| **In-House Script (DIY)** | 18-30 hrs (TED v3 Expert Query syntax + 24-language map + UBL XML parser) | Breaks when notices omit English titles or switch eForms subtypes | Manual wrapper required | EUR 900+ engineering setup |
| **Enterprise Tender Portals** | Multi-week sales onboarding & annual seat lock-in | High | Closed proprietary web UI | EUR 350-EUR 1,200/mo minimum |
| **This Actor (`eu-ted-procurement-tenders-mcp`)** | **0 hrs (Instant Apify API, CLI, or MCP)** | **3-Tier Self-Healing (TED API v3 + eForms UBL XML + HTML/JSON-LD)** | **Native JSON & MCP Schema** | **Pay-Per-Event ($0.002 / notice)** |

***

### 30-Second Copy-Paste Quickstart

Run a live EU TED procurement query filtering by keyword, 8-digit CPV codes (`72000000` IT Services, `48000000` Software Packages), and 2-letter or 3-letter ISO buyer countries:

```bash
python3 workspaces/actors/eu-ted-procurement-tenders-mcp/main.py \
  --query "cybersecurity" \
  --cpv "72000000,48000000" \
  --countries "DE,FR,NL,IE" \
  --published-after "2026-01-01" \
  --max-items 5
```

Or pass an inline JSON configuration payload matching `INPUT_SCHEMA.json`:

```bash
python3 workspaces/actors/eu-ted-procurement-tenders-mcp/main.py \
  --input-json '{"searchQuery":"cloud","cpvCodes":["72000000","48000000"],"countries":["DEU","FRA","NLD","IRL","CYP"],"publishedAfter":"2026-01-01","maxItems":10}'
```

Run the built-in deterministic self-test and unit test suite:

```bash
python3 workspaces/actors/eu-ted-procurement-tenders-mcp/main.py --self-test
python3 -m unittest discover -s workspaces/actors/eu-ted-procurement-tenders-mcp -p "test_*.py" -v
```

***

### 3-Tier Self-Healing EU TED Extraction Architecture

1. **Pre-Flight Query Builder & ISO/CPV Normalizer (`build_ted_expert_query`)**:
   - Automatically maps 2-letter ISO-3166-1 alpha-2 country codes (`DE`, `FR`, `NL`, `IE`, `CY`, `EL`/`GR`, `UK`/`GB`, `SE`, `PL`, etc.) to the mandatory 3-letter ISO-3166-1 alpha-3 codes required by TED v3 (`DEU`, `FRA`, `NLD`, `IRL`, `CYP`, `GRC`, `GBR`, `SWE`, `POL`).
   - Sanitizes 8-digit CPV codes (`normalize_cpv_codes`), stripping optional check digits (`48000000-8` -> `48000000`).
   - Validates `publishedAfter` dates (`YYYY-MM-DD` or `YYYYMMDD`) and compiles valid TED v3 Expert Search queries (`publication-date >= 20260101 AND buyer-country IN (DEU FRA) AND classification-cpv IN (72000000) AND FT ~ "cybersecurity"`).
2. **Tier 1 Primary — Official EU TED Search API v3 (`POST https://api.ted.europa.eu/v3/notices/search`)**:
   - Queries the official European Union Publications Office REST v3 endpoint with pagination, exponential backoff on HTTP `429`/`5xx`, and deterministic multilingual resolution (`extract_multilingual_text`) across all 24 official EU languages (prioritizing English `eng`, then `deu`, `fra`, `nld`, `esp`, `ita`).
3. **Tier 2 Fallback — Official EU TED eForms UBL XML Parser (`parse_ted_eforms_xml`)**:
   - If the TED Search API gateway experiences transient maintenance, the Actor automatically falls back to parsing official eForms UBL XML notices (`https://ted.europa.eu/en/notice/{publication_number}/xml`), extracting `<efbc:NoticePublicationID>`, `<cac:PartyName>`, `<cac:ProcurementProject>`, `<cbc:EstimatedOverallContractAmount>`, and `<cbc:ItemClassificationCode listName="cpv">`.
4. **Tier 3 Self-Healing HTML & JSON-LD Fallback (`extract_with_fallbacks`)**:
   - Multi-tier regex and `application/ld+json` selector chain with strict `BrokenSelectorError` enforcement so corrupt or empty responses are never billed.

***

### Input & Output Schema Reference

#### Input Parameters (`INPUT_SCHEMA.json`)

- `searchQuery` *(string, optional)*: Keyword (e.g. `"software"`, `"cloud"`, `"cybersecurity"`) or raw TED v3 Expert Search expression.
- `cpvCodes` *(array of strings, optional)*: 8-digit CPV classification codes (e.g. `["72000000", "48000000"]`).
- `countries` *(array of strings, optional)*: Buyer countries in 2-letter (`DE`, `FR`, `CY`) or 3-letter (`DEU`, `FRA`, `CYP`) ISO format.
- `publishedAfter` *(string, optional)*: Publication date floor in `YYYY-MM-DD` or `YYYYMMDD` format (default `"2026-01-01"`).
- `maxItems` *(integer, required, default `25`, min `1`, max `500`)*: Maximum number of procurement notices to extract.

#### Output Record Fields

- `publication_number`: Official TED publication identifier (e.g. `"15980-2026"`).
- `publication_date`: Notice publication date (`YYYY-MM-DD`).
- `title`: Normalized tender title (English preferred with automatic EU multilingual fallback).
- `buyer_name`: Official legal name of the contracting authority.
- `country_code`: 3-letter ISO-3166-1 alpha-3 buyer country code (`DEU`, `FRA`, `IRL`, etc.).
- `cpv_codes`: Deduplicated array of 8-digit CPV classification codes.
- `notice_type`: Official TED notice type (`cn-standard`, `can-standard`, `pin-only`, etc.).
- `total_value`: Disclosed estimated or total contract value (numeric or `null` when not disclosed by the buyer).
- `ted_url`: Canonical URL to the notice on `ted.europa.eu`.
- `pdf_url`: Direct link to the official notice PDF.
- `ppe_event`: `"ted_notice_extracted"`.
- `billable`: `true` when the record passes schema validation.
- `network_mode`: `"live_eu_ted_v3_api"` (or `"live_eu_ted_eforms_xml"` on Tier 2 failover).
- `extracted_at`: ISO-8601 UTC extraction timestamp.

***

### Scope & Compatibility (What's Included / What's Not Included)

- **What's Included**:
  - Zero-dependency Python 3.11+ Apify Actor (`main.py`) querying the official EU TED Search API v3 (`POST https://api.ted.europa.eu/v3/notices/search`) with automatic 2-to-3 letter ISO country code normalization, 8-digit CPV code sanitization, multilingual field extraction, and eForms UBL XML fallback.
  - Direct links to the official TED HTML notice (`ted_url`), multilingual PDF (`pdf_url`), and machine-readable eForms XML (`xml_url`).
  - Full Apify Store and Model Context Protocol (MCP) compatibility (`INPUT_SCHEMA.json`, `.actor/actor.json`, `.actor/dataset_schema.json`, `.actor/output_schema.json`, `Dockerfile`, and `test_actor.py`).
- **What's Not Included (Anti-Refund Warranty Boundary)**:
  - Does not bypass password-protected national e-Tendering portals (such as external buyer login portals linked inside tender specifications for submitting sealed bids).
  - Does not populate `total_value` when the contracting authority has legally omitted or redacted the estimated contract value in the official TED notice (`total_value` will be `null` in those cases).
  - Does not cover municipal micro-tenders below EU publication thresholds that are not published in the Supplement to the Official Journal of the European Union (OJ S / TED).

# Actor input Schema

## `searchQuery` (type: `string`):

Optional keyword (e.g. 'software', 'cloud', 'cybersecurity') or full TED v3 Expert Search expression. Leave empty to filter purely by CPV codes, countries, and publication date.

## `cpvCodes` (type: `array`):

List of 8-digit EU Common Procurement Vocabulary (CPV) codes (e.g. \['72000000', '48000000'] for IT services and software packages).

## `countries` (type: `array`):

List of buyer country codes in ISO-3166-1 alpha-3 (e.g. \['DEU', 'FRA', 'NLD', 'IRL', 'CYP']) or alpha-2 (e.g. \['DE', 'FR', 'NL', 'IE', 'CY']). Automatically normalizes 2-letter codes to official TED 3-letter codes.

## `publishedAfter` (type: `string`):

Minimum notice publication date in YYYY-MM-DD or YYYYMMDD format (e.g. '2026-01-01' or '20260101').

## `maxItems` (type: `integer`):

Maximum number of procurement notices to extract (1 to 500).

## Actor input object example

```json
{
  "searchQuery": "cybersecurity",
  "cpvCodes": [
    "72000000",
    "48000000"
  ],
  "countries": [
    "DEU",
    "FRA",
    "NLD",
    "IRL"
  ],
  "publishedAfter": "2026-01-01",
  "maxItems": 25
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset containing normalized EU TED tender notices, buyer details, CPV classifications, and direct notice/PDF/XML links.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchQuery": "cybersecurity",
    "cpvCodes": [
        "72000000",
        "48000000"
    ],
    "countries": [
        "DEU",
        "FRA",
        "NLD",
        "IRL"
    ],
    "publishedAfter": "2026-01-01",
    "maxItems": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("creatortsv/eu-ted-procurement-tenders-mcp").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQuery": "cybersecurity",
    "cpvCodes": [
        "72000000",
        "48000000",
    ],
    "countries": [
        "DEU",
        "FRA",
        "NLD",
        "IRL",
    ],
    "publishedAfter": "2026-01-01",
    "maxItems": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("creatortsv/eu-ted-procurement-tenders-mcp").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQuery": "cybersecurity",
  "cpvCodes": [
    "72000000",
    "48000000"
  ],
  "countries": [
    "DEU",
    "FRA",
    "NLD",
    "IRL"
  ],
  "publishedAfter": "2026-01-01",
  "maxItems": 25
}' |
apify call creatortsv/eu-ted-procurement-tenders-mcp --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,creatortsv/eu-ted-procurement-tenders-mcp"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jhRRLLrrZ3KapUw52/builds/RkaU71czr4gPzSpWA/openapi.json
