# Construction Tender Radar PL (`danielmichalek/construction-tender-radar-pl`) Actor

Find Polish BZP construction tenders by CPV, buyer province and title. Export clean JSON/CSV with official notice links, published deadlines and changes against your previous snapshot. Bounded searches with transparent coverage and continuation cursors.

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

## Pricing

$10.00 / 1,000 qualified construction notice delivereds

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 PL

Find Polish public procurement notices matching construction CPV codes, publication dates, title terms and buyer regions. Get deduplicated notices with published deadlines, buyer organisations, official links and changes compared with a snapshot you supply.

### Quick start

1. Choose an inclusive publication date range of up to 31 days, or leave both dates empty to use the last 7 UTC days. Keep CPV prefix `45` for construction broadly, or enter narrower prefixes such as `453` for building installation work.
2. Optionally add title terms or buyer province codes, then set your result and spending limits and start the run. Dates filter publication, not the submission deadline.
3. Open the dataset or download the CSV. Check the coverage report before using the results. Save the `SNAPSHOT` output and supply it as `previousSnapshots` on a later run to see changes.

### Input

```json
{
  "dateFrom": "2026-10-19",
  "dateTo": "2026-10-20",
  "cpvPrefixes": ["45"],
  "titleTerms": [],
  "provinceCodes": [],
  "pageSize": 10,
  "maxPages": 2,
  "maxResults": 20,
  "maxRetries": 1,
  "maxRunTimeMs": 60000,
  "previousSnapshots": []
}
```

These dates are a historical example; choose your own date range.

| Input | Meaning |
| --- | --- |
| `dateFrom`, `dateTo` | Optional inclusive publication dates, `YYYY-MM-DD`, interpreted as full UTC days. When both are omitted, the last 7 UTC days ending today are used; a single omitted endpoint is derived from the other one. |
| `cpvPrefixes` | One or more 2–8 digit prefixes beginning `45`. Any matching main or secondary CPV can qualify. |
| `titleTerms` | Optional case-insensitive literal terms. A title matching any term qualifies. Attachments are not searched. |
| `provinceCodes` | Optional source buyer province codes, such as `PL14`. This is the buyer's location, not necessarily the place of works. |
| `maxResults`, `maxPages`, `pageSize` | Bounds on returned notices, requested pages and records per source page. |
| `maxRunTimeMs` | Total source time allowance, including request spacing, retries and backoff. Set the Actor timeout high enough for this allowance and output storage. |
| `previousSnapshots` | The prior `SNAPSHOT` array. Compared fields are title, CPV, deadline, buyer name, city and province. |
| `searchAfter` | Optional `report.nextSearchAfter` cursor from a capped result. Keep the same dates and filters when continuing. |

The input form documents all options, including per-request timeout and retries. Unknown fields, invalid dates and duplicate snapshot keys are rejected.

### Results

The default dataset contains one row per retained BZP notice. When multiple versions of the same base notice number are observed, the newest is kept. Separate notices from one procurement procedure remain separate.

| Output field | Meaning |
| --- | --- |
| `noticeKey`, `noticeNumber` | Base notice identity and published version number. |
| `title`, `cpvCodes` | Source title and normalized construction classification codes. |
| `publicationDate`, `deadline` | Source publication time and submission deadline; an absent deadline remains `null`. |
| `buyerName`, `buyerCity`, `buyerProvince` | Buyer organisation and its location. |
| `noticeUrl` | Official BZP notice detail link. |
| `sourceUrl`, `sourceRecordId`, `observedAt` | Source request, exact record identifier and observation time. |
| `changeType`, `changedFields` | `new`, `changed` or `unchanged` against your supplied snapshot, with fields that differ. |

`new` means absent from your baseline, not necessarily newly published. Missing baseline notices are never labelled removed.

The run also produces:

- **OUTPUT:** actual run status (`success`, `partial`, `empty` or `failed`), query scope, completeness, stop reason, continuation cursor and structured source errors. In pay-per-event mode it includes the named charged count and any spending-limit stop.
- **SNAPSHOT:** normalized fields to use for the next comparison.
- **TENDERS:** a UTF-8 CSV download. Array values use `|`; cells that could be interpreted as spreadsheet formulas are escaped.

### Pricing

When pay-per-event pricing is enabled, one `notice-returned` event represents one distinct matching notice delivered to the dataset. See the **Pricing** tab for the configured rate. At $0.01 per notice, four delivered notices cost $0.04 in notice events.

