# zakupki.gov.ru Tender Monitor (`sashaebashu/zakupki-monitor`) Actor

Search and monitor Russian public procurement on zakupki.gov.ru by keyword, law (44-ФЗ / 223-ФЗ) and value. Two registries: open notices (subject, customer, initial price, deadline) and signed contracts (price actually paid, signing date). Alerts you to new tenders and stage changes.

- **URL**: https://apify.com/sashaebashu/zakupki-monitor.md
- **Developed by:** [Sasha Ebashu](https://apify.com/sashaebashu) (community)
- **Categories:** Lead generation, Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

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

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Russian Public Procurement (zakupki.gov.ru) Tender Monitor

Searches and monitors the Russian state procurement portal by keyword, law and contract value —
and alerts you to **new tenders** matching your keywords between runs.

Two registries, one Actor:

| Registry | Answers | Use it to |
|---|---|---|
| **Notices** (default) | What is open for bidding right now | Watch your market and bid |
| **Contracts** | What customers actually signed, and for how much | See real demand and real prices |

### What you get

Every row: `noticeNumber`, `law` (44-ФЗ / 223-ФЗ), `method`, `stage`, `subject`, `customer`,
`customerUrl`, `customerInn` / `customerCode`, **`price`**, `priceKind`, `currency`, `publishedAt`,
`updatedAt`, `applicationDeadline`, `url`, `searchQuery`, `scrapedAt`.

Contracts additionally carry `contractNumber`, `contractSignedAt` and — when the contract came from
a competitive procedure — `sourceNoticeNumber`, which joins it back to the original notice.

**`price` means different things per registry**, and `priceKind` says which: `initial` is the
starting price of a notice, `contract` is the amount actually signed. Comparing the two across the
registries shows how far tenders in your market get bid down.

#### Monitor mode

Compares against the previous run for the **same keywords and filters** and labels every row:

| `changeType` | Means |
|---|---|
| `new` | This notice/contract appeared since the last run — the alert you care about |
| `stage_change` | Moved to another stage (e.g. bidding closed) |
| `deadline_change` | The bid deadline moved |
| `price_change` | The stated price changed |
| `unchanged` | Nothing moved |

Turn on **Output only new & changed** so each run returns just what appeared or moved.

### Input

| Field | What it does |
|---|---|
| **Keywords** | What to watch, e.g. `["уголь", "медицинское оборудование"]`. Searched separately, merged, deduplicated by notice number. |
| **Registry** | Notices (open tenders) or Contracts (signed deals). |
| **Law** | 44-ФЗ (state & municipal buyers), 223-ФЗ (state-owned companies), or both. |
| **Initial price from / to, ₽** | Ignore tenders outside your contract-size range. |
| **Max pages per keyword** | 10 notices per page. The log reports how many notices your keyword matches in total, so you can see whether you are taking a sample or the lot. |
| **Monitor mode / Output only new & changed** | The alert behaviour above. |

### Notes & limits

These are properties of the source, verified rather than assumed:

- **Residential RU proxy is required.** zakupki.gov.ru does not answer datacenter proxies at all —
  every attempt returned an upstream error with an empty body. The default is correct; keep it.
- **The winning supplier is not available.** The contract card's HTML has an «Информация о заказчике»
  section but no supplier section (checked across six contracts), so the Contracts registry tells you
  who *bought* and at what price — not who won. This Actor does not guess it.
- **Some notices have no subject or deadline.** Purchases from a single supplier (ст. 93) are listed
  with only the customer, price and dates — the portal itself shows nothing else. Those fields come
  back `null` rather than filled with something invented.
- **`sourceNoticeNumber` exists only for competitive contracts.** Direct and small purchases carry
  the customer's own reference instead (`030/2026`, `Ф.2026.389`); that value is kept in
  `contractNumber`.
- **Buyer identifier differs by law**: 223-ФЗ organisation links carry an INN, 44-ФЗ ones carry an
  internal `organizationCode`. One of the two is always present.
- The Actor honours your **Maximum cost per run** and stops when the cap is reached.
- A keyword that matches nothing is a normal result: the run succeeds with zero rows and says so.

# Actor input Schema

## `searchQueries` (type: `array`):

What to watch for, e.g. \["уголь", "медицинское оборудование"]. Each keyword is searched separately and results are merged, deduplicated by notice number. Leave empty to take the newest notices with no keyword filter.

## `registry` (type: `string`):

«Notices» is the alert feed: what is open for bidding. «Contracts» is the history: what customers actually signed and at what price, which shows how far the initial price was bid down. NOTE: zakupki does not publish the winning supplier in the contract card's HTML, so contracts identify the CUSTOMER, not the winner.

## `law` (type: `string`):

44-ФЗ covers state and municipal buyers; 223-ФЗ covers state-owned companies. Different rules, often different suppliers.

## `priceFrom` (type: `integer`):

Ignore tenders below this contract value. 0 = no lower bound.

## `priceTo` (type: `integer`):

Ignore tenders above this contract value. 0 = no upper bound.

## `maxPagesPerQuery` (type: `integer`):

10 notices per page. The Actor stops earlier when a keyword runs out of new notices.

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

Stop after this many notices in total. 0 = no limit.

## `maxConcurrency` (type: `integer`):

Parallel searches. zakupki.gov.ru is a government portal — keep this modest.

## `monitorMode` (type: `boolean`):

Compare against the previous run for the SAME keywords and filters, and label every notice: new / stage\_change / deadline\_change / price\_change / unchanged. This is what turns the Actor into a tender alert instead of a one-off dump.

## `onlyChanges` (type: `boolean`):

Monitor mode only — drop the `unchanged` notices so each run returns just what appeared or moved since last time.

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

IMPORTANT: keep the default (Apify Proxy, RESIDENTIAL, country RU). zakupki.gov.ru does not answer datacenter proxies at all.

## Actor input object example

```json
{
  "searchQueries": [
    "уголь"
  ],
  "registry": "notices",
  "law": "any",
  "priceFrom": 0,
  "priceTo": 0,
  "maxPagesPerQuery": 5,
  "maxItems": 0,
  "maxConcurrency": 3,
  "monitorMode": false,
  "onlyChanges": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "RU"
  }
}
```

# Actor output Schema

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

No description

## `changes` (type: `string`):

No description

## `details` (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 = {
    "searchQueries": [
        "уголь"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "RU"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("sashaebashu/zakupki-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchQueries": ["уголь"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "RU",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("sashaebashu/zakupki-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    "уголь"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "RU"
  }
}' |
apify call sashaebashu/zakupki-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sashaebashu/zakupki-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/SFh7Ow8xfGCpbvCq2/builds/6m75UMtJtQGNitGBv/openapi.json
