# EU & Poland Tenders Monitor: Public Procurement, TED + BZP (`atlasdataworks/procurement-monitor`) Actor

Public procurement tenders from TED (all EU) and BZP (Poland, below the EU threshold) in one actor and one normalized schema. Filter by CPV code, keyword and country; delta mode returns only new tender notices each run. Official APIs, no proxies. Pay-per-event.

- **URL**: https://apify.com/atlasdataworks/procurement-monitor.md
- **Developed by:** [Atlas Dataworks](https://apify.com/atlasdataworks) (community)
- **Categories:** Business, Lead generation, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 qualified-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

## EU & Poland Tenders Monitor: Public Procurement Notices from TED + BZP

Monitor **public procurement tenders** across the EU and Poland from one actor. It reads the two official sources, returns every tender notice in **one normalized schema**, and bills pay-per-event, so you pay per notice delivered:

- **TED** (Tenders Electronic Daily, `api.ted.europa.eu`), the EU's official journal of
  public procurement. Covers every EU country, including all Polish notices **above the EU
  threshold**. You can widen or narrow the country filter freely (default: Poland +
  neighbors).
- **BZP** (Biuletyn Zamówień Publicznych via `ezamowienia.gov.pl`), Poland's official
  domestic bulletin. Covers Polish notices **below the EU threshold**, which never reach
  TED.

Together the two sources cover every Polish public procurement tender, plus as much
of the rest of the EU as you ask for. Both are official open government APIs; no scraping
of third-party aggregators, no proxies, no credentials.

### The product: a delta feed, not a dump

With `deltaMode` on (the default), the actor remembers every notice it has already emitted
(in the named key-value store `dataworks-procurement-state`) and each run outputs **only
notices it has never emitted before**. Run it on a schedule and the dataset of each run is
a clean incremental feed:

1. Create a Task with your CPV codes / keywords / countries.
2. Schedule it (e.g. every 6 hours; both sources publish continuously on business days).
3. Consume each run's dataset (webhook, integration, or MCP): every record is new, no
   dedup work on your side.

The date window defaults to "since the last successful run" (first run: last 24 h), so a
missed schedule slot heals itself on the next run. A zero-emit delta run tells you which
kind of zero it was: the status message starts with "Nothing new" and counts the
notices fetched and already seen when the sources are alive (fine), but a source that fetched/matched
nothing at all is flagged as degraded in the status message and in
`OUTPUT.degradedSources`: treat that as a pipeline problem (state store deleted, API
change) rather than a quiet market, because Polish public procurement alone publishes
hundreds of notices every business day. Outside delta mode, a run where every requested
source matches zero notices fails red instead of shipping an empty dataset.

Set `deltaMode: false` for a one-off, stateless date-range query (backfills, research).

### Input

| Field | Default | Notes |
|---|---|---|
| `sources` | `both` | `ted`, `bzp`, or `both`. |
| `cpvCodes` | `[]` = all | CPV codes or prefixes (`"72"` = the whole IT-services family). Prefix/hierarchical matching on both sources. |
| `keywords` | `[]` = all | OR-combined. TED: server-side full-text search. BZP: matched against title + buyer name. |
| `tedCountries` | `["POL","DEU","CZE","SVK","LTU","UKR"]` | TED buyer-country filter, ISO 2- or 3-letter. Empty list = all EU. |
| `includeAwardNotices` | `false` | Also emit contract-award/result notices. |
| `dateFrom` / `dateTo` | last run → now | ISO dates. Explicit values override delta defaults. |
| `maxNotices` | `200` | Clamped to 1..5000, applied to the emitted feed. |
| `deltaMode` | `true` | See above. |

### Output: one normalized record for both sources

| Field | Example | Notes |
|---|---|---|
| `source` | `"ted"` / `"bzp"` | |
| `noticeId` | `"557023-2026"` / `"2026/BZP 00388143/01"` | Source-native id. |
| `uid` | `"ted:557023-2026"` | Globally unique, the dedup key. |
| `type` | `"contract-notice"` | `"contract-award"` | `"other"` | Normalized. |
| `rawType` | `"cn-standard"` / `"ContractNotice"` | The source's own type string. |
| `title` | `"Poland – IT services … umowa ramowa"` | TED: English preferred (EU-translated), original otherwise; BZP: Polish. `titleLang` says which. |
| `buyer` | `{ name, nameLang, city, province, country }` | `province` is a NUTS code where available. |
| `cpv` | `["72000000","72250000"]` | Normalized 8-digit codes, deduped. |
| `publishedAt` | `"2026-08-12"` / `"2026-08-12T04:50:05Z"` | TED publishes day granularity; BZP a full timestamp. |
| `deadlineAt` | `"2026-09-11"` | Tender-submission deadline when the notice carries one. |
| `belowEU` | `true` | BZP only, below-EU-threshold flag. |
| `sourceUrl` | notice page on ted.europa.eu / ezamowienia.gov.pl | Human-readable. |
| `rawLinks` | `{ html, xml, pdf }` | Machine formats (TED XML, BZP PDF). |
| `scrapedAt` | ISO timestamp | |

Each run also writes an `OUTPUT` summary (per-source fetch/match counts, already-seen
count, per-source errors) to its default key-value store; check it before trusting an
empty dataset.

### Pricing (pay-per-event)

| Event | Fires | Price |
|---|---|---|
| `qualified-notice` | per normalized notice delivered | **$0.002** ($2.00 / 1,000 notices) |
| `Actor Start` | once per run, charged by the platform | $0.00005 ($0.05 / 1,000 runs) |
| `Result` | per record stored, charged by the platform | $0.00001 ($0.01 / 1,000 records) |

The last two rows are Apify platform events rather than ours, listed so this table matches the
pricing shown on the Store page exactly. They matter most on a schedule: a delta run that finds
nothing new still pays the run charge, so an hourly schedule costs about $0.04 a month even in a
completely quiet month. Platform usage, compute and network, is bundled: no separate usage line.

You pay for delivered records, not runtime: filtered-out and already-seen notices cost
nothing. Runs respect your budget cap (`maxTotalChargeUsd`): when the cap is reached the
actor stops delivering, reports how much of the result set was delivered, and never
charges past it.

### Using it from an AI agent (MCP)

The actor works well as an agent tool via Apify's MCP server: an agent can call it with a
CPV set and get back a compact, normalized JSON feed it can reason over.

> "Watch Polish public tenders for IT services. Every morning give me anything new above
> or below the EU threshold, with deadlines."

An agent handling this calls the actor with `{"cpvCodes": ["72"], "sources": "both"}` on a
schedule (delta mode on) and summarizes each run's dataset, the `uid`, `deadlineAt`, and
`sourceUrl` fields are all it needs to track, remind, and link.

### Honest limits

- **Poland-complete, EU-partial by design.** Below-EU-threshold coverage is Poland only
  (BZP). Other countries' domestic below-threshold bulletins are not included.
- **TED publishes day-granularity dates**, so `publishedAt`/`deadlineAt` from TED carry no
  time of day. Delta mode makes overlapping windows safe.
- **BZP filtering is client-side** (the official API has no CPV/keyword parameters), so a
  very narrow filter over a long date range reads more pages than it emits records. Keep
  scheduled windows short; delta mode does this for you.
- **Keyword search is literal**, not stemmed: `kolej` also matches `kolejny`, and Polish
  inflection means `ministerstwo` will not match `ministerstwa`. Prefer CPV codes for
  precision; keywords are a complement.
- **`maxNotices` caps collection per run.** On very broad filters with a long window the
  cap can truncate; anything truncated in delta mode is emitted by the next run.
- Data is republished from official public registers unchanged; verify legal details on
  the linked source notice before acting on them.

# Actor input Schema

## `sources` (type: `string`):

Which procurement sources to query. TED covers the whole EU (including Polish above-EU-threshold notices); BZP covers Poland's domestic below-EU-threshold notices.

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

Common Procurement Vocabulary codes or prefixes, e.g. \["72000000", "48"]. A prefix matches its whole family ("72" matches 72250000). Empty = no CPV filter (all notices).

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

Optional keywords, OR-combined. On TED they run server-side as full-text search; on BZP they match client-side against the notice title (order object) and buyer name. Empty = no keyword filter.

## `tedCountries` (type: `array`):

ISO country codes (2- or 3-letter) filtering TED by buyer country. Default: Poland + neighbors (POL, DEU, CZE, SVK, LTU, UKR). Empty list = all countries. Ignored for BZP (always Poland).

## `includeAwardNotices` (type: `boolean`):

When enabled, contract-award (result) notices are collected alongside contract notices: TED form-type 'result', BZP TenderResultNotice.

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

ISO date or datetime (e.g. 2026-08-12 or 2026-08-12T06:00:00Z). Default: in delta mode, the last successful run's timestamp; otherwise 24 hours ago.

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

ISO date or datetime. Default: now.

## `maxNotices` (type: `integer`):

Hard cap on notices emitted per run (clamped to 1..5000).

## `deltaMode` (type: `boolean`):

When enabled (default), the actor remembers which notice IDs it has already emitted (named key-value store 'dataworks-procurement-state') and emits only unseen notices — run it on a schedule to get a clean incremental feed. Disable for a pure date-range query with no state.

## Actor input object example

```json
{
  "sources": "both",
  "tedCountries": [
    "POL",
    "DEU",
    "CZE",
    "SVK",
    "LTU",
    "UKR"
  ],
  "includeAwardNotices": false,
  "maxNotices": 200,
  "deltaMode": true
}
```

# Actor output Schema

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

One record per notice matching your CPV codes and keyword filters, normalized across both sources. In delta mode only notices not seen on a previous run are emitted, so a run returning zero means nothing new was published — not that the query failed.

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

Per-run counts and health: sources queried, degradedSources, deltaMode, the date window, collectedCount, alreadySeenCount, emittedCount, deliveredCount, anySourceFailed and anySourceHitFetchCap. Use anySourceFailed to tell a quiet market apart from a broken feed.

# 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("atlasdataworks/procurement-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("atlasdataworks/procurement-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 atlasdataworks/procurement-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atlasdataworks/procurement-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/YVHYa9n6TEfYgdyEF/builds/ztkWyJudtKlu70tJR/openapi.json