There is no notice event for an empty feed, a rejected or duplicate record, or a source error without a delivered notice. If useful notices are collected before a later source failure, those delivered notices may be charged and the report shows partial coverage. The spending limit can stop retrieval or delivery. Separate runs can return and charge for the same notice again; a supplied snapshot adds comparison, not a lifetime deduplication entitlement.

The official BZP API is free. The service fee covers retrieval, filtering, normalization and comparison.

### Coverage and limits

This Actor covers structured Polish BZP `ContractNotice` records matching construction CPVs. A secondary construction CPV can qualify a notice; a match does not establish its legal contract type. It does not inspect attachments, lots, qualification conditions or separate amendment/cancellation notices. Verify requirements and deadlines through the official link before acting.

Results are bounded by your query and limits. `report.complete` means an empty or short final page was observed without invalid records; it does not guarantee complete national coverage or all changes to a tender. Caps and source problems remain visible. Do not replace a complete historical baseline with a partial snapshot without accounting for its scope.

Maximums are 31 calendar days, 20 pages, 100 records per page, 1,000 returned notices, 1,000 prior snapshots and 1 MiB of input. Source time is configurable from 1 to 600 seconds. The official limit is 30 requests per minute per IP; requests are spaced at least 2.1 seconds apart, including retries. HTTP 403 stops retrieval. Coordinate simultaneous runs sharing an IP.

An interrupted run checks persisted output before resuming. If a delivery or charge cannot be confirmed, it stops with a reconciliation error instead of blindly appending or charging again. Use fresh default storage for a new run.

### Source and attribution

Source: **Urząd Zamówień Publicznych / e-Zamówienia / Biuletyn Zamówień Publicznych**. The official [integration guidance](https://ezamowienia.gov.pl/pl/integracja/) and [platform rules](https://ezamowienia.gov.pl/pl/regulamin/) describe free filtered reading of published notices. The [API traffic limit](https://ezamowienia.gov.pl/pl/mechanizm-kontroli-ruchu-dla-endpointow-w-ramach-api-bzp/) applies.

Outputs preserve source links, publication and observation dates, attribution and the transformation description. No account login, attachment collection or personal-contact extraction is required. Source titles and buyer names can contain incidental personal information; assess onward publication accordingly. This service does not grant permission to republish attachments or an unrestricted copy of the source database.

# Actor input Schema

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

Optional real YYYY-MM-DD, inclusive UTC day. Leave both dates empty to use the last 7 UTC days ending today. Maximum 31 calendar days including endpoints.

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

Optional real YYYY-MM-DD, inclusive UTC day; publication date, not offer deadline. Defaults to today (UTC) when omitted.

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

Match any 2–8-digit construction prefix beginning 45. The shared prefix is sent as a broad source filter; exact matches are checked locally.

## `titleTerms` (type: `array`):

Case-insensitive literal substring; searches title only, not attachments.

## `provinceCodes` (type: `array`):

Exact source organisationProvince, e.g. PL14. Buyer location, not place of works.

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

Paste the previous SNAPSHOT array. Changes compare only supplied notices; absence never means removal. Maximum complete input 1 MiB.

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

Maximum distinct matching notices returned in this run.

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

Maximum source pages requested before reporting partial coverage.

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

Maximum records requested per source page (1-100).

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

Maximum time for each source request, including its response body.

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

Maximum retries after an initial transient source request failure.

## `searchAfter` (type: `string`):

Paste report.nextSearchAfter from a capped result to continue the SAME date/filter scope. Omit to begin at the first source page.

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

Includes polite spacing, source attempts and backoff. The adapter keeps the wall-clock deadline across process restarts.

## Actor input object example

```json
{
  "cpvPrefixes": [
    "45"
  ],
  "titleTerms": [],
  "provinceCodes": [],
  "previousSnapshots": [],
  "maxResults": 100,
  "maxPages": 5,
  "pageSize": 50,
  "timeoutMs": 15000,
  "maxRetries": 2,
  "maxRunTimeMs": 180000
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/construction-tender-radar-pl").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("danielmichalek/construction-tender-radar-pl").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 danielmichalek/construction-tender-radar-pl --silent --output-dataset

```

## MCP server setup

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

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/hBEdLz3uGVGMRaHdb/builds/NR0gmnY3eij0KadDj/openapi.json
