Ukraine ProZorro Tenders — Buyers, Suppliers & Values avatar

Ukraine ProZorro Tenders — Buyers, Suppliers & Values

Pricing

$4.00 / 1,000 tenders

Go to Apify Store
Ukraine ProZorro Tenders — Buyers, Suppliers & Values

Ukraine ProZorro Tenders — Buyers, Suppliers & Values

Search Ukraine's ProZorro public procurement — tenders, buyers, awarded suppliers, values, items (CPV) and status from the official OpenProcurement API. Filter by keyword, category, value and date; ideal for reconstruction-contract monitoring. Open data, no key.

Pricing

$4.00 / 1,000 tenders

Rating

0.0

(0)

Developer

Berkan Kaplan

Berkan Kaplan

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

1

Monthly active users

12 days ago

Last modified

Categories

Share

Ukraine Public Tenders — Prozorro 🇺🇦

foXLabs procurement series: EU TED · World Bank · IADB · UK Contracts Finder · US federal awards · India

🎉 Turn Prozorro into clean, structured Ukrainian tender data — no login, no API key, one row per tender, with the buyer and its contact person, e-mail and phone, the expected value, the items with CPV codes and the award decisions with supplier names and amounts. Built for suppliers, bid teams and reconstruction-market intelligence.

🔍 What is Ukraine Public Tenders — and when should you use it?

Give this Actor a keyword, a status, a category, a buyer region and a value range, and it returns the matching tenders from Prozorro, Ukraine's public e-procurement system, read through its public OpenProcurement API. The Actor does not query a search index: it walks Prozorro's feed — a list of tenders, most recently changed first — backwards. A status or region filter is decided from that list; only the tenders that can still match are opened in full and checked against keyword, category and value.

Use it when you need: the tenders that still accept bids or questions, with a person to contact; the newest award decisions with supplier names and amounts; or the tenders of one status or one region over the last days.

Use something else when: you need a keyword search across months of history in one run. Keyword, category and value are checked by reading tenders one by one — see "How far back a run reaches".

🤖 Use with AI agents

Already on the Apify MCP server? Ask for this Actor by name: foxlabs/ukraine-prozorro-tenders. Checked on 2026-10-08: the server lists it as the tool foxlabs--ukraine-prozorro-tenders.

