# EU Tender Match (`coppered_swamphen/eu-tender-match-ai`) Actor

Find and rank relevant European public procurement opportunities from the official TED Search API.

- **URL**: https://apify.com/coppered\_swamphen/eu-tender-match-ai.md
- **Developed by:** [LEMAIRE ANTOINE](https://apify.com/coppered_swamphen) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 results

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 Tender Match — European procurement opportunity monitor

Search published European procurement notices using simple country, sector and date filters. Return structured fields, explain the words matched and optionally suppress previously delivered notices in a recurring watch. The source is the official TED Search API v3. No language model, browser or proxy is used by this implementation.

### Quick start: French IT notices

```json
{
  "countries": ["FR"],
  "sector": "it",
  "lookbackDays": 30,
  "noticeKind": "competition",
  "keywords": [],
  "matchingMode": "words",
  "limit": 5,
  "maxPages": 1,
  "minKeywordMatches": 0,
  "maxResults": 5,
  "maxRuntimeSeconds": 60,
  "historyMode": "off"
}
```

The rolling publication window includes today in UTC. `competition` selects notice types; it does **not** establish that the submission deadline is still open. A successful empty search is possible. A five-result cap is not a promise that five relevant notices exist.

### Simple and expert search

In simple mode, choose buyer `countries`, `sector`, optional `cpvCodes`, `lookbackDays` and `noticeKind`. IT uses CPV families `48*` and `72*`; construction uses `45*`; engineering uses `71*`. Additional CPV codes are OR-combined with the preset. These presets are practical starting points, not exhaustive industry definitions.

Supply a non-empty `tedQuery` to use TED Expert Search instead. The expert query takes precedence over all simple search filters. Do not assume that a country selected in the form also restricts an expert query.

For compatibility, existing API inputs with `tedQuery` and no explicit `matchingMode` use `legacy` substring matching and the original default fields. Select `matchingMode: "words"` explicitly to use the improved matching. New simple searches use `words` by default. A caller-provided `fields` list replaces the defaults; `publication-number` is always included.

### Matching you can inspect

`words` matches complete literal tokens and phrases, ignoring case and accents. It does not translate, stem or infer intent. For example, `data` does not match `datamateriel`; `logiciel` and `logiciels` are distinct terms. Use variants as appropriate.

The preferred matching fields are procedure/lot titles and descriptions. When none is available, the Actor falls back to the multilingual `notice-title` and adds a warning: this field can contain generic CPV labels. A category phrase such as “except software packages” can therefore still produce a literal match in fallback mode. This is not semantic AI.

`excludeKeywords` removes a notice when any excluded term matches. `minKeywordMatches` is the minimum count of distinct requested terms. `matchScore` is the rounded percentage of requested terms matched, **not a probability of eligibility or contract award**. With no keywords in `words` mode, the score is null and `matchingStatus` explains that scoring was not performed. Legacy mode preserves its previous no-keyword score of 100.

Each result includes `matchEvidence`, with the actual source fields and bounded literal passages, and `matchWarnings` when applicable.

### Readable results without invented data

The default dataset exposes flat columns for publication number/date, title, buyer, buyer country, CPV codes, notice type/category, tender deadlines when supplied, estimated procedure value/currency when unambiguous, direct TED URL, score and matched terms.

The original `notice`, `source`, `matchScore` and `matchedKeywords` keys remain available. TED may return multilingual/nested values. The preferred output language selects a value already present; it does not translate missing content.

Missing or ambiguous values remain null. An estimated value is a decimal string, not a guessed contract price. Submission deadlines use explicit tender-receipt fields, not an additional-information deadline. Dates may refer to different lots and do not establish opening hours or time zones. Always inspect the underlying notice before deciding to submit a bid. `deadlineStatus` is descriptive, never an `isOpen` guarantee.

### Recurring watches

Set `historyMode` to `newOnly` or `newOrChanged` and provide a stable `watchName`. History is scoped by Actor, watch name and selection fingerprint in the account owning the run. A different country, keyword set or returned field list creates a separate selection history. Changing the current day does not reset a simple rolling-window watch.

`newOnly` suppresses publication numbers already confirmed as delivered. `newOrChanged` also emits a previously delivered publication number when its returned raw content changes. This is notice-level history, not procedure-level aggregation: a new publication number for a correction or award is a new notice. It is not necessarily a new commercial opportunity.

The first pass delivers matching notices within the scan limits. The same incomplete scan repeated can return more previously undelivered notices; zero on the second pass is guaranteed only for an identical sample already fully delivered. Default retention is 90 days, configurable from 7 to 365 days. After expiry an older notice can be delivered again. History is capped at 20,000 entries; capacity problems fail explicitly rather than silently resetting history.

Concurrent runs of the same watch use a lease. A busy watch stops with `WATCH_BUSY`. A crashed lease expires after 600 seconds. History is committed after verified dataset writes. A crash in that cross-service gap can cause a repeat; **exactly-once delivery is not promised**. Migration/resurrection into an already populated default dataset is deliberately rejected; start a new run.

### Limits, costs and failures

`limit` controls page size (1–250). `maxPages` caps pages (1–10; default 1). `maxResults` caps delivered items (1–250). Ranking is within each inspected page, not across the entire TED corpus. `SUMMARY` records inspected counts, delivered counts, stop reason and whether the query was exhausted. A capped run is marked `BOUNDED_PARTIAL`, even if the process succeeds.

The work phase has a time limit of 15–180 seconds. Initialization and diagnostic cleanup can add time. HTTP responses are bounded to 8 MiB and recoverable requests to three attempts. Persistent history and verification reads consume additional platform storage operations; actual cloud costs must be measured.

When configured on Apify, the synthetic default-dataset event is charged by the platform. The implementation does not add a duplicate custom charge. It checks the reported event budget before further collection/writes. Platform usage and billing depend on the caller's Apify plan and the Store pricing currently configured for this Actor.

Malformed or timed-out TED responses fail rather than becoming billable result rows. Rows delivered before a later error may remain in the dataset; inspect `SUMMARY` and do not assume a failed run delivered nothing.

# Changelog

This Actor's version history is a separate document: https://apify.com/coppered\_swamphen/eu-tender-match-ai/changelog.md

# Actor input Schema

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

FR/FRA, BE/BEL, DE/DEU etc. Empty means all. Ignored when tedQuery is supplied.

## `sector` (type: `string`):

Presets use CPV families: IT = 48\*/72\*, construction = 45\*, engineering = 71\*. Ignored in expert mode.

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

Eight digits or prefixes such as 72\*. OR-combined with the sector codes. Ignored in expert mode.

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

Includes today in UTC. Used only in simple mode; range is rolling, not a fixed historical date.

## `noticeKind` (type: `string`):

Competition is a notice category, NOT a guarantee the deadline is open. Ignored in expert mode.

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

Literal terms or phrases. No semantic AI. Empty means no keyword filter or score.

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

Any matching excluded term removes the notice. No regular expressions.

## `matchingMode` (type: `string`):

Words: accents normalized, complete tokens/phrases in titles/descriptions. Legacy: substring matching in every returned field.

## `minKeywordMatches` (type: `integer`):

Threshold over distinct keywords. Ignored with an empty keyword list.

## `preferredLanguage` (type: `string`):

FR, EN, DE or a TED three-letter code. Uses available translations; never translates with an LLM.

## `limit` (type: `integer`):

Old limit input preserved: page size, 1-250. Total inspection is bounded by limit multiplied by maxPages.

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

One page by default. Up to 10 pages; stops earlier at result or charge limits. Ranking is per page.

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

Hard cap on dataset items in a run.

## `maxRuntimeSeconds` (type: `integer`):

Local timeout around collection, retries, locks and writes. Platform initialization/cleanup may add time.

## `historyMode` (type: `string`):

Off: no cross-run history. New only: ignore seen publication IDs. New or changed: also emit changed content for the same ID.

## `watchName` (type: `string`):

Required when history is enabled. 1-60 letters, digits, \_ or -. Each account and selection gets its own history.

## `historyDays` (type: `integer`):

Delivered IDs expire after this many days. A historical query may re-emit an expired ID. Hard cap: 20,000 IDs.

## `tedQuery` (type: `string`):

When non-empty, overrides countries/sector/cpvCodes/lookbackDays/noticeKind. Existing API inputs remain accepted.

## `fields` (type: `array`):

Empty uses defaults. Providing fields replaces the default field set. publication-number is always added. Missing fields are not invented.

## Actor input object example

```json
{
  "countries": [],
  "sector": "all",
  "cpvCodes": [],
  "lookbackDays": 7,
  "noticeKind": "competition",
  "keywords": [],
  "excludeKeywords": [],
  "minKeywordMatches": 1,
  "preferredLanguage": "fr",
  "limit": 100,
  "maxPages": 1,
  "maxResults": 50,
  "maxRuntimeSeconds": 90,
  "historyMode": "off",
  "historyDays": 90,
  "fields": []
}
```

# Actor output Schema

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

No description

# 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("coppered_swamphen/eu-tender-match-ai").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("coppered_swamphen/eu-tender-match-ai").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 coppered_swamphen/eu-tender-match-ai --silent --output-dataset

```

## MCP server setup

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

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/1ocQzSE5SRcSSTwXM/builds/J4LBAzNJJCsE22OxU/openapi.json
