# World Bank Tenders, Contract Awards & Bid Deadlines (`yadroo/world-bank-procurement`) Actor

Tender notices and contract awards of every World Bank-financed project as rows: what is bought, the bid deadline in UTC with days left, borrower country, project, procurement method, reference number and the notice text. Filter by keyword, country, notice type or method. Keyless official API.

- **URL**: https://apify.com/yadroo/world-bank-procurement.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Business, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.05 / 1,000 procurement notice rows

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

## World Bank Tenders, Contract Awards & Bid Deadlines

Tender notices and contract awards of every World Bank-financed project as rows: what is bought, the bid deadline in
UTC with days left, borrower country, project, procurement method, reference number and the notice text. Filter by
keyword, country, notice type or method. Keyless official API.

The source is the Bank's own procurement-notice index — about 420 000 records today, 300 000 of them awarded
contracts, the rest opportunities that were once open for offers. It covers what the Bank finances in its borrower
countries: a road in Nigeria, a hospital's equipment in Pakistan, a feasibility study in Kazakhstan. It does **not**
cover the Bank's own office purchasing and it is not a national tender portal — a country's tenders appear here only
when the Bank pays for the project. No key, no login, no captcha, no browser: a run is a handful of small JSON
requests, sent one at a time. Data is published by the World Bank under Creative Commons Attribution 4.0. Made by
Yadroo.

Two things the source cannot do, and this actor does instead: it has **no date parameter of any kind**, so the
publication and deadline windows below are applied while paging, in UTC; and it takes **one value per filter field**,
so several countries or notice types are sent as several searches and merged by notice id.

### Use cases

- **What can I bid on, and what closes first** — `onlyOpen: true`, `deadlineWithinDays: 30`, `sort: "deadlineAsc"`:
  the queue a bid manager works through, with `daysToDeadline` already counted and the buying agency's name, e-mail
  and phone on every row.
- **My market, my trade** — `countries: ["Nigeria"]`, `procurementGroups: ["CW"]`: civil-works tenders of financed
  projects in one country. Swap `GO` for equipment, `CS` for consulting, `NC` for transport, printing or maintenance.
- **Consulting assignments only** — `noticeTypes: ["Request for Expression of Interest"]`, `procurementGroups: ["CS"]`:
  the expression-of-interest feed that firms and individual experts live off, without a single equipment tender in it.
