# EU & UK Tender Match Monitor (`fetchfinch/eu-uk-tender-match-monitor`) Actor

Find, rank, normalize, and monitor official EU TED and UK Find a Tender notices.

- **URL**: https://apify.com/fetchfinch/eu-uk-tender-match-monitor.md
- **Developed by:** [FetchFinch](https://apify.com/fetchfinch) (community)
- **Categories:** Lead generation, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 tender matches

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/platform/actors/running/actors-in-store#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 & UK Tender Match Monitor

Find procurement opportunities that fit a supplier, not just notices that contain a word.

> **Unofficial Actor:** This product is not affiliated with, sponsored by, or endorsed by
> the European Union, TED, the UK Cabinet Office, or Find a Tender.

This Apify Actor reads the official EU Tenders Electronic Daily (TED) and UK Find a
Tender APIs, converts both formats into one stable schema, assigns an explainable fit
score, and can emit only new or materially changed matches on recurring runs. It does
not scrape result pages, require source credentials, or call an LLM.

### What it gives you

- One normalized dataset for EU/EEA and UK notices
- Profile, keyword, exclusion, CPV, country, stage, date, and deadline filters
- Deterministic `fitScore` from 0 to 100 with human-readable `matchReasons`
- Stable procurement IDs and cross-run change detection
- Direct official notice, PDF, XML, and tender-document links when published
- A `RUN_SUMMARY` record with source counts, generated TED query, filtering counts,
  partial-source errors, and monitor statistics
- JSON, JSONL, CSV, Excel, XML, RSS, and API delivery through Apify datasets

### Quick start

Use a concrete company description and add the phrases buyers are likely to publish:

```json
{
  "companyProfile": "We build secure cloud data platforms, analytics dashboards, API integrations, and data migration solutions for public-sector organizations.",
  "keywords": ["data platform", "analytics dashboard", "API integration"],
  "cpvCodes": ["72000000"],
  "countries": ["DEU", "FRA", "IRL", "GBR"],
  "sources": ["TED", "UK_FTS"],
  "stages": ["competition"],
  "lookbackDays": 14,
  "minFitScore": 25,
  "maxItems": 100,
  "scanLimit": 500,
  "mode": "search"
}
```

See [examples/search.json](examples/search.json) and
[examples/monitor.json](examples/monitor.json) for complete starting points.

### Search and monitor modes

`search` ranks current matches and writes them with `changeType: "search_result"`.

`monitor` stores a compact snapshot in a named Apify key-value store. Later runs with
the same `monitorId` emit `new` and `changed` matches; unchanged records remain quiet
unless `emitUnchanged` is enabled. Use `baselineBehavior: "store_only"` to make the
first run silent, or `"emit"` to receive the current backlog.

For a recurring monitor:

1. Create an Apify Task with `mode: "monitor"` and a unique `monitorId`.
2. Leave `publishedFrom` and `publishedTo` unset so `lookbackDays` moves with every run.
3. Run the Task once to establish the baseline.
4. Add an Apify schedule and a webhook, email, Slack, Make, or Zapier integration.

The Actor retains inactive state for 400 days and caps each monitor at 25,000 tracked
procurements. Ranking-only fields do not trigger false change events. Material changes
such as deadline, value, title, stage, buyer, CPV, documents, or notice publication do.

### How matching works

The Actor combines priority keywords with useful terms derived from `companyProfile`.
Explicit keywords receive priority weight. Terms inferred from the profile receive a
lower supporting weight, and generic standalone words inferred from prose are ignored.
Words and phrases match only at complete word boundaries, so `api` does not match a
place name such as `Krapina`, and `build` does not match `building`.
Each normalized notice is scored deterministically:

- Priority/profile title match: 18/12 points per term, up to 54
- Priority/profile description match: 8/4 points per term, up to 24
- Priority/profile buyer-name match: 4/2 points per term, up to 8
- Broad matching CPV family (2-3 meaningful prefix digits): 20 points
- Specific matching CPV family (4+ meaningful prefix digits): 32 points

CPV filters are hierarchical: `72000000` matches child codes in the IT family, while a
more specific code such as `7226` is selective. At the default `minFitScore` of 25, a
broad CPV match needs supporting text; set the threshold to 15 to include CPV-only
leads. Any complete-word `excludeKeywords` match drops the record. This model is
intentionally transparent and reproducible; raise `minFitScore` toward 40 for a more
selective result set.

### Output

Every dataset item follows the same contract regardless of source:

```json
{
  "source": "TED",
  "canonicalId": "TED:cca0901b-c163-4e7f-be96-fb495ad37197",
  "sourceNoticeId": "529733-2026",
  "title": "Data platform and migration services",
  "buyerName": "Example Contracting Authority",
  "countries": ["DEU"],
  "cpvCodes": ["72000000", "72260000"],
  "noticeStage": "competition",
  "publicationDate": "2026-07-31+02:00",
  "deadline": "2026-08-25T23:59:59+02:00",
  "estimatedValue": 1250000,
  "currency": "EUR",
  "fitScore": 38,
  "matchReasons": ["Title matches: data platform", "CPV matches (broad family, 20 points): 72000000"],
  "matchedKeywords": ["data platform"],
  "matchedCpvCodes": ["72000000"],
  "changeType": "new",
  "changedFields": [],
  "url": "https://ted.europa.eu/en/notice/-/detail/529733-2026"
}
```

Missing source values are returned as `null`, an empty string, or an empty array as
appropriate. `estimatedValue` is not currency-converted. TED procedure values are
preferred; otherwise the first published lot value may be used and identified by
`valueBasis`.

### Important input behavior

- The date window is inclusive. TED applies it to publication date. Find a Tender's
  public OCDS endpoint exposes an update window, so UK results represent notices
  created or updated during the same period.
- `scanLimit` applies separately to each selected source and must be at least
  `maxItems`. It bounds API work before local scoring.
- UK Find a Tender is skipped when `countries` is non-empty and excludes `GBR`.
- `expertQuery` is ANDed with generated TED filters and does not affect UK results.
- If one source fails, results from the other source are still delivered and the run
  summary reports `succeeded_with_source_errors`. The run fails if every attempted
  source fails.

### Run locally

Python 3.11 or newer is required.

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt
mkdir -p storage/key_value_stores/default
cp examples/search.json storage/key_value_stores/default/INPUT.json
APIFY_LOCAL_STORAGE_DIR=./storage .venv/bin/python -m src
```

Quality checks:

```bash
.venv/bin/ruff check src tests
.venv/bin/pytest -q
```

Local output is written under `storage/datasets/default`, with the summary at
`storage/key_value_stores/default/RUN_SUMMARY.json`.

### Deploy to Apify

Install the Apify CLI, then run:

```bash
apify login
apify push
```

The repository includes the Dockerfile, Actor definition, input UI, dataset view, and
output links needed for a cloud build. The Actor is HTTP-only and capped at 512 MB; no
Apify Proxy or browser is required.

After publishing, the Actor can be called with Apify's normal run endpoints and the
default dataset endpoint. Use the API tab in Apify Console to copy account-specific
URLs and code snippets without exposing a token in source control.

### Sources and reuse

- [TED Search API](https://docs.ted.europa.eu/api/latest/search.html) — official,
  authentication-free API for published procurement notices and data reusers
- [TED legal notice](https://ted.europa.eu/en/legal-notice) — procurement notices can
  generally be reused commercially or non-commercially unless otherwise noted
- [Find a Tender data documentation](https://www.find-tender.service.gov.uk/Developer/Documentation)
  and [OCDS release endpoint](https://www.find-tender.service.gov.uk/apidocumentation/1.0/GET-ocdsReleasePackages)
  — official UK notice data in OCDS JSON, available under the Open Government Licence

Source names and official links are retained for attribution. Procurement data can
contain third-party material; users remain responsible for their downstream use.

### Scope

This version finds and monitors public notice metadata. It does not log in to buyer
portals, download gated attachments, prepare bids, assess legal eligibility, or replace
review of the official notice. A fit score is a discovery aid, not a bid/no-bid decision.

# Actor input Schema

## `companyProfile` (type: `string`):

A concrete description of distinctive capabilities, buyers, and deliverables. The Actor derives lower-weight supporting terms while ignoring generic standalone words.

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

Optional exact words or phrases that should influence both retrieval and ranking. Use this to intentionally include broad terms that profile inference ignores.

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

Drop notices containing any of these terms in the title, description, or buyer name.

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

Optional 2-8 digit Common Procurement Vocabulary codes. Prefixes such as 72 match the wider IT family.

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

Optional ISO alpha-3 country codes such as DEU, FRA, or GBR. Empty means all covered countries.

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

TED covers the EU/EEA market. UK\_FTS covers the United Kingdom.

## `stages` (type: `array`):

Competition is the actionable default. Add planning for pipeline signals, result for awards, or change for amendments.

## `publishedFrom` (type: `string`):

Optional inclusive start date. If omitted, lookbackDays is used.

## `publishedTo` (type: `string`):

Optional inclusive end date. Defaults to today.

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

Used only when publishedFrom is omitted.

## `minFitScore` (type: `integer`):

0 returns everything retrieved; 15 includes broad CPV-only leads; the default 25 requires keyword support for broad CPV families; 40+ is selective.

## `onlyFutureDeadlines` (type: `boolean`):

Discard notices whose submission deadline has already passed.

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

Maximum ranked records written to the dataset per run.

## `scanLimit` (type: `integer`):

Safety limit for notices downloaded from each selected API before local scoring.

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

Search emits current matches. Monitor persists a baseline and emits only new or changed matches.

## `monitorId` (type: `string`):

Optional stable ID for this saved search. Reuse it on scheduled runs. Changing filters with the same ID intentionally reuses the baseline.

## `baselineBehavior` (type: `string`):

Emit returns current matches on the first run. Store only creates a quiet baseline.

## `emitUnchanged` (type: `boolean`):

Usually leave disabled in monitor mode to avoid repeat records.

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

Named key-value store shared by scheduled runs. Advanced use only.

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

Three-letter TED language code, for example eng, deu, or fra.

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

Optional raw TED Search API clause ANDed with generated filters. It affects TED only.

## `requestTimeoutSecs` (type: `integer`):

Advanced network setting.

## `maxRequestRetries` (type: `integer`):

Retry throttling, server errors, and transient network failures.

## Actor input object example

```json
{
  "companyProfile": "We build cloud data platforms, analytics dashboards, API integrations, and data migration services for public-sector organizations.",
  "keywords": [
    "data platform",
    "analytics",
    "API integration"
  ],
  "excludeKeywords": [],
  "cpvCodes": [
    "72000000"
  ],
  "countries": [],
  "sources": [
    "TED",
    "UK_FTS"
  ],
  "stages": [
    "competition"
  ],
  "lookbackDays": 7,
  "minFitScore": 25,
  "onlyFutureDeadlines": true,
  "maxItems": 100,
  "scanLimit": 500,
  "mode": "search",
  "baselineBehavior": "emit",
  "emitUnchanged": false,
  "stateStoreName": "eu-uk-tender-monitor-state",
  "preferredLanguage": "eng",
  "requestTimeoutSecs": 45,
  "maxRequestRetries": 2
}
```

# Actor output Schema

## `matches` (type: `string`):

No description

## `runSummary` (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("fetchfinch/eu-uk-tender-match-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("fetchfinch/eu-uk-tender-match-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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 fetchfinch/eu-uk-tender-match-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=fetchfinch/eu-uk-tender-match-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/m8EIyGkbgbZ1MCVLE/builds/gixy38Y4wtTVqOayG/openapi.json
