# Construction Tender Radar DE — TED (`danielmichalek/construction-tender-radar-de-ted`) Actor

Monitor single-lot German construction notices on TED. Track source-linked deadline changes, published outcome codes and official notice links. Includes coverage reasons, JSON and CSV; excludes multi-lot notices and does not cover all German tenders.

- **URL**: https://apify.com/danielmichalek/construction-tender-radar-de-ted.md
- **Developed by:** [Daniel Michałek](https://apify.com/danielmichalek) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 qualified ted notices

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

## Construction Tender Radar DE - TED

Find single-lot German construction works in TED notices, with source deadlines, official links and exact parent-linked comparisons. Coverage excludes unsupported notices, German tenders absent from TED and complete cancellation history.

### Quick start

Choose an inclusive publication window of 1-31 days, optional `cpvPrefixes`/performance `nutsPrefixes` and a small result limit. Download the dataset/CSV and read coverage. These historical dates include expired notices; update them for current monitoring.

```json
{"dateFrom":"2026-10-01","dateTo":"2026-10-20","cpvPrefixes":["45"],"maxResults":3,"maxPages":2,"maxParentLookups":2,"maxRunTimeMs":30000}
```

No TED account, key or proxy is needed. No contacts, full descriptions or attachments are requested. German titles are retained; missing facts are not invented.

### Qualification and fields

Only `cn-standard`/`can-standard` notices with exactly one lot qualify. Lot MAIN CPV must match a configured prefix beginning `45`, main nature must be `works`, and performance countries must identify Germany alone (`DEU`). NUTS filters lot performance. Buyer/procedure fields or additional CPV never substitute.

Multi-lot notices are excluded because flat arrays cannot prove each lot's field associations. Absence/exclusion does not prove no tender exists. Identical UUID/version rows are deduplicated per run; conflicting copies are excluded.

| Fields | Meaning |
| --- | --- |
| `recordKey`, `publicationNumber`, `noticeId`, `noticeVersion`, `procedureId`, `lotId`, `noticeType` | Source identity and notice type. |
| `titleDe`, `mainCpv`, `additionalCpv`, `mainNature`, `performanceCountries`, `performanceNuts` | German title and lot classification/location. |
| `publicationDateRaw`, `deadlineDateRaw`, `deadlineTimeRaw`, `deadline` | Raw dates/time; combined deadline requires unambiguous values and matching offsets, otherwise null. |
| `eventType`, `winnerSelectionStatus`, `nonAwardReason`, `reportedNoticeState`, `changeReason` | Source codes and the classifications explained below. |
| `changeParentBusinessId`, `parentReference`, `comparison`, `lineageComplete` | Explicit parent evidence; lineageComplete is always false. |
| `sourceUrl`, `sourceXmlUrl`, `observedAt`, `sourceAttribution`, `transformations` | Official links, observation time, credit and processing labels. |
| `qualityFlags`, `observationChange` | Missing/ambiguous fields and comparison with your supplied snapshot. |

### Lifecycle meaning

`competition_observation` does not mean bidding remains open. `deadline_changed` requires the exact parent UUID/version, same procedure/lot and two known deadlines. `comparison` gives before/after, delta seconds and both official links. Both UUIDs can have version 1; version number alone proves no chronology.

`lot_closed_no_winner` preserves `clos-nw`: no winner chosen and competition closed. It is not automatically formal cancellation. `award_reported` preserves `selec-w`: at least one winner chosen; no winner identity, award value or signed-contract claim is extracted. `all-rej` includes tenders, requests or projects withdrawn or found inadmissible, not simply "all bids rejected."

`reported_notice_state` preserves explicit `cancel`, `cancel-intent` or `susp-review` codes separately; it does not infer the status of the entire procedure. Missing, ambiguous, unavailable, out-of-scope or capped parents yield `comparison.status: unresolved`, with no invented prior value. Only one explicit parent is compared, never full ancestry.

Historical pair 644373-2026 to 674305-2026 proves a move from 2026-10-05T10:30:00+02:00 to 2026-10-07T10:30:00+02:00 (+172800 seconds), not a live opportunity.

### Outputs and snapshots

Dataset: qualified observations in JSON or standard Apify exports. `OUTPUT`: counts, exclusions, unresolved comparisons, errors and coverage. `TENDERS`: CSV with formula-like cells escaped. `SNAPSHOT`: compact `{recordKey,contentHash}` pairs for the next input's `previousSnapshots`.

`observationChange` is new/unchanged/changed against supplied source-field hashes, not a legal amendment. Hashes do not authenticate history; absence never means removal. Parent availability alone does not change that hash.

### Bounds and coverage

Limits: `maxResults` 20, `maxPages` 2, `pageSize` 20, `maxParentLookups` 10, `maxHttpAttempts` 14 including retries, `maxRetries` 1, `timeoutMs` 30000 per request/body, 2 MiB per response and 16 MiB total. `maxRunTimeMs` is at most 60000 including spacing/backoff; storage adds time. Input: at most 1 MiB and 1000 snapshots.

Requests start at least 2.1 seconds apart: our policy, not a claimed TED limit. Bounded Retry-After waits are respected. HTTP 401/403 stops source calls; no challenge bypass. Interrupted fetching fails closed on restart so request/byte/spacing budgets cannot reset; start a fresh explicitly scoped run. Prepared output, delivery and artifact phases support safe same-run recovery.

Stateless page-number results can change mid-run; there is no stable continuation cursor. `complete:true` requires a short page consistent with the API total, no exclusions/errors and resolved comparisons. It applies only to this bounded query observation, not all German procurement, full lineage or cancellation coverage. Caps, ambiguity and source failures produce partial/failed reports. Narrow or overlap publication windows deliberately.

### Pricing and recovery

Pricing is **USD 0.01 per delivered qualified distinct observation (USD 10 per 1,000)** via `notice-returned`, with platform usage included. Check the Actor Pricing tab for the actual configuration. No startup fee or notice event applies to empty, excluded or failed observations. Delivered notices with unresolved comparisons are billable; parent lookups and artifacts have no separate event. Separate runs can deliver and charge for the same notice again, including unchanged observations.

A zero spending limit stops before retrieval/delivery. Paid same-run recovery checks exact stored rows and named charge counts; ambiguous acknowledgement stops for reconciliation instead of blindly appending/charging again. Free configuration emits no product event; platform usage follows its selected pricing model.

### Source and authority

Source: **TED (Tenders Electronic Daily), Publications Office of the European Union**, via the [Search API](https://docs.ted.europa.eu/api/latest/search.html). Metadata is normalized; deltas, hashes and quality flags are computed. This independent service is not an official/endorsed TED product. The official notice is authoritative.

Official API documentation supports anonymous access and commercial added-value services. This is not a licence to mirror all content or attachments. The separate [TED legal notice](https://ted.europa.eu/legal-notice) applies; no blanket CC BY licence is asserted.

# Actor input Schema

## `dateFrom` (type: `string`):

Required real YYYY-MM-DD, inclusive publication date as recorded by TED. The window must be 1–31 calendar days; this is not a bid deadline. Prefill is a historical example; replace both dates for live monitoring.

## `dateTo` (type: `string`):

Required real YYYY-MM-DD, inclusive publication date as recorded by TED. The window must be 1–31 calendar days; this is not a bid deadline. Prefill is a historical example; replace both dates for live monitoring.

## `cpvPrefixes` (type: `array`):

Return a notice only when its single lot main CPV begins with one of these 2–8 digit construction prefixes. Additional CPV codes do not qualify a notice.

## `nutsPrefixes` (type: `array`):

Optional OR filter on the single lot place of performance, e.g. DE2 or DEG0R. Missing or nonmatching lot NUTS is excluded. Buyer location is never substituted.

## `previousSnapshots` (type: `array`):

Paste a prior compact SNAPSHOT array to label source metadata new/unchanged/changed. Absence is never removal. Hashes are comparisons, not authentication. Maximum entire input 1 MiB.

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

Maximum distinct qualifying notice versions delivered. Reaching this bound reports partial coverage. Prefill returns at most 3 observations for the historical example.

## `maxPages` (type: `integer`):

Maximum stateless page-number search pages. This is bounded observation, not a complete crawl or stable cursor.

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

Maximum raw notices requested per search page, before strict single-lot filtering.

## `maxParentLookups` (type: `integer`):

Look up only the explicitly referenced UUID and version. No recursive ancestry traversal. Unresolved references are reported.

## `maxHttpAttempts` (type: `integer`):

Maximum source attempts including parent reads and retries. An interrupted retrieval fails closed on restart so these counters cannot reset.

## `timeoutMs` (type: `integer`):

Timeout for each HTTP attempt including body streaming, also bounded by the remaining source time allowance.

## `maxRetries` (type: `integer`):

Retry only transient transport/timeouts, HTTP 429 and server failures. HTTP 403 is terminal; there is no challenge bypass.

## `maxRunTimeMs` (type: `integer`):

Includes request spacing, attempts, parent lookups and backoff. The checkpoint keeps this deadline across restarts. Storage finalization is additional.

## Actor input object example

```json
{
  "dateFrom": "2026-10-01",
  "dateTo": "2026-10-20",
  "cpvPrefixes": [
    "45"
  ],
  "nutsPrefixes": [],
  "previousSnapshots": [],
  "maxResults": 3,
  "maxPages": 2,
  "pageSize": 10,
  "maxParentLookups": 10,
  "maxHttpAttempts": 14,
  "timeoutMs": 10000,
  "maxRetries": 1,
  "maxRunTimeMs": 60000
}
```

# Actor output Schema

## `notices` (type: `string`):

No description

## `report` (type: `string`):

No description

## `snapshot` (type: `string`):

No description

## `csv` (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 = {
    "dateFrom": "2026-10-01",
    "dateTo": "2026-10-20",
    "maxResults": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/construction-tender-radar-de-ted").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 = {
    "dateFrom": "2026-10-01",
    "dateTo": "2026-10-20",
    "maxResults": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("danielmichalek/construction-tender-radar-de-ted").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 '{
  "dateFrom": "2026-10-01",
  "dateTo": "2026-10-20",
  "maxResults": 3
}' |
apify call danielmichalek/construction-tender-radar-de-ted --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danielmichalek/construction-tender-radar-de-ted"
        }
    }
}
```

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/GlhkWJWd8ya44cL8F/builds/iFIxnjc5OKmh3TGaa/openapi.json
