# EU TED Tender Monitor — Procurement Notice Alerts API (`marcuslee/tender-monitor`) Actor

Monitor EU TED API for new procurement tenders matching your criteria. Webhook alerts on new/changed/deadline\_changed events.

- **URL**: https://apify.com/marcuslee/tender-monitor.md
- **Developed by:** [Marcus Lee](https://apify.com/marcuslee) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## 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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## Public Tender Monitor — Apify Actor

Monitor EU TED API for new procurement tenders matching your criteria. Emits structured events to Apify dataset and delivers signed webhooks.

### Quick Start

```json
{
  "search_queries": [
    {
      "keywords": ["software", "IT services", "cloud infrastructure"],
      "cpv_codes": ["72000000", "48000000"],
      "countries": ["DE", "FR", "NL", "ES"],
      "notice_types": ["contract-notice", "prior-information-notice"],
      "min_value_eur": 50000,
      "max_deadline_days": 60
    }
  ]
}
````

### Features

- **TED API v3** — queries using expert search syntax, no auth required
- **Multi-query** — 1-20 independent search queries per run
- **Smart dedup** — cross-query deduplication (one event per notice, merged `matched_queries`)
- **Delta detection** — baseline run stores state; subsequent runs emit new/changed/deadline\_changed events
- **Webhook delivery** — HMAC-SHA256 signed payloads with retry, DLQ, and SSRF protection
- **Free Actor** — no separate Actor charge; ordinary Apify platform usage still applies
- **Crash-consistent** — 7-step idempotent pipeline with outbox persistence

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `search_queries` | array\[object] | yes | 1-20 search queries |
| `max_results_per_query` | integer | no | Maximum notices fetched per query (default: `100`, range: `1`-`1000`) |
| `webhook_url` | string | no | Public HTTPS endpoint for event delivery |
| `webhook_secret` | string (secret) | no | HMAC signing key (required if webhook\_url set) |
| `webhook_filter` | object | no | Alert on event types (default: `["new"]`) |
| `self_test` | boolean | no | Probe TED API without monitoring |
| `replay_dlq` | boolean | no | Replay dead-letter queue events |

#### Search Query

| Field | Type | Description |
|---|---|---|
| `keywords` | string\[] | Match against notice title |
| `cpv_codes` | string\[] | EU CPV codes (8 digits, e.g. `72000000`) |
| `countries` | string\[] | Buyer country codes (ISO 3166-1 alpha-2, e.g. `DE`) |
| `notice_types` | string\[] | `contract-notice`, `prior-information-notice`, etc. |
| `min_value_eur` | integer | Minimum estimated contract value |
| `max_deadline_days` | integer | Notices closing within N days |

### Output

#### Dataset (event stream)

```json
{
  "event_id": "sha256:tender-monitor:237195-2016:new:01",
  "event_type": "new",
  "publication_number": "237195-2016",
  "notice_version": "01",
  "title": "Cloud infrastructure modernization services",
  "buyer": "Bundesministerium des Innern",
  "buyer_country": "DE",
  "notice_type": "contract-notice",
  "cpv_codes": ["72000000", "48810000"],
  "deadline": "2026-08-15T17:00:00Z",
  "estimated_value_eur": 2500000,
  "first_seen_at": "2026-07-12T10:30:00Z",
  "source": "TED",
  "source_url": "https://ted.europa.eu/udl?uri=TED:NOTICE:237195-2016:2026:TEXT",
  "matched_queries": [0, 2]
}
```

#### KV Store (run summary)

```json
{
  "run_mode": "delta",
  "run_timestamp": "2026-07-12T10:30:00Z",
  "queries_executed": 3,
  "queries_succeeded": 3,
  "queries_failed": 0,
  "total_notices_fetched": 145,
  "new_events": 12,
  "changed_events": 2,
  "deadline_changed_events": 1,
  "webhook_delivered": 14,
  "webhook_failed": 1,
  "webhook_dlq": 1,
  "status": "OK"
}
```

### Webhook Payload

HMAC-SHA256 signed POST to your endpoint. Headers:

- `X-Tender-Monitor-Signature`: `v1=<hex>` (HMAC-SHA256 of body + timestamp + delivery\_id)
- `X-Tender-Monitor-Timestamp`: ISO 8601
- `X-Tender-Monitor-Delivery-Id`: UUID

Receiver must verify signature and deduplicate by `delivery_id`.

### Pricing

This Actor is **free**. It has no separate Actor event charge. You pay only the normal Apify platform usage consumed by runs and storage under your Apify plan.

### Support this Actor

If it is useful, bookmark it and leave an honest Apify review. If something does not work as expected, use the Actor's Issues tab so it can be fixed.

### Error Codes

| Code | Meaning |
|---|---|
| `ERR_CONNECT` | Network error connecting to TED API |
| `ERR_RATE_LIMITED` | TED API rate limited (429) after retries |
| `ERR_TIMEOUT` | TED API request timed out |
| `ERR_PARSE` | TED API response parse failure |
| `ERR_HTTP_NNN` | Unexpected HTTP status |
| `ERR_CONCURRENT_RUN` | Overlapping run detected |
| `ERR_STATE_CORRUPT` | State read failed, reset required |
| `ERR_BILLING` | Internal event-accounting hook failed; the Actor is currently free |
| `ERR_PARTIAL` | Some queries failed |
| `ERR_PAGINATION_LIMIT` | Query exceeded 10K result limit |
| `ERR_INTERNAL` | Internal error |

### Limits

- 1-20 search queries per input
- Maximum `max_results_per_query` of 1,000 notices per query (default: 100)
- Webhook: 100 events/batch, 1MB payload
- State retention: 30 days for snapshots
- DLQ: max 100 failed events

### Development

```bash
pip install -r requirements.txt -r requirements-dev.txt
pytest
```

### Self-test

Run with `self_test: true` to verify TED API connectivity. Note: `search_queries` is still required (used for the probe query):

```json
{
  "self_test": true,
  "search_queries": [{"keywords": ["test"]}]
}
```

### Non-affiliation disclaimer

Not affiliated with, endorsed by, or sponsored by the EU Publications Office. TED data is published under EU open data terms (CC BY 4.0).

# Actor input Schema

## `search_queries` (type: `array`):

1-20 search queries. Each query runs independently.

## `max_results_per_query` (type: `integer`):

Maximum notices fetched per query

## `webhook_url` (type: `string`):

Public HTTPS URL for event delivery

## `webhook_secret` (type: `string`):

Secret key for HMAC-SHA256 signature

## `webhook_filter` (type: `object`):

Control which event types trigger webhooks

## `self_test` (type: `boolean`):

Probe TED API connectivity without monitoring

## `replay_dlq` (type: `boolean`):

Replay dead-letter queue events

## Actor input object example

```json
{
  "max_results_per_query": 100,
  "webhook_filter": {
    "alert_on": [
      "new"
    ]
  },
  "self_test": false,
  "replay_dlq": false
}
```

# Actor output Schema

## `results` (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("marcuslee/tender-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("marcuslee/tender-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 marcuslee/tender-monitor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "EU TED Tender Monitor — Procurement Notice Alerts API",
        "description": "Monitor EU TED API for new procurement tenders matching your criteria. Webhook alerts on new/changed/deadline_changed events.",
        "version": "0.1",
        "x-build-id": "9agSoY3tqBXdYudaM"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/marcuslee~tender-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-marcuslee-tender-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/marcuslee~tender-monitor/runs": {
            "post": {
                "operationId": "runs-sync-marcuslee-tender-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/marcuslee~tender-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-marcuslee-tender-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "search_queries"
                ],
                "properties": {
                    "search_queries": {
                        "title": "Search Queries",
                        "minItems": 1,
                        "maxItems": 20,
                        "type": "array",
                        "description": "1-20 search queries. Each query runs independently.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "keywords": {
                                    "type": "array",
                                    "title": "Keywords",
                                    "description": "Match against notice title",
                                    "items": {
                                        "type": "string"
                                    },
                                    "default": [],
                                    "editor": "stringList"
                                },
                                "cpv_codes": {
                                    "type": "array",
                                    "title": "CPV Codes",
                                    "description": "EU procurement classification codes (8 digits)",
                                    "items": {
                                        "type": "string"
                                    },
                                    "default": [],
                                    "editor": "stringList"
                                },
                                "countries": {
                                    "type": "array",
                                    "title": "Countries",
                                    "description": "Buyer country codes (ISO 3166-1 alpha-2)",
                                    "items": {
                                        "type": "string"
                                    },
                                    "default": [],
                                    "editor": "stringList"
                                },
                                "notice_types": {
                                    "type": "array",
                                    "title": "Notice Types",
                                    "description": "E.g. contract-notice, prior-information-notice",
                                    "items": {
                                        "type": "string"
                                    },
                                    "default": [],
                                    "editor": "stringList"
                                },
                                "min_value_eur": {
                                    "type": "integer",
                                    "title": "Min Value (EUR)",
                                    "description": "Minimum estimated contract value",
                                    "editor": "number"
                                },
                                "max_deadline_days": {
                                    "type": "integer",
                                    "title": "Max Deadline (days)",
                                    "description": "Only notices with deadline within this many days",
                                    "editor": "number"
                                }
                            }
                        }
                    },
                    "max_results_per_query": {
                        "title": "Max Results Per Query",
                        "minimum": 1,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "Maximum notices fetched per query",
                        "default": 100
                    },
                    "webhook_url": {
                        "title": "Webhook URL",
                        "pattern": "^https://",
                        "type": "string",
                        "description": "Public HTTPS URL for event delivery"
                    },
                    "webhook_secret": {
                        "title": "Webhook Secret",
                        "type": "string",
                        "description": "Secret key for HMAC-SHA256 signature"
                    },
                    "webhook_filter": {
                        "title": "Webhook Filter",
                        "type": "object",
                        "description": "Control which event types trigger webhooks",
                        "properties": {
                            "alert_on": {
                                "type": "array",
                                "title": "Alert On",
                                "description": "Event types to deliver",
                                "items": {
                                    "type": "string"
                                },
                                "default": [
                                    "new"
                                ],
                                "editor": "stringList"
                            }
                        },
                        "default": {
                            "alert_on": [
                                "new"
                            ]
                        }
                    },
                    "self_test": {
                        "title": "Self-test Mode",
                        "type": "boolean",
                        "description": "Probe TED API connectivity without monitoring",
                        "default": false
                    },
                    "replay_dlq": {
                        "title": "Replay DLQ",
                        "type": "boolean",
                        "description": "Replay dead-letter queue events",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