- **Who won** — `mode: "awards"`, `query: "solar"`: awarded contracts in a sector. The winning firm, the amount and
  the evaluation are inside `noticeText`, because that is where the source puts them (see
  [What an award row contains](#what-an-award-row-contains)).
- **The full history of one programme** — `projectIds: ["P177816"]`, `mode: "all"`: every notice and every award of one
  financed project, from the first general notice to the signed contracts. What auditors, journalists and NGOs ask for.
- **A weekly digest that never repeats itself** — `sinceDays: 7`, `onlyNew: true` on a schedule: the actor remembers
  the notice ids it has already written and returns only what appeared since the previous run.
- **A tool for an agent** — one call answers one question, the row is flat JSON, and `fields` cuts it down to the
  handful of keys a model should see.

### Input

Nothing is required. With no input at all the actor writes the 50 newest tender notices of any kind, in any country.

| Field | Type | Default | Notes |
|---|---|---|---|
| `mode` | string | `notices` | `notices` (the four opportunity types), `awards` (contract awards), `all`. Presets `noticeTypes`; see [Modes](#modes) |
| `query` | string | — | Free text over bid description, project name and notice text. Prefilled with `solar` in the console |
| `queryMatch` | string | `all` | `all` = keep only notices containing every word; `any` = the source's own behaviour. See [Keyword matching](#keyword-matching) |
| `countries` | string\[] | — | Country names as the source spells them, e.g. `["Kazakhstan"]`, `["Nigeria", "Ghana"]`. See [Countries and regions](#countries-and-regions) |
| `countryField` | string | `either` | `either`, `project`, `implementing`. Which of the two country fields to match; see [Two countries per notice](#two-countries-per-notice) |
| `noticeTypes` | string\[] | follows `mode` | Multi-select of the 5 [notice types](#notice-types). One search per value |
| `procurementGroups` | string\[] | — | `GO`, `CS`, `CW`, `NC` — see [Categories](#categories) |
| `procurementMethods` | string\[] | — | Method codes, e.g. `["RFB", "ICB"]`. See [Procurement methods](#procurement-methods) |
| `projectIds` | string\[] | — | e.g. `["P177816", "P171577"]`. The letter P and five to seven digits |
| `languages` | string\[] | — | `English`, `French`, `Spanish; Castilian`, `Portuguese`. See [Languages](#languages) |
| `onlyOpen` | boolean | `false` | Keep only rows whose deadline day has not passed in UTC. Drops awards and most general notices, which have no deadline |
| `deadlineWithinDays` | integer | — | 1–365. Keep deadlines between today and N days out; switches `onlyOpen` on |
| `sinceDays` | integer | — | 1–3650. Keep notices published in the last N days, by notice date |
| `onlyNew` | boolean | `false` | Write only notice ids no earlier run with the same filters has written. See [Scheduling](#use-it-from-code--agents) |
| `sort` | string | `noticeDateDesc` | `noticeDateDesc`, `noticeDateAsc`, `deadlineAsc`, `deadlineDesc`. See [Order and paging](#order-and-paging) |
| `maxItems` | integer | `50` | 1–5000. Hard cap on the rows written, and on what you pay for |
| `maxRequests` | integer | `25` | 1–200. Second guard: the run stops after this many searches and says so |
| `includeNoticeText` | boolean | `true` | Add the notice body as plain text (HTML removed, entities resolved) |
| `noticeTextMaxChars` | integer | `4000` | 200–40000. Cut per row; `noticeTextTruncated` marks the rows that were cut |
| `fields` | string\[] | all | Keep only these output fields, in this order |

Three rules decide what a run returns:

1. **Different filters are combined with AND.** `countries: ["Nigeria"]` + `procurementGroups: ["CW"]` +
   `noticeTypes: ["Invitation for Bids"]` returned 335 records on 30 September 2026 — Nigerian civil-works tenders,
   nothing else.
2. **Several values inside one filter are combined with OR**, at the price of one request per value, because the source
   refuses a list (a comma-separated country returns nothing, a repeated parameter returns only the first value). Four
   notice types and two countries are eight searches; the actor merges them and drops duplicate notice ids.
3. **The windows are ours, the rest is the source's.** Keyword, notice type, country, category, method, project id and
   language are filters the source applies, so rows outside them are never fetched. `onlyOpen`,
   `deadlineWithinDays`, `sinceDays`, `onlyNew` and all-words matching are applied here, on fetched rows — the run
   summary says how many rows each of them removed.

### Reference

#### Modes

`mode` is a preset for `noticeTypes`, nothing more; setting `noticeTypes` yourself overrides it.

| Mode | Keeps | Deadlines | Typical use |
|---|---|---|---|
| `notices` | Invitation for Bids, Request for Expression of Interest, General Procurement Notice, Invitation for Prequalification | yes, on most rows | bidding, monitoring |
| `awards` | Contract Award | no | market research, who won, prices paid |
| `all` | everything, no type filter at all | on the opportunity rows | one project's full history |

#### Notice types

Counts are the whole index on 30 September 2026 (420 975 records), to show the shape of the data.

| Value | What it is | Records |
|---|---|---|
| `Invitation for Bids` | A concrete tender open for offers, with a deadline | ~45 700 |
| `Request for Expression of Interest` | Consultants and experts asked to apply for an assignment | ~60 600 |
| `General Procurement Notice` | Early notice of what a project intends to buy, months before the tender | ~3 500 |
| `Invitation for Prequalification` | Bidders qualified before a large tender is issued | ~700 |
| `Contract Award` | A contract that has been awarded | ~310 400 |

Short forms are accepted and corrected out loud: `ifb`, `reoi`, `eoi`, `gpn`, `ipq`, `award`, `tender`.

#### Categories

`procurementGroups` — the source's own four groups. The readable label is written to `procurementGroupName`.

| Code | Name | Covers |
|---|---|---|
| `GO` | Goods | equipment, vehicles, medicines, supplies |
| `CS` | Consulting services | studies, design, supervision, individual experts |
| `CW` | Civil works | construction, roads, buildings, networks |
| `NC` | Non-consulting services | transport, printing, catering, maintenance, drilling |

#### Procurement methods

`procurementMethods` takes the codes below; `procurementMethodName` carries the readable name. Records from before the
Bank's 2016 procurement framework use the older codes, which is why both sets are listed. Counts are of the whole index
on 30 September 2026.

| Code | Name | Records |
|---|---|---|
| `RFQ` | Request for Quotations | ~119 700 |
| `INDV` | Individual Consultant Selection | ~89 300 |
| `RFB` | Request for Bids | ~85 400 |
| `QCBS` | Quality And Cost-Based Selection | ~29 900 |
| `CQS` | Consultant Qualification Selection | ~27 700 |
| `CDS` | Direct Selection (consulting) | ~19 400 |
| `DIR` | Direct Selection | ~18 400 |
| `ICB` | International Competitive Bidding (legacy) | ~16 400 |
| `LCS` | Least Cost Selection | ~3 300 |
| `QBS` | Quality Based Selection | ~1 600 |
| `RFP` | Request for Proposals | ~900 |
| `FBS` | Fixed Budget Selection | ~700 |
| `NCB` | National Competitive Bidding (legacy) | ~600 |
| `UN` | UN Agencies (Direct) | ~460 |
| `SSS` | Single Source Selection (legacy) | 2 |

A code outside this list is rejected with the list in the message instead of silently returning nothing. About 7 000
records carry no method code at all — mostly general procurement notices.

#### Languages

`languages` takes `English` (~80 % of the index), `French` (~74 300 records), `Spanish; Castilian` (~30 500) and
`Portuguese`. The Spanish value looks odd because the source stores the cataloguing label with its suffix; `spanish`,
`es`, `fr`, `pt` and `en` are accepted as shorthands. No other language exists in the index — Russian, Arabic and
Chinese return nothing.

#### Two countries per notice

Every record carries two countries and they disagree more often than you would expect:

- `projectCountry` is the country **the financed project is booked under**. For a regional programme that is a region:
  `Central Asia`, `Western and Central Africa`, `Eastern and Southern Africa`, `Caribbean`, `Pacific 1`.
- `implementingCountry` is the country of the **agency that publishes the notice and receives the bids**.

Kazakhstan on 30 September 2026: 854 records under `Kazakhstan`, plus 418 more whose project country reads
`Central Asia` and whose buyer sits in Kazakhstan. `countryField: "either"` (the default) sends both searches, merges
them by notice id and writes the country you asked for into `matchedCountry`, so nothing regional is lost. Use
`project` or `implementing` when you want exactly one side.

#### Countries and regions

Names must match the source's spelling; it does no partial matching (`Egypt` finds nothing, `Egypt, Arab Republic of`
finds 1 447 records). An unknown name stops the run with the closest labels named, and an obvious typo is corrected
with a warning (`Kazakhstaan` → `Kazakhstan`). Labels in the index today:

Afghanistan · Albania · Algeria · Angola · Antigua and Barbuda · Argentina · Armenia · Azerbaijan · Bangladesh ·
Barbados · Belarus · Belize · Benin · Bhutan · Bolivia · Bosnia and Herzegovina · Botswana · Brazil · Bulgaria ·
Burkina Faso · Burundi · Cabo Verde · Cambodia · Cameroon · Central African Republic · Chad · Chile · China ·
Colombia · Comoros · Congo, Democratic Republic of · Congo, Republic of · Costa Rica · Cote d'Ivoire · Croatia ·
Djibouti · Dominica · Dominican Republic · Ecuador · Egypt, Arab Republic of · El Salvador · Eritrea · Eswatini ·
Ethiopia · Fiji · Gabon · Gambia, The · Georgia · Ghana · Grenada · Guatemala · Guinea · Guinea-Bissau · Guyana ·
Haiti · Honduras · Hungary · India · Indonesia · Iran, Islamic Republic of · Iraq · Jamaica · Jordan · Kazakhstan ·
Kenya · Kiribati · Korea, Republic of · Kosovo · Kyrgyz Republic · Lao People's Democratic Republic · Lebanon ·
Lesotho · Liberia · Madagascar · Malawi · Maldives · Mali · Marshall Islands · Mauritania · Mauritius · Mexico ·
Micronesia, Federated States of · Moldova · Mongolia · Montenegro · Morocco · Mozambique · Myanmar · Namibia · Nepal ·
New Caledonia · Nicaragua · Niger · Nigeria · North Macedonia · Pakistan · Palau · Panama · Papua New Guinea ·
Paraguay · Peru · Philippines · Poland · Romania · Russian Federation · Rwanda · Samoa · Sao Tome and Principe ·
Senegal · Serbia · Seychelles · Sierra Leone · Solomon Islands · Somalia · Somalia, Federal Republic of ·
South Africa · South Sudan · Sri Lanka · St Maarten · St. Kitts and Nevis · St. Lucia ·
St. Vincent and the Grenadines · Sudan · Suriname · Syrian Arab Republic · Tajikistan · Tanzania · Thailand ·
Timor-Leste · Togo · Tonga · Trinidad and Tobago · Tunisia · Turkey · Turkiye · Turkmenistan · Tuvalu · Uganda ·
Ukraine · Uruguay · Uzbekistan · Vanuatu · Viet Nam · Vietnam · West Bank and Gaza · Yemen, Republic of · Zambia ·
Zimbabwe

Regional and programme labels of the project-country field, which are not countries: Africa · Andean Countries ·
Caribbean · Central Africa · Central Asia · DRC - Angola · East Asia and Pacific · Eastern Africa ·
Eastern and Southern Africa · Europe and Central Asia · Horn of Africa · Latin America ·
Middle East and North Africa · OECS Countries · Pacific 1 · Pacific 2 · Pacific Islands · South Asia ·
Southern Africa · Southwest Indian Ocean · Western Africa · Western Balkans · Western and Central Africa · World

Everyday names are translated for you: `egypt`, `yemen`, `iran`, `syria`, `russia`, `korea`, `laos`, `kyrgyzstan`,
`macedonia`, `gambia`, `ivory coast`, `cape verde`, `swaziland`, `east timor`, `micronesia`, `palestine`, `drc`,
`dr congo`, `congo`, `burma`, `mena`, `oecs`, `saint lucia`, `st kitts`, `sint maarten`. Two countries kept two
spellings in the index and both are queried when you name one of them: `Viet Nam` (4 517 records) with `Vietnam`
(1 005), and `Turkiye` with `Turkey`.

#### Keyword matching

The source treats a multi-word `query` as **any of these words**: `water supply` matched 84 617 records on
30 September 2026, far more than either word alone. `queryMatch: "all"` (the default) keeps only the fetched rows whose
bid description, project name, reference or notice text contains every word, case-insensitively. Set `any` to write
the source's own result set unchanged. A single word behaves the same either way. Because a word may sit in the notice
text rather than in the short description, a matched row does not always show the word in `bidDescription`.

#### Order and paging

`sort` orders the dataset after the merge, so it applies to the run as a whole and not per search. Rows with no notice
date or no deadline sort last in both directions.

The **paging** order is chosen by the tightest window you set, which is what keeps a monitoring run cheap:

| Window set | Paged by | Stops at |
|---|---|---|
| `sinceDays` | notice date, newest first | the first notice older than the window |
| `onlyOpen` or `deadlineWithinDays` | deadline, furthest first | the first deadline that has passed |
| none | notice date, newest first (or deadline for `deadlineDesc`, oldest-first for `noticeDateAsc`) | `maxItems` |

With a window set, the walk covers the whole window before the sort picks the rows, so "the earliest deadline in the
next 60 days" really is the earliest one. `deadlineAsc` without any window cannot do that — the earliest deadlines in
the index are from 2003 — so it then orders the newest notices by deadline and says so in a warning.

#### What an award row contains

The source publishes awards as prose: there is no supplier field, no amount field and no score field in the record.
The winning firm, the contract value, the duration and the evaluation of the other bidders are inside `noticeText`,
under headings such as *Awarded Bidder*, *Contract Amount* and *Evaluated Bidders*. This actor passes that text
through as plain text and does not pretend to have parsed it. `bidDescription`, `projectId`, `procurementMethodCode`
and `bidReference` are structured on award rows as on any other.

### Examples

Open tenders closing in the next two months, published in the last three weeks, earliest deadline first:

```json
{
  "mode": "notices",
  "sinceDays": 21,
  "onlyOpen": true,
  "deadlineWithinDays": 60,
  "sort": "deadlineAsc",
  "maxItems": 25
}
```

Civil-works tenders in Nigeria (335 records in the index on 30 September 2026):

```json
{
  "mode": "notices",
  "countries": ["Nigeria"],
  "countryField": "project",
  "procurementGroups": ["CW"],
  "noticeTypes": ["Invitation for Bids"],
  "maxItems": 25
}
```

Consulting assignments, newest first, without the notice text for a narrow dataset:

```json
{
  "mode": "notices",
  "noticeTypes": ["Request for Expression of Interest"],
  "procurementGroups": ["CS"],
  "includeNoticeText": false,
  "fields": ["bidDescription", "projectCountry", "submissionDeadline", "daysToDeadline", "contactOrganization", "url"],
  "maxItems": 50
}
```

Everything Kazakh, regional Central Asia programmes included, notices and awards together:

```json
{
  "mode": "all",
  "countries": ["Kazakhstan"],
  "countryField": "either",
  "sort": "noticeDateDesc",
  "maxItems": 25
}
```

Awarded contracts that mention solar, with the award text (the winner and the amount live in there):

```json
{
  "mode": "awards",
  "query": "solar",
  "queryMatch": "all",
  "includeNoticeText": true,
  "noticeTextMaxChars": 8000,
  "maxItems": 25
}
```

A scheduled weekly digest of new hospital tenders in three markets:

```json
{
  "mode": "notices",
  "query": "hospital",
  "countries": ["Pakistan", "Bangladesh", "Nepal"],
  "sinceDays": 7,
  "onlyNew": true,
  "maxRequests": 40,
  "maxItems": 100
}
```

### Output

One row per notice. A real row from a cloud run of the first example above (run `ul1eCKC8rs58fieLT`, 25 rows in 7 s);
`noticeText` is shortened here, the run wrote 4 000 characters of it:

```json
{
  "rowType": "notice",
  "noticeId": "OP00462366",
  "noticeType": "Request for Expression of Interest",
  "noticeStatus": "Published",
  "noticeDate": "2026-09-10",
  "noticeLanguage": "English",
  "bidReference": "S43 (IDP)",
  "bidDescription": "Selection of Training Service Provider for skill enhancement of the officials and instructors of DYD under IDP Procurement",
  "projectId": "P178077",
  "projectName": "Economic Acceleration and Resilience for NEET (EARN)",
  "projectCountry": "Bangladesh",
  "implementingCountry": "Bangladesh",
  "matchedCountry": null,
  "procurementGroup": "CS",
  "procurementGroupName": "Consulting services",
  "procurementMethodCode": "QCBS",
  "procurementMethodName": "Quality And Cost-Based Selection",
  "submissionDeadline": "2026-09-30T00:00:00.000Z",
  "submissionDeadlineTime": "03:00",
  "daysToDeadline": 0,
  "isOpen": true,
  "noticeSubmittedDate": "2026-09-10",
  "contactOrganization": "Department of Youth Development",
  "contactName": "Kazi Moklesur Rahman",
  "contactEmail": "pd.earn@dyd.gov.bd",
  "contactPhone": "+880-02-55101121",
  "contactAddress": "Level-19, NSC Tower, 62/3, Purana Paltan-1000, Dhaka, Bangladesh., Tel: (880-2) 9559389",
  "noticeText": "Government of the People’s Republic of Bangladesh\nDepartment of Youth Development\nEconomic Acceleration and Resilience for NEET (EARN) Project\n…",
  "noticeTextTruncated": true,
  "url": "https://projects.worldbank.org/en/projects-operations/procurement-detail/OP00462366",
  "fetchedAt": "2026-09-30T21:02:17.046Z"
}
```

Always filled: `rowType`, `noticeId`, `noticeType`, `noticeStatus`, `projectId`, `projectName`, `projectCountry`,
`isOpen`, `url`, `fetchedAt`.

| Field | Type | When it is filled |
|---|---|---|
| `rowType` | string | `notice` for data, `notFound` for the single row a run writes when nothing matched |
| `noticeId` | string | The source's stable id, e.g. `OP00462366`. Use it to deduplicate across runs |
| `noticeType` | string | One of the five [notice types](#notice-types) |
| `noticeStatus` | string | `Published` on every record in the index; see [Limits](#limits--faq) |
| `noticeDate` | string | Publication day, `YYYY-MM-DD`, converted from the source's `10-Sep-2026`. Empty on about a thousand of the oldest records |
| `noticeLanguage` | string | Language label of the notice |
| `bidReference` | string|null | The buyer's own reference number of the lot or contract |
| `bidDescription` | string|null | The one-line "what is procured". Never filled on general procurement notices, rarely missing elsewhere |
| `projectId` / `projectName` | string | The financed project, e.g. `P178077` |
| `projectCountry` | string | Country **or region** the project is booked under |
| `implementingCountry` | string|null | Country of the buying agency; often empty on awards |
| `matchedCountry` | string|null | The country you filtered on, repeated; `null` when you filtered on none |
| `procurementGroup` / `procurementGroupName` | string|null | `CS` and `Consulting services`; empty on some general notices |
| `procurementMethodCode` / `procurementMethodName` | string|null | `QCBS` and its readable name |
| `submissionDeadline` | string|null | Deadline day as ISO 8601 UTC at midnight — the source publishes no time zone. `null` on awards |
| `submissionDeadlineTime` | string|null | Local clock time of the deadline as published, e.g. `03:00`. No zone is given, so treat it as the buyer's local time |
| `daysToDeadline` | number|null | Whole UTC days from the run day to the deadline day; `0` = closes today, negative = passed |
| `isOpen` | boolean | `true` while the deadline day has not passed in UTC; `false` when it has or when there is none |
| `noticeSubmittedDate` | string|null | When the notice reached the Bank, ISO date |
| `contactOrganization` | string|null | The buying agency. Filled on opportunities, usually empty on awards |
| `contactName` / `contactEmail` / `contactPhone` / `contactAddress` | string|null | The bid-submission contact exactly as the source publishes it for bidders. Drop them with `fields` if you do not need them |
| `noticeText` | string|null | The notice body as plain text, HTML and entities resolved; `null` with `includeNoticeText: false` |
| `noticeTextTruncated` | boolean | `true` when the text hit `noticeTextMaxChars` |
| `url` | string | The notice page on the source's site |
| `fetchedAt` | string | Run timestamp, ISO 8601 UTC |
| `found` / `message` | boolean / string | Only on a `notFound` row: what matched nothing and what to change |

Dataset views (Export → view in the console): **Notices** — id, published, type, country, what is procured, deadline,
days left, method, category, project, reference, link. **Deadlines** — the bidder's queue: deadline, local time, days
left, still open, both countries, matched country, what is procured, buyer, type, link. **Contract awards** —
published, country, contract, project id, project, method, reference, award text, link.

Every run also writes a `SUMMARY` record to the default key-value store: the searches sent, requests used, rows
fetched against rows written, how many rows each of the actor's own filters removed, duplicates merged, the window in
UTC days, the paging order, whether `maxRequests` stopped the run, and the widest match count the source reported.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~world-bank-procurement/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"notices","onlyOpen":true,"deadlineWithinDays":30,"sort":"deadlineAsc","maxItems":25}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/world-bank-procurement').call({
    countries: ['Nigeria'], procurementGroups: ['CW'], noticeTypes: ['Invitation for Bids'], maxItems: 25,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const summary = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('SUMMARY');
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/world-bank-procurement").call(run_input={
    "mode": "awards", "query": "solar", "projectIds": ["P177816"], "maxItems": 25})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

MCP: add `https://mcp.apify.com` to Claude, Cursor or any MCP client and call the `yadroo/world-bank-procurement` tool
with the same JSON input. One call answers one question, which is what makes it a clean tool for an agent. Cut the row
down first so a model reads only what it needs:
`"fields": ["bidDescription","projectCountry","submissionDeadline","daysToDeadline","procurementGroupName","url"]` —
`rowType` is always kept, and switching `includeNoticeText` off keeps the rows small.

On a schedule, set `onlyNew: true` and a `sinceDays` window. The notice ids are remembered in a named key-value store,
one slot per filter combination, so two schedules with different filters do not blind each other; the first run
returns the current window and later runs return only what is new. A daily run with `sinceDays: 2` costs two or three
requests.

### Pricing

Pay per event: **$0.001 per run start + $0.0015 per dataset row**. The start event is charged on every run, including
a run whose filters match nothing. Apify plan tiers discount both prices (Bronze −10 %, Silver −20 %, Gold and above
−30 %).

Worked examples at full price:

- A 25-row deadline queue: $0.001 + 25 × $0.0015 = **$0.0385**.
- The default 50 rows: $0.001 + 50 × $0.0015 = **$0.076**.
- A 500-row market export: $0.001 + 500 × $0.0015 = **$0.751**.
- A scheduled run with `onlyNew` that finds nothing new: $0.001 start + one `notFound` row = **$0.0025**.

You pay for rows written, not rows fetched: the deadline and date windows and the all-words keyword throw their rows
away before anything is written, and `maxItems` caps the rest. A typical run is 256 MB for well under a minute — the
25-row example above used 8 seconds and 7 requests — so compute is a fraction of a cent. `maxRequests` is the brake on
a wide crawl: raise it only when the run summary says it stopped you.

### Limits & FAQ

- **No date filter at the source, at all.** Every documented date parameter and every range syntax left the match
  count unchanged, so `sinceDays` and the deadline windows are applied while paging. A wide window therefore costs
  requests: `sinceDays: 21` over the four opportunity types took 7 requests and fetched 1 400 rows to write 25.
  `maxRequests` stops it and the summary says the window may be incomplete.
- **One value per filter field.** Several countries, types, categories, methods, project ids or languages are sent as
  the cross product of one search each, capped at 60 searches per run. The run warns when the number of searches
  exceeds `maxRequests`.
- **Awards carry no structured supplier or amount.** They are in `noticeText` only; see
  [What an award row contains](#what-an-award-row-contains).
- **`projectCountry` can be a region**, and then the buyer's country is in `implementingCountry`. This is why
  `countryField: "either"` is the default.
- **Deadlines are days, not moments.** The source dates every deadline at midnight and puts the local clock time in a
  separate field without a time zone. A notice therefore counts as open while its deadline day has not passed in UTC,
  and `daysToDeadline` is a whole number of days — the hour of your run never changes a row.
- **`notice_status` is `Published` on everything.** A cancelled, postponed or amended tender is not marked as such in
  the index; the amendment appears as a new notice. Check the notice page before you bid.
- **Paging depth.** The source refuses an offset beyond 100 000 rows of one search (HTTP 400). To go deeper into a
  400 000-record filter, narrow it — by country, type, category or keyword.
- **General procurement notices have no bid description** and no deadline: they announce what a project plans to buy,
  and the detail is in the notice text.
- **About a thousand of the oldest records carry no notice date.** With `sort: "noticeDateAsc"` they are skipped and
  counted in the summary, because "oldest first" is useless when the date is empty.
- **No accounts, no attachments.** Bidding documents live behind the buyer's own process; this actor reads the public
  notice index only. It does not log in anywhere and sends no more than a couple of requests per second.
- **Coverage is Bank-financed projects.** National portals, other development banks and the Bank's own corporate
  purchasing are out of scope.
- **Licence and attribution.** The notices are open data under Creative Commons Attribution 4.0, which allows
  commercial reuse with attribution to the World Bank; every row keeps the `url` of the original notice. Check the
  source's terms before you republish large extracts.
- **If the source changes.** Fields are read by name, so a new or reordered field cannot corrupt a row; a renamed one
  would need a fix. The actor reads exactly one endpoint on `search.worldbank.org` and no other host.

***

Made by **Yadroo**. Sibling actors: [world-bank-indicators](https://apify.com/yadroo/world-bank-indicators) ·
[data-gov-datasets](https://apify.com/yadroo/data-gov-datasets) ·
[federal-register-documents](https://apify.com/yadroo/federal-register-documents) ·
[uk-planning-applications](https://apify.com/yadroo/uk-planning-applications) ·
[sanctions-screen](https://apify.com/yadroo/sanctions-screen)

# Actor input Schema

## `mode` (type: `string`):

Two different questions out of one source. `notices` keeps the four opportunity types (invitation for bids, request for expression of interest, general procurement notice, invitation for prequalification) and is what a bidder wants: these rows carry a submission deadline. `awards` keeps the contract-award records, the history of who won which contract on a financed project; those rows have no deadline and the winner, the amount and the scoring live inside `noticeText`, because the source publishes them as notice text and not as separate fields. `all` mixes both and is useful when you follow one project from first notice to signed contract. The mode only presets `noticeTypes`; set that field yourself to be precise.

## `query` (type: `string`):

Free-text search over the notice - the bid description, the project name and the full notice text. One word (`solar`, `hospital`, `dredging`) behaves the way you expect. Several words are treated by the source as "any of these words", which is why `water supply` alone matches over 80 000 notices; `queryMatch` below turns that into "all of these words" for you. Leave empty to browse a country, a project or a date window without a keyword.

## `queryMatch` (type: `string`):

Only matters for a multi-word `query`. `all` filters the fetched rows down to the ones whose bid description, project name or notice text contains every word, case-insensitively - a two-word query then returns the handful of relevant notices instead of tens of thousands. `any` writes what the source returned, unfiltered. A word may sit in the notice text rather than in the short description, so a row can match without the word being visible in `bidDescription`.

## `countries` (type: `array`):

Country names as the source spells them, e.g. \["Kazakhstan"], \["Nigeria", "Ghana"], \["Viet Nam"], \["Congo, Democratic Republic of"]. Combined with OR. Long official forms occur ("Somalia, Federal Republic of", "Egypt, Arab Republic of"), so an unknown name yields an error item naming the closest matches instead of a silently empty run. Mind `countryField`: many notices belong to a regional project whose country field reads "Central Asia", "Western and Central Africa" or "Eastern and Southern Africa" even though the buyer sits in one country. README > Reference lists the regional labels.

## `countryField` (type: `string`):

The source carries two countries per notice: the project country, which for a regional programme is a region and not a country, and the country of the contact agency that actually buys. Kazakhstan is a clear case: about 850 notices are booked under "Kazakhstan" and about 400 more under the regional label "Central Asia" with a Kazakh agency as the buyer. `either` sends both queries and removes duplicates by notice id, so you see all of them; `project` and `implementing` restrict to one side. The name that matched is repeated in the `matchedCountry` output field.

## `noticeTypes` (type: `array`):

Empty follows `mode`. Set it to override the mode, e.g. only \["Request for Expression of Interest"] for consulting work, or \["General Procurement Notice"] to see what a project plans to buy months before the tender. One request per type is sent and the results are merged.

## `procurementGroups` (type: `array`):

The source's own four categories, combined with OR. This is the cheapest way to split a market: a construction company wants CW, an equipment supplier GO, an engineering firm CS. The readable label is added to every row as `procurementGroupName`.

## `procurementMethods` (type: `array`):

Method codes as the source writes them, combined with OR. Current framework: RFB request for bids, RFQ request for quotations, RFP request for proposals, QCBS quality and cost-based selection, FBS fixed budget selection, LCS least cost selection, CQS consultant qualification selection, INDV individual consultant selection, CDS and DIR direct selection. Notices from before the 2016 framework use the older codes, e.g. ICB international competitive bidding (about 16 000 rows), NCB national competitive bidding, SSS single-source selection. The readable name is in `procurementMethodName`; leave the field empty unless you know which methods matter to you.

## `projectIds` (type: `array`):

World Bank project ids, e.g. \["P177816"], \["P171577", "P179008"]. Combined with OR. Returns every notice and award of that project, which is how you follow one financed programme over years - a large programme has hundreds of rows. An id the source does not know yields an error item, never an empty success.

## `languages` (type: `array`):

Keep notices published in these languages. Most notices are English; French, Spanish and Portuguese appear for West and Central Africa, Latin America and Lusophone countries. The source stores the Spanish label with its cataloguing suffix, which is why the value looks unusual.

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

Keep only rows whose submission deadline is still in the future at run time, compared in UTC. This is the filter a bidder wants and the source has no equivalent, so the actor walks the notices in deadline order and stops when the deadlines fall into the past. Rows without a deadline (contract awards, most general procurement notices) are dropped by this switch, so leave it off when you study history.

## `deadlineWithinDays` (type: `integer`):

Keep only notices whose deadline falls between now and N days from now, e.g. 14 for "what closes in the next two weeks". Implies the open-deadline filter above. Leave empty for no upper bound.

## `sinceDays` (type: `integer`):

Keep only notices published in the last N days, by the notice date. 1 gives yesterday's and today's batch, 7 a weekly digest, 30 a month. This is the window to use for a scheduled run: with it the actor pages by notice date and stops at the edge of the window, so a daily run costs two or three requests.

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

Remember the notice ids of every run in the actor's key-value store and write only ids that were not there before. Turns a schedule into an alert feed: the first run returns the current window, later runs return only what appeared since. Independent of `sinceDays` - combine them, because the memory is what prevents duplicates while the window keeps the run cheap.

## `sort` (type: `string`):

Order of the rows in the dataset. Rows without a deadline sort last in the two deadline orders. When several filter values are merged the order is applied after the merge, so the dataset is sorted as a whole and not per request.

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

Hard cap on the rows written, and on what you pay for. 25-50 answers a question, a few hundred fills a dashboard, and the whole index holds over 400 000 notices, so keep the cap and a filter in place. Paging stops as soon as the cap is reached.

## `maxRequests` (type: `integer`):

Second guard, on the source side: the run stops after this many search requests even if the caps and windows above are not exhausted, and says so in the run summary. It matters when a filter that the actor applies itself (all-words keyword, open deadline) throws most fetched rows away. Raise it for a wide crawl, leave it for a monitoring run.

## `includeNoticeText` (type: `boolean`):

Add the body of the notice as plain text with the source's HTML markup removed. For a tender this is the part that names the lots, the eligibility rules, the bid security and the address for submission; for a contract award it is the only place where the winning firm, the contract amount and the evaluation scores appear. Switch it off for narrow rows and a small dataset.

## `noticeTextMaxChars` (type: `integer`):

Upper bound on the plain text kept per row; some notices run to tens of thousands of characters. When a text is cut, `noticeTextTruncated` is true, so you can spot the rows where you need the full notice from its page.

## `fields` (type: `array`):

Keep only these output fields, in this order, e.g. \["bidDescription", "submissionDeadline", "projectCountry", "url"]. Empty returns every field. Field names are listed in README > Output.

## Actor input object example

```json
{
  "mode": "notices",
  "query": "solar",
  "queryMatch": "all",
  "countryField": "either",
  "onlyOpen": false,
  "onlyNew": false,
  "sort": "noticeDateDesc",
  "maxItems": 50,
  "maxRequests": 25,
  "includeNoticeText": true,
  "noticeTextMaxChars": 4000
}
```

# Actor output Schema

## `results` (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 = {
    "query": "solar"
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/world-bank-procurement").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 = { "query": "solar" }

# Run the Actor and wait for it to finish
run = client.actor("yadroo/world-bank-procurement").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 '{
  "query": "solar"
}' |
apify call yadroo/world-bank-procurement --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yadroo/world-bank-procurement"
        }
    }
}
```

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/sURWgtf3YeF9chv3c/builds/sIQakX9Ka7wLsIa0s/openapi.json
