# EU TED Published Lot End Dates & Supplier Watch (`isolovyev/eu-contract-expiry-watch`) Actor

Track published TED lot end dates and linked award suppliers. Export evidence and repeat changes. Опубликованные даты окончания лотов TED и связанные поставщики.

- **URL**: https://apify.com/isolovyev/eu-contract-expiry-watch.md
- **Developed by:** [Ivan Solovyev](https://apify.com/isolovyev) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 complete contract-lot observations

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 TED Published Lot End Dates & Supplier Watch

<table width="100%"><tr><td style="padding:24px 28px; background:#EEF2FF; border:1px solid #C7D2FE; border-top:4px solid #4F46E5; border-radius:12px"><span style="font-size:23px; font-weight:800; color:#1E1B4B">Turn published TED lot end dates into supplier signals</span><br><span style="font-size:15px; color:#4B5563">Link each published date to its lot, settled contract, winning tender and named supplier using official TED XML evidence.</span><br></td></tr></table>

Use direct TED notice numbers for deterministic checks, or search a bounded end-date window with optional buyer-country and CPV filters. Each complete Dataset row represents one source-verified contract-lot observation with grouped winning suppliers. `NEW`, `UPDATED` and `UNCHANGED` rows use the same output unit.

### What the date means

`publishedLotEndDate` reproduces TED field `BT-537-Lot`: the published estimated end date for the lot duration. Depending on the notice, this date can describe a contract, framework agreement, dynamic purchasing system, qualification system or setup phase. Use `procedureType`, `frameworkType`, `dpsUsage` and `durationDescription` to qualify the signal. It is not a forecast of a new tender or renewal.

### Input

Direct mode takes priority when `noticeNumbers` is present. Set-like arrays are sorted and deduplicated before the search and monitor scope are created.

```json
{
  "noticeNumbers": ["599729-2026"],
  "buyerCountries": ["DEU"],
  "cpvCodes": ["45315700"],
  "maxNotices": 20,
  "monitorKey": "facilities-de"
}
```

For search mode, omit `noticeNumbers` and provide both dates:

```json
{
  "endFrom": "2027-10-01",
  "endTo": "2027-12-31",
  "buyerCountries": ["DEU"],
  "cpvCodes": ["45315700"],
  "maxNotices": 25,
  "monitorKey": "facilities-de"
}
```

- `noticeNumbers`: 1 to 20 TED publication numbers.
- `endFrom`, `endTo`: ISO dates in `YYYY-MM-DD` form, ordered within a 366-day window.
- `buyerCountries`: optional ISO alpha-3 buyer-country codes, such as `DEU` or `FRA`.
- `cpvCodes`: optional exact eight-digit CPV codes.
- `maxNotices`: 1 to 100 notices.
- `monitorKey`: stable key for one isolated recurring workflow.

Search mode reads page 1 of the public TED Search API and processes at most `maxNotices` candidates. `CAPPED_FIRST_PAGE` confirms this bounded page was processed. It does not mean the whole date window or all EU notices were covered.

### Output and monitoring

```json
{
  "observationId": "notice-specific-id",
  "contractKey": "stable-procedure-contract-lot-key",
  "noticeNumber": "599729-2026",
  "publicationDate": "2026-09-01",
  "lotId": "LOT-0000",
  "contractId": "CON-0001",
  "publishedLotEndDate": "2027-12-31",
  "dateBasis": "PUBLISHED_LOT_DURATION_END",
  "winnerNames": ["Siemens AG"],
  "winners": [{
    "organisationId": "ORG-0001",
    "name": "Siemens AG",
    "tenderIds": ["TEN-0001"],
    "tenderValues": []
  }],
  "changeType": "UNCHANGED",
  "isAlert": false,
  "sourceXmlUrl": "https://ted.europa.eu/en/notice/599729-2026/xml"
}
```

The Actor emits a row only when XML references connect the lot result, winning tender, settled contract, tendering party and named organisation. Multiple suppliers for one contract and lot stay grouped in `winners`. If several validated notices describe the same contract-lot in one run, the Actor selects the version with the latest published date and records the selection in `SUMMARY` diagnostics. Conflicting duplicate XML IDs are rejected instead of being silently overwritten.

The monitor keeps a bounded snapshot for the validated input scope and `monitorKey`. The first complete observation is `NEW`. A changed observation for the same contract-lot is `UPDATED`. A repeated complete observation is `UNCHANGED` with `isAlert=false`, so a scheduled run remains useful without creating a false alert. State advances only after exact Dataset payload readback and snapshot readback succeed while the run still owns the monitor lock.

`SUMMARY.coverageStatus` means:

- `VERIFIED_DIRECT_NOTICES`: direct notices were parsed without source issues.
- `CAPPED_FIRST_PAGE`: page 1 of the TED search was processed up to `maxNotices`; window-wide completeness is unknown.
- `PARTIAL_SOURCE_ERRORS`: one or more candidates, links or source documents could not be fully verified.

Billing follows each delivered, complete contract-lot observation, including `NEW`, `UPDATED` and `UNCHANGED`. Multiple winners remain part of that single result. A run-start fee applies even when no complete observation is produced. Completed observations are charged separately, including unchanged checks. Apify compute and proxy usage are included in the event prices. See Pricing for the current tariff.

The Dataset row is the source-verified contract-lot unit. `SUMMARY` separately reports observed, new, updated, unchanged, alert and delivered counts. Source or delivery failures are reported as failed or partial health and do not produce unverified contract-lot rows.

***

## EU TED: опубликованные даты окончания лотов и мониторинг поставщиков

Используйте известные номера извещений TED для детерминированной проверки или ищите в ограниченном диапазоне дат с фильтрами по стране заказчика и CPV. Каждая полная строка Dataset соответствует одной проверенной по первичному XML связке договор-лот с объединённым списком победителей. Строки `NEW`, `UPDATED` и `UNCHANGED` являются одной и той же единицей результата.

### Что означает дата

`publishedLotEndDate` воспроизводит поле TED `BT-537-Lot`: опубликованную оценочную дату окончания периода лота. В зависимости от извещения дата может относиться к договору, рамочному соглашению, динамической системе закупок, квалификационной системе или этапу настройки. Для интерпретации используйте `procedureType`, `frameworkType`, `dpsUsage` и `durationDescription`. Дата не является прогнозом новой закупки или продления.

### Входные данные

Прямой режим имеет приоритет, если задан `noticeNumbers`. Массивы, используемые как множества, сортируются и очищаются от повторов до поиска и создания области монитора.

```json
{
  "noticeNumbers": ["599729-2026"],
  "buyerCountries": ["DEU"],
  "cpvCodes": ["45315700"],
  "maxNotices": 20,
  "monitorKey": "facilities-de"
}
```

Для режима поиска не задавайте `noticeNumbers`, но укажите обе даты:

```json
{
  "endFrom": "2027-10-01",
  "endTo": "2027-12-31",
  "buyerCountries": ["DEU"],
  "cpvCodes": ["45315700"],
  "maxNotices": 25,
  "monitorKey": "facilities-de"
}
```

- `noticeNumbers`: от 1 до 20 номеров публикаций TED.
- `endFrom`, `endTo`: даты ISO в форме `YYYY-MM-DD`, по порядку и в окне не более 366 дней.
- `buyerCountries`: необязательные коды стран заказчика ISO alpha-3, например `DEU` или `FRA`.
- `cpvCodes`: необязательные точные восьмизначные коды CPV.
- `maxNotices`: от 1 до 100 извещений.
- `monitorKey`: стабильный ключ одного изолированного повторяемого сценария.

Режим поиска читает страницу 1 публичного TED Search API и обрабатывает не более `maxNotices` кандидатов. `CAPPED_FIRST_PAGE` подтверждает обработку этой ограниченной страницы. Этот статус не означает полного покрытия диапазона дат или всех извещений ЕС.

### Результат и мониторинг

```json
{
  "observationId": "notice-specific-id",
  "contractKey": "stable-procedure-contract-lot-key",
  "noticeNumber": "599729-2026",
  "publicationDate": "2026-09-01",
  "lotId": "LOT-0000",
  "contractId": "CON-0001",
  "publishedLotEndDate": "2027-12-31",
  "dateBasis": "PUBLISHED_LOT_DURATION_END",
  "winnerNames": ["Siemens AG"],
  "winners": [{
    "organisationId": "ORG-0001",
    "name": "Siemens AG",
    "tenderIds": ["TEN-0001"],
    "tenderValues": []
  }],
  "changeType": "UNCHANGED",
  "isAlert": false,
  "sourceXmlUrl": "https://ted.europa.eu/en/notice/599729-2026/xml"
}
```

Строка создаётся только тогда, когда ссылки XML связывают результат лота, победившую заявку, заключённый договор, сторону заявки и названную организацию. Несколько поставщиков одной связки договор-лот остаются в одном массиве `winners`. Если несколько проверенных извещений в одном запуске описывают одну связку, Actor выбирает версию с самой поздней датой публикации и фиксирует выбор в диагностике `SUMMARY`. Противоречивые повторяющиеся ID в XML отклоняются и не перезаписывают сведения незаметно.

Монитор хранит ограниченный снимок для проверенной области входных параметров и `monitorKey`. Первое полное наблюдение имеет тип `NEW`. Изменённое наблюдение для той же связки имеет тип `UPDATED`. Повторное полное наблюдение имеет тип `UNCHANGED` и `isAlert=false`, поэтому запуск по расписанию остаётся полезным и не создаёт ложное оповещение. Состояние обновляется только после точной обратной проверки содержимого Dataset и снимка, пока запуск владеет блокировкой монитора.

Значения `SUMMARY.coverageStatus`:

- `VERIFIED_DIRECT_NOTICES`: прямые извещения разобраны без ошибок источника.
- `CAPPED_FIRST_PAGE`: обработана страница 1 поиска TED с ограничением `maxNotices`; полнота всего окна неизвестна.
- `PARTIAL_SOURCE_ERRORS`: один или несколько кандидатов, связей или исходных документов не удалось проверить полностью.

Тарификация применяется к каждой доставленной полной проверке связки договор-лот, включая `NEW`, `UPDATED` и `UNCHANGED`. Несколько победителей входят в один результат. Плата за старт применяется и при отсутствии полной проверки. Полные доставленные проверки оплачиваются отдельно, включая проверки без изменений. Вычисления и прокси Apify включены в цену событий. Актуальный тариф указан в разделе Pricing.

Строка Dataset является проверенной по источнику единицей договор-лот. `SUMMARY` отдельно показывает число наблюдений, новых, изменённых, неизменённых, сигнальных и доставленных строк. Ошибки источника или доставки отражаются как неуспешное или частичное состояние и не создают непроверенных строк договор-лот.

# Actor input Schema

## `noticeNumbers` (type: `array`):

Directly check up to 20 TED publication numbers.

## `endFrom` (type: `string`):

ISO date in YYYY-MM-DD form.

## `endTo` (type: `string`):

ISO date in YYYY-MM-DD form.

## `buyerCountries` (type: `array`):

ISO alpha-3 codes such as DEU or FRA.

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

Filter awarded lots by up to 20 exact eight-digit CPV codes, such as 45315700.

## `maxNotices` (type: `integer`):

Process at most this many notices. Search mode processes only the first page, up to this limit.

## `monitorKey` (type: `string`):

Stable key for an isolated saved snapshot. Use a different key for each workflow.

## Actor input object example

```json
{
  "noticeNumbers": [
    "599729-2026"
  ],
  "maxNotices": 25,
  "monitorKey": "default"
}
```

# Actor output Schema

## `dataset` (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 = {
    "noticeNumbers": [
        "599729-2026"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("isolovyev/eu-contract-expiry-watch").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 = { "noticeNumbers": ["599729-2026"] }

# Run the Actor and wait for it to finish
run = client.actor("isolovyev/eu-contract-expiry-watch").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 '{
  "noticeNumbers": [
    "599729-2026"
  ]
}' |
apify call isolovyev/eu-contract-expiry-watch --silent --output-dataset

```

## MCP server setup

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

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/MdMPAHofhHntwBHrm/builds/Tk8697WJHmDZIGj4E/openapi.json
