# EU Public Tenders (TED): One Row per Tender, Not per Notice (`gubidonius/eu-tenders`) Actor

Open procurement notices from TED, the EU's official journal. Groups every notice about the same procurement into one row and takes the deadline from the newest one, so a corrected tender is not three rows with three closing dates. No key and no login.

- **URL**: https://apify.com/gubidonius/eu-tenders.md
- **Developed by:** [Gregory Bolshakov](https://apify.com/gubidonius) (community)
- **Categories:** Business, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EU Public Tenders

Open procurement notices from TED, the EU's official journal. One row per tender, with the
deadline that is current.

### TED publishes notices, not tenders

Every correction to a procurement is a new notice with a new publication number, because
the publication is the legal act. Measured on 2026-08-30: 34% of the contract notices
published in the two days before carried a correction code, the families run from two to
nine notices, and 85% of those families hold more than one deadline.

So a feed of notices gives you the same tender several times, with several closing dates,
and nothing on the row telling you which one still applies. Here is one real family:

```
536789-2026   published 3 Aug    deadline 31 Aug
573864-2026   published 19 Aug   deadline 31 Aug
595004-2026   published 28 Aug   deadline 7 Sep
```

This Actor groups them by TED's own procedure identifier and returns one row, with the
7 September deadline, `deadlineChanged` true, `previousDeadline` set to 31 August, and the
two earlier publication numbers listed in case you already have them on file.

On a run of 200 open German tenders, 55 had more than one notice and 40 carried a deadline
that a later notice had already replaced. That is one row in five.

### Where each field comes from

The newest notice names the row. Every other field is answered by the newest notice that
carries one. That matters because an award notice has no deadline at all, so taking the
newest notice wholesale would empty the closing date on exactly the tenders that have
finished. `deadlineFromPublicationNumber` says which notice the date came from, and `stage`
says whether the procurement is planning, tendering or already awarded.

### Reading the numbers

`estimatedValue` is the procedure value as published. `lotValues` are the per lot values.
Neither is derived from the other, because they disagree in both directions: on one Dutch
notice the procedure value was 4,600,000 against a single lot of 5,400,000, and on a
Croatian one it was the exact sum of three lots.

TED does not convert currencies, so every value carries its own. A minimum value filter
compares the number as published, which means a Polish notice is compared in PLN.

### Language

Titles exist in all 24 official languages and buyer names do not. Of 250 open notices
sampled, every one had an English title and none had an English buyer name. Pick the
language you want and the title follows it. The buyer name comes back in the national
language, and `titleLanguage` tells you what you actually got.

### Watching for new tenders

Turn on **Only new since the last run** and each run returns only what was not there
before. The first run records a baseline and returns everything with `isNew` empty, because
nothing is new the first time you look.

Nothing is ever reported as closed. A tender leaving your result set has usually just
passed its deadline, and a false disappearance in your history cannot be told apart later
from a real one.

### What is in TED

Every procurement above the EU thresholds, from all 27 member states. It also carries
notices from countries outside the EU that publish there, so a run can return Norway or
Switzerland alongside the rest. The country code on the row says which.

### Access

Free, no key, no login. The API is capped at 250 rows per request and at 15,000 rows per
paged window, while the open set alone is 48,731 notices, so this Actor uses TED's
iteration mode to read past the window. It paces its requests, because sixteen unpaced ones
earned a 429.

# Actor input Schema

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

Three letter codes for the country of the contracting authority, such as DEU, POL, FRA, ESP. Leave empty for the whole EU.

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

Common Procurement Vocabulary codes. A division such as 45000000 covers construction, 72000000 covers IT services. Matches the codes on the notice, so a division does not include its own subcodes.

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

cn-standard is a call for tenders, can-standard is a contract award, pin-only is a prior information notice. Leave empty for all of them.

## `onlyOpen` (type: `boolean`):

Keeps notices whose deadline for requests has not passed. TED matches a notice when any one of its lots is still open, so a notice can be open while the lot you want has closed. The deadline columns say which.

## `publishedWithinDays` (type: `integer`):

Use for a monitor run. 7 gives the last week of publications.

## `keyword` (type: `string`):

Full text search across the notice. Titles exist in all 24 languages, so an English word matches notices written in any of them.

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

Compares the procedure value as published. TED does not convert currencies, so a Polish notice is compared in PLN and a Swedish one in SEK. Every row carries its own currency.

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

Language for the title and the description. Titles are translated into all 24, buyer names are not, so a buyer name comes back in the national language whatever you pick.

## `expertQuery` (type: `string`):

Replaces every filter above with TED's own query language, for example buyer-country=POL AND classification-cpv=45000000. Dates use their compact form, 20260825 or today(-7), and an ISO date is rejected. The sort is always set for you.

## `maxResults` (type: `integer`):

Counted in procurements, not notices. One procurement corrected three times is one row here and three notices at TED.

## `onlyNewSinceLastRun` (type: `boolean`):

Returns only procurements that were not in the previous run of the same query, marked isNew. The first run records a baseline and returns everything with isNew empty. Nothing is ever reported as closed, because a tender leaving the result set has usually just passed its deadline.

## Actor input object example

```json
{
  "countries": [
    "DEU"
  ],
  "cpvCodes": [],
  "noticeTypes": [
    "cn-standard"
  ],
  "onlyOpen": true,
  "language": "eng",
  "maxResults": 200,
  "onlyNewSinceLastRun": false
}
```

# Actor output Schema

## `tenders` (type: `string`):

Title, buyer, deadline, value and the link to the notice.

## `summary` (type: `string`):

The query sent, how many notices matched, how many collapsed into rows, and how many deadlines a later notice had changed.

# 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": [
        "DEU"
    ],
    "cpvCodes": [],
    "noticeTypes": [
        "cn-standard"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gubidonius/eu-tenders").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": ["DEU"],
    "cpvCodes": [],
    "noticeTypes": ["cn-standard"],
}

# Run the Actor and wait for it to finish
run = client.actor("gubidonius/eu-tenders").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": [
    "DEU"
  ],
  "cpvCodes": [],
  "noticeTypes": [
    "cn-standard"
  ]
}' |
apify call gubidonius/eu-tenders --silent --output-dataset

```

## MCP server setup

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

```

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/3PVvnLZc2soVJ6wGr/builds/A5fkEbjtVwcdxJhJw/openapi.json
