# German Tender Monitor – New & Changed Public Tenders (DE) (`datvanu/de-tender-monitor`) Actor

Daily feed of German public tenders from the official oeffentlichevergabe.de Open Data API (CC0). Filter by CPV, keywords, Bundesland/NUTS, procedure type. Only new or changed notices, deadline countdown, no personal contact data.

- **URL**: https://apify.com/datvanu/de-tender-monitor.md
- **Developed by:** [Kilian Schieri](https://apify.com/datvanu) (community)
- **Categories:** Business, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.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/actors/running/actors-in-store.md#pay-per-usage

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

## German Tender Monitor – new & changed public tenders (Germany)

Daily feed of **German public procurement notices** (Bund, Länder, Kommunen) from the **official Open Data API of
[oeffentlichevergabe.de](https://oeffentlichevergabe.de)** (Bekanntmachungsservice, CC0 licence).
Run it on a schedule and get **only notices you have not seen yet** – plus **updates** when a known procedure gets a
correction or a deadline extension (`changeType: UPDATED`).

### Why this Actor

- **Official source, no scraping, no login** – bulk export of all notices published on the Bekanntmachungsservice, including national (below-threshold) notices that never reach TED.
- **Only new or changed** – dedupe state is stored in a named key-value store for 120 days. You never pay twice for the same notice.
- **Change detection** – corrections / deadline extensions are delivered as `UPDATED` with `previousDeadline` and `deadlineChanged`.
- **Filters** – CPV codes *including all sub-codes*, keywords (title, description, lot titles), exclude words, Bundesland or NUTS region, procedure type, notice category, minimum days left.
- **No personal data** – contact persons, e-mail addresses, phone and fax numbers are never output; free-text fields are scrubbed; buyers flagged as natural persons are withheld.
- **Same field style as our EU Tender Monitor (TED)** – combine both for full coverage.

### Input (main fields)

| Field | Example | Notes |
|---|---|---|
| `cpvCodes` | `["72000000", "4882"]` | Trailing zeros = whole branch (72000000 → 72\*). |
| `keywords` / `excludeKeywords` | `["Software"]` | Case-insensitive, OR. |
| `regions` | `["Bayern", "NRW", "DE212"]` | Bundesland name/code or NUTS prefix. `regionScope`: place of performance, buyer or both. |
| `procedureTypes` | `["open"]` | Groups `open`, `restricted`, `negotiated`, `competitive-dialogue`, `innovation-partnership`, `other` or raw eForms codes. |
| `noticeCategories` | `["competition", "change"]` | Also `result`, `planning`, `cont-modif`, `dir-awa-pre`. |
| `daysBack` or `dateFrom`/`dateTo` | `3` | Publication days (Europe/Berlin), up to yesterday, max. 62 per run. |
| `onlyNewOrChanged` | `true` | Dedupe + change detection. `stateStoreName` separates monitors. |
| `maxItems` | `200` | Cost cap per run; the rest stays "new" for the next run. |

Recommended schedule: daily, `daysBack: 3`, with `onlyNewOrChanged: true` (overlap is free – dedupe removes repeats). The form starts with `onlyNewOrChanged` off so a test run always shows results.

### Output (one item per procedure)

`id`, `noticeVersion`, `procedureId`, `changeType` (NEW/UPDATED), `previousDeadline`, `deadlineChanged`, `publicationDay`,
`noticeType`, `noticeCategory`, `legalBasis`, `title`, `description`, `contractNature`, `buyerName`, `buyerId`, `buyerCity`,
`buyerPostalCode`, `buyerNuts`, `buyerState`, `buyerStateSource` (`nuts` or `postalCode`), `cpvCodes`, `mainCpv`, `placeOfPerformanceNuts`, `states`, `procedureType`,
`procedureGroup`, `deadline`, `deadlines`, `daysLeft`, `lotCount`, `estimatedValue`, `currency`, `documentsUrl`,
`changedNoticeId`, `changeReason`, `url` (notice page on oeffentlichevergabe.de), `source`.

```json
{ "changeType": "NEW", "id": "0bcc9d85-0a37-4ea8-aea7-d2e8968c5252", "procedureId": "8ae715fa-3dbf-4829-8ad5-68b3c3d4f948",
  "title": "Vertrag über die Wartung und Inspektion von Dräger Panorama Nova Atemschutz-Vollmasken",
  "buyerName": "Bundesamt für Strahlenschutz", "buyerCity": "Salzgitter", "legalBasis": "de-uvgo",
  "procedureType": "de-open", "procedureGroup": "open", "deadline": "2026-11-17T10:00:00+01:00", "daysLeft": 20,
  "documentsUrl": "https://www.evergabe-online.de/tenderdetails.html?id=902432",
  "url": "https://oeffentlichevergabe.de/ui/de/search/details?noticeId=0bcc9d85-0a37-4ea8-aea7-d2e8968c5252" }
```

#### Bundesland when a notice has no NUTS code

Many national notices carry a postal code but no NUTS code. The Actor then derives the **Bundesland from the postal code**
(buyer address or place of performance) – `buyerStateSource: "postalCode"`. Land-level filters (`Bayern`, `NW` …) use it;
finer NUTS filters (`DE212`) only match real NUTS codes. If both exist, the NUTS code wins.

### How much does it cost? (pay per event)

| Event | Price |
|---|---|
| `notice` – per delivered notice (new or updated procedure) | **USD 0.004** |
| `apify-actor-start` – per run (Apify default) | USD 0.00005 |

Repeats are free (dedupe), so a daily monitor with ~10 matching notices costs about **USD 1.20 per month**.
Use `maxItems` as a hard cost cap. The current price is always shown on the Pricing tab.

### FAQ

- **Does it cover below-threshold tenders?** Yes – everything published on the Bekanntmachungsservice, national and EU.
- **Why no contact e-mail?** Personal data is never output. Use `documentsUrl` / `url` to reach the procurement platform.
- **Several monitors?** Give each one its own `stateStoreName`, otherwise they share the "already seen" list.
- **Integrations:** use Apify schedules plus webhooks, Make, Zapier, Slack or e-mail integrations on the dataset.

### Data source & licence

- API: `GET https://oeffentlichevergabe.de/api/notice-exports?pubDay=YYYY-MM-DD&format=eforms.zip` –
  [API documentation (Swagger)](https://oeffentlichevergabe.de/documentation/swagger-ui/opendata/index.html),
  [OpenAPI JSON](https://oeffentlichevergabe.de/documentation/api/opendata).
- Licence: [Open-Data-Richtlinie, section 4](https://oeffentlichevergabe.de/ui/en/Open-Data-Richtlinie) – data provided as Open Data
  is under **Creative Commons CC Zero (CC0)**; each OCDS export also states `"license": "https://opendefinition.org/licenses/cc-zero/"`.
- Postal code → Bundesland table built from [GeoNames](https://www.geonames.org/) postal code data (DE),
  licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/); compressed into postal-code ranges (`src/plz-de.js`).
- This Actor is not affiliated with the Beschaffungsamt des BMI / Bekanntmachungsservice. Always check the original notice
  and procurement documents before bidding. Data is available up to the previous day.

***

### Deutsch: Vergabe-Wächter Deutschland

Der Actor liefert täglich **neue und geänderte öffentliche Ausschreibungen in Deutschland** aus der offiziellen
Open-Data-Schnittstelle von oeffentlichevergabe.de (CC0). Er deckt Bund, Länder und Kommunen ab, auch
nationale Bekanntmachungen unterhalb der EU-Schwellenwerte.

- **Nur Neues:** Bereits gelieferte Verfahren werden 120 Tage gespeichert und nicht erneut ausgegeben (und nicht erneut berechnet).
- **Änderungserkennung:** Berichtigungen und Fristverlängerungen kommen als `UPDATED`, mit alter Frist (`previousDeadline`).
- **Filter:** CPV inklusive Unter-Codes, Stichworte und Ausschlusswörter, Bundesland oder NUTS-Code, Verfahrensart, Bekanntmachungsart, Mindest-Restlaufzeit.
- **Datenschutz:** Kontaktpersonen, E-Mail-Adressen, Telefon- und Faxnummern werden nie ausgegeben. Freitexte werden bereinigt, Auftraggeber, die natürliche Personen sind, werden ohne Namen geliefert.
- **Bundesland ohne NUTS-Code:** fehlt der NUTS-Code, wird das Land aus der Postleitzahl abgeleitet (`buyerStateSource: "postalCode"`, Tabelle aus GeoNames, CC BY 4.0). Das gilt nur für Filter auf Landesebene.
- **Preis:** 0,004 USD je gelieferter Bekanntmachung, Wiederholungen kostenlos.
- **Empfehlung:** täglicher Zeitplan mit `daysBack: 3`. Für den Zeitplan `onlyNewOrChanged` einschalten.

Quelle: [oeffentlichevergabe.de – Open-Data-Richtlinie (CC0)](https://oeffentlichevergabe.de/ui/de/Open-Data-Richtlinie) ·
[API-Dokumentation](https://oeffentlichevergabe.de/documentation/swagger-ui/opendata/index.html).
Vor einer Angebotsabgabe immer die Originalbekanntmachung und die Vergabeunterlagen prüfen.

# Actor input Schema

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

Any match. Sub-codes are included automatically: 72000000 or 72 = all IT services, 4882 = servers. Empty = all.

## `excludeCpvCodes` (type: `array`):

Skip notices whose CPV codes all fall under these codes (e.g. 45 = construction works).

## `keywords` (type: `array`):

Case-insensitive search in title, description and lot titles. Any keyword matches. Empty = no keyword filter.

## `excludeKeywords` (type: `array`):

Skip notices containing any of these words.

## `regions` (type: `array`):

Bundesland name or code (Bayern, BY, NRW, Berlin …) or NUTS prefix (DE21, DE212). Empty = all of Germany. If a notice has no NUTS code, the Land is derived from the postal code (Land-level filters only).

## `regionScope` (type: `string`):

Which address the region filter is matched against.

## `procedureTypes` (type: `array`):

Groups: open, restricted, negotiated, competitive-dialogue, innovation-partnership, other – or raw eForms codes (e.g. de-open, neg-w-call). Empty = all.

## `noticeCategories` (type: `array`):

Which notice types to include. Default: open calls for tender and their corrections.

## `minDaysLeft` (type: `integer`):

Skip notices closing sooner than this. Notices without a deadline are kept. Empty = no limit.

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

Number of publication days to scan, ending yesterday (data is published daily, up to yesterday). Ignored if dateFrom/dateTo is set.

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

Optional fixed window start (publication day, Europe/Berlin). Max. 62 days per run; data starts 2022-12-01.

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

Optional fixed window end. Defaults to yesterday; today is never available yet.

## `onlyNewOrChanged` (type: `boolean`):

Remembers delivered procedures for 120 days. A procedure is delivered again only when a newer notice (correction, deadline extension) appears – marked changeType UPDATED. Switch ON for scheduled monitoring (API default: on).

## `includeUpdates` (type: `boolean`):

If off, only brand-new procedures are delivered; corrections of known procedures are recorded but not output.

## `includeDescription` (type: `boolean`):

Adds the notice description (max. 1000 characters, e-mails/phone numbers/contact names removed).

## `maxItems` (type: `integer`):

Upper limit of notices delivered per run (cost cap). Notices above the limit stay new for the next run.

## `stateStoreName` (type: `string`):

Named key-value store holding the dedupe state. Use different names for independent monitors. Letters, digits and dashes.

## Actor input object example

```json
{
  "cpvCodes": [
    "72000000",
    "48000000"
  ],
  "regions": [
    "Bayern",
    "BW"
  ],
  "regionScope": "placeOrBuyer",
  "noticeCategories": [
    "competition",
    "change"
  ],
  "daysBack": 3,
  "onlyNewOrChanged": false,
  "includeUpdates": true,
  "includeDescription": true,
  "maxItems": 20
}
```

# Actor output Schema

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

All items of the default dataset (JSON). Add ?format=csv or xlsx for other formats.

## `runSummary` (type: `string`):

Counts, warnings and charged events of this run (key RUN_SUMMARY).

# 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 = {
    "cpvCodes": [
        "72000000",
        "48000000"
    ],
    "regions": [
        "Bayern",
        "BW"
    ],
    "onlyNewOrChanged": false,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("datvanu/de-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 = {
    "cpvCodes": [
        "72000000",
        "48000000",
    ],
    "regions": [
        "Bayern",
        "BW",
    ],
    "onlyNewOrChanged": False,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("datvanu/de-tender-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 '{
  "cpvCodes": [
    "72000000",
    "48000000"
  ],
  "regions": [
    "Bayern",
    "BW"
  ],
  "onlyNewOrChanged": false,
  "maxItems": 20
}' |
apify call datvanu/de-tender-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datvanu/de-tender-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/bHJ3dXkBV4MKIJlDg/builds/mfNlLBmSe6n3zltKt/openapi.json
