# EU Tender Watch: TED Public Procurement Alerts & Deadlines (`jdepablos/eu-tender-watch`) Actor

Get alerted when a new EU public tender matches your keywords, CPV codes or countries. Watches TED (Tenders Electronic Daily) for government contracts and RFPs, flags only what is new since your last run, and gives you the buyer, estimated value, deadline and days left to bid.

- **URL**: https://apify.com/jdepablos/eu-tender-watch.md
- **Developed by:** [Juan de Pablos](https://apify.com/jdepablos) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 tender watches

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 Tender Watch: TED Public Procurement Alerts & Deadlines

**Hear about new EU public tenders the day they are published, with the number of days you have left to bid.** Tell the Actor what you sell (keywords, CPV codes, countries). Run it once a day and it reports the government contracts and RFPs that appeared on TED since the previous run, with the buyer, the estimated value, the submission deadline and a direct link to the tender documents.

TED (Tenders Electronic Daily) is the official journal where every public buyer in the EU must publish contracts above the EU thresholds: about 2,000 new calls for tender every working day, in 24 languages. Nobody can read that by hand. Most tender scrapers hand you the whole pile again on every run. This one remembers what it already showed you and only raises an alert when something is new.

### What you get per watch

```json
{
  "watch": "software / IRL",
  "totalInSource": 21,
  "checked": 21,
  "returned": 19,
  "newTenders": [
    {
      "publicationNumber": "656715-2026",
      "headline": "Ireland – Hydraulic equipment – Request for Tender for The Supply, Delivery and Commissioning of No. Three Hydraulics / Electro-Hydraulics Training Systems with Simulation and Software",
      "buyerName": "Kerry Education and Training Board",
      "buyerCountry": "IRL",
      "deadline": "2026-10-26T16:00:00+01:00",
      "daysLeft": 34,
      "estimatedValue": 265500,
      "currency": "EUR",
      "url": "https://ted.europa.eu/en/notice/-/detail/656715-2026"
    }
  ],
  "tenders": [
    {
      "publicationNumber": "656715-2026",
      "title": "Request for Tender for The Supply, Delivery and Commissioning of No. Three Hydraulics / Electro-Hydraulics Training Systems with Simulation and Software",
      "buyerName": "Kerry Education and Training Board",
      "buyerCountry": "IRL",
      "buyerCity": "Tralee",
      "mainCpv": "43328100",
      "cpvCodes": ["43328100", "48000000", "34152000"],
      "contractNature": "supplies",
      "procedureType": "open",
      "estimatedValue": 265500,
      "currency": "EUR",
      "deadline": "2026-10-26T16:00:00+01:00",
      "deadlineType": "tender",
      "daysLeft": 34,
      "nuts": ["IE053"],
      "publicationDate": "2026-09-23",
      "url": "https://ted.europa.eu/en/notice/-/detail/656715-2026",
      "pdfUrl": "https://ted.europa.eu/en/notice/656715-2026/pdf",
      "submissionUrl": "https://www.etenders.gov.ie/epps/cft/viewTenders.do?resourceId=9128296",
      "documentsUrl": "https://www.etenders.gov.ie/epps/cft/listContractDocuments.do?resourceId=9128296",
      "isNew": true
    }
  ],
  "meta": {
    "newCount": 1,
    "isBaseline": false,
    "noveltyTracking": "on",
    "noveltyFrom": "2026-09-09",
    "watchKey": "w-software-65037a97f1",
    "knownTenders": 21,
    "lookbackDays": 14,
    "sourceTruncated": false,
    "closed": 2,
    "hiddenByFilters": 0
  },
  "attribution": "Source: TED (Tenders Electronic Daily), https://ted.europa.eu, © European Union, 1998-2026. Reformatted by this Actor; the original notice on TED is the authoritative version."
}
```

`newTenders` is the alert. `tenders` is every open tender that matches, new ones first, each with `isNew`. If you only care about what changed, read `meta.newCount` and stop when it is zero.

`headline` is TED's own English line (country, what is being bought, original title), so you can tell what a Polish or Finnish tender is about without translating it. `title` stays in the language of the notice, which is also the language you will bid in.

### Why it saves you time

- **Deadline first.** Every tender comes with its deadline in the buyer's time zone and `daysLeft` in calendar days. Set `minDaysLeft` to the time you need to write a proposal and the rest disappears.
- **The right deadline.** Restricted procedures have no tender deadline yet, only a deadline to request to participate. Calls based on a prior information notice ask for expressions of interest. The Actor picks whichever applies and tells you which one in `deadlineType`, instead of showing a blank.
- **Straight to the documents.** `submissionUrl` and `documentsUrl` point to the buyer's e-procurement platform, where the RFP documents live.
- **All EU languages at once.** Keywords are searched in the full text of the notice, accents ignored. Use CPV codes to cover the languages you do not speak.
- **Values you can compare.** The value filter understands that 1,000,000 PLN is not 1,000,000 EUR.

### Who it is for

- **SMEs and consultancies** that bid for government contracts and want the relevant tenders in their inbox instead of searching TED every morning.
- **Bid managers and sales teams** that track public-sector RFPs in a few countries or CPV divisions.
- **Tender alert services and CRMs** that need a clean daily feed to route to their own customers.
- **AI agents** that answer "are there open public tenders for X in Y?" with one tool call.

### Input

| Field | What it does |
|---|---|
| `keywords` | Words or phrases searched in the full notice text, any EU language. A tender matches if it contains any of them. |
| `cpvCodes` | CPV codes, hierarchical: `72000000` (IT services) includes every code below it. Prefixes like `"72"` work. |
| `countries` | Buyer countries as ISO codes: `ESP`, `DEU`, `FRA`, `POL`... Two-letter codes are converted. |
| `noticeTypes` | Default `cn-standard` (contract notices). Also social services, design contests, subcontracting, qualification systems and prior information notices used as a call for competition. |
| `minDaysLeft` | Hide tenders that close sooner than this. Display filter only. |
| `minValue` | Hide tenders with a published value below this amount in EUR. Tenders without a published value are kept. Display filter only. |
| `maxTendersPerWatch` | Caps the `tenders` list (default 100). Never cuts `newTenders`. |
| `watches` | Up to 25 watches per run, each tracked and billed on its own, e.g. `[{"name": "IT Spain", "keywords": ["software"], "countries": ["ESP"]}, {"name": "Roads PL", "cpvCodes": ["45233000"], "countries": ["POL"], "minValue": 500000}]` |

Filters combine with AND: `keywords` + `countries` means the keyword in those countries. Values inside one filter combine with OR. When you use `watches`, the keywords, CPV codes and countries of the form are ignored and each watch defines its own search; notice types, minimum days, minimum value and the cap apply to every watch unless the watch sets its own.

### What counts as new, and what you pay for

A watch is a search: its keywords, CPV codes, countries and notice types. For each watch the Actor keeps the list of TED notices it has already checked for you, in a key-value store called `tender-watch-state` inside **your own** Apify account. Your first run creates it and only your runs can read it.

There are two events. Prices are on the Pricing tab.

| Event | When it is charged |
|---|---|
| `tender-watch` | Once per watch per run. In a watch service, a quiet day is still the service. |
| `match-found` | Once per watch per run, and only when that watch has at least one tender you had not been shown before. Never per tender: a day with 1 new tender and a day with 175 cost the same. |

The rules behind `match-found`, so your invoice is never a surprise:

- **The first run of a watch never triggers it.** There is nothing to compare against yet, so that run records the baseline (every open tender of the last 14 days) and charges `tender-watch` only. Look for `meta.isBaseline`.
- **Nothing new, nothing charged.** Running a quiet watch every day costs `tender-watch` and nothing else.
- **An old tender never counts as new.** Only tenders published from 7 days before your watch started can ever be new. The exact date is in `meta.noveltyFrom`.
- **Your display filters never create a charge.** A new tender hidden by `minDaysLeft` or `minValue` is not an alert and is not charged. It is still remembered, so it will not be announced later if you relax the filter.
- **Failed watches are never charged** and are not recorded as seen.
- If the watch state cannot be read or saved, you still get your data and `match-found` is not charged. `meta.noveltyTracking` says which mode you got.

Changing keywords, CPV codes, countries or notice types starts a **separate watch** with its own baseline, because it is a different search. Changing `minDaysLeft`, `minValue`, `maxTendersPerWatch` or the watch name does not.

To restart a watch from scratch, delete its `w-...` record (or the whole `tender-watch-state` store) in your Apify storage. The next run records a fresh baseline.

### How often to run it

TED publishes Monday to Friday, so one run per working day is enough; running twice a day only finds the same tenders twice. Create a schedule in Apify with the cron `0 9 * * 1-5`. Each run looks back 14 days, so a missed day, a bank holiday or even a weekly schedule does not lose any tender.

### Use with AI agents (MCP)

This Actor is exposed as a tool on the [Apify MCP Server](https://mcp.apify.com):

```json
{
  "mcpServers": {
    "eu-tender-watch": {
      "url": "https://mcp.apify.com?tools=jdepablos/eu-tender-watch",
      "headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
    }
  }
}
```

### Data source and license

All data comes from [TED, Tenders Electronic Daily](https://ted.europa.eu), the online version of the Supplement to the Official Journal of the European Union, through its public Search API. © European Union, 1998-2026. According to the [TED legal notice](https://ted.europa.eu/en/legal-notice), procurement notices "can be freely reused, for commercial or non-commercial purposes" under Commission Decision 2011/833/EU, which asks reusers to acknowledge the source and not to distort the original meaning, and states that the Commission is not liable for any consequence of the reuse.

This Actor selects and reformats fields (dates, values, deadlines, links); it does not change what a notice says. The notice on TED is always the authoritative version, so check it before you bid. Every record carries an `attribution` field so the credit travels with the data. This Actor is not affiliated with or endorsed by the Publications Office of the European Union.

### FAQ

**Does it cover every public tender in Europe?** It covers TED: every EU tender above the EU thresholds, plus EEA countries and others that publish there. Smaller national tenders below the thresholds are only on national portals and are not included yet.

**How fresh is it?** TED publishes each working day and the Actor queries the live Search API, so a run finds what was published that day.

**Why is `estimatedValue` often empty?** About half of the notices on TED do not publish an estimated value. The Actor reports what the buyer published and never guesses.

**Can I see tenders that close soon?** Yes, leave `minDaysLeft` at 0. Tenders that are already closed are always left out.

**Can the developer see what I watch?** No. The watch state lives in a key-value store created inside your own Apify account by your own runs, next to the datasets those runs produce. Other accounts cannot read it, and neither can we.

### Other data tools by this developer

- [Global Trademark Watch](https://apify.com/jdepablos/trademark-watch-tmview): the same watch model applied to trademark filings, with opposition deadlines
- [UK Company Data (Companies House)](https://apify.com/jdepablos/companies-house-feed): check a buyer or a competitor before you bid
- [French Company Data](https://apify.com/jdepablos/france-company-feed): official SIREN registry
- [Spanish Company Feed (BORME)](https://apify.com/jdepablos/borme-company-feed): daily registry acts as JSON

# Actor input Schema

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

Words or phrases to look for in the full text of the notice, in any EU language (accents do not matter). Each one is matched as a phrase and a tender matches if it contains ANY of them. Example: "software", "cybersecurity", "road maintenance".

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

Common Procurement Vocabulary codes. They are hierarchical: 72000000 (IT services) also matches 72212000 and every other code below it. You can type a prefix such as "72" or a full code with its check digit such as "72000000-5".

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

Country of the contracting authority, as ISO codes: ESP, DEU, FRA, ITA, POL, IRL... Two-letter codes (ES, DE) are converted for you. Empty means all countries that publish on TED.

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

Which calls for competition to watch. The default, contract notices, covers almost every open tender.

## `minDaysLeft` (type: `integer`):

Hide tenders whose deadline is closer than this, because you would not have time to prepare an offer. It only filters what you see: it never creates a new watch and never triggers a charge. Closed tenders are always hidden.

## `minValue` (type: `integer`):

Hide tenders with a published estimated value below this amount. Other currencies are converted with approximate rates for this filter only. About half of TED notices publish no value at all: those are kept, so this filter never hides a tender it cannot judge.

## `maxTendersPerWatch` (type: `integer`):

Caps the full list (tenders). New tenders are listed first and the alert list (newTenders) is never cut. It limits what is shown, not what is watched.

## `watches` (type: `array`):

Up to 25 watches per run, each billed and tracked on its own. Each entry: {"name": "IT Spain", "keywords": \["software"], "cpvCodes": \["72000000"], "countries": \["ESP"], "minDaysLeft": 10, "minValue": 100000}. When this list is set, the keywords, CPV codes and countries above are ignored; notice types, minimum days, minimum value and the cap still apply to every watch unless the watch sets its own.

## `monitorMode` (type: `boolean`):

For scheduled smoke tests: if TED is fully unavailable the run exits SUCCEEDED with a clear message and notifies monitorEmail; if every watch returns zero notices the run fails. Code faults always fail the run.

## `monitorEmail` (type: `string`):

Email to notify (via apify/send-mail) when monitor mode detects that TED is unavailable.

## Actor input object example

```json
{
  "keywords": [
    "software"
  ],
  "countries": [
    "ESP",
    "PRT",
    "IRL"
  ],
  "noticeTypes": [
    "cn-standard"
  ],
  "minDaysLeft": 0,
  "maxTendersPerWatch": 100,
  "monitorMode": false
}
```

# Actor output Schema

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

Per watch: newTenders (the alert), tenders (full list with deadline, daysLeft and isNew) and meta (newCount, isBaseline, noveltyTracking).

# 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"
    ],
    "countries": [
        "ESP",
        "PRT",
        "IRL"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jdepablos/eu-tender-watch").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"],
    "countries": [
        "ESP",
        "PRT",
        "IRL",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("jdepablos/eu-tender-watch").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"
  ],
  "countries": [
    "ESP",
    "PRT",
    "IRL"
  ]
}' |
apify call jdepablos/eu-tender-watch --silent --output-dataset

```

## MCP server setup

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

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/K8QckpDdZ3yIxeVAx/builds/fIpC9bgQnF1DTpxuN/openapi.json
