# TED Tender Amendment & Deadline Monitor (`afkdigital/ted-tender-amendment-deadline-monitor`) Actor

Watch published TED procurement changes with verified deadline, scope and quantity deltas. Persistent private monitoring, evidence URLs, and no charges on unchanged records.

- **URL**: https://apify.com/afkdigital/ted-tender-amendment-deadline-monitor.md
- **Developed by:** [AFK Digital](https://apify.com/afkdigital) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 83.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$29.00 / 1,000 verified ted tender amendments

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

## TED Tender Amendment & Deadline Monitor

**Status:** Published Apify Actor (`afkdigital/ted-tender-amendment-deadline-monitor`).

Monitor existing, **published TED tender notices** for evidence-backed amendments to deadlines, scope, quantities and estimated values. Unlike a tender discovery feed, each later run starts from the last observed notice in a published amendment chain and identifies what actually changed.

### Source, API and terms

- **Only network source:** TED's public read-only **Search API v3**, `POST https://api.ted.europa.eu/v3/notices/search`.
- Official docs: https://docs.ted.europa.eu/api/latest/search.html and https://docs.ted.europa.eu/ODS/latest/reuse/search-api.html
- Official live OpenAPI contract: https://api.ted.europa.eu/api-v3.yaml; accessed in development 2026-10-13.
- Public published-notice Search API allows **anonymous requests**, including commercial reusers. No auth token, unpublished-notice endpoint, submission/validation route, portal scraping, XML document fetch, or protected API is used by this Actor.
- Legal/reuse and attribution: https://ted.europa.eu/en/legal-notice. **TED data source: Publications Office of the European Union, Tenders Electronic Daily (TED)**. Third-party content is attributed by preserving official publication and evidence links. This is an independent monitor, not an official TED service.
- Search uses `scope: "ALL"`, `onlyLatestVersions: false`, `paginationMode: "ITERATION"` and consumes `iterationNextToken`. Current API limits include 250 notices/page and 10,000 field values/page; this Actor additionally caps each page to 100 and each user query to 100 tracked notices for predictable cost. Non-final iteration pages are consumed consistently; expired tokens, partial timeouts, syntax errors, 429/5xx and malformed responses are never treated as `unchanged`.
- TED eForms change notices carry a *new notice ID* and a published reference to their predecessor (`BT-758` / Search field `change-notice-version-identifier`). In this Actor, `notice-version` is an edition/version within a notice, **not** the amendment-chain position. Official guide: https://docs.ted.europa.eu/eforms/latest/schema/change-notice.html.

### Input

Provide `noticeIds` (publication numbers, UUIDs or UUID-version) **or** a TED `expertQuery`. The expert query is used to select the initial watch set, not to enroll new matching tenders on later runs. Keep `monitorId` and watched fields unchanged across runs. `initialSnapshot` allows a historical published baseline when auditing old changes.

Example: **first run** creates a baseline; subsequent runs with the **same input** detect later notices:

```json
{
  "monitorId": "proposal-team-eu-01",
  "noticeIds": ["200138-2026"],
  "initialSnapshot": {
    "baselinePublicationNumbers": ["200138-2026"]
  },
  "watchedFields": ["deadline", "scope", "quantity"],
  "maxResults": 25,
  "maxTrackedNotices": 25,
  "pageSize": 50
}
```

For everyday tracking, **omit** `initialSnapshot`: the first run finds the latest published amendment at that moment and saves it without billing; subsequent runs compare newer published notices against the saved head. Use a different `monitorId` to change query, identifiers or watched fields. Each monitor is independently stored.

### Output and comparability

A `recordType: "verified-amendment"` item is emitted only if a newly discovered published change notice both (1) explicitly references the immediately preceding notice and (2) has at least one actual, readable change in a requested field. One changed notice generates **one paid** item, even if several fields differ. Such an item contains:

- `noticeId`, `previousPublicationNumber`, `publicationNumber`, `previousNoticeVersion`, `newNoticeVersion`, previous/new publication dates (the TED Search API may provide a **date only**, not an exact time).
- `changes[]`: affected `field`, source field, and concrete `oldValue`/`newValue`. `deadlineChanges[]` highlights lot-by-lot tender submission deadline changes including the time where available.
- `changeReference`, `sourceUrl`, `evidence.previousUrl`, `evidence.newUrl`, `evidence.referenceField`, TED change-reason code/description where supplied.

When a successful baseline or follow-up poll finds no new verified amendment, the dataset instead contains one **free** `recordType: "monitor-status"` receipt with `monitorId`, `status`, `checkedAt`, `trackedRoots`, and `verifiedEvents: 0`. This row does not claim a change and is never charged as `verified-amendment`. Filter `recordType == "verified-amendment"` for amendment-only integrations. Failed TED source checks still fail the run and do not emit a success receipt.

Only official TED-returned links count as evidence. If an evidence link or predecessor chain is absent/ambiguous, no paid event is emitted. Changes are based on field values returned by Search API; this Actor does **not** diff documents attached elsewhere or infer unpublished changes. If a field or a common language/lot alignment is missing, it is reported in `incompleteComparisons`/`unreadableFields`; it is **not claimed unchanged**. The detailed summary record is written under `RESULT_SUMMARY` in each **run's default key-value store**; the dataset receipt carries only brief status information.

**Example historical verification (published source):** `200138-2026` -> `250810-2026`: receipt date changed from `2026-04-17Z` to `2026-04-24Z` for `LOT-0000`. Evidence is available at https://ted.europa.eu/en/notice/250810-2026/xml, linked by the API.

### State, isolation, retention and constraints

- State survives **separate Actor runs** in a **named Apify key-value store** keyed by a SHA-256-derived namespace of platform user ID and `monitorId` (`api23-...-state`). Each root stores the **last observed published notice** and selected fields, not the entire TED history.
- A second **named Apify request queue** (`api23-...-dedup`) atomically reserves a unique event key consisting of root, previous publication, new publication, and watched fields. Re-runs and repeated API pages therefore do not create another billable event. If the queue reservation is made but pushing an item later fails, a retry may **skip that item rather than double bill** (at-most-once, not guaranteed exactly-once delivery). Do not run concurrent jobs under the same monitor ID unless you accept that snapshots may race; persistent queue keys still suppress duplicate charges.
- Platform user's identity is part of storage namespace; if unavailable on Apify, execution is refused. No cross-user shared registry, shared dataset, external logging of user inputs, email collection, data resale, or third-party persistence.
- Apify **named** key-value stores and request queues persist until deleted according to Apify storage policies. This Actor does **not** automatically expire these named stores. Read current retention/privacy details in https://docs.apify.com/storage/key-value-store and https://docs.apify.com/storage/request-queue. Users can delete their own stored monitor data in Apify Console. Unnamed default run stores follow the platform's default retention.
- Safety caps: up to 100 initial identifiers, at most 100 tracked notice roots per monitor, up to 100 output events per run, at most 100 results per TED page, 12/15 page limits for filter search and 25 successor hops. Huge/ambiguous result sets fail explicitly; they are not silently truncated as complete. Persistent state and queue usage can grow across months of monitoring, incurring storage cost.
- When first run has **no historical snapshot**, a published amendment already available at creation becomes part of the baseline, not a newly billable event. This prevents charging users for old news.
- Exact source availability and published-change linkage varies between legacy TED and eForms. An absent reference or unavailable field is not evidence of an amendment. Retired/closed tenders may still produce published notices; `scope:ALL` monitors actual publication history, not legal eligibility to bid.
- This is a **polling Actor**: schedule repeated runs from Apify tasks/schedules as desired. It does not wake itself up, create an external webhook, or guarantee a publication SLA.

### Pricing

The release price is **$0.029 per verified changed notice** ($29 per 1,000 verified changes); zero custom fees for baseline checks, no changes, duplicate changes, and source failures. Compared with generic TED notice scraping at roughly $0.002–$0.004 per raw notice, this premium covers validated predecessor linkage, individual field diffs and evidence URLs. Without a start fee the publisher absorbs empty polling costs and profitability depends on observed load. The intended pricing model is Apify **Pay per event**, with exactly one custom primary event called `verified-amendment`: one verified changed **notice** (not one field, API request, date check, or baseline). New state and unchanged or malformed records have **zero custom event charges**.

The public Actor is configured with one custom `verified-amendment` event priced at $0.029 and no synthetic start or dataset-item event (verified through the Apify Actor API on 2026-10-15). Billing must be checked again after any publishing setting change.

**Known tradeoff:** without a start fee, the provider absorbs platform costs for checks without changes; test economics with realistic frequency before Store listing. Apify may recommend a small start charge, but this product intentionally **does not** charge for unchanged/error runs. Apify docs: https://docs.apify.com/actors/publishing/monetize/pay-per-event .

### Operations, error handling and local tests

Only HTTP 429, 500, 502, 503, 504 and transport timeout/connect errors are retried (3 attempts, short backoff, bounded `Retry-After`). Bad query/401/403, expired pagination token, malformed JSON, missing evidence links, ambiguous successor branches and conflicting lot identifiers return clear errors. A bad source is not logged as an unchanged notice. `RESULT_SUMMARY` contains `sourceErrors`, `incompleteComparisons`, `unchangedRoots`, `amendmentsInspected`, `verifiedEvents` and `apiCalls`.

```bash
cd /opt/APIFY/API23
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests -v
```

Run locally by writing an `INPUT.json` file under the local Apify SDK storage default KVS (`APIFY_LOCAL_STORAGE_DIR`) and running `.venv/bin/python -m src`. The Actor supports the Apify Dockerfile and normal Apify deployment build. The published Actor was verified with real Apify PPE charges during release.

Real integration fixtures, as checked on 2026-10-13, include the published deadline change `200138-2026` -> `250810-2026` and a non-material amendment `80681-2026` -> `103490-2026`. `tests/test_monitor_live.py` covers a new event, no watched-field change, repeated polling and deduplicated replay. `tests/test_monitor_unit.py` covers HTTP 503, null/unknown field, cursor paging, input validation and a reused monitor with different input. These are repeatable tests; TED may later change historic source availability, in which case the live test reports an error rather than inventing results.

### QA repair (2026-10-15)

The input schema prepopulates one published TED notice for the Store's automated test. The valid baseline and no-change runs return one free `monitor-status` dataset receipt. Four new regression tests cover prefilled input, free baseline and repeat receipts, and distinct paid amendment records. Existing verified amendment billing and duplicate suppression remain intact.

# Actor input Schema

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

Stable name for an independently tracked collection. Keep unchanged across runs.

## `noticeIds` (type: `array`):

Published numbers like 200138-2026, TED UUIDs, or UUID-01. First run establishes baseline.

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

TED expert query used only to select initial published notices. New matching notices are not automatically enrolled.

## `watchedFields` (type: `array`):

Monitor lot deadline, scope, quantity and/or estimated value. Fields absent from source are marked unreadable. Valid values: deadline, scope, quantity, estimatedValue.

## `initialSnapshot` (type: `object`):

JSON object: {"baselinePublicationNumbers":\["200138-2026"]}. Only use to anchor an earlier published version for replay/audit.

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

Only verified changed notices are counted. This is not a maximum TED query count.

## `maxTrackedNotices` (type: `integer`):

Safety cap on monitored published notices; filters above this cap fail.

## `pageSize` (type: `integer`):

Each public search API iteration page, maximum 100 here.

## Actor input object example

```json
{
  "monitorId": "default",
  "noticeIds": [
    "200138-2026"
  ],
  "expertQuery": "",
  "watchedFields": [
    "deadline",
    "scope",
    "quantity"
  ],
  "maxResults": 25,
  "maxTrackedNotices": 25,
  "pageSize": 50
}
```

# Actor output Schema

## `amendments` (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 = {
    "noticeIds": [
        "200138-2026"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("afkdigital/ted-tender-amendment-deadline-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 = { "noticeIds": ["200138-2026"] }

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/ted-tender-amendment-deadline-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 '{
  "noticeIds": [
    "200138-2026"
  ]
}' |
apify call afkdigital/ted-tender-amendment-deadline-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,afkdigital/ted-tender-amendment-deadline-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/8nljqILw6OLNSYmPF/builds/c3pT4IUcLesFMx1jW/openapi.json
