# EU + Poland Procurement Monitor — TED + BZP delta feed (`atlasdataworks/procurement-monitor`) Actor

One actor, two official procurement sources: TED (EU-wide) + BZP (Poland domestic). CPV/keyword-filtered, delta-only qualified notices in one normalized schema. Pay-per-event.

- **URL**: https://apify.com/atlasdataworks/procurement-monitor.md
- **Developed by:** [Atlas Dataworks](https://apify.com/atlasdataworks) (community)
- **Categories:** Automation, Developer tools, Other
- **Stats:** 2 total users, 1 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?

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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## EU + Poland Procurement Monitor — TED + BZP delta feed

One actor, complete Polish procurement coverage, one normalized schema:

- **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 are the complete Polish public-procurement picture — 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 reads "Nothing new — fetched N notices, all
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/U6oTQL7GbeDKXxXTP/openapi.json
