# EU and Dutch Tender Alerts: TED + TenderNed Monitor (`vizellie/eu-nl-tender-monitor`) Actor

Get new public tenders from TED and TenderNed every day. Filter by CPV code, keyword, country, buyer, notice type and minimum value. Scheduled runs can return only new notices, skip TED duplicates and remove personal data. Works with schedules, webhooks and AI agents over MCP. $0.003 per notice.

- **URL**: https://apify.com/vizellie/eu-nl-tender-monitor.md
- **Developed by:** [Vi Zellie](https://apify.com/vizellie) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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 and NL Public Tender Monitor

Find new public tenders from TED (the EU's official journal) and TenderNed (the Dutch government platform, including national tenders that never appear on TED). Results come back in one consistent list, ready to filter, schedule and send on.

Not legal advice; general information only.

### Who it is for

Bid teams, agencies and researchers who want a daily or weekly view of new calls for tender in a sector, country or buyer list.

### What you get

- One row per notice, with title, buyer, country, CPV codes, estimated value, dates, procedure and a link to the official notice.
- Keyword, CPV, country, buyer, date, value and notice-type filters.
- New notices only, so a schedule does not send the same tender twice.
- A date window that can start from the previous run.
- Dutch and EU notices together, with EU-wide Dutch notices de-duplicated when both sources are on.
- Personal data removed before anything is saved. See the policy below.

### Tutorial

1. Set **Buyer countries** to the markets you cover, for example `NL` or `NL`, `BE` and `DE`.
2. Add **Keywords** and **CPV codes** for your sector. A notice matches if it hits any keyword.
3. Leave **Notice categories** on open calls, or add awards and planning notices.
4. Set **Max results** to the number of notices you want per run.
5. For a daily alert, turn on **New items only** and **Since last run**, and set a **State key** you will reuse.
6. Run the Actor. Notices are saved while the run is still working. If the run is close to its time limit, it stops and the `OUTPUT` record says it stopped early. Notices saved up to that point stay in the dataset.
7. Create an Apify schedule, or send the dataset to Sheets, Slack or email with an integration.

### Example input

```json
{
  "sources": ["ted", "tenderned"],
  "countries": ["NL"],
  "cpvCodes": ["72000000", "48000000"],
  "keywords": ["software"],
  "noticeCategories": ["competition"],
  "sinceLastRun": true,
  "onlyNewItems": true,
  "stateKey": "nl-it-daily",
  "maxResults": 50
}
```

### Example output

```json
{
  "source": "TED",
  "noticeId": "TED:744846-2026",
  "title": "Leveren, implementeren en onderhoud handhavingsapplicatie",
  "buyerName": "gemeente Soest",
  "country": "NL",
  "cpvCodes": ["72260000", "72200000"],
  "mainCpv": "72260000",
  "estimatedValue": null,
  "currency": null,
  "publicationDate": "2026-10-27",
  "deadline": "2026-11-10T14:00:00+01:00",
  "url": "https://ted.europa.eu/en/notice/-/detail/744846-2026",
  "description": "De opdracht omvat de levering, implementatie en onderhoud van een handhavingsapplicatie."
}
```

TenderNed rows use the same fields. Estimated value is empty for TenderNed, because that source does not publish it. Many TED notices also leave the value blank.

### Pricing

You pay a small fee when the run starts, plus a fee for each notice that is saved.

| What | Price |
|---|---|
| Run start | $0.00005 |
| Notice saved | $0.003 |

Notices that are filtered out, already seen, or skipped as duplicates are not charged. The start fee is Apify's default, so a scheduled run that saves nothing stays cheap.

**Examples**

- One scan that saves 50 notices: $0.00005 + (50 × $0.003) = **$0.15005**.
- A weekday alert that saves 20 new notices: $0.00005 + (20 × $0.003) = **$0.06005** per run, about **$1.20** over 20 runs.
- A quiet day that saves nothing: **$0.00005**.

Use **Max results** and Apify's maximum cost per run to cap a single run. The pricing tab on the store is the price you are charged if it ever differs.

### Scheduling and integrations

Save a task, turn on an Apify schedule, and keep the same state key. The dataset can go to Google Sheets, Slack, email, Make, Zapier or your own system through webhooks or the Apify API. The rows are small enough to pass to an AI agent over MCP.

### No personal data (AVG/GDPR)

This Actor does not collect contact details.

- Email addresses, phone numbers and street addresses are removed from every field, including titles, buyer names, links and descriptions.
- Contact person names are removed wherever they appear.
- Buyer and supplier names that belong to an organisation, such as a municipality or a company with a legal form, are kept. That is public procurement information.
- A name that reads as a private person, including a sole trader (eenmanszaak) trading under their own name, is left out. Some company names look like personal names and are left out as well. The link to the official notice remains, so you can open the legal publication there.
- The Actor does not save raw source payloads. Its memory of past runs stores notice ids and the last run time only.
- Logs and error text are scrubbed the same way.
- Your own search words are sent to TED or TenderNed so the search can run. They are not copied into the dataset or the run memory.

### FAQ

**Why is a notice missing a value?**
Many notices do not state one. TenderNed's open data never includes estimated value. Turn off **Keep notices without a value** if you want to drop those when a minimum value is set.

**Why do I see the same Dutch tender only once?**
Above the EU threshold, Dutch notices appear on both TED and TenderNed. With both sources on, the European TenderNed copies are skipped by default.

**Can I watch several sectors?**
Yes. Use a different state key for each saved search so their "already seen" lists stay separate.

**Where does the data come from?**
TED is European Union open data. TenderNed open data is published under CC-0. This Actor is not affiliated with the EU Publications Office or TenderNed.

### Feedback

Missing a source or a field? Open an issue on the Actor's Issues tab.

# Actor input Schema

## `sources` (type: `array`):

Which official sources to query. TED = EU Tenders Electronic Daily (all EU/EEA countries). TenderNed = Dutch government tender platform (includes national, below-EU-threshold Dutch tenders).

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

Full-text search terms. A notice matches if it matches ANY keyword. Words within one keyword must all appear (e.g. 'cloud hosting'). Works in any language; for Dutch tenders use Dutch terms too (e.g. 'onderhoud', 'schoonmaak', 'software').

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

Common Procurement Vocabulary codes. Trailing zeros act as a wildcard: '72000000' matches all IT services (72xxxxxx), '45000000' all construction work. Check digits (e.g. '45000000-7') are optional.

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

ISO 2-letter country codes of the contracting authority (e.g. NL, BE, DE). Leave empty for all countries (TED). TenderNed is only queried when NL is included or the list is empty.

## `buyerName` (type: `string`):

Only notices whose contracting authority name contains this text, e.g. 'gemeente Utrecht' or 'Rijkswaterstaat'.

## `noticeCategories` (type: `array`):

competition = open calls for tender (what most bidders want); planning = prior information notices & market consultations; result = contract award notices; change = corrections & modifications; other = early terminations, direct-award pre-announcements.

## `publishedSince` (type: `string`):

Start of the publication-date range: YYYY-MM-DD or relative like '7 days', '2 weeks', 'yesterday'.

## `publishedUntil` (type: `string`):

Optional end of the publication-date range (YYYY-MM-DD, inclusive).

## `sinceLastRun` (type: `boolean`):

Ignore 'Published since' and search from the day before the previous run (stored in the state store). Ideal for schedules.

## `onlyNewItems` (type: `boolean`):

Skip notices already returned by earlier runs with the same state store + state key. Turn on for scheduled monitoring/alerts.

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

Only notices with an estimated value at or above this amount (in the notice's own currency, almost always EUR). TenderNed does not publish values in its open API.

## `includeUnknownValue` (type: `boolean`):

When a minimum value is set, keep notices that don't state an estimated value (many don't, and TenderNed never does).

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

Maximum number of notices saved per run, newest first across sources.

## `skipTenderNedEuropean` (type: `boolean`):

When TED is also selected, skip TenderNed notices flagged as European - they are also published on TED, so this avoids duplicates.

## `tenderNedDetails` (type: `boolean`):

Fetch each TenderNed notice's detail record to add CPV and NUTS codes (one extra request per notice).

## `includeDescription` (type: `boolean`):

Add a short (max 1,000 characters) description. Email addresses, phone numbers and contact-person names are removed.

## `stateStoreName` (type: `string`):

Named key-value store used to remember seen notice IDs and the last run date.

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

Use a different key per saved search/schedule so their 'seen' lists don't mix.

## Actor input object example

```json
{
  "sources": [
    "ted",
    "tenderned"
  ],
  "keywords": [
    "software",
    "cloud hosting",
    "schoonmaak"
  ],
  "cpvCodes": [
    "72000000",
    "48000000"
  ],
  "countries": [
    "NL"
  ],
  "buyerName": "Rijkswaterstaat",
  "noticeCategories": [
    "competition"
  ],
  "publishedSince": "7 days",
  "sinceLastRun": false,
  "onlyNewItems": false,
  "includeUnknownValue": true,
  "maxResults": 20,
  "skipTenderNedEuropean": true,
  "tenderNedDetails": true,
  "includeDescription": true,
  "stateStoreName": "tender-monitor-state",
  "stateKey": "default"
}
```

# Actor output Schema

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

The overview dataset view, one row per notice.

## `noticeDetails` (type: `string`):

The details dataset view.

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

The OUTPUT record in the default key-value store.

# 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": [
        "NL"
    ],
    "publishedSince": "7 days",
    "maxResults": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("vizellie/eu-nl-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"],
    "countries": ["NL"],
    "publishedSince": "7 days",
    "maxResults": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("vizellie/eu-nl-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"
  ],
  "countries": [
    "NL"
  ],
  "publishedSince": "7 days",
  "maxResults": 20
}' |
apify call vizellie/eu-nl-tender-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vizellie/eu-nl-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/p7TCafWA5T7EIjEKh/builds/x5qu9j2yovE4btvEw/openapi.json
