# EU & UK Public Tender Amendment & Deadline Diff Monitor (`afkdigital/newapi18-tender-amendment-deadline-monitor`) Actor

Official TED eForms and Find a Tender OCDS amendment tracking. Verified before/after deadlines, persistent change deduplication and pay only for unique changes.

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

## Pricing

$6.00 / 1,000 verified 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

## NEWAPI18 — EU & UK Public Tender Amendment & Deadline Diff Monitor

**Version:** 1.0.0 | **Actor:** `newapi18-tender-amendment-deadline-monitor` | **Product code:** NEWAPI18

Detect **verified amendments**, including before/after submission deadlines, in **Find a Tender (United Kingdom)** and **Tenders Electronic Daily (European Union)**. A change is emitted only where *two independently retrievable official versions* show a meaningful difference. No emails, SMS, chat messages, or third-party notifications are sent.

### Official sources and provenance

- **UK**: [Find a Tender OCDS release packages](https://www.find-tender.service.gov.uk/apidocumentation/1.0/GET-ocdsReleasePackages) and [OCDS record packages](https://www.find-tender.service.gov.uk/apidocumentation/1.0/GET-ocdsRecordPackages). The public API is mapped to OCDS 1.1.5 and published under the [UK Open Government Licence](https://www.find-tender.service.gov.uk/Developer/Documentation). `records[].releases[]` provides original releases and `tenderUpdate` versions, with their unique release ID and publication time. Changes are compared only within the tender-stage chain, not between planning and tender.
- **EU**: [TED public Search API v3](https://docs.ted.europa.eu/api/latest/search.html) and [official notice XML](https://docs.ted.europa.eu/ODS/latest/reuse/download-direct.html). TED eForms Change notices reference the previous published notice through **BT-758 / ChangedNoticeIdentifier**, either OJ S publication number or exact UUID+version. The current and predecessor are fetched separately as published records through TED Search API v3, including lot IDs and timezone-preserving deadline fields. Direct XML is separately validated in regression tests and can be blank from cloud egress, so runtime uses authoritative Search API fields. Non-change notices are **not** billed. See [TED Change Notice schema](https://docs.ted.europa.eu/eforms/latest/schema/change-notice.html).
- **No** private scraping, login credentials for TED/UK, unofficial mirrors, or emails.

### Input

| Field | Meaning | Default |
|---|---|---|
| `noticeIds` | Specific IDs: `UK:ocds-h6vhtk-0778b4`, `UK:099899-2026`, `EU:722708-2026` | `[]` (discover via filters) |
| `sources` | `["UK", "EU"]` | Both |
| `cpvCodes` | Classification prefixes, e.g. `["45","72000000"]` | All |
| `countryCodes` | ISO alpha-2 or alpha-3. UK is GB/GBR/UK | All |
| `daysBack` | Days of recent published EU notices or updated UK releases | 2 |
| `maxResults` | Maximum **verified change records**, not raw notices | 10 |
| `maxCostUsd` | Maximum **actor PPE events** for this run, $0.006 per change | $0.06 |
| `maxNoticesScanned` | Upper bound of retrieved feed rows **per source** | 80 |
| `maxRequests` | Maximum official-source HTTP requests including retries | 130 |
| `timeoutSeconds` | Runtime safety cap (maximum 270) | 240 |
| `monitorId` | Persistent isolation namespace for a recurring monitor | `default` |

Use `noticeIds` *or* discovery filters. When exact publication IDs (e.g. `UK:099899-2026` or `EU:722708-2026`) are supplied, only the selected official amendment is eligible regardless of its date; its predecessor is read solely as evidence, not emitted as a newly discovered change. An `UK:ocds-...` process ID follows the regular `daysBack` window and skips historical corrections. Discovery is also recent-date bounded. CPV/country filtering applies to discovered rows and EU targeted lookups; UK targeted OCIDs are filtered by CPV too.

The number of scanned notices is deliberately capped. On unusually busy dates, the search may return **a subset** rather than every eligible notice. Increase `maxNoticesScanned` and `maxRequests` for comprehensive daily monitoring. Treat a zero-result run as "**no verified change observed within the scan limits**," not proof that none exist.

Example:

```json
{
  "noticeIds": ["EU:722708-2026", "UK:ocds-h6vhtk-0778b4"],
  "maxResults": 2,
  "maxCostUsd": 0.012,
  "monitorId": "sample-verification"
}
```

### Output

One dataset row per distinct **official predecessor → successor release**:

```json
{
  "source": "EU_TED",
  "notice_id": "722708-2026",
  "authority_name": "...",
  "amendment_date": "2026-10-20+02:00",
  "changed_fields": [
    {
      "field": "submission_deadline",
      "lot_id": "LOT-0001",
      "previous": "2026-10-27T11:00:00+01:00",
      "current": "2026-10-30T11:00:00+01:00"
    }
  ],
  "previous_deadline": "2026-10-27T11:00:00+01:00",
  "new_deadline": "2026-10-30T11:00:00+01:00",
  "change_summary": "submission_deadline (LOT-0001): ...",
  "official_url": "https://ted.europa.eu/en/notice/-/detail/722708-2026",
  "observed_at": "<UTC observation timestamp>",
  "previous_notice_id": "625808-2026",
  "notice_type": "ContractNotice",
  "deadline_timezone": "+01:00",
  "change_id": "<stable sha256>",
  "source_provenance": "official_change_notice_BT-758"
}
```

Extra fields include `previous_notice_id`, `notice_type`, `deadline_timezone`, `change_id` and `source_provenance`. `changed_fields` preserves per-lot differences. For a single deadline the before/after top-level fields are strings; for multiple lots, they can be objects indexed by lot ID. Input/source date+offset are preserved; equivalent instants expressed in different timezones alone do **not** create a change.

Both formats are natively exportable from the Apify Dataset:

- JSON: `GET /v2/datasets/{datasetId}/items?format=json`
- CSV: `GET /v2/datasets/{datasetId}/items?format=csv&bom=true`

### Billing, state and recovery

**Pay-per-event (PPE): $0.006 per unique verified change record, or $6 per 1,000.** No charge for baseline observations, unchanged re-runs, or failed source lookups. The Actor uses the **single** custom event `verified-change`; do not configure an additional paid dataset-item/start event, as this would double-charge. Platform/compute/storage usage is separate from the publisher's revenue margin; see [Apify PPE documentation](https://docs.apify.com/actors/publishing/monetize/pay-per-event).

A *named Apify Key-Value Store*, `newapi18-tender-monitor-state-v1`, keeps durable observations and event-hash journals under SHA-256-scoped `monitorId` prefixes. A rerun of the same monitor skips already emitted source-version transitions even if the tender is still in the lookback window. Distinct monitors have separate ledgers within the owning account. Access is managed by Apify account permissions; no credentials or private data are logged.

**Interruption policy:** before a billable dataset push, the event hash is recorded as `reserved`; after successful push, it is marked `emitted`. On restart, both `reserved` and `emitted` are suppressed to prevent double billing. For rare ambiguous failures between reservation and dataset write, inspect the referenced `run_id` and `dataset_id` in the private journal, reconcile whether the dataset item exists, and only then manually clear a truly unbilled reservation. This prioritizes **at-most-once charging** over automatic replay of an ambiguous payment. Concurrent executions with the *same* `monitorId` should be serialized; Apify KV does not expose compare-and-swap locking.

On 429/5xx errors, the client retries with bounded exponential backoff. Unknown schemas, unavailable predecessors, missing BT-758, cross-schema legacy TED notices, or unverified histories produce **no fabricated changes**. Request, time, result and cost caps terminate work; repeating a run resumes via idempotent official-source rescanning, not an in-memory-only cursor.

### Verified real historical examples (2026-10-22)

- **UK**: `ocds-h6vhtk-0778b4`, releases `097692-2026` → `099899-2026`: submission deadline `2026-10-23T14:00:00+01:00` → `2026-10-30T14:00:00Z`. Find a Tender public OCDS record package is the source of both versions.
- **EU**: TED `625808-2026` → Change notice `722708-2026` (`LOT-0001`): deadline `2026-10-27T11:00:00+01:00` → `2026-10-30T11:00:00+01:00`. Both official XMLs were independently fetched in direct-source regression tests, and official Search API records were separately retrieved; the new notice's BT-758 references the previous publication.

### Local test and deployment

```bash
cd /opt/NEWAPI/NEWAPI18
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python -m unittest discover -s tests -v
npx apify-cli validate-schema
npx apify-cli push
```

Default input targets <5 minutes with an explicit 240-second app budget plus Apify run timeout. Live tests require public internet and respect rate limits. Do not copy `.venv`, API tokens, local Apify state or secrets into the Actor image.

**Version 1.0 limitations:** legacy TED-XML predecessor cross-schema diffs are conservatively skipped unless a reliable common mapping is implemented; certain UK updates that change fields not exposed in the compared OCDS subset may be skipped. This avoids false claims or unsupported fees.

# Actor input Schema

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

EU:722708-2026 or UK:099899-2026 or UK:ocds-h6vhtk-0778b4. Specific IDs override discovery.

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

Official sources

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

Empty means all. 2–8 digits.

## `countryCodes` (type: `array`):

Use GB, DE, FR or 3-letter variants. Empty means all.

## `daysBack` (type: `integer`):

Last N calendar days

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

Maximum change rows

## `maxCostUsd` (type: `number`):

$0.006 per unique verified change. Budget also respects the Apify per-run cap.

## `maxNoticesScanned` (type: `integer`):

Maximum scanned notices per source

## `maxRequests` (type: `integer`):

Maximum official requests

## `timeoutSeconds` (type: `integer`):

Runtime budget seconds

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

Use the same monitorId for deduplication on reruns. State is stored in a named private Apify KV store per account.

## Actor input object example

```json
{
  "noticeIds": [],
  "sources": [
    "UK",
    "EU"
  ],
  "cpvCodes": [],
  "countryCodes": [],
  "daysBack": 2,
  "maxResults": 10,
  "maxCostUsd": 0.06,
  "maxNoticesScanned": 80,
  "maxRequests": 130,
  "timeoutSeconds": 240,
  "monitorId": "default"
}
```

# Actor output Schema

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

No description

## `csv` (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("afkdigital/newapi18-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("afkdigital/newapi18-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 '{}' |
apify call afkdigital/newapi18-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/newapi18-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/RIjALnareb0wAQbfu/builds/4fGZbuXCUFl2dasG4/openapi.json
