# Ukraine Prozorro Tenders API – English, CPV & Daily Alerts (`enterprising_hamster/ukraine-prozorro-tenders`) Actor

Ukraine public tenders from the official Prozorro API for foreign contractors and reconstruction work: CPV, keywords, delivery oblast, UAH value, deadlines, links. Daily only-new alerts. JSON, CSV, Excel.

- **URL**: https://apify.com/enterprising_hamster/ukraine-prozorro-tenders.md
- **Developed by:** [Kamil Kaczmarek](https://apify.com/enterprising_hamster) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 tenders

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Ukraine Prozorro Tenders API – English, CPV & Daily Alerts

**Ukraine public tenders from Prozorro**, the official e-procurement system of Ukraine, as clean rows with English field names: title, buyer, delivery oblast, CPV codes, value in UAH, status, bid deadline and a link to the tender on prozorro.gov.ua. Filter by **CPV code**, **keywords**, **oblast** and **value**, keep only tenders still open for bids, and schedule an **only-new daily alert**. Built for foreign contractors, suppliers and consultants following **Ukraine reconstruction tenders**.

Data is read live from the [Prozorro public API](https://public-api.prozorro.gov.ua/api/2.5/tenders) during each run. No login, no API key, no website scraping.

Need Czechia, Slovakia, Hungary or EU TED tenders in the same run? Use [EU TED Tenders API – Czech, Slovak, Hungarian & Prozorro](https://apify.com/enterprising_hamster/cee-public-tenders).

### Use with AI agents (MCP)

Agents can call this Actor through the [Apify MCP server](https://mcp.apify.com):

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=enterprising_hamster/ukraine-prozorro-tenders"
    }
  }
}
```

- "Which construction tenders were published on Prozorro in the last day?" → `{"cpvCodes": ["45000000-7"], "lastDays": 1, "maxItems": 50}`
- "Find open Ukrainian tenders for generators and energy equipment." → `{"keywords": ["генератор", "енергетичн", "energy"], "lastDays": 1, "maxItems": 50}`
- "Show reconstruction works in Kharkiv oblast above 1 million UAH." → `{"cpvCodes": ["45"], "regions": ["Харківська область"], "minValueUah": 1000000, "lastDays": 1}`

### What data can you get

| Field | Meaning |
| --- | --- |
| `id`, `tenderId` | Prozorro internal id and the public tender number such as `UA-2026-10-31-000509-a` |
| `title`, `titleEn` | Title in Ukrainian; `titleEn` only when the buyer published an English title (rare) |
| `description` | Tender description, only with `includeDetails` |
| `procuringEntity` | Buyer (contracting authority) name |
| `region` | Delivery oblast(s) from the tender items, comma-joined; `null` when the items carry no delivery region |
| `cpv`, `cpvCodes` | `cpv` is the item code that matched your `cpvCodes` filter (the first item code without a filter); `cpvCodes` lists every item code |
| `value`, `currency` | Expected value and currency as published |
| `status` | Prozorro status, e.g. `active.tendering` (bids open) or `active.enquiries` (questions period) |
| `tenderPeriod.start`, `tenderPeriod.end` | Bid submission period; `end` is the bid deadline |
| `url` | `https://prozorro.gov.ua/tender/{tenderId}` |
| `publishedAt` | Publication time with offset |

Example row (title shortened):

```json
{
  "tenderId": "UA-2026-10-31-000509-a",
  "title": "Послуга з усунення аварій в житловому фонді (поточний ремонт електромереж …)",
  "procuringEntity": "ДЕПАРТАМЕНТ З ПИТАНЬ САМООРГАНІЗАЦІЇ НАСЕЛЕННЯ ДНІПРОВСЬКОЇ МІСЬКОЇ РАДИ",
  "region": "Дніпропетровська область",
  "cpv": "45310000",
  "cpvCodes": ["45310000"],
  "value": 463400,
  "currency": "UAH",
  "status": "active.tendering",
  "tenderPeriod": { "start": "2026-10-31T15:22:12.888456+02:00", "end": "2026-11-16T00:00:00+02:00" },
  "url": "https://prozorro.gov.ua/tender/UA-2026-10-31-000509-a",
  "publishedAt": "2026-10-31T15:22:44.072836+02:00"
}
```

Direct awards without competition (`reporting`, `negotiation`, `negotiation.quick`) are excluded.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `lastDays` | `1` | Tenders published in the last N days (1–90) |
| `cpvCodes` | `[]` | CPV prefixes, e.g. `45000000-7` or `45` for construction works, `09310000` for electricity |
| `keywords` | `[]` | Any of these words in the title, description or item descriptions; case- and accent-insensitive; most tenders are written in Ukrainian, so use Ukrainian words |
| `regions` | `[]` | Delivery oblasts as Prozorro writes them, e.g. `Київська область`, `Харківська область` |
| `minValueUah`, `maxValueUah` | none | Value bounds; tenders without a value or in another currency are dropped when a bound is set |
| `onlyOpen` | `true` | Only tenders in `active.enquiries` or `active.tendering` |
| `includeDetails` | `false` | Adds `description` (charged as `tender-with-details`) |
| `maxItems` | `50` | Maximum rows saved per run (1–10,000) |
| `onlyNew`, `stateKey` | `false` | Skip tenders delivered by earlier runs with the same key |
| `maxRequestsPerSecond` | `5` | Request rate to Prozorro (0.2–5) |

`{}` returns up to 50 open tenders published in the last day; our test run took 20 seconds.

### Daily alert of new tenders

1. **Save the input as a task** with `onlyNew: true`, `lastDays: 1` and your own `stateKey` (a secret field, use one key per alert). Two ready-made tasks: [daily construction tenders (CPV 45)](https://apify.com/enterprising_hamster/ukraine-prozorro-tenders/examples/daily-ukraine-construction-tenders-alert) and [daily energy and generator tenders](https://apify.com/enterprising_hamster/ukraine-prozorro-tenders/examples/daily-ukraine-energy-generator-tenders-alert).
2. **Schedule it** in *Schedules*, for example `0 7 * * *`.
3. **Send the results onward** with an integration: e-mail, Slack, Google Sheets, Make, Zapier, n8n or a webhook on *Run succeeded*.

```json
{
  "cpvCodes": ["45000000-7"],
  "lastDays": 1,
  "onlyOpen": true,
  "maxItems": 200,
  "onlyNew": true,
  "stateKey": "ua-construction-daily",
  "maxRequestsPerSecond": 5
}
```

### How long a run takes

Prozorro's list feed has no CPV or keyword filter, so with `cpvCodes`, `keywords` or `regions` the Actor opens every open tender changed in the window and filters it after. Test runs on Sunday 2026-11-01: CPV 45 with `lastDays: 1` took 78 s (12 tenders), energy keywords with `lastDays: 1` took 79 s (8 tenders). CPV 45 with `lastDays: 3` reached the 900 s timeout after saving 165 tenders. Weekdays carry more tenders than weekends.

- For a daily alert keep `lastDays: 1`.
- For a backfill of several days, keep the default *Timeout* of 3600 s or raise it in Run options. Rows saved before a timeout stay in the dataset and are charged once.

### Pricing (pay per event)

| Event | Price (USD) | When |
| --- | --- | --- |
| `tender` | 0.003 | Each saved tender |
| `tender-with-details` | 0.006 | Each saved tender when `includeDetails` is on |

50 tenders cost $0.15 ($0.30 with details). Higher Apify plans pay less: SILVER $0.0027 and GOLD $0.0024 per tender. Tenders skipped by `onlyNew` or by your filters are not charged. Set a maximum charge per run to cap spending.

### FAQ

**Is there an official Prozorro API?** Yes, the public API at `public-api.prozorro.gov.ua` ([docs](https://prozorro-api-docs.readthedocs.io/en/latest/tendering/basic-actions/tenders.html)). This Actor walks its list feed, opens each tender and turns it into a flat row.

**Is reuse allowed?** Prozorro data is open: it may be copied and reused, including commercially, with a link to the source ([prozorro.gov.ua/openprocurement](https://prozorro.gov.ua/openprocurement)). Every row carries `url`.

**Why is `titleEn` usually missing?** English titles exist only when the buyer filled `title_en`; most tenders are published in Ukrainian only. Use Ukrainian keywords for filtering and a translation step downstream.

**Why is `region` sometimes empty?** The oblast comes from the items' delivery address. Some buyers leave it out; those tenders do not match a `regions` filter.

**Does it cover awarded contracts?** Set `onlyOpen: false` to include later stages (qualification, awarded, complete, cancelled). Direct awards are always excluded.

### Limitations

- No server-side CPV filter on Prozorro's list, so filtered windows longer than a day are slow (see above).
- The only-new memory keeps 50,000 tender ids per `stateKey`.
- Values are not converted between currencies.

### Disclaimer

Unofficial tool. Not affiliated with or endorsed by Prozorro or the Ministry of Economy of Ukraine. Always check the tender at its source before you bid.

### Development

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt -r requirements-dev.txt
.venv/bin/python -m pytest -q
```

Fixtures under `tests/fixtures/` are static samples, not live responses.

# Actor input Schema

## `lastDays` (type: `integer`):

Walk the modification feed newest first until dateModified is older than this window. Publication filters apply on the tender document.

## `onlyOpen` (type: `boolean`):

Keep active.enquiries and active.tendering only. Direct awards (reporting, negotiation) are always excluded.

## `includeDetails` (type: `boolean`):

When enabled, store Prozorro's description and charge the tender-with-details event.

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

Stop scheduling new detail requests once this many rows are pushed.

## `maxRequestsPerSecond` (type: `number`):

Minimum spacing between list and detail HTTP starts. The list docs describe a separate sync cadence of about five minutes, which is not this cap.

## `onlyNew` (type: `boolean`):

Skip ids stored under stateKey in the ukraine-prozorro-seen key-value store (50,000 ids per key).

## `stateKey` (type: `string`):

Key in the seen store when onlyNew is true. Use a unique name per alert task.

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

Optional. Keep tenders whose title, English title, description, or item text contains any keyword (case- and accent-insensitive).

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

Optional. Match any item CPV code that starts with the given prefix (2–8 digits). The list API cannot filter by CPV.

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

Optional. Match items\[].deliveryAddress.region as published (for example Херсонська область). Buyer address is not used.

## `minValueUah` (type: `number`):

Optional. Tenders without UAH value fail this filter.

## `maxValueUah` (type: `number`):

Optional. Tenders without UAH value fail this filter.

## Actor input object example

```json
{
  "lastDays": 1,
  "onlyOpen": true,
  "includeDetails": false,
  "maxItems": 50,
  "maxRequestsPerSecond": 5,
  "onlyNew": false,
  "keywords": [],
  "cpvCodes": [],
  "regions": []
}
```

# Actor output Schema

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

Tender records stored in the default dataset.

# 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 = {
    "lastDays": 1,
    "onlyOpen": true,
    "includeDetails": false,
    "maxItems": 50,
    "maxRequestsPerSecond": 5,
    "onlyNew": false,
    "keywords": [],
    "cpvCodes": [],
    "regions": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("enterprising_hamster/ukraine-prozorro-tenders").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 = {
    "lastDays": 1,
    "onlyOpen": True,
    "includeDetails": False,
    "maxItems": 50,
    "maxRequestsPerSecond": 5,
    "onlyNew": False,
    "keywords": [],
    "cpvCodes": [],
    "regions": [],
}

# Run the Actor and wait for it to finish
run = client.actor("enterprising_hamster/ukraine-prozorro-tenders").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 '{
  "lastDays": 1,
  "onlyOpen": true,
  "includeDetails": false,
  "maxItems": 50,
  "maxRequestsPerSecond": 5,
  "onlyNew": false,
  "keywords": [],
  "cpvCodes": [],
  "regions": []
}' |
apify call enterprising_hamster/ukraine-prozorro-tenders --silent --output-dataset

```

## MCP server setup

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

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/aecqw7DU2MsNjAYM9/builds/Uw5LNJaa3bYp5QL6m/openapi.json
