# EU Tenders (TED) Search & Monitor – open data (`signalrows/tenders-eu-ted`) Actor

EU public procurement notices from TED (Tenders Electronic Daily) via the official TED Search API: contract notices, awards and prior information with buyer, country, CPV, deadline, value and notice links. Open data (Commission Decision 2011/833/EU); no personal data.

- **URL**: https://apify.com/signalrows/tenders-eu-ted.md
- **Developed by:** [Signalrows Studio](https://apify.com/signalrows) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 record extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Tenders (TED) Search & Monitor – open data

Get **EU public procurement notices** from **TED (Tenders Electronic Daily)**, the official EU procurement journal, as clean structured data: **contract notices (calls for competition)**, **contract award notices** and **prior information notices** with buyer, country, **CPV codes**, **submission deadline**, estimated / awarded value and direct links to the notice page, PDF and eForms XML. Filter by free text or a full **TED expert query**, by country and by CPV. Uses only the official TED Search API — no scraping, no login, no API key. Open data licensed for commercial reuse (Commission Decision 2011/833/EU); no personal data. Runs in one click with sensible defaults (contract notices of the last 7 days), and `monitor` mode returns only what changed since the last run.

### What you get

One record per TED notice, in the same `TenderRecord` schema as the other tender actors of this studio, plus TED-specific fields.

| Field | Type | Example |
|---|---|---|
| `source` | string | `"ted"` |
| `sourceId`, `tenderNumber` | string | `"660259-2026"` (OJ S publication number) |
| `url` | url | `https://ted.europa.eu/en/notice/-/detail/660259-2026` |
| `title` | string | procedure title in the original language: `"Turnkey contract double hall Vennesla"` |
| `titleEn` | string | TED's English title: `"Norway – Construction work for sports facilities – Turnkey contract …"` |
| `buyer` | object | `{ "name": "Vennesla kommune", "unit": null, "type": "authority" }` (from eForms `buyer-legal-type`) |
| `buyerName`, `countryCode` | string | flat copies for tables; country as ISO 3166-1 alpha-3 (`"NOR"`) |
| `tenderType` | enum | `public` (contract notices & awards) · `rfi` (prior information) · `other` |
| `noticeTypeKey` | enum | `contract-notice` · `contract-award` · `prior-information` (input key) |
| `noticeType`, `formType` | string | eForms values: `cn-standard` / `competition`, `can-standard` / `result`, `pin-only` / `planning` |
| `procedureType`, `contractNature` | string | `open`, `restricted`, … · `works` / `supplies` / `services` |
| `status` | enum | `open` · `closed` (deadline passed) · `awarded` (award notices) · `unknown` (no deadline) |
| `publishedAt` | ISO date | `"2026-09-24"` |
| `deadlineAt` | ISO datetime/null | latest lot deadline, offset-aware: `"2026-10-23T10:00:00Z"` |
| `lotDeadlines` | string\[] | every lot deadline, ascending |
| `categories`, `cpv` | string\[] | unique CPV codes, main first: `["45212200", "45000000", …]` |
| `cpvLabels` | string\[] | CPV division labels: `["Construction work"]` |
| `location` | object | `{ "city": "Vennesla", "region": "NOR" }` |
| `placeOfPerformance` | string\[] | NUTS / country codes: `["NO092", "NOR"]` |
| `estimatedValue` | object/null | `{ "amount": 155000000, "currency": "NOK" }` |
| `awardValue`, `winners` | object/null, array | award notices: `{ "amount": 4314672, "currency": "PLN" }`, `[{ "name": "VEOLIA … Sp. z o.o.", "country": "POL", "winnerType": "organisation" }]`; winners that look like natural persons are masked by default (`name: null`, `winnerType: "masked-individual"`) |
| `documents` | array | notice page (EN + original language), PDF, original PDF, eForms XML |
| `buyerLegalType`, `mainActivity`, `buyerProfileUrl` | string | eForms codes (`body-pl-la`, `pub-os`) and buyer profile URL |
| `language` | string | original language (ISO 639-2: `eng`, `pol`, `fra`, …) |
| `description` | string/null | ≤300-char excerpt of the procedure description (original language), redacted |
| `noticeId`, `procedureId` | string | eForms UUIDs |
| `contact` | null | contact persons are never requested from TED |
| `attribution` | string | licence attribution (see "Data source & licence") |
| `changeType` | enum | `new` · `updated` · `deadline-changed` · `closed` · `awarded` · `unchanged` (monitor mode) |
| `source_url`, `fetched_at`, `actor_version` | provenance | `https://ted.europa.eu/…`, `2026-09-24T17:24:43.901Z`, `0.1` |

```json
{
  "source": "ted",
  "sourceId": "660259-2026",
  "url": "https://ted.europa.eu/en/notice/-/detail/660259-2026",
  "title": "Turnkey contract double hall Vennesla",
  "titleEn": "Norway – Construction work for sports facilities – Turnkey contract double hall Vennesla",
  "buyer": { "name": "Vennesla kommune", "unit": null, "type": "authority" },
  "buyerName": "Vennesla kommune",
  "countryCode": "NOR",
  "tenderType": "public",
  "noticeTypeKey": "contract-notice",
  "noticeType": "cn-standard",
  "formType": "competition",
  "procedureType": "open",
  "contractNature": "works",
  "tenderNumber": "660259-2026",
  "status": "open",
  "publishedAt": "2026-09-24",
  "updatedAt": null,
  "deadlineAt": "2026-10-23T10:00:00Z",
  "lotDeadlines": ["2026-10-23T10:00:00Z"],
  "categories": ["45212200", "45000000", "45111000", "45210000"],
  "cpv": ["45212200", "45000000", "45111000", "45210000"],
  "cpvLabels": ["Construction work"],
  "location": { "city": "Vennesla", "region": "NOR" },
  "placeOfPerformance": ["NO092", "NOR"],
  "estimatedValueILS": null,
  "estimatedValue": { "amount": 155000000, "currency": "NOK" },
  "awardValue": null,
  "winners": [],
  "documents": [
    { "title": "Notice page (EN)", "url": "https://ted.europa.eu/en/notice/-/detail/660259-2026" },
    { "title": "Notice PDF (EN)", "url": "https://ted.europa.eu/en/notice/660259-2026/pdf" },
    { "title": "Original PDF (ENG)", "url": "https://ted.europa.eu/en/notice/660259-2026/pdfs" },
    { "title": "Notice XML (eForms)", "url": "https://ted.europa.eu/en/notice/660259-2026/xml" }
  ],
  "buyerLegalType": "body-pl-la",
  "mainActivity": "pub-os",
  "buyerProfileUrl": "https://eu.eu-supply.com/ctm/company/companyinformation/index/311790",
  "language": "eng",
  "description": "Vennesla municipality shall establish a new double hall in Skolevegen. The project comprises two separate individual halls (double hall) and shall include …",
  "noticeId": "f6525edb-d034-4715-bab4-a6b6c044be61",
  "procedureId": "d56fc555-1c95-4677-b84b-a696d102b1b6",
  "contact": null,
  "attribution": "Source: Tenders Electronic Daily (TED), Publications Office of the European Union (https://ted.europa.eu). Reused under Commission Decision 2011/833/EU. Unofficial extract; the linked notice is the authoritative text.",
  "changeType": "new",
  "source_url": "https://ted.europa.eu/en/notice/-/detail/660259-2026",
  "fetched_at": "2026-09-24T17:24:43.901Z",
  "actor_version": "0.1"
}
```

You can download the dataset in JSON, CSV, Excel, XML or HTML from the run's Output tab or via the API.

### Who uses it

- **Bid and sales teams** of suppliers, contractors and consultancies: a daily feed of new EU tenders in their CPV sectors and target countries, with deadlines, straight into a CRM, Slack or a spreadsheet.
- **Procurement consultants and bid writers** who track calls for competition across several member states and need one normalized schema instead of 24 language versions.
- **Market intelligence and analysts**: award notices with winners and awarded values by country, sector (CPV) and buyer type, for competitor tracking, pricing benchmarks and pipeline forecasts (prior information notices).
- **AI agents (via the Apify MCP server) and workflow tools** (Make, Zapier, n8n): "find open IT tenders in Germany above EUR 1 million", `mode=monitor` on a schedule for a clean change feed.

### Pricing

Pay per result: **$0.004 per notice** (event `record-extracted`). `run-started` is free. No charge for runs with no matching notices. Set `maxItems` to cap your spend. In `monitor` mode you pay only for changed records.

### Input

| Parameter | Default | Notes |
|---|---|---|
| `query` | `""` | Plain words = full-text search; anything with operators (`=`, `~`, `IN`, `AND`, `SORT BY`) is passed through as a TED expert query |
| `countries` | `[]` | Buyer countries, ISO alpha-2 (`DE`) or alpha-3 (`DEU`) |
| `cpv` | `[]` | CPV codes: 8 digits exact, 2–7 digits = prefix (`72` = all IT services) |
| `noticeTypes` | `["contract-notice"]` | Any of `contract-notice`, `contract-award`, `prior-information` |
| `publishedFrom` | 7 days ago | `YYYY-MM-DD`, publication date on TED |
| `openOnly` | `true` | Drop notices whose deadline has passed; notices without a deadline (awards, planning) are kept |
| `mode` | `search` | `monitor` = only changes vs. the previous run's `STATE` |
| `maxItems` | 100 | Max 2000 |
| `maskContacts` | `true` | Redact e-mails/phones/IDs in free text and drop the raw payload (contact persons are never requested) |
| `maskWinnerIndividuals` | `true` | Award notices: keep a winner name only if it looks like an organisation (legal-form marker such as GmbH, Ltd, S.A., Sp. z o.o., s.r.o., or an acronym); otherwise `name: null`, `winnerType: "masked-individual"`. Off = names kept, `winnerType: "individual"` |
| `proxy` | none | The TED API needs no proxy |
| `maxPages` | 40 | Advanced: cap on API pages (250 notices each, 1 request/second) |
| `stateStore` | – | Advanced: named key-value store for monitor `STATE` (persists across runs) |

#### Expert query examples (`query`)

TED's expert search language, tested at https://ted.europa.eu/en/search/expert-search, is passed straight to the API; the actor appends the notice-type, country, CPV and date filters and `SORT BY publication-date DESC` unless you supply your own `SORT BY`.

| Goal | `query` |
|---|---|
| IT tenders in Germany or Austria | `classification-cpv IN (72*) AND buyer-country IN (DEU AUT)` |
| Title contains "solar" | `notice-title~("solar")` |
| Restricted procedures only | `procedure-type=restricted` |
| Deadline still at least a week away | `deadline-receipt-tender-date-lot>=20261001` |
| Works above EUR 5 million (estimated) | `contract-nature=works AND estimated-value-proc>=5000000 AND estimated-value-cur-proc=EUR` |

### Monitor mode

`mode=monitor` scrolls through every notice matching the query (TED "iteration" mode, a consistent snapshot), compares each with the `STATE` saved by the previous run (fingerprint of title, status, deadline, documents) and pushes only records whose `changeType` is `new`, `updated`, `deadline-changed`, `closed` or `awarded`. `openOnly` narrows new/updated records to ones whose deadline has not passed; closures are always reported. Schedule it daily; the key-value store key `RUN_SUMMARY` holds the counts. On the Apify platform the run's default key-value store is per run, so set `stateStore` (e.g. `tenders-eu-ted-state`) or run from a saved task to keep state between runs.

### Use from code, Make/Zapier/n8n, or AI agents (MCP)

```bash
curl -X POST "https://api.apify.com/v2/acts/signalrows~tenders-eu-ted/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"countries": ["DE", "AT"], "cpv": ["72"], "noticeTypes": ["contract-notice"], "maxItems": 50}'
```

This actor is also exposed as a tool on the Apify MCP server, so AI agents can call it directly (e.g. "list open EU tenders for cloud services in the Netherlands published this week").

### Limits and fair use

- Source: the official **TED Search API v3** (`POST https://api.ted.europa.eu/v3/notices/search`), 250 notices per request, **1 request/second, one request at a time**, identifying `User-Agent` and `From:` headers, `Retry-After` honoured. No website pages are fetched.
- Search mode uses TED page-number pagination (server limit 15,000 notices per query); monitor mode uses iteration mode (no limit) up to `maxPages`.
- `deadlineAt` is the **latest** lot deadline (a multi-lot notice stays `open` while any lot is open); `lotDeadlines` lists all of them. Always check the notice before submitting.
- Titles are shown in the original language (`title`) and in TED's machine-translated English (`titleEn`); descriptions are excerpts. Attached procurement documents live on the buyer's platform (`buyerProfileUrl`) and are not downloaded.
- Values are as published (`estimatedValue`, `awardValue`), not converted; `estimatedValueILS` is always `null`.
- No personal data: contact-person fields are never requested; free text is redacted by default. In award notices, winner names are kept only when they look like organisations (`winnerType: "organisation"`); names that may belong to a natural person (sole traders) are replaced by `null` with `winnerType: "masked-individual"` unless you set `maskWinnerIndividuals: false` for a declared, lawful purpose. The heuristic errs on the side of masking, so some organisations without a legal-form marker may be masked too.
- This is an unofficial extract provided "as is"; the Publications Office and the studio accept no liability for consequences of reuse. The TED and SIMAP logos are not used and may not be used without the Publications Office's consent.
- Source structure changes are monitored daily and fixed automatically; if a run fails, please open an issue.

### Data source & licence

Data: **Tenders Electronic Daily (TED), Publications Office of the European Union — https://ted.europa.eu**. Procurement notices published in the Supplement to the Official Journal of the EU "can be freely reused, for commercial or non-commercial purposes" under the Commission's reuse policy (Commission Decision 2011/833/EU), provided the source is acknowledged and the original meaning is not distorted. Every record carries this attribution in its `attribution` field; please keep it when you republish the data:

> Source: Tenders Electronic Daily (TED), Publications Office of the European Union (https://ted.europa.eu). Reused under Commission Decision 2011/833/EU.

### Data removal / contact

If you represent the source or a buyer and want content excluded, contact signalrows.data@gmail.com. Requests are handled within 24 hours.

### Development

- Shared schema/normalizer/masking/diff code lives in `ventures-src/apify/shared/tenders` and is vendored into `src/shared/tenders`; run `scripts\sync-shared.cmd` (or `npm run sync-shared`) after changing the shared module — never edit the vendored copy.
- `npm test` runs offline tests on redacted fixtures (`test/fixtures`, captured from the live API) including a local HTTP emulation of the search endpoint. `LIVE=1 npm run test:live` performs a 1-request smoke test against the real API.
- `apify run` (input in `storage/key_value_stores/default/INPUT.json`) writes records to `storage/datasets/default` and `RUN_SUMMARY` to the default key-value store.

# Actor input Schema

## `query` (type: `string`):

Optional. Plain words search the full text of notices (e.g. "solar panels"). Text containing operators (=, ~, IN, AND, OR, SORT BY) is passed through as a TED expert query, e.g. classification-cpv IN (72\*) AND buyer-country=DEU. Build and test expert queries at https://ted.europa.eu/en/search/expert-search.

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

Optional. ISO 3166-1 codes of the buyer's country, alpha-2 (DE, FR, PL) or alpha-3 (DEU, FRA, POL). Empty = all countries.

## `cpv` (type: `array`):

Optional. Common Procurement Vocabulary codes. 8 digits = exact code (72000000); 2–7 digits = prefix (72 = all IT services, 4521 = building construction). A trailing \* is accepted. Empty = all sectors.

## `noticeTypes` (type: `array`):

Which TED notice families to return: contract notices (calls for competition, default), contract award notices (results, with winners and awarded values) and prior information / planning notices.

## `publishedFrom` (type: `string`):

Optional ISO date (YYYY-MM-DD). Only notices published on TED on or after this date. Default: 7 days before the run.

## `openOnly` (type: `boolean`):

Drop notices whose (latest lot) submission deadline has already passed. Notices without a deadline (award and planning notices) are kept.

## `mode` (type: `string`):

search: return matching notices. monitor: page through all matches, compare with the previous run's STATE (key-value store) and return only new/changed/closed/awarded notices with a changeType.

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

Stop after this many records. You are charged per record extracted.

## `maskContacts` (type: `boolean`):

Default on: e-mails, phone numbers and ID-like numbers are redacted from titles/descriptions and the raw API payload is dropped. Contact-person fields are never requested from TED in either setting. Turn off only with a declared, lawful purpose (keeps the raw payload).

## `maskWinnerIndividuals` (type: `boolean`):

Default on: in contract award notices a winner name is kept only when it looks like an organisation (legal-form marker such as GmbH, Ltd, S.A., Sp. z o.o., or an acronym); otherwise the name is replaced by null and winnerType is set to masked-individual. Turn off only with a declared, lawful purpose.

## `proxy` (type: `object`):

Optional. The TED API is reachable without a proxy; use Apify Proxy only if your runs are blocked.

## `maxPages` (type: `integer`):

Safety cap on TED API pages (250 notices each, 1 request/second) per run. Monitor mode pages through all matches up to this cap.

## `stateStore` (type: `string`):

Optional named key-value store for monitor STATE so it persists across runs (the run's default store is per run on the platform). Example: tenders-eu-ted-state

## Actor input object example

```json
{
  "query": "",
  "countries": [],
  "cpv": [],
  "noticeTypes": [
    "contract-notice"
  ],
  "openOnly": true,
  "mode": "search",
  "maxItems": 100,
  "maskContacts": true,
  "maskWinnerIndividuals": true,
  "proxy": {
    "useApifyProxy": false
  },
  "maxPages": 40
}
```

# Actor output Schema

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

No description

# 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 = {
    "countries": [],
    "cpv": [],
    "noticeTypes": [
        "contract-notice"
    ],
    "proxy": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalrows/tenders-eu-ted").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 = {
    "countries": [],
    "cpv": [],
    "noticeTypes": ["contract-notice"],
    "proxy": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("signalrows/tenders-eu-ted").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 '{
  "countries": [],
  "cpv": [],
  "noticeTypes": [
    "contract-notice"
  ],
  "proxy": {
    "useApifyProxy": false
  }
}' |
apify call signalrows/tenders-eu-ted --silent --output-dataset

```

## MCP server setup

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

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/IHqapj68FDV32G6x5/builds/IhDC1R03ujGibgqmI/openapi.json
