# EU Public Tenders (TED) Feed & Contract Expiry Radar (`larches/eu-ted-tenders-feed`) Actor

Clean, deduplicated feed of EU/EEA public tenders, contract awards and soon-to-expire contracts from the official TED Search API. Filter by country, CPV (IT/cyber presets), keyword, value. Personal data stripped by default.

- **URL**: https://apify.com/larches/eu-ted-tenders-feed.md
- **Developed by:** [Larches Technologies Limited](https://apify.com/larches) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$4.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 Public Tenders (TED) Feed & Contract Expiry Radar

Get one clean, deduplicated feed of **public procurement notices from all 27 EU countries plus EEA, Switzerland and candidate countries**. The data comes from **TED (Tenders Electronic Daily)**, the official online version of the *Supplement to the Official Journal of the EU*. The feed covers about 800 new notices every working day.

The Actor uses the official **TED Search API v3** (`api.ted.europa.eu`). It does no HTML scraping and needs no login or API key.

### What you can do with it

- **Find open tenders.** Get calls for competition that are still open for bids, filtered by buyer country, CPV code, keyword and value. You can also include prior-information (pipeline) notices.
- **Track contract awards.** See who won what, from which buyer, for how much and when. Use it for competitor and partner intelligence, or to build lists of companies that win public contracts.
- **Watch for expiring contracts (renewal radar).** Find awarded contracts whose **contract end date falls in the next N months**. This lets you meet the buyer before the re-tender is published. The date filter runs on TED itself, so the results are complete, not sampled.
- **Use sector presets:**
  - **IT**: CPV 72, 48, 302 and 324.
  - **Cyber security**: a multilingual title keyword set, including cyber\*, ciber\*, kyber\*, kiber\*, pentest, ransomware, zero trust, SOC, NIS2, ISO 27001, firewall and threat intelligence. Insurance lots that only cover "cyber risks" are excluded.
  - **Construction, facilities and medical.**
- **Get only new notices.** Schedule the Actor daily. With `onlyNew`, each procedure is delivered (and paid for) once.
- **Read titles in English.** TED's English title (or the language you choose) comes alongside the original-language title and description.

### Example inputs

**IT tenders from Germany, France and Ireland published in the last 3 days:**

```json
{ "mode": "opportunities", "countries": ["DE", "FR", "IE"], "cpvPreset": "it", "lookbackDays": 3 }
```

**Cyber security tenders across Europe, new since the last run (schedule daily):**

```json
{ "mode": "opportunities", "cpvPreset": "cyber", "lookbackDays": 2, "onlyNew": true }
```

**IT contracts in DE, NL or FR that end within the next 9 months:**

```json
{ "mode": "expiringContracts", "cpvPreset": "it", "countries": ["DE", "NL", "FR"], "expiringWithinMonths": 9 }
```

**Awards over €1m in Spain, with stated values only:**

```json
{ "mode": "awards", "countries": ["ES"], "minValue": 1000000, "requireValue": true, "lookbackDays": 14 }
```

**Your own keywords in several languages:**

```json
{ "mode": "opportunities", "keywords": ["cloud", "nuage", "nube", "\"microsoft 365\""], "keywordScope": "titleDescription" }
```

**Advanced: add any TED expert-query clause** (for example, only open procedures in Île-de-France):

```json
{ "mode": "opportunities", "customQuery": "procedure-type IN (open) AND place-of-performance IN (FR1*)" }
```

### Input (main fields)

| Field | Default | Notes |
|---|---|---|
| `mode` | `opportunities` | `opportunities`, `awards`, `expiringContracts` or `all` |
| `countries` | all | ISO-2 or ISO-3 buyer country codes |
| `lookbackDays` | 3 | Days of publication history. For `expiringContracts`, the default is 730. eForms end dates exist from late 2023 onwards. |
| `cpvPreset` / `cpvPrefixes` | – | Matches main or additional CPV codes of any lot |
| `keywords` / `excludeKeywords` / `keywordScope` | – | Searched in every language. `cyber*` is a prefix match. Scope can be `title`, `titleDescription` or `fullText`. |
| `minValue`, `maxValue`, `requireValue` | – | In the notice currency (usually EUR) |
| `openOnly` | true | Drops tenders whose deadline has passed |
| `includePriorInformation` | false | Adds planning and pipeline notices |
| `expiringWithinMonths` | 12 | For `expiringContracts` |
| `onlyNew` | false | State is kept in the named key-value store `eu-ted-feed-state`, separately for each input |
| `maxItems` / `maxPages` | 500 / 30 | Caps cost and run time. Each page holds up to 100 notices. |
| `customQuery` | – | ANDed to the query in TED expert syntax |

### Output

Each notice is one row, and the **Overview** table view comes first. The fields are:

- **Notice:** `publicationNumber`, `noticeUrl`, `pdfUrl`, `procedureId`, `noticeType`, `stage` (`planning` / `tender` / `award`) and `feed`.
- **Text:** `title` (in your chosen language), `titleOriginal`, `originalLanguage` and `description`.
- **Buyer:** `buyerName`, `buyerCountry`, `buyerCity`, `buyerPostalCode`, `buyerWebsite` and `buyerLegalType`.
- **Classification:** `contractNature`, `procedureType`, `mainCpv`, `cpvCodes` and `placeOfPerformance` (NUTS codes).
- **Value and dates:** `estimatedValue`, `currency`, `submissionDeadline`, `contractStart`, `contractEnd`, `durationMonths`, `frameworkAgreement`, `suitableForSme` and `lotCount`.
- **Award:** `awardDate`, `winners[]` (name, country, size, national ID), `awardedValueTotal` and `awardedCurrency`.
- **Licence:** the `licence` statement.

See `sample-output.json`. A `RUN_SUMMARY` record in the default key-value store holds the queries used and the counts: fetched, kept, duplicates, already delivered and filtered.

Rows are deduplicated by procedure and stage. TED often republishes a procedure as corrigenda or as one award notice per lot, and only one row per stage is kept. When TED provides no value, the value fields are `null`. That is common: buyers may withhold the estimate.

### Privacy by default (GDPR)

TED notices are public, but they can still contain personal data. **By default, this Actor removes it:**

- Contact people, emails and phone numbers are not collected. Any email address or phone number found in titles and descriptions is replaced with `[email removed]` or `[phone removed]`.
- Award winners that TED flags as **natural persons** (sole traders) are withheld unless their name carries a company legal form (GmbH, AB, Ltd, Sp. z o.o. and so on). `winnersWithheld` counts how many were withheld.

Turn on `includeContacts` only if you have a lawful basis for processing that data. You are the controller of any personal data you process.

### Pricing

**Pay per result: $0.004 per notice row** (that is, $4 per 1,000 notices) plus normal Apify platform usage. There is no start fee. Rows you have already received are skipped with `onlyNew` and are not charged. Use `maxItems` to cap the cost of any run.

### Politeness and reliability

- About 1 request per second, well under TED's fair-use limit of 700 requests per minute. A descriptive User-Agent is sent.
- Retries with back-off on 429 and 5xx errors.
- Uses TED's iteration (scroll) pagination with a hard page cap.
- When TED rejects a query (for example, a bad `customQuery`), the run fails with TED's own error message, so a broken schedule is not silently "successful".

### Licence and attribution

The notice data is © European Union, from TED (https://ted.europa.eu). The TED legal notice says that procurement notices "can be freely reused, for commercial or non-commercial purposes". Reuse is governed by **Commission Decision 2011/833/EU** on the reuse of Commission documents, and its conditions apply:

- acknowledge the source;
- do not distort the original meaning;
- the Commission is not liable for any consequence of reuse.

Every row carries a `licence` statement. Keep it when you republish. Only the notices published in the Official Journal (OJ S) are authentic, so **always read the official notice before you bid.** This Actor is not affiliated with or endorsed by the European Union or the Publications Office.

### Run locally

```bash
cd eu-ted-tenders-feed
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
apify run -i '{"mode":"opportunities","cpvPreset":"cyber","lookbackDays":7,"maxItems":20}'   # no login or API key needed
```

The results are written to `storage/datasets/default/`.

# Actor input Schema

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

opportunities = open contract notices (calls for tender); awards = contract award notices (who won, for how much); expiringContracts = awarded contracts whose end date falls in the next N months (re-tender leads); all = all three.

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

ISO country codes of the buying organisation, 2- or 3-letter (e.g. DE, FR, IE or DEU, FRA, IRL). Leave empty for all of Europe.

## `lookbackDays` (type: `integer`):

Notices published in the last N days. For expiringContracts use a long window (e.g. 365-1095), since contracts are awarded years before they end. eForms data (with contract end dates) starts around late 2023.

## `cpvPreset` (type: `string`):

Adds a ready-made CPV prefix list (or, for cyber, a multilingual keyword set: cyber\*, ciber\*, SIEM, pentest, NIS2, ISO 27001...) to your own filters.

## `cpvPrefixes` (type: `array`):

e.g. 72 = IT services, 48 = software, 7222 = IT strategy, 45 = construction. Matches main or additional CPV codes of any lot.

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

Any of these words. Use a trailing \* for prefix match (cyber\*). Phrases are matched as phrases. TED indexes every notice language, so add local-language words too (e.g. cloud, nuage, nube).

## `keywordScope` (type: `string`):

Full text includes legal boilerplate (e.g. data-protection clauses), so it returns more false positives. Sector-preset keywords always match titles only.

## `excludeKeywords` (type: `array`):

Drop notices whose title or description contains any of these words (whole word; trailing \* for prefix).

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

Drop notices with a known value below this (in the notice's own currency, usually EUR). Notices with no value are kept unless 'Require a stated value' is on.

## `maxValue` (type: `integer`):

Drop notices with a known value above this (notice currency).

## `requireValue` (type: `boolean`):

Drop notices that state no estimated or awarded value.

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

Opportunities: drop notices whose latest submission deadline has passed.

## `includePriorInformation` (type: `boolean`):

Opportunities: also return planning / prior-information-only notices (early signals before a tender opens).

## `expiringWithinMonths` (type: `integer`):

expiringContracts: keep awards whose contract end date is between today and N months from now.

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

Language for titles and links where TED provides a translation. The original-language title is always included.

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

Remembers delivered procedures (per input) so scheduled runs only output new rows. You are only charged for rows that are output.

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

Stop after this many output rows (shared fairly across feeds in 'all' mode). Caps your cost.

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

Each page = up to 100 notices. Hard cap to control run time.

## `customQuery` (type: `string`):

Optional clause ANDed to the query, in TED expert-search syntax, e.g. procedure-type IN (open) or place-of-performance IN (FR1\*).

## `includeContacts` (type: `boolean`):

Off by default: e-mails and phone numbers are removed from free text, and award winners who are natural persons (sole traders) are withheld. Only enable if you have a lawful basis and will comply with the GDPR yourself.

## `includeRaw` (type: `boolean`):

Attach the raw TED API fields for each notice (free text is not scrubbed).

## Actor input object example

```json
{
  "mode": "opportunities",
  "countries": [],
  "lookbackDays": 3,
  "cpvPreset": "none",
  "cpvPrefixes": [],
  "keywords": [],
  "keywordScope": "titleDescription",
  "excludeKeywords": [],
  "requireValue": false,
  "openOnly": true,
  "includePriorInformation": false,
  "expiringWithinMonths": 12,
  "language": "en",
  "onlyNew": false,
  "maxItems": 500,
  "maxPages": 30,
  "includeContacts": false,
  "includeRaw": false
}
```

# Actor output Schema

## `notices` (type: `string`):

All notices returned by this run, as a dataset table.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("larches/eu-ted-tenders-feed").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("larches/eu-ted-tenders-feed").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 '{}' |
apify call larches/eu-ted-tenders-feed --silent --output-dataset

```

## MCP server setup

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

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/OBARxEsrwANMEjSSk/builds/dy7paULi6smzgbZCs/openapi.json
