# EU Tender Fit & Amendment Radar (`herazur/eu-tender-fit-amendment-radar`) Actor

Find EU tenders that fit your company, score opportunities, and monitor deadline changes, amendments, awards, and other material updates using official TED data.

- **URL**: https://apify.com/herazur/eu-tender-fit-amendment-radar.md
- **Developed by:** [Furkan Toluç](https://apify.com/herazur) (community)
- **Categories:** Lead generation, Automation, Other
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 actionable tender updates

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 Tender Fit & Amendment Radar

Status: **Phase 0 GO; production Actor implemented and locally verifiable.**

This Apify Actor turns public TED procurement notices into deterministic company-fit signals,
notice-version correlation, material change detection, and actionable monitoring events. It is not
a generic tender scraper: the default dataset contains events that describe what requires action.

The benchmark uses the anonymous official TED API v3 and does not use an LLM, paid upstream API,
browser, customer credentials, or proxy.

### Run the Actor

Requirements: Node.js 20 or newer. No credentials, cookies, proxy, or API key are used.

```bash
npm install
npm run build
npm run lint
npm start
```

On Apify, configure a schedule using the same `monitorId` and `stateStoreName`. The named key-value
store carries notice, procedure, pending-update, and deduplication state across runs. Use a distinct
monitor ID for every company profile. Set `resetState` only when intentionally creating a new
monitoring baseline.

At least one positive fit signal is required: target CPV, country, NUTS region, buyer term, or
capability keyword. Optional `expertQuery` text uses the official TED expert-query syntax and is
combined with the Actor's publication lookback window.

`maxScanNotices` is an acquisition safety cap; `maxResults` limits customer-visible rows. The Actor
groups positive signals into country, CPV, NUTS, buyer, and capability query shards, scans them in
round-robin order, unions them by stable notice-version identity, then ranks all scanned matches.
It never describes a capped scan as complete. Check `searchTruncated`, `coverageComplete`,
`totalMatchesObserved`, and per-shard metrics in `OUTPUT`. Unreturned ranked updates remain pending
for later runs.

Each dataset row represents one logical update and contains `eventTypes[]` plus
`materialChanges[]`. Headline event types include:

- `NEW_MATCHING_TENDER`, `NEW_HIGH_PRIORITY_TENDER`, `FIT_SCORE_INCREASED`
- `URGENCY_INCREASED`, `DEADLINE_EXTENDED`, `DEADLINE_SHORTENED`
- `MATERIAL_CHANGE_DETECTED`, `PROCEDURE_LIFECYCLE_UPDATE`
- `PROCEDURE_CANCELLED`, `AWARD_PUBLISHED`, `NO_LONGER_ACTIONABLE`

`fitScore` measures company/tender compatibility and never changes merely because time passed.
`urgencyScore` changes in deterministic deadline bands; `priorityScore` combines the two. Value
ranges require an explicit matching three-letter currency. Eight-digit CPVs are exact; only CPVs
ending in `*` are prefix matches.

Every update includes stable identities, lifecycle classification, source URL, previous scores,
and the current normalized notice. Stored open tenders are reevaluated every run even when they are
outside `lookbackDays`, so deadline aging can produce urgency or expiry events without a new TED
publication.

Overlapping runs for the same `stateStoreName + monitorId` are rejected using a leased monitor lock,
verified ownership, and a state revision guard. This is the safest practical key-value-store lock,
not a database transaction: a platform crash after dataset write but before state commit can still
produce an at-least-once duplicate on retry. Do not intentionally schedule overlapping runs. State
is bounded by `stateRetentionDays` and `maxStateNotices`; any open-state or pending-update pruning is
reported explicitly in `OUTPUT`.

When Apify reports `PAY_PER_EVENT` pricing, each successfully delivered consolidated row is charged
exactly once as `actionable-tender-update`. Rows containing multiple `eventTypes[]` or
`materialChanges[]` still represent one event. The Actor preflights the remaining event budget,
stops before an unpaid row, and retains undelivered rows in pending state for a later run. Under any
other pricing model, rows are delivered without a custom PPE charge. The synthetic
`apify-actor-start` event remains platform-managed, and `apify-default-dataset-item` must not be
configured because it would double-charge the default dataset write.

### Verify

```bash
npm run build
npm test
npm run lint
npm run format:check
npx apify-cli validate-schema
```

The real-data benchmark writes `PHASE0_REPORT.md`, `benchmark-output/phase0-report.json`,
`benchmark-output/normalized-sample.json`, and `benchmark-output/amendment-pairs.json`. Live results
will vary as TED data and service behavior change.

### Phase 0 result

The passing run tested 60/60 matrix queries, 4,679 unique notice versions, 100 correlated version
pairs, page sizes 25/100/250, ten consecutive pages, resume/repeat behavior, ITERATION continuation,
and concurrency 1/3/5/10. See `PHASE0_REPORT.md` for measured retries, costs, field availability, and
limitations. Scores remain transparent deterministic heuristics; no LLM, paid upstream API,
credentials, browser, proxy, or HTML scraping is used.

# Actor input Schema

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

Stable ID used to isolate recurring state.

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

Named key-value store retained across Actor runs.

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

Optional official TED expert-search expression combined with the publication window.

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

How far back to search by TED publication date on every run.

## `maxScanNotices` (type: `integer`):

Acquisition safety cap across all query shards. A capped run explicitly reports incomplete coverage.

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

Maximum customer-visible update rows returned per run. Overflow remains pending for the next run.

## `targetCpvCodes` (type: `array`):

Exact codes or parent prefixes ending in \*.

## `targetCountries` (type: `array`):

Three-letter TED country codes such as DEU or FRA.

## `targetNutsRegions` (type: `array`):

NUTS codes or prefixes for target places of performance.

## `buyerKeywords` (type: `array`):

Case-insensitive terms matched against buyer names.

## `procedureTypes` (type: `array`):

Exact TED procedure-type values such as open or restricted.

## `capabilityKeywords` (type: `array`):

Case-insensitive terms matched in buyer, title, description, and criteria text.

## `excludedKeywords` (type: `array`):

Any matching term makes the notice ineligible.

## `minimumContractValue` (type: `integer`):

Optional inclusive lower value bound in the notice's stated currency.

## `maximumContractValue` (type: `integer`):

Optional inclusive upper value bound in the notice's stated currency.

## `contractCurrency` (type: `string`):

Optional three-letter currency required before applying value-range points.

## `minimumFitScore` (type: `integer`):

Minimum deterministic score for an eligible match.

## `highPriorityScore` (type: `integer`):

Score threshold for NEW\_HIGH\_PRIORITY\_TENDER.

## `deadlineNearDays` (type: `integer`):

Remaining-day threshold for DEADLINE\_NEAR scoring.

## `stateRetentionDays` (type: `integer`):

How long closed, expired, and emitted update state is retained.

## `maxStateNotices` (type: `integer`):

Safety bound for non-actionable history; open actionable notices are never dropped solely to meet this bound.

## `resetState` (type: `boolean`):

Ignore existing monitor state on this run.

## Actor input object example

```json
{
  "monitorId": "default",
  "stateStoreName": "eu-tender-radar-state",
  "lookbackDays": 14,
  "maxScanNotices": 5000,
  "maxResults": 500,
  "targetCpvCodes": [],
  "targetCountries": [],
  "targetNutsRegions": [],
  "buyerKeywords": [],
  "procedureTypes": [],
  "capabilityKeywords": [],
  "excludedKeywords": [],
  "minimumFitScore": 35,
  "highPriorityScore": 70,
  "deadlineNearDays": 21,
  "stateRetentionDays": 365,
  "maxStateNotices": 20000,
  "resetState": false
}
```

# Actor output Schema

## `events` (type: `string`):

No description

## `summary` (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("herazur/eu-tender-fit-amendment-radar").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("herazur/eu-tender-fit-amendment-radar").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 herazur/eu-tender-fit-amendment-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,herazur/eu-tender-fit-amendment-radar"
        }
    }
}

```

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/QOpj8DMXm619hANOj/builds/t0OZko2vhXE204DWP/openapi.json
