# Poland e-Zamówienia Tenders Scraper (BZP Public Procurement) (`galhali/ezamowienia-tenders-scraper`) Actor

High-performance hybrid scraper for Polish public procurement (BZP) via official e-Zamówienia REST APIs. Features server-side Elasticsearch keyword search (SearchTenders) and temporal sliding-window bulk extraction. 128 MB RAM execution, 0 headless browser overhead.

- **URL**: https://apify.com/galhali/ezamowienia-tenders-scraper.md
- **Developed by:** [Galhali](https://apify.com/galhali) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 tender 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

## 🇵🇱 Poland e-Zamówienia Tenders Scraper (BZP Public Procurement API)

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange?style=flat-square\&logo=apify)](https://apify.com)
[![Version](https://img.shields.io/badge/Version-0.2.0-blue?style=flat-square)](https://apify.com)
[![Pricing Model](https://img.shields.io/badge/Pricing-Pay--Per--Event%20\($0.0015/item\)-green?style=flat-square)](https://apify.com)
[![Memory Footprint](https://img.shields.io/badge/RAM-128%20MB-blue?style=flat-square)](https://apify.com)
[![Architecture](https://img.shields.io/badge/Architecture-Hybrid%20Dual--Track%20REST-brightgreen?style=flat-square)](https://ezamowienia.gov.pl)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.4-blue?style=flat-square\&logo=typescript)](https://www.typescriptlang.org)

Extract, monitor, and automate official Polish public procurement notices (*Biuletyn Zamówień Publicznych - BZP*) directly from the official **e-Zamówienia** platform (`ezamowienia.gov.pl`).

Built for B2B lead generation agencies, government contractors, bid intelligence platforms, and workflow automation engineers (n8n, Make.com, AI/MCP agents).

***

### ⚡ Why Use This Scraper?

Public tenders in Poland represent billions of euros in annual government spending across IT, construction, medical, engineering, and consulting sectors. However, tracking opportunities through official government portals is traditionally frustrating:

- Manual search portals lack automated webhook alerts or structured data exports.
- Commercial tender aggregation portals charge **$150 – $500/month** for slow, delayed updates.
- Traditional web scrapers using headless browsers (Puppeteer/Playwright) are notoriously slow, prone to memory leaks, crash frequently, and break whenever the website DOM updates.

#### The Architectural Advantage: Hybrid Dual-Track REST Engine (v0.2.0)

This Actor does **not** render web pages in headless Chrome. Instead, it features an intelligent dual-track hybrid acquisition engine interfacing directly with the official, authoritative **e-Zamówienia backend REST APIs**:

1. **Track 1: Server-Side Full-Text Search (`SearchTenders`)**:
   - Activated whenever `searchQuery` is provided.
   - Queries the official e-Zamówienia Elasticsearch read-models engine (`/mp-readmodels/api/Search/SearchTenders`).
   - Binds keywords server-side to procurement dossiers, returning relevant active and historical tenders.
   - Enforces strict descending chronological sort (`SortingColumnName=InitiationDate&SortingDirection=DESC`).
   - Supports native pagination via `PageNumber` and reads `x-pagination` metadata headers.

2. **Track 2: Forward Temporal Sliding-Window Stream (`/mo-board/api/v1/notice`)**:
   - Activated when extracting general procurement notices (no `searchQuery`).
   - Dynamically advances the temporal cursor (`PublicationDateFrom = lastItem.publicationDate`) down to microsecond precision.
   - Implements in-memory boundary deduplication (`Set<string>`) and stall detectors (+1ms fallback) to bypass the upstream single-page limit.
   - Enables uninterrupted streaming of 1,000+ distinct notices across multi-week or multi-month ranges.

| Metric / Feature | Traditional Headless Scraper | Poland e-Zamówienia Scraper v0.2.0 |
| :--- | :--- | :--- |
| **Extraction Method** | Headless Chrome (Puppeteer / Playwright) | **Hybrid Official REST APIs (Search + Stream)** |
| **Search Engine** | Brittle DOM element searching | **Official Elasticsearch index (`SearchTenders`)** |
| **Bulk Pagination** | Manual clicks / brittle pagination | **Dynamic Temporal Sliding Window (1,000+ items)** |
| **RAM Footprint** | 1,024 MB – 2,048 MB | **128 MB (Ultra-lightweight Alpine container)** |
| **Execution Speed** | 5 – 15 tenders / minute | **Up to 1,500+ tenders / minute** |
| **DOM Breakage Risk** | High (Breaks on CSS / HTML updates) | **Zero (Backed by official API schemas)** |
| **Rate Limit Resilience**| Prone to aggressive IP bans | **Adaptive pacing + automatic WAF cool-down & proxy rotate** |
| **Compute Cost** | High ($0.25+ in compute units per run) | **Negligible (~$0.0001 per run in compute)** |
| **Pricing Model** | High monthly rental or heavy CU | **Pay-Per-Event ($0.0015 per saved tender)** |

***

### 🎯 High-Value Use Cases

1. **Automated B2B Lead Generation for Contractors**:
   Filter tenders by your exact industry CPV code (e.g. `72000000-5` for IT software & cloud services) and get instant alerts the minute a municipal or national government body posts an RFP.
2. **Competitor Intelligence & Award Tracking**:
   Track `TenderResultNotice` (*Wynik postępowania*) to find out which competitors won government contracts, their final bid pricing, and contract terms.
3. **Subcontractor & Supplier Outreach**:
   Identify large public infrastructure or construction projects right after contract award to pitch your equipment, staffing, or material supply services to the winning prime contractors.
4. **AI Market Intelligence & Copilots**:
   Feed structured tender data into LLMs, Vector DBs, or Model Context Protocol (MCP) servers so AI agents can answer questions like: *"What cloud migration tenders were published in Mazowieckie this week?"*

***

### 📋 Table of Contents

- [Key Features](#-key-features)
- [Input Parameters Guide](#-input-parameters-guide)
  - [Dynamic 7-Day Fallback Window](#-dynamic-7-day-fallback-window-zero-maintenance-scheduling)
  - [Notice Types & Order Categories](#-notice-types--order-categories)
- [Sample Input Configurations](#-sample-input-configurations)
- [Output Dataset Structure](#-output-dataset-structure)
  - [Real-World Output Item Sample](#-real-world-output-item-sample)
- [Workflow Automation & Integration Guides](#-workflow-automation--integration-guides)
  - [1. n8n Automation Recipe](#1-n8n-automation-recipe)
  - [2. Make.com (Integromat) Setup](#2-makecom-integromat-setup)
  - [3. AI Agents & Model Context Protocol (MCP)](#3-ai-agents--model-context-protocol-mcp)
  - [4. Python & Node.js Quickstarts](#4-python--nodejs-quickstarts)
- [Azure WAF & Rate Limiting Engine](#-azure-waf--rate-limiting-engine)
- [Transparent Pricing (Pay-Per-Event)](#-transparent-pricing-pay-per-event)
- [Polish & EU B2B Invoicing (VAT-EU Reverse Charge)](#-polish--eu-b2b-invoicing-vat-eu-reverse-charge)
- [Statutory Attribution & Legal Notice](#️-statutory-attribution--legal-notice-polish-open-data-act)
- [FAQ](#-frequently-asked-questions)

***

### ✨ Key Features

- **23 Rich Fields per Notice / Dossier**: Extracts BZP bulletin numbers, tender IDs, purchasing organization, city, province, tax ID (NIP/REGON), CPV codes, submission deadlines, full HTML notice body (for notices), and direct clickable links to public dossiers.
- **Server-Side Full-Text Search (`SearchTenders`)**: Instantly search Poland's entire procurement archive with Elasticsearch indexing when `searchQuery` is provided, sorted descending by initiation date.
- **Temporal Sliding-Window Bulk Extraction**: Continuously stream 1,000+ distinct notices via dynamic forward microsecond cursor sliding, overcoming the upstream API's single-page cap.
- **Dynamic Date Scheduling**: Automatically calculates a rolling 7-day window when dates are omitted—schedule it once in Apify and let it run daily without updating input parameters.
- **Smart Pacing & WAF Handling**: Complies with Azure Application Gateway WAF thresholds with configurable delay (default 3,500 ms) and automatic cool-down retry logic on HTTP 403/429.
- **Direct Dossier URL Generation**: Every notice and search result includes a computed `detailUrl` linking directly to the interactive government dossier on `ezamowienia.gov.pl`.
- **Apify Proxy Support**: Fully compatible with Apify Datacenter proxies for ultra-high throughput.

***

### ⚙️ Input Parameters Guide

| Parameter | Type | Required | Default | Description |
| :--- | :--- | :--- | :--- | :--- |
| `searchQuery` | String | No | *None* | Keyword query triggering server-side full-text search (`SearchTenders`) on tender title and purchaser. Omit for bulk BZP streaming. |
| `noticeType` | String | **Yes** | `"ContractNotice"` | Type of procurement notice in the official Polish bulletin (BZP). See [Notice Types](#-notice-types--order-categories). |
| `orderType` | String | No | `"All"` | Category of procurement. Strict PascalCase: `"All"`, `"Services"`, `"Delivery"`, `"Works"`. |
| `cpvCode` | String | No | *None* | Common Procurement Vocabulary code prefix (e.g. `72000000-5`, `45000000-7`, `33000000-0`). |
| `publicationDateFrom` | String | No | *Now - 7 days* | Start date in ISO format (`YYYY-MM-DD` or `YYYY-MM-DDTHH:mm:ssZ`). Defaults to rolling 7 days ago. |
| `publicationDateTo` | String | No | *Current time* | End date in ISO format (`YYYY-MM-DD` or `YYYY-MM-DDTHH:mm:ssZ`). Defaults to current moment. |
| `maxItems` | Integer| No | `100` | Maximum number of tender notices to push to the Apify dataset (Range: `1` to `50,000`). |
| `pacingDelayMs` | Integer| No | `3500` | Inter-page request delay in milliseconds (min `2000`, recommended `3500`) to respect Azure WAF limits. |
| `proxyConfiguration` | Object | No | *None* | Standard Apify Proxy configuration. Datacenter proxies are fully supported. |

***

#### 🕒 Dynamic 7-Day Fallback Window (Zero-Maintenance Scheduling)

One of the biggest pain points with API-based scrapers is that the upstream government API strictly rejects requests missing publication dates (returning HTTP 400 Bad Request). If you had to manually change `publicationDateFrom` and `publicationDateTo` every single day, automated scheduling would be impossible.

**This Actor solves this completely:**

- If you leave `publicationDateFrom` and `publicationDateTo` empty, the scraper dynamically calculates:
  - `publicationDateFrom = NOW() - 7 days` (in ISO 8601 UTC)
  - `publicationDateTo = NOW()` (in ISO 8601 UTC)
- **Result**: You can set up a daily or weekly cron schedule in Apify Console or n8n without ever having to touch the input dates!

***

#### 🏷️ Notice Types & Order Categories

##### Notice Types (`noticeType`):

- `ContractNotice` (*Ogłoszenie o zamówieniu*): New tenders open for bidding. **Most popular for lead generation**.
- `TenderResultNotice` (*Wynik postępowania*): Completed tenders with awarded contractor, price, and decision. Ideal for competitor tracking.
- `NoticeUpdateNotice` (*Zmiana ogłoszenia*): Clarifications, deadline extensions, and specification updates.
- `AgreementUpdateNotice` (*Zmiana umowy*): Modifications and annexes to active public contracts.
- `ConcessionNotice` (*Ogłoszenie o koncesji*): Concessions for construction works or public services.

##### Order Types (`orderType`):

- `All`: All procurement categories.
- `Services` (*Usługi*): IT, consulting, cleaning, transportation, legal, software development.
- `Delivery` (*Dostawy*): Hardware, medical supplies, vehicles, fuel, equipment.
- `Works` (*Roboty budowlane*): General construction, road building, renovations, infrastructure.

> **💡 Technical Note on BZP vs TED:**\
> This scraper queries the domestic Polish bulletin (*Biuletyn Zamówień Publicznych - BZP*), which covers all national public tenders up to EU financial thresholds (e.g. < €140,000 for government supplies/services, < €5,538,000 for construction works). This represents **over 85% of all public tenders in Poland**.

***

### 💡 Sample Input Configurations

#### Example 1: Daily Lead Gen for IT Companies (Automated Schedule)

Find all new IT service and software tenders published in the last 7 days without hardcoded dates:

```json
{
  "noticeType": "ContractNotice",
  "orderType": "Services",
  "cpvCode": "72000000-5",
  "maxItems": 100
}
```

#### Example 2: Medical & Hospital Equipment Deliveries

Find tenders for medical supplies with keyword search:

```json
{
  "noticeType": "ContractNotice",
  "orderType": "Delivery",
  "cpvCode": "33000000-0",
  "searchQuery": "szpital",
  "maxItems": 50
}
```

#### Example 3: Construction Award Monitoring (Competitor Tracking)

Track road and building construction contracts awarded during October 2026:

```json
{
  "noticeType": "TenderResultNotice",
  "orderType": "Works",
  "cpvCode": "45000000-7",
  "publicationDateFrom": "2026-10-01",
  "publicationDateTo": "2026-10-26",
  "maxItems": 200
}
```

***

### 📊 Output Dataset Structure

Each scraped tender item pushed to your Apify dataset is normalized into a clean, developer-friendly JSON object:

| Field Name | Type | Description |
| :--- | :--- | :--- |
| `objectId` | String | Unique UUID identifier of the notice in e-Zamówienia. |
| `bzpNumber` | String | Official Public Procurement Bulletin number (e.g. `2026/BZP 00497408`). |
| `noticeNumber` | String | Full notice identifier with version suffix (e.g. `2026/BZP 00497408/01`). |
| `noticeType` | String | Notice type (`ContractNotice`, `TenderResultNotice`, etc.). |
| `orderType` | String | Procurement category (`Services`, `Delivery`, `Works`). |
| `clientType` | String | Official institutional classification code of the buyer. |
| `tenderType` | String | Procurement procedure type code. |
| `isTenderAmountBelowEU` | Boolean | `true` if tender value is below European Union thresholds. |
| `organizationName` | String | Official name of the government institution or municipal entity. |
| `organizationCity` | String | City where the purchasing organization is registered. |
| `organizationProvince` | String | Voivodeship code (e.g. `PL14` for Mazowieckie, `PL21` for Małopolskie). |
| `organizationCountry` | String | Country code (`PL`). |
| `organizationNationalId` | String | Tax ID (NIP or REGON) of the purchasing body. |
| `tenderTitle` | String | Full official title and subject of the tender (`orderObject`). |
| `tenderDescriptionSnippet` | String | Cleaned, plain-text summary snippet (HTML tags stripped). |
| `cpvCode` | String | CPV codes with official descriptions. |
| `publicationDate` | String | ISO 8601 UTC timestamp of official notice publication. |
| `submissionDeadline` | String / null | Bid submission deadline timestamp (crucial for RFP alerts). |
| `procedureResult` | String / null | Outcome status (populated for `TenderResultNotice`). |
| `tenderId` | String | Public OCDS dossier identifier. |
| `detailUrl` | String | **Direct clickable URL** to view the live dossier on `ezamowienia.gov.pl`. |
| `htmlBody` | String / null | Complete raw HTML body containing legal notice clauses. |
| `scrapedAt` | String | ISO 8601 timestamp when this record was extracted by the Actor. |

***

#### 📄 Real-World Output Item Sample

```json
{
  "objectId": "08df2f30-9b81-90f9-701a-d0000164a818",
  "bzpNumber": "2026/BZP 00497408",
  "noticeNumber": "2026/BZP 00497408/01",
  "noticeType": "ContractNotice",
  "orderType": "Services",
  "clientType": "1.1.13",
  "tenderType": "1.1.2",
  "isTenderAmountBelowEU": true,
  "organizationName": "Uniwersytet Warszawski",
  "organizationCity": "Warszawa",
  "organizationProvince": "PL14",
  "organizationCountry": "PL",
  "organizationNationalId": "5250011266",
  "tenderTitle": "Realizacja międzynarodowego ilościowego badania sondażowego metodą CAWI (Computer-Assisted Web Interview) w 12 krajach partnerskich",
  "tenderDescriptionSnippet": "Ogłoszenie nr 2026/BZP 00497408 z dnia 2026-10-21. Przedmiotem zamówienia jest usługa realizacji ilościowego badania sondażowego metodą CAWI...",
  "cpvCode": "79315000-5 (Usługi badań społecznych), 79311200-9 (Usługi przeprowadzania badań ankietowych)",
  "publicationDate": "2026-10-21T05:03:12.366Z",
  "submissionDeadline": "2026-10-29T09:00:00Z",
  "procedureResult": null,
  "tenderId": "ocds-148610-71fcc7b3-19e6-439f-85f0-9f7cb6ee8d2d",
  "detailUrl": "https://ezamowienia.gov.pl/mp-client/search/list/ocds-148610-71fcc7b3-19e6-439f-85f0-9f7cb6ee8d2d",
  "htmlBody": "<html><head><meta charset=\"UTF-8\">...</head><body><div class=\"notice-content\">...</div></body></html>",
  "scrapedAt": "2026-10-26T15:04:39.966Z"
}
```

***

### 🔄 Workflow Automation & Integration Guides

#### 1. n8n Automation Recipe

Automatically run the scraper every morning at 08:00 CET and dispatch new tender notifications directly to a Slack / Discord channel or your company CRM.

```
[Cron Trigger (08:00 Daily)] 
      │
      ▼
[Apify Node: Run Actor "ezamowienia-tenders-scraper"]
      │
      ▼
[Item Lists / Split In Batches]
      │
      ▼
[Filter: Deadline > Today]
      │
      ▼
[Slack / Telegram: "🚨 New Polish Public Tender: {{ $json.tenderTitle }}"]
```

##### Step-by-Step n8n Configuration:

1. In n8n, add an **Apify** node (or **HTTP Request** node calling `https://api.apify.com/v2/acts/<your-username>~ezamowienia-tenders-scraper/runs?token=<APIFY_TOKEN>`).
2. Set the JSON Body in the Apify node:
   ```json
   {
     "noticeType": "ContractNotice",
     "orderType": "Services",
     "cpvCode": "72000000-5",
     "maxItems": 50
   }
   ```
3. Connect an **If** or **Filter** node checking `{{ $json.submissionDeadline }} != null`.
4. Connect a **Slack** node with Block Kit layout:
   ```markdown
   *New Polish IT Tender Announced!*
   • *Title*: {{ $json.tenderTitle }}
   • *Buyer*: {{ $json.organizationName }} ({{ $json.organizationCity }})
   • *Deadline*: {{ $json.submissionDeadline }}
   • *Official Dossier*: <{{ $json.detailUrl }}|View Tender on e-Zamówienia>
   ```

***

#### 2. Make.com (Integromat) Setup

Set up a zero-code pipeline to synchronize public tenders directly into Google Sheets, Airtable, or HubSpot.

1. **Trigger**: Add the **Apify** module -> Select **"Watch Dataset Items"** or **"Run an Actor and Get Dataset"**.
2. **Actor Selection**: Choose `ezamowienia-tenders-scraper`.
3. **Actor Input**: Specify your target category (e.g. `orderType: "Works"`, `cpvCode: "45000000-7"`).
4. **Action**: Add the **Airtable** module -> **"Create a Record"**:
   - `Tender Title` ➔ `tenderTitle`
   - `BZP Number` ➔ `bzpNumber`
   - `Purchasing Authority` ➔ `organizationName`
   - `City` ➔ `organizationCity`
   - `Deadline` ➔ `submissionDeadline`
   - `Dossier Link` ➔ `detailUrl`
5. **Deduplication**: Use Airtable's primary key on `objectId` to prevent duplicate records on recurring runs.

***

#### 3. AI Agents & Model Context Protocol (MCP)

Connect this Actor directly to Claude Desktop, Cursor, or autonomous LangChain / AutoGPT agents using the official **Apify MCP Server**.

##### Claude Desktop MCP Configuration (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "apify": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-apify"],
      "env": {
        "APIFY_TOKEN": "your_apify_api_token_here"
      }
    }
  }
}
```

##### Real-World Agent Prompt Examples:

- *"Check for new IT tenders announced in Poland today related to cloud migration and summarize the budget, buyer, and submission deadline."*
- *"Find all medical equipment tenders published in Mazowieckie in the last 72 hours and rank them by urgency of the deadline."*
- *"Who won the recent road construction tenders in Warsaw according to e-Zamówienia award notices?"*

***

#### 4. Python & Node.js Quickstarts

##### Python (`apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run_input = {
    "noticeType": "ContractNotice",
    "orderType": "Services",
    "cpvCode": "72000000-5",
    "maxItems": 50,
    "pacingDelayMs": 3500
}

## Run the Actor and wait for completion
run = client.actor("your-username/ezamowienia-tenders-scraper").call(run_input=run_input)

## Fetch dataset items
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"[{item['bzpNumber']}] {item['tenderTitle']} | Buyer: {item['organizationName']}")
    print(f"URL: {item['detailUrl']}\n")
```

##### Node.js (`apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const input = {
    noticeType: 'ContractNotice',
    orderType: 'Services',
    cpvCode: '72000000-5',
    maxItems: 50,
};

const run = await client.actor('your-username/ezamowienia-tenders-scraper').call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();

items.forEach((item) => {
    console.log(`Tender: ${item.tenderTitle}`);
    console.log(`Link: ${item.detailUrl}`);
});
```

***

### 🛡️ Azure WAF & Rate Limiting Engine

The Polish government e-Zamówienia portal is protected by Microsoft Azure Application Gateway Web Application Firewall (WAF). Overaggressive or concurrent scraping triggers HTTP 403 / 429 rate limits (~30 requests per minute per IP).

**This Actor includes built-in protection:**

1. **Intelligent Pacing**: By default, an inter-page delay of `3500 ms` (`pacingDelayMs`) ensures request rates remain safely under the Azure WAF threshold.
2. **Automated Cool-Down**: If a temporary rate limit (403/429) is detected, the Actor automatically detects the WAF signature, pauses execution for 45–50 seconds to let the firewall cool down, and retries seamlessly with exponential backoff.
3. **Zero Browser Overhead**: Standard HTTP requests prevent TCP connection flooding, ensuring continuous, stable data harvesting.

***

### 💰 Transparent Pricing (Pay-Per-Event)

This Actor uses Apify's modern **Pay-Per-Event (PPE)** pricing model. You **never pay for idle time**, startup latency, or empty queries. You only pay for the actual tender records delivered to your dataset:

$$\mathbf{$0.0015\text{ USD per scraped tender notice}}\quad(\mathbf{$1.50\text{ per 1,000 tenders}})$$

#### Cost Comparison:

| Volume | Scraped Notices | Actor Cost | Typical Commercial Portal Cost |
| :--- | :--- | :--- | :--- |
| **Free Tier / Trial** | ~3,330 tenders | **$0.00 (Covered by Apify's $5 free monthly credit)** | $199.00 / month |
| **Small Contractor** | 1,000 tenders / month | **$1.50 / month** | $249.00 / month |
| **Enterprise Agency** | 20,000 tenders / month | **$30.00 / month** | $899.00 / month |

- **No monthly platform fee**: Pay only for what you extract.
- **Ultra-low compute cost**: Running in a 128 MB RAM container, compute unit (CU) costs are practically non-existent (< $0.001 per run).

***

### 🧾 Polish & EU B2B Invoicing (VAT-EU Reverse Charge)

For businesses operating in Poland and the European Union, subscription and Pay-Per-Event usage charges are invoiced directly by **Apify Technologies s.r.o.** (established in Prague, Czech Republic, EU VAT ID: `CZ04788290`).

- **Reverse Charge Mechanism (*Odwrotne obciążenie / WNT*)**: When providing an active EU VAT number (e.g. Polish `PL` NIP registered for intra-community transactions in VIES), invoices are issued with **0% VAT** pursuant to Article 196 of Council Directive 2006/112/EC.
- **Tax Deductibility**: All invoice line items (Compute Units, PPE dataset events) constitute 100% tax-deductible operational business expenses (*koszty uzyskania przychodów - KUP*).
- **Automated Monthly Invoicing**: Apify provides compliant PDF invoices and monthly breakdown summaries in the account billing portal for effortless accounting.

***

### ⚖️ Statutory Attribution & Legal Notice (Polish Open Data Act)

Public procurement notices published in the official Polish Public Procurement Bulletin (*Biuletyn Zamówień Publicznych - BZP*) on the **e-Zamówienia** portal (`ezamowienia.gov.pl`) constitute public sector information (*informacja sektora publicznego*).

Pursuant to Article 15 of the Polish Act of 11 August 2021 on Open Data and the Re-Use of Public Sector Information (*Ustawa o otwartych danych i ponownym wykorzystywaniu informacji sektora publicznego*, Dz.U. z 2021 r. poz. 1641, t.j. Dz.U. z 2023 r. poz. 1524), transposing Directive (EU) 2019/1024:

1. **Source of Public Sector Information**:
   This Actor retrieves public procurement notices published in the official Public Procurement Bulletin (Biuletyn Zamówień Publicznych - BZP) maintained by the Public Procurement Office of the Republic of Poland (Urząd Zamówień Publicznych - UZP) via ezamowienia.gov.pl.

2. **Legal Basis for Re-Use**:
   Public sector information is re-used pursuant to the Polish Act of 11 August 2021 on Open Data and the Re-Use of Public Sector Information (Dz.U. z 2021 r. poz. 1641, t.j. Dz.U. z 2023 r. poz. 1524), transposing Directive (EU) 2019/1024 of the European Parliament and of the Council.

3. **Data Modification Disclosure**:
   Original administrative notices are retrieved via the public BZP WebService, filtered, parsed, and converted into structured JSON records for analytical processing.

4. **Disclaimer of Warranties & Liability**:
   Data is provided "AS IS" without warranties of completeness, currency, or fitness for a particular procurement bid. Neither the author nor Urząd Zamówień Publicznych shall be held liable for any commercial, contractual, or legal decisions made based on this automated data feed. Bidders must consult official tender documentation on ezamowienia.gov.pl before submitting formal bids.

5. **No Official Government Affiliation**:
   This Actor is an independent commercial software tool and is not endorsed by, sponsored by, or affiliated with Urząd Zamówień Publicznych or any Polish governmental body.

***

### ❓ Frequently Asked Questions

##### Q: How does this scraper differ from TED (Tenders Electronic Daily)?

**A**: TED only covers high-value tenders exceeding EU thresholds (> €140k for supplies/services, > €5.5M for works). The Polish *Biuletyn Zamówień Publicznych (BZP)* on e-Zamówienia contains all public tenders from Polish schools, municipalities, police departments, universities, and regional agencies that never appear on TED.

##### Q: Does it break when the e-Zamówienia website changes its layout?

**A**: **No.** This Actor connects directly to the official government REST API backend. It does not scrape HTML DOM or CSS selectors, making it virtually immune to website redesigns.

##### Q: Can I run this scraper without specifying dates?

**A**: **Yes!** When date parameters are omitted, the Actor dynamically sets the search window to the last 7 rolling days. This makes it ideal for automated daily schedules on Apify, n8n, or Make.

##### Q: What if I need notices older than 7 days?

**A**: Simply set `publicationDateFrom` and `publicationDateTo` to your desired dates (e.g. `2026-01-01` to `2026-06-30`).

##### Q: Do I need proxies?

**A**: For standard daily scraping (up to a few thousand notices per day), direct connections work seamlessly thanks to the built-in pacing engine. For heavy enterprise scraping (50,000+ notices in minutes), enabling Apify Datacenter proxies is recommended.

***

### 🤝 Support & Custom Solutions

Need custom integrations, automated tender enrichment with Polish REGON/KRS databases, or specialized tender alerting?\
Feel free to open an issue or reach out via the Apify Actor console.

# Actor input Schema

## `searchQuery` (type: `string`):

Optional keyword filter matched against tender title, organization name, CPV description, or full notice text.

## `noticeType` (type: `string`):

Type of procurement notice in the official Polish Public Procurement Bulletin (Biuletyn Zamówień Publicznych - BZP).

## `orderType` (type: `string`):

Category of procurement order. Note: e-Zamówienia backend requires strict PascalCase ('Services', 'Delivery', 'Works').

## `cpvCode` (type: `string`):

Common Procurement Vocabulary code filter (e.g. '45000000-7' for Construction, '72000000-5' for IT Services, '33000000-0' for Medical Equipment).

## `publicationDateFrom` (type: `string`):

Start date for publication window (e.g. '2026-10-21' or '2026-10-21T00:00:00Z'). If omitted, dynamically defaults to 7 days ago.

## `publicationDateTo` (type: `string`):

End date for publication window (e.g. '2026-10-26' or '2026-10-26T23:59:59Z'). If omitted, dynamically defaults to current date.

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

Maximum number of notices to scrape and push into the Apify dataset.

## `pacingDelayMs` (type: `integer`):

Delay between page requests in milliseconds to respect Azure Application Gateway WAF rate limits (minimum 2000ms, recommended 3500ms).

## `proxyConfiguration` (type: `object`):

Proxy configuration to distribute requests across distinct egress IPs and prevent Azure WAF throttling. Standard Apify Datacenter proxies provide high throughput.

## Actor input object example

```json
{
  "searchQuery": "szpital",
  "noticeType": "ContractNotice",
  "orderType": "All",
  "cpvCode": "72000000-5",
  "maxItems": 50,
  "pacingDelayMs": 3500,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

Full JSON dataset containing extracted public procurement notices.

## `overview` (type: `string`):

Overview table of tender notices scraped from Biuletyn Zamówień Publicznych (BZP).

# 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 = {
    "searchQuery": "szpital",
    "noticeType": "ContractNotice",
    "orderType": "All",
    "cpvCode": "72000000-5",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("galhali/ezamowienia-tenders-scraper").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 = {
    "searchQuery": "szpital",
    "noticeType": "ContractNotice",
    "orderType": "All",
    "cpvCode": "72000000-5",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("galhali/ezamowienia-tenders-scraper").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 '{
  "searchQuery": "szpital",
  "noticeType": "ContractNotice",
  "orderType": "All",
  "cpvCode": "72000000-5",
  "maxItems": 50
}' |
apify call galhali/ezamowienia-tenders-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,galhali/ezamowienia-tenders-scraper"
        }
    }
}
```

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/COSdYfsPCDJfMPD9F/builds/fuhjCPMBQvUd4XwxQ/openapi.json