Your agent can pay for its own runs. For an agent with a wallet and no human to sign in, Apify sells a prepaid, spend-capped API token over x402 at agi.apify.com; such a token runs any Actor that uses limited permissions, as this one does (the terms of that path are Apify's own — see apify.com/agents.md). The Actor also meets Apify's conditions for per-request agentic payments — pay-per-event only, limited permissions, no Standby — and Apify Store lists it as eligible for them (read from the Store on 2026-10-08). The Actor charges the same events either way: one per tender delivered.

Otherwise paste this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

I want Ukrainian public tenders (Prozorro) from the Apify Actor `foxlabs/ukraine-prozorro-tenders`.
Input: `searchTerm` is text to find in the tender title, description, item descriptions or CPV codes (Ukrainian words in Cyrillic, or a CPV code such as 45453000). `status` is "any", "tendering" (accepting bids or questions, deadline not over), "active" (every status that starts with "active", including awarded), "complete", "cancelled" or "unsuccessful". `category` is "any", "goods", "works" or "services". `region` is part of the buyer's region in Ukrainian. `minValue` / `maxValue` bound the expected value (nearly always UAH). `maxResults` caps the tenders delivered; each one is charged. Leave `maxScan` out.
Start with: {"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100}
Ask me what to look for, run the Actor, then show the tenders as a table with title, buyer, value, status and contact e-mail, and tell me the run's status message: it says how far back the run looked and why it stopped.

The machine-readable API and MCP config live at apify.com/foxlabs/ukraine-prozorro-tenders.md; that page links to the OpenAPI definition.

📋 Overview

Everything you need to turn Prozorro tenders into structured data — in one Actor, with no login, cookies or API key.

Why teams pick this Actor:

  • ✅ Read live from Prozorro's public API — every run reads the feed as it stands; nothing is kept from an earlier run.
  • 📇 A person to contact — the buyer's contact name and e-mail were on 495 of 500 tenders in one measured run, a phone on 493.
  • 🏆 Award decisions with suppliers — each entry carries the supplier's name, its registry number, the amount and the status of the award.
  • 📦 What is being bought — every item with its description, CPV code, quantity and unit.
  • 🧾 Measured, not promised — this page says how often each field was filled, and every run reports how far back it looked.
  • 💰 Pay only for tenders delivered — a search that finds nothing charges nothing.
  • 🤖 Agent-ready — MCP tool + agentic payments.

✨ Features

  • 🔎 Keyword — a piece of text matched in the title, the description, the item descriptions and the CPV codes, so a product name or a CPV code both work.
  • 🚦 Status and category — accepting bids or questions (tendering), active, complete, cancelled or unsuccessful; goods, works or services.
  • 🗺️ Buyer region and value range — in Ukrainian and (nearly always) in hryvnia, as Prozorro stores them.
  • 📅 Dates — the last 7, 30 or 90 days, all time or a custom range, in Kyiv calendar days, on the date the tender was last changed.
  • 🧹 Clean schema — 39 camelCase fields. A count of bids, lots, documents or contracts that is not in Prozorro's record stays null; supplierCount is 0 when the row has no award entry.

🎬 Quick Start

curl -X POST "https://api.apify.com/v2/acts/foxlabs~ukraine-prozorro-tenders/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100}'

The call starts a run and returns it; the rows are in the run's default dataset. On 2026-10-08 this input returned 100 works tenders in the statuses active.tendering or active.enquiries, in 162 seconds with 512 MB of memory. (The measured runs sent the token in a header; this command line was not run as written.)

🚀 Getting Started (3 steps)

  1. Choose your filters — a keyword, status, category, region or value range; leave them empty for the newest tenders of every kind.
  2. Bound the run — maxResults is the most tenders you get and pay for; a maximum charge for the run is the second bound. maxScan can stay empty.
  3. Run it and read the status message — it says how far back the run looked and why it stopped.

📥 Input

{"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100}

Every field is optional; an empty input returns the 500 most recently changed tenders, which costs $2.00.

FieldTypeDescription
searchTermstringA piece of text to find, case-insensitive, in the title, description, item descriptions and CPV codes. It is matched as written — one unbroken piece, no AND / OR, no transliteration, no word forms: школ finds школа, школи and школі, школа only the first. An apostrophe matches whichever sign the tender uses (', ’, ʼ). Write Ukrainian in Cyrillic, prefer the stem, or give a CPV code (45453000).
statusstringany (default); tendering — active.enquiries or active.tendering with the bidding deadline not over; active — every status that begins with active, awarded tenders included; complete, cancelled or unsuccessful.
categorystringany (default), goods, works or services.
regionstringPart of the buyer's region as Prozorro writes it: Львівська область, Київська область, м. Київ. Київ alone matches both the region and the city. With this filter set, tenders without a region never match.
minValue / maxValueintegerBounds on the expected value, inclusive. The number is compared as it is; nearly every value is in hryvnia (UAH). With a bound set, tenders without a value never match. A minValue above the maxValue fails the run.
datePresetstringThe range of last-change dates: last_7_days, last_30_days (default), last_90_days, all_time or custom. A preset counts back from today in Kyiv calendar days: on 8 October last_7_days started at 00:00 on 1 October. all_time is bounded too: one run looks at 3,000,000 changes at most.
dateFrom / dateTostringYYYY-MM-DD, Kyiv calendar days, each included whole; only with datePreset custom — next to another preset they are not used, and the status message says so. The run enters the feed at the end of dateTo (now, if empty or in the future) and stops at the start of dateFrom (no start, if empty). A date in another form or one that does not exist, a dateFrom after dateTo, or custom with neither date fails the run with a message that says which.
maxResultsintegerMost tenders to deliver — and to pay for. Default 500; 1 to 100,000.
maxScanintegerMost tenders to read in full; 100 to 1,000,000. Tenders that a status or region filter rules out do not count. Empty means the automatic limit of 20,000 — and, for a keyword typed in Latin letters, an early stop once 3,000 tenders read in full have matched nothing (looked at when a listing page is done, so up to a few hundred more); a number of your own switches that stop off. A region typed in Latin letters ends the run after 20,000 changes without a match, with or without a maxScan.

How far back a run reaches

A run that finishes ends with a status message that says how far it got and why it stopped; while it walks, its status line says how far it is. From the example above (a "change" is one tender at its latest change):

Delivered 100 tenders. The run looked at 14,301 changes, back to 2026-10-08T11:26:28.093236+03:00, and read 2,475 tenders in full. It stopped at maxResults 100; older tenders were not looked at.

The time is the change the run stopped at — here the 100th row's dateModified — and both counts end there.

A run stops at whichever comes first: maxResults, the start of the date range, maxScan, the maximum charge set for the run, the early stop for Latin letters, 3,000,000 changes or the end of the feed. Measured on Thursday 2026-10-08, 23:27 to 23:58 Kyiv time, in runs with 512 MB of memory, half of what a run gets when none is chosen:

  • Status or region is decided from the feed's listing, so the run reaches far. {"status":"cancelled","datePreset":"last_7_days","maxResults":100000} covered its whole range in 119 seconds: 109,897 changes looked at, 814 read in full, 812 delivered ($3.25), 2 masked ones left out. {"region":"Львівська","minValue":500000,"maxScan":3000} looked at 52,448 changes, back to 12:56 on 6 October, and delivered 460 tenders; the build before this one got back about two hours for the same search and delivered 33.
  • Keyword, category or value needs every remaining tender read in full, so maxScan decides. {"searchTerm":"томограф","maxResults":20} read the automatic 20,000 in 1,016 seconds, got back to 08:14 that morning and delivered 7 tenders, 5 of them beyond its first 3,000. The automatic limit is about one working day.
  • A custom range is entered at its dateTo, with no reading of newer tenders. {"status":"cancelled","datePreset":"custom","dateFrom":"2026-10-06","dateTo":"2026-10-06","maxResults":100000} covered that day in 22 seconds — 19,102 changes looked at, 157 tenders delivered, each with a dateModified of 6 October.
  • The run's time limit bounds it too. The Actor's default is 3,600 seconds (read from the platform on 2026-10-08). At the measured rate — 20,000 tenders read in full in 1,016 seconds — an hour holds about 70,000 reads with 512 MB of memory, so a maxScan far above that, or maxResults 100,000 without a status or region filter, does not finish in a default run. By the code a run that is cut off by its time limit, or aborted, keeps its rows, which are charged, and its last status line; it has no closing message.

📤 Output

One row per tender, saved to the dataset. Texts are in Ukrainian, as published.

{
"tenderID": "UA-2026-09-14-005480-a",
"id": "8dd86c06dd40420c82f72b378b30cbe0",
"title": "Монітор",
"description": null,
"status": "active.awarded",
"procurementMethod": "aboveThreshold",
"category": "goods",
"buyerName": "Тернопільський національний педагогічний університет імені Володимира Гнатюка",
"buyerId": "02125544",
"buyerKind": "general",
"buyerRegion": "Тернопільська область",
"buyerLocality": "Тернопіль",
"buyerAddress": "вул. М. Кривоноса, 2",
"buyerPostalCode": "46015",
"buyerContactName": "Б.",
"buyerContactEmail": "zakupivli@tnpu.edu.ua",
"buyerContactPhone": "+3803…",
"valueAmount": 35175,
"valueCurrency": "UAH",
"vatIncluded": false,
"awardCriteria": "lowestCost",
"itemCount": 1,
"cpvMain": "30230000-0",
"items": [{ "description": "Монітор", "cpv": "30230000-0", "quantity": 3, "unit": "штука" }],
"supplierCount": 1,
"awards": [{ "supplier": "ТОВ \" КІБЕР ВЕКТОР\"", "supplierId": "46200885", "awardValue": 24390, "awardCurrency": "UAH", "awardStatus": "active", "awardDate": "2026-10-06" }],
"bidCount": 2,
"lotCount": 1,
"documentCount": 7,
"contractCount": 1,
"dateCreated": "2026-09-14",
"noticePublishedAt": "2026-09-14",
"enquiryPeriodEnd": "2026-09-27",
"tenderPeriodStart": "2026-09-14",
"tenderPeriodEnd": "2026-09-30",
"dateModified": "2026-10-08T21:03:18.150484+03:00",
"tenderUrl": "https://prozorro.gov.ua/tender/UA-2026-09-14-005480-a",
"source": "ProZorro / OpenProcurement — Ukraine public procurement (open data)",
"scrapedAt": "2026-10-08T18:31:33.846Z"
}

The example is a real row of the run described here, shown whole: 39 fields, only description empty; the contact's name and phone are shortened here. "Filled" is the number of rows that carried the field in one unfiltered run on 2026-10-08 (21:31 Kyiv time): the 500 most recently changed tenders — 228 of them with a bidding period in the record and 272 without one (methods reporting, negotiation, negotiation.quick).

FieldWhat it isFilled (of 500)
tenderID / idProzorro's public tender number and its internal ID — both unique per tender500
titleTender title500
descriptionLonger description, when the buyer wrote one88
statusIn the measured runs: active.enquiries, active.tendering, active.auction, active.qualification, active.awarded, active, complete, cancelled, unsuccessful500
procurementMethodFor example aboveThreshold, belowThreshold, priceQuotation, negotiation, reporting500
categorygoods, works or services500
buyerName / buyerId / buyerKindThe buying entity, its registry number and its kind (general, special, defense, authority …)500
buyerRegion / buyerLocalityRegion and town of the buyer440 / 423
buyerAddress / buyerPostalCodeStreet address and postal code430 / 431
buyerContactName / buyerContactEmailThe buyer's contact person and e-mail, as typed by the buyer495
buyerContactPhoneThe contact's phone, as typed493
valueAmount / valueCurrency / vatIncludedExpected value, its currency and whether it includes VAT495
awardCriterialowestCost on these rows; one row of the later runs had lifeCycleCost228 — every row with a bidding period, none without
itemCount / cpvMain / itemsNumber of items, the first item's CPV code, and every item with description, cpv, quantity, unit500
supplierCount / awardsNumber of award entries, and each entry with supplier, supplierId, awardValue, awardCurrency, awardStatus, awardDate379 tenders had at least one entry, 351 an active one
bidCountNumber of bids when the record lists them; null when it carries no bid list118
lotCount / documentCount / contractCountLots, attached documents, contracts186 / 477 / 353
dateCreatedDay the tender was created500
noticePublishedAt / tenderPeriodStart / tenderPeriodEndPublication day and the bidding period — the day only; the hour of a deadline is not in the row228 — every row with a bidding period
enquiryPeriodEndLast day for questions174
dateModifiedWhen the tender was last changed (Kyiv time)500
tenderUrlThe tender's page on prozorro.gov.ua500
source / scrapedAtThe Actor's label for the source, and when the row was read (UTC)500

awards holds every award decision, not only the winner: of the 453 entries in that run 370 were active, 30 pending, 29 unsuccessful and 24 cancelled — read awardStatus. All 453 carried the supplier's name, registry number and amount. supplierCount counts entries, not suppliers: one tender had 8 entries for 4 suppliers.

bidCount was null on 382 rows: 272 procedures without a bidding period, 107 tenders in active.enquiries or active.tendering, and 3 that had ended cancelled or unsuccessful.

The run also writes a summary to the key-value store under OUTPUT: tenders delivered, changes looked at, masked and unreadable tenders, why the run stopped, and scanned, the tender requests made. Requests go out 100 at a time, so scanned can exceed the status message's count: 2,533 against the 2,475 quoted above.

💼 Use cases

1. Tenders in your category that still accept bids. Input: {"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100}. Output: 100 works tenders on 2026-10-08, each with value, bidding period and the buyer's contact name, e-mail and phone: 89 in active.tendering, 11 in active.enquiries, none with a tenderPeriodEnd before 10 October.

2. Product or CPV watch — tenders that mention what you sell.

{"searchTerm":"ремонт","status":"tendering","maxResults":100}

Output: 100 rows on 2026-10-08, 94 with the word in the title. Of the other 6, 2 name repair works in an item; 4 are tenders for food or cable whose description quotes a sanctions clause that contains the word — delivered and charged like the rest. A CPV code is matched the same way; no run of this build used one.

3. Regional pipeline above a value — larger contracts of one region.

{"region":"Львівська","minValue":500000,"maxScan":3000}

Output: 460 tenders on 2026-10-08 ($1.84), changed between 6 and 8 October, in nine statuses — add status to narrow it, maxResults to bound it.

🔗 Integration

JavaScript / Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('foxlabs/ukraine-prozorro-tenders').call({"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0]);

Python

from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('foxlabs/ukraine-prozorro-tenders').call(run_input={"status":"tendering","category":"works","datePreset":"last_7_days","maxResults":100})
for item in client.dataset(run.default_dataset_id).iterate_items():
print(item)

Written for apify-client 3 for Python, where call() returns a run object; with version 1 or 2 it returns a dict — write run['defaultDatasetId'] there.

Automation (n8n / Zapier / Make) — a standing watch. The Actor remembers nothing between runs: two runs whose date ranges overlap deliver — and charge — every matching tender that lies in both, changed or not. The seven-day and the one-day run above both delivered the 157 cancelled tenders of 6 October: 157 rows charged twice ($0.63). Presets overlap from one day to the next. To avoid that, give each scheduled run a closed range of its own (datePreset custom, dateFrom and dateTo set to the last Kyiv day that has ended) and check that its status message says It covered the whole date range. — as the one-day run's did — and does not say could not be read. A preset, a range without dateTo and a dateTo that is not over end at the start of the run, and the message says that instead. With a keyword, category or value filter set maxScan yourself: 6 October held 19,102 changes, and each of the four working days up to the measurement about 18,000 to 21,000.

📊 Pricing

Pay-per-event. Read from the Store on 2026-10-08: $0.004 per tender delivered ($4 per 1,000); there is no run-start fee — the 15 runs measured that evening were charged tender events only, one per row. A run that delivers nothing charges nothing: the keyword road repair read 3,000 tenders and charged no event. Two things bound a bill: maxResults and a maximum charge for the run (maxTotalChargeUsd in the API; with $0.02 a run delivered 5 tenders and stopped; with $0.001 it made no request and ended with No tender delivered: the run's maximum charge does not cover one tender.). maxScan does not: a status or region filter now reaches further back for the same maxScan — the region example went from 33 tenders ($0.13) to 460 ($1.84). Overlapping runs charge the same tender again. View current pricing.

🚧 What it does not do

  • No keyword search across history in one request. Keyword, category and value are checked by reading tenders in full, up to maxScan.
  • active is not "open for bids": of the 247 unfiltered rows with a status that begins with active, 140 were awarded, in qualification or plain active. Use tendering.
  • Only tendering checks the bidding deadline. With another status, a row in active.tendering can carry a tenderPeriodEnd that has passed: 3 of the 143 rows in the two bidding stages in the region example.
  • The date range is about the last change, not the publication day: of the 812 cancelled tenders last changed between 1 and 8 October, 458 had been created before 1 October.
  • No memory between runs, so no "only new" mode.
  • No translation. Titles, buyers and items stay in Ukrainian.
  • No documents and no bid details — the row carries their counts.
  • Masked records are left out. Prozorro masks some records: the title reads Тимчасово замасковано… and zeros replace the buyer, the contact, the value and the item. A run neither delivers nor charges them; its status message counts those that matched: 2 next to those 812 tenders.
  • A tender whose record cannot be read is left out and counted in the status message — after it was asked for twice, three tries each, while Prozorro answered other requests. All 31,216 tender requests of the 15 runs were answered, so no measured run met this.
  • minValue / maxValue do not convert currencies or adjust for VAT. One of the 2,192 rows of the 15 runs carried a value in EUR, the others in UAH; 140 of 495 values in the unfiltered run included VAT.

❓ FAQ

Do I need an account, login or API key for Prozorro? No. The Actor reads Prozorro's public API.

How current is the data? Every run reads the live feed; in a run on 2026-10-08 the newest tender had been changed 14 seconds before it was read.

Why are so many rows complete with the method reporting? Among the 500 newest tenders 260 had the method reporting; 232 of those were already complete, and none had a bidding period, award criteria or a bid count. What Prozorro means by the method was not checked at the source. status tendering leaves them out.

Does the buyer's contact work? It is what the buyer typed into Prozorro, unchecked. Of 495 e-mail addresses 377 were on free mail services (ukr.net 218, gmail.com 142); of 493 phones 299 were one number in the form +380…, 156 the same without the plus and 38 in other spellings. defense buyers (69 of 500) carry less: 42 had no region, 5 no e-mail, 5 no value.

What does each row represent? One Prozorro tender in its current state.

Can I export to CSV / Excel / JSON? The measurements read the dataset as JSON. Other formats are Apify's dataset exports and were not tested here; items and awards are arrays (one tender had 106 items) and 7 of 500 titles contain line breaks.

🐛 Troubleshooting

  • Fewer rows than expected, or none — the status message gives the reason: It stopped at maxResults … (raise maxResults), It stopped at maxScan … (raise maxScan), It covered the whole date range. (nothing more is in that range), It covered the date range, which ends at the start of the run. (a preset or a range without dateTo: what changes later comes with a later run), It stopped at the maximum charge set for the run (raise that limit).
  • No tender matched, so the run stopped early. — only a keyword typed in Latin letters with maxScan empty, or a region typed in Latin letters, ends a run this way; the message says which. road repair stopped after 3,000 tenders read in full, Lviv after 20,000 changes with no tender read. Tender text and region names are Ukrainian: write the region as in buyerRegion (Одеська область); Latin text matches only where a tender has it, such as a brand name (canon: 5 of the 500 unfiltered rows). To search further back for a keyword anyway, set maxScan; a region in Latin letters is not searched further — of the 2,625 buyer regions in the rows measured on 2026-10-08, none carried a Latin letter.
  • bidCount is null — the record carries no bid list; the number is unknown, which is not the same as zero.
  • The same tender came back in a later run — runs keep no memory; see "a standing watch" under Integration.
  • The run failed — a custom date was not a calendar date: dateFrom "01.10.2026" is not a calendar date. Use YYYY-MM-DD, for example 2026-09-30. (no row, no charge). custom with neither date failed the same way. By the code a run also fails for a dateFrom after dateTo, for a minValue above the maxValue, and when Prozorro stops answering: a listing page that does not come in seven tries (their pauses add up to 97 seconds), tender requests that stay unanswered for the minute and a half the run waits for Prozorro, or no tender read at all. Such a run's message says how far it got; rows already delivered stay in the dataset and stay charged. No measured run failed in these ways.

The Actor reads what Prozorro's public API returns without a login or a key. This page does not state a licence: Prozorro's terms of use were not checked for it, so check them for your use (the words "open data" in the source field are the Actor's own label). The rows carry personal data: the buyer's contact person, e-mail and phone, and — for suppliers who are natural persons — their name and a 10-digit supplierId, the form of a personal taxpayer number (162 of 453 award entries in the unfiltered run, 102 different numbers). If you process such data, data-protection law such as the GDPR may apply to you. foXLabs is not affiliated with Prozorro.

🤝 Support & contact

Changelog

0.2.12 — 2026-10-09 — status and region decided from the feed, date ranges in Kyiv days, new "accepting bids" status, masked tenders left out; README rewritten

  • A status or region filter no longer reads every tender in full. ProZorro's feed can carry the status and the buyer next to each tender id; the run now asks for them and reads in full only the tenders that can still match. In the newest 5,000 changes on 2026-10-08, 58 of 100 were not active…, and 87 of 100 lay outside the largest region (Київська область), 94 or more outside any other — tenders the run used to download one by one and throw away. The same maxScan therefore reaches further back.
  • maxScan counts the tenders read in full and has no fixed value in the form any more. Left empty, the limit is 20,000, as before. A keyword typed in Latin letters is the exception: tender text is Ukrainian, so such a run stops once it has found nothing in 3,000 tenders read in full, and its status message says so; a maxScan you set yourself is read to its end. A region typed in Latin letters ends the run after 20,000 changes without a match, with or without a maxScan: none of the 2,625 buyer regions in the rows measured on 2026-10-08 carried a Latin letter, and a region is decided from the listing without reading a tender, so maxScan never fills.
  • New status tendering: tenders in active.enquiries or active.tendering whose bidding deadline is not over — the tenders a supplier can still ask about or bid on. (Of 100 rows in those two stages measured on 2026-10-08, 3 had a deadline one to six days in the past; such tenders are left out.) active keeps its meaning — every active… status, including evaluation and awarded.
  • Date ranges are Kyiv calendar days, the calendar of ProZorro's own timestamps: a range for 15 September returns rows dated 15 September. Ranges were cut at UTC midnight — of the 50 rows of a one-day range measured on 2026-10-08, 25 carried the next day's date. The presets start at Kyiv midnight too.
  • A custom date range starts where it ends. The feed's position is a moment in time, so a custom range is entered at its dateTo instead of being reached from today, tender by tender; a range that ended a working day or more earlier was out of reach at the default maxScan. Custom dates are validated: a date that does not exist stops the run with a message that names the field (a malformed dateTo used to be ignored, and the run delivered tenders newer than the range). So do custom with neither date (it used to mean every date) and a dateFrom after the dateTo.
  • Masked tenders are not delivered. ProZorro masks some records: the title is a placeholder and buyer, value and items are zeros. Two of the 807 rows of a run measured on 2026-10-08 were such records, delivered and charged. They are now left out and counted in the status message.
  • A run that finishes ends with a status message: tenders delivered, changes looked at, the change the run stopped at and why it stopped — date range covered, maxResults, maxScan, maximum charge, nothing found, 3,000,000 changes or the end of the feed. While the run walks, its status line says how far it is; a run that is aborted or cut off by its time limit keeps that line and has no closing message (the Apify SDK ends an aborted run at once). Tenders ProZorro did not return are counted in the message instead of being skipped in silence. Dates given next to a preset are named as not used. Only a custom range whose dateTo is over is called "the whole date range"; a preset, a range without dateTo and a dateTo that is not over are said to end at the start of the run. The run stops at its maximum charge, at once when the charge is used up; a charge that covers no tender stops it before the first request. The OUTPUT record in the key-value store keeps scanned and pushed and gains looked, skippedByListing, unreadable, masked, reachedBackTo and stopped.
  • Interruptions at ProZorro are ridden out. Every request used to have three tries within one second: a listing page that did not come ended the run with fetch failed (HTTP 502) and nothing else, and a tender that did not come was left out without a word. Now a listing page is asked for seven times, with pauses that add up to 97 seconds. A tender is asked for three times; when tenders do not come, the run asks the feed whether ProZorro is there at all — if it is, the missing tenders are asked for once more and what still does not come is counted as unreadable; if it is not, the run waits for it for up to a minute and a half and then reads the rest. When ProZorro does not come back — or its feed answers and a whole batch of tenders does not — the run fails with a message that says how far it got and how many tenders it had delivered, and writes its OUTPUT record first. Checked against a simulated feed only (interruptions of 3, 30 and 60 seconds cost no tender there; one of five minutes ended the run with that message): no measured run met an interruption.
  • A keyword with an apostrophe finds every spelling. Tender text writes the Ukrainian apostrophe with several signs — in the 2,878 tenders of the measured rows ’ in 369, ' in 237, a backtick in 20 — and the keyword was compared as written: комп'ютер typed with the plain sign found 18 of the 63 tenders that carry the word, typed with ’ 43. The signs are now read as one, in the keyword and in the tender text (counted on the saved rows; no platform run of this build yet).
  • The README described another Actor. Its examples used input keys this Actor does not have (queries, maxResultsPerQuery, with a literal undefined value), its input table listed phantom fields (maxConcurrency, includeRaw) and its text described a company registry ("relevance-ranked name search or exact registry-ID lookup", "legal form, formation date"). Every example input is now a real one and was run on the platform (the curl line itself was not run as written); the output table says how often each field was filled in a measured run.
  • The Python example works with the current Apify client. It read the results with run['defaultDatasetId']; with apify-client 3 for Python call() returns a run object and that line raised TypeError: 'Run' object is not subscriptable after the run had finished. It now reads run.default_dataset_id; with client version 1 or 2, keep run['defaultDatasetId'].
  • No output field or pricing change. Behaviour changes for inputs that already exist: a run with a status or region filter reaches further back for the same maxScan, so it can deliver — and charge for — more tenders than before; the edges of a custom range move by two or three hours (Kyiv days instead of UTC days), and a preset counts its days back from the Kyiv date — a run started between 00:00 and 03:00 Kyiv time begins its range 21 to 22 hours later than before; masked tenders are no longer delivered; a keyword with an apostrophe also finds the tenders that write it with another apostrophe sign (more rows than before); a keyword in Latin letters with maxScan left empty, and a region in Latin letters with or without one, can stop early; maxScan is no longer rounded up to the next hundred (250 used to read 300); custom with neither date, a dateFrom after the dateTo and a minValue above the maxValue stop the run; a run fails when ProZorro stops answering tender requests — it used to end as a shorter or an empty result.

0.2 — 2026-09-07

  • The README was regenerated from a template shared by many Actors; the entry of that day read "Enabled AI-agent payments (x402) + rebuilt the README to the full standard". The label is the generator's: the builds of that day were numbered like the others of this Actor. That README is the one the entry above corrects — its examples and parts of its text did not describe this Actor.

0.2 — 2026-08-01

  • Buyer contacts: buyerContactName, buyerContactEmail, buyerContactPhone — already present in the fetched API response, so no extra requests or runtime.
  • Added buyerKind, buyerAddress, buyerPostalCode, vatIncluded, awardCriteria, cpvMain, enquiryPeriodEnd, tenderPeriodStart, noticePublishedAt, bidCount, lotCount, documentCount, contractCount.
  • Keyword search now also matches item descriptions and CPV codes, not just the tender title.
  • Counts that are not in the record stay null instead of reporting a misleading 0.
  • New "Buyer contacts" dataset view.

0.0

  • Initial release, 2026-07-09 (the Actor's first changelog called it 0.1): Prozorro tenders with buyers, award entries (supplier, registry number, award value), expected values, CPV-coded items and status; filters by keyword, status, category, region, value and date; newest first with a maxScan cap.