# EU TED Tender Monitor (`embellished_dewberry/eu-ted-tender-monitor`) Actor

Search and monitor EU public procurement notices from the official TED Search API v3 by keyword, CPV code, country, date and estimated value. Normalised output, optional only-new mode for scheduled alerts. No personal contact data.

- **URL**: https://apify.com/embellished_dewberry/eu-ted-tender-monitor.md
- **Developed by:** [Drucilla Seeringtl](https://apify.com/embellished_dewberry) (community)
- **Categories:** Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 tender notices

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 TED Tender Monitor

Search and monitor **EU public procurement notices** published on **TED (Tenders Electronic Daily)**, the official EU journal for public tenders. The Actor uses only the **official, anonymous TED Search API v3**. It does no web scraping, needs no login, and works around no limits.

Use it to:

- get a daily or weekly list of new calls for tender that match your keywords, CPV codes and countries
- feed tenders into a CRM, spreadsheet, Slack or e-mail alert (through Apify integrations)
- build a lead list of public buyers for a sector

### What it does

- **Filters:** full-text keywords, CPV codes (with or without sub-codes), buyer country, a publication-date range or the last N days, TED form type, minimum estimated value, and "still open for submission".
- **Normalised output:** one flat record per notice. Each record has the notice number, title, buyer, country, CPV codes, estimated value and currency, tender deadline, and links to the notice page, PDF and XML.
- **Only-new mode:** for scheduled runs. Notices that an earlier run with the same filters already delivered are skipped. The state is kept in a named key-value store.
- **Privacy by default:** contact persons, e-mail addresses and phone numbers are **never requested from TED and never output**.
- **Max-cost aware:** if you set a maximum cost per run, the Actor stops cleanly when it reaches that limit.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `keywords` | array of strings | – | Full-text keywords or phrases, combined with OR. TED searches the **full notice text in any language**, so a keyword can match a notice whose title does not contain it. |
| `cpvCodes` | array of strings | – | CPV codes such as `72000000` or `48000000-8`, combined with OR. |
| `cpvIncludeSubcodes` | boolean | `true` | When on, `72000000` also matches `72212190` and other sub-codes. |
| `countries` | array of strings | – | Buyer country as ISO alpha-2 (`DE`) or alpha-3 (`DEU`), combined with OR. |
| `lastNDays` | integer | `7` | Publication window that ends today (UTC). Today counts as day 1. Ignored when `publishedFrom` is set. |
| `publishedFrom` / `publishedTo` | date | – | Explicit publication-date range. Both ends are inclusive. |
| `formTypes` | array | `["competition"]` | TED form types. `competition` means calls for tender. Leave empty to get all types. |
| `minEstimatedValue` | integer | – | Keep notices whose estimated value is at least this amount in `valueCurrency`. |
| `valueCurrency` | string | `EUR` | ISO 4217 currency used by the value filter. |
| `includeWithoutValue` | boolean | `false` | When a minimum value is set, also keep notices with no published value or with a different currency. |
| `onlyOpenForSubmission` | boolean | `false` | Keep only notices whose earliest deadline is still in the future. |
| `onlyNew` | boolean | `false` | Skip notices already delivered by an earlier run with the same filters. |
| `stateKey` | string | derived from the filters | Custom name for the only-new state. |
| `maxItems` | integer | `100` | Maximum number of notices output per run (1–10,000). |
| `language` | string | `eng` | Preferred 3-letter language for titles and links. When TED has no title in that language, the original language is used. |
| `includeBuyerWebsite` | boolean | `false` | Adds the buyer organisation's website URL. This is never a personal contact. |

Example:

```json
{
  "keywords": ["software"],
  "cpvCodes": ["72000000"],
  "countries": ["DE", "AT"],
  "lastNDays": 7,
  "minEstimatedValue": 100000,
  "onlyNew": true,
  "maxItems": 200
}
```

### Output

Each dataset item looks like this. The example is a real item from a test run on 2026-11-03; see `sample_output.json`.

```json
{
  "publicationNumber": "763816-2026",
  "title": "USŁUGA UTRZYMANIA I ROZWOJU GDAŃSKIEJ PLATFORMY EDUKACYJNEJ",
  "titleLanguage": "pol",
  "tedTitle": "Poland – System and support services – USŁUGA UTRZYMANIA I ROZWOJU GDAŃSKIEJ PLATFORMY EDUKACYJNEJ",
  "buyerName": "Gdańskie Centrum Informatyczne",
  "buyerNames": ["Gdańskie Centrum Informatyczne"],
  "buyerCountry": "POL",
  "buyerCountries": ["POL"],
  "buyerCity": "Gdańsk",
  "cpvMain": "72250000",
  "cpvCodes": ["72250000", "72230000", "72611000"],
  "placeOfPerformance": ["PL634", "POL"],
  "estimatedValue": null,
  "estimatedValueCurrency": null,
  "estimatedValueSource": null,
  "tenderDeadline": "2026-11-17T10:00:00+01:00",
  "publicationDate": "2026-11-03",
  "noticeType": "cn-standard",
  "formType": "competition",
  "procedureType": "open",
  "contractNature": "services",
  "noticeUrl": "https://ted.europa.eu/en/notice/-/detail/763816-2026",
  "pdfUrl": "https://ted.europa.eu/en/notice/763816-2026/pdf",
  "xmlUrl": "https://ted.europa.eu/en/notice/763816-2026/xml",
  "source": "TED - Tenders Electronic Daily (ted.europa.eu), (c) European Union",
  "scrapedAt": "2026-11-03T12:09:44+00:00"
}
```

- `estimatedValue` uses the procedure-level value. If that is missing, it uses the framework value, and otherwise the **sum of the lot values** when all lots share one currency. `estimatedValueSource` tells you which one was used. A value that TED publishes as `0`, meaning not disclosed, is output as `null`.
- `tenderDeadline` is the earliest deadline across lots. When TED gives a date with no time, only the date is output.

The Actor also writes a `RUN_SUMMARY` record to the run's default key-value store. It holds the TED query used, the total matches on TED, the number of notices scanned, delivered and skipped, the number of TED requests, and the duration.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start (`apify-actor-start`, Apify default, per GB of memory) | $0.00005 |
| `tender-notice`: one normalised notice written to the dataset | $0.002 ($2.00 per 1,000) |

You pay only for notices that are delivered. Notices skipped in only-new mode, or removed by the value or open filters, are free. You can cap the cost of any run with Apify's "maximum cost per run" setting.

### Scheduling alerts

1. Save a task with your filters and `onlyNew: true`.
2. Schedule it, for example daily.
3. Connect an integration (e-mail, Slack, Google Sheets, webhook) to the dataset.

Each run then delivers only notices that were not delivered before. The state is kept per filter combination in the named key-value store `eu-ted-tender-monitor-state`, under `STATE-<hash>` or under your own `stateKey`. It remembers the most recent 50,000 notice numbers. Delete that record to start again.

### Data source, terms and attribution

- **Source:** TED Search API v3 (`POST https://api.ted.europa.eu/v3/notices/search`). It is anonymous and needs no API key. Docs: <https://docs.ted.europa.eu/api/latest/index.html>. OpenAPI spec: <https://api.ted.europa.eu/api-v3.yaml>.
- **Reuse:** TED data may be reused free of charge, including commercially, under the Commission's reuse policy (Decision 2011/833/EU, <https://eur-lex.europa.eu/eli/dec/2011/833/oj/eng>). See also the TED legal notice <https://ted.europa.eu/en/legal-notice> and the data-reuse help page <https://ted.europa.eu/en/help/data-reuse>. Acknowledge the source and do not distort the data. Every item carries a `source` attribution field.
- **Fair use:** TED publishes fair-use limits for re-users at <https://ted.europa.eu/en/simap/developers-corner-for-reusers>. The Actor makes at most **2 requests per second** to TED, one run at a time. It backs off and retries on HTTP 429 and 5xx responses, and fetches up to 100 notices per request.
- **Disclaimer:** this Actor is not affiliated with or endorsed by the Publications Office of the European Union. The official notice on TED is the legally binding version, so always check it before you bid.

### Personal data

Notices can contain names, e-mail addresses and phone numbers of contact persons. These are personal data under the GDPR. The Actor never requests those fields from TED, so they cannot appear in the output. Buyer organisation names and cities are output because they identify public bodies. You remain responsible for how you process the data you receive.

### Limitations

- **No currency conversion.** The value filter compares only values in `valueCurrency`. Other currencies are excluded unless `includeWithoutValue` is on.
- The value and open-for-submission filters run **after** the TED search. The Actor may therefore scan more notices than it delivers, up to 50,000 per run. Those notices are not charged.
- Many notices publish no estimated value, so a minimum-value filter can remove a large share of the results. In the test run, 8 of 20 notices had a value.
- `buyerCountry` is the lead buyer. Joint procurements list every buyer in `buyerCountries`, and the country filter matches any of them.
- Full-text keyword matching is done by TED and covers the whole notice text.
- Notices are returned newest first, sorted by publication number.

### Run locally

```bash
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
apify run --input='{"keywords":["software"],"lastNDays":7,"maxItems":20}'
## or simulate pay-per-event pricing (optional max charge in USD):
./run_local.sh 0.05
## offline unit tests
.venv/bin/python -m unittest discover -s tests
```

# Actor input Schema

## `keywords` (type: `array`):

Full-text keywords or phrases (TED full-text search, any notice language). Multiple entries are combined with OR. Leave empty for no keyword filter.

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

Common Procurement Vocabulary codes, e.g. 72000000 or 48000000-8. Multiple codes are combined with OR.

## `cpvIncludeSubcodes` (type: `boolean`):

If enabled, 72000000 also matches 72212190 etc. (prefix match on the code without trailing zeros).

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

Country of the contracting authority, ISO 3166-1 alpha-2 (DE) or alpha-3 (DEU). Combined with OR.

## `lastNDays` (type: `integer`):

Publication-date window ending today (UTC), counting today as day 1. Ignored when 'Published from' is set.

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

Optional start of the publication-date range (inclusive). Overrides 'last N days'.

## `publishedTo` (type: `string`):

Optional end of the publication-date range (inclusive).

## `formTypes` (type: `array`):

TED form types to include. 'competition' = calls for tender (contract notices). Leave empty for all types.

## `minEstimatedValue` (type: `integer`):

Only notices whose estimated value is at least this amount (in the currency below). Notices without a published value, or with a different currency, are excluded unless 'Include notices without comparable value' is enabled. No currency conversion is done.

## `valueCurrency` (type: `string`):

ISO 4217 currency code, e.g. EUR, PLN, SEK.

## `includeWithoutValue` (type: `boolean`):

When a minimum value is set, also include notices that publish no estimated value or use another currency.

## `onlyOpenForSubmission` (type: `boolean`):

Keep only notices whose earliest tender/request deadline is still in the future at run time.

## `onlyNew` (type: `boolean`):

Skip notices already delivered by a previous run with the same filters (state is kept in the named key-value store 'eu-ted-tender-monitor-state'). Skipped notices are not charged.

## `stateKey` (type: `string`):

Custom name for the only-new state. By default it is derived from your filters, so different searches keep separate states.

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

Maximum number of notices to output in this run.

## `language` (type: `string`):

3-letter language code (eng, deu, fra, ...) used for titles and notice links when TED provides that language; otherwise the original language is used.

## `includeBuyerWebsite` (type: `boolean`):

Add the contracting authority's website URL. Contact persons, e-mail addresses and phone numbers are never output.

## Actor input object example

```json
{
  "keywords": [
    "software"
  ],
  "cpvIncludeSubcodes": true,
  "lastNDays": 7,
  "formTypes": [
    "competition"
  ],
  "valueCurrency": "EUR",
  "includeWithoutValue": false,
  "onlyOpenForSubmission": false,
  "onlyNew": false,
  "maxItems": 100,
  "language": "eng",
  "includeBuyerWebsite": false
}
```

# Actor output Schema

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

One item per normalised TED notice (publication number, title, buyer, country, CPV codes, value, deadline, links). No personal contact data.

## `runSummary` (type: `string`):

JSON record RUN_SUMMARY: TED query used, total matches on TED, items scanned, delivered and skipped (already seen or filtered), TED requests made, duration and whether the max-cost limit was reached.

# 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 = {
    "keywords": [
        "software"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("embellished_dewberry/eu-ted-tender-monitor").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 = { "keywords": ["software"] }

# Run the Actor and wait for it to finish
run = client.actor("embellished_dewberry/eu-ted-tender-monitor").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 '{
  "keywords": [
    "software"
  ]
}' |
apify call embellished_dewberry/eu-ted-tender-monitor --silent --output-dataset

```

## MCP server setup

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

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/Sd3hOd7ZEbO0dEuce/builds/WYIfqEwVWAuwLPao7/openapi.json
