# Bahrain Tender Board Tenders Scraper (`automatic/bahrain-tenderboard-tenders-scraper`) Actor

Bahrain government tenders from the Tender Board: tender number, subject, buying authority, publish, purchase and closing dates, initial bond, fees and description. Filter by keyword, authority, status or dates, and run it as a daily feed of new tenders.

- **URL**: https://apify.com/automatic/bahrain-tenderboard-tenders-scraper.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 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

## Bahrain Tender Board Tenders Scraper

Bahrain government tenders from the Tender Board (tenderboard.gov.bh), the body that publishes and awards public procurement for Bahraini ministries, authorities and state companies. One row per tender: tender number, subject, description, purchasing authority, publication, purchase-before and closing dates, initial bond, document fees, bid validity, contract duration and the tender's public detail page.

Use it for tender monitoring and alerts: schedule a daily run with an authority, keyword or date window and send the new tenders to email, Slack or a spreadsheet.

### Use cases

- Watch Bahraini government tenders by authority, keyword or closing date.
- Build a daily feed of tenders before their offer deadline, with the bond and fees at hand.
- Find tenders reserved for SMEs, or with an alternate bid allowed.
- Send each opportunity straight to its official detail page for the documents.

### What it returns

Every row has `tenderId`, `tenderNumber`, `paReference`, `title`, `buyer`, `tenderType`, `procurementMethod`, `status`, `publishedAt`, `purchaseBeforeAt`, `deadlineAt`, `openingAt`, `description`, `notes`, `initialBond`, `tenderFees`, `bidValidityDays`, `contractDuration`, `alternateBidAllowed`, `smeOnly`, `currency`, `documents`, `url`, `changeType`, `changedFields`, `detailIssue`, `sourceQuery`, `source`, `country`, `scrapedAt` and a few reserved fields (`titleEnglish`, `category`, `estimatedValue`) that stay null, explained below.

- `status` is `open` until the printed closing date has passed, then `closed`.
- Dates are ISO 8601 (`YYYY-MM-DD`). The portal prints dates without a time, so no time is invented.
- `description` and `notes` are the published text, including any Arabic translation the authority added.
- `initialBond` and `tenderFees` are in BHD. Neither is a contract estimate, so `estimatedValue` stays null.
- `documents` is an empty list: the portal serves tender documents only through a signed-in purchase. `url` is the tender's official public detail page.
- If one tender's page cannot be read, the row is still delivered with `detailIssue` explaining what was missing.
- Rows are deduplicated on the portal's tender id.

### Input

| Field | Type | Default | What it does |
|---|---|---:|---|
| `keyword` | string | `""` | Text matched in the tender subject, e.g. `maintenance`. |
| `buyer` | string | `""` | Part of the purchasing authority name, e.g. `Ministry of Works`. |
| `tenderNumber` | string | `""` | Part of the tender number, e.g. `IN/MOW/2025/559`. |
| `status` | string | `any` | `open` or `closed` according to the printed closing date. |
| `publishedFrom`, `publishedTo` | string | `""` | Publication window, `YYYY-MM-DD`. |
| `closingFrom`, `closingTo` | string | `""` | Offer deadline window, `YYYY-MM-DD`. |
| `maxItems` | integer | `20` | Maximum tenders returned. |
| `includeDetails` | boolean | `true` | Read each tender's page. Slower, and gives the full description and amounts. |
| `onlyNewSinceLastRun` | boolean | `false` | Deliver only tenders that are new or changed since the last run. |
| `stateKey` | string | `""` | Optional name for this search's ledger. Defaults to a key derived from the filters. |

All filters are optional and can be combined. An empty input returns the latest published tenders, newest first.

### Example queries

Tenders closing in the next two weeks:

```json
{
  "status": "open",
  "closingFrom": "2026-11-10",
  "closingTo": "2026-11-24",
  "maxItems": 50
}
```

A daily feed of new Ministry of Works tenders:

```json
{
  "buyer": "Ministry of Works",
  "onlyNewSinceLastRun": true,
  "stateKey": "works-daily"
}
```

One tender by number, with its full description:

```json
{"tenderNumber": "IN/MOW/2025/559"}
```

### Use it as a daily feed

Set `onlyNewSinceLastRun` and run the Actor on a schedule. The first run delivers
every tender that matches your filters. Later runs deliver only tenders that are
new, or whose dates, status, values or description changed, with `changeType` set
to `new` or `changed` and `changedFields` naming what moved. Every matched tender
that did not change is skipped, and the run summary says how many were skipped.
Use `stateKey` to keep separate ledgers for separate searches.

### Output example

```json
{"source":"tenderboard.gov.bh","country":"Bahrain","tenderId":"230460587","tenderNumber":"2/2026/MLSD (RFP/INT/MOSD/2026/2)","paReference":"RFP/INT/MOSD/2026/2","title":"Annual Comprehensive Maintenance Services for Firefighting, Fire Protection, and Fire Alarm Systems across Ministry Buildings","titleEnglish":null,"buyer":"Ministry of Social Development","category":null,"tenderType":"Internal","procurementMethod":"Public (LOCAL GCC)","status":"open","publishedAt":"2026-11-10","purchaseBeforeAt":"2026-11-23","deadlineAt":"2026-12-01","openingAt":"2026-12-02","estimatedValue":null,"currency":"BHD","documents":[],"url":"https://www.tenderboard.gov.bh/TenderDetails/?id=2%2f2026%2fMLSD+(RFP%2fINT%2fMOSD%2f2026%2f2)","changeType":null,"changedFields":null,"scrapedAt":"2026-11-10T08:36:38+00:00","sourceQuery":"published tenders","description":"The Ministry of Social Development (MOSD) intends to issue a tender for Annual Comprehensive Maintenance Services for Firefighting, Fire Protection, and Fire Alarm Systems across Ministry Buildings. Bidders wishing to participate must comply with the requirements and conditions contained in the tender documents.","notes":"ترغب وزارة التنمية الاجتماعية في طرح مناقصة خدمات الصيانة الشاملة السنوية لأنظمة مكافحة الحريق، والوقاية من الحريق، وأنظمة إنذار الحريق عبر مباني الوزارة. على مقدمي العطاءات الراغبين في المشاركة في هذه المناقصة التقيد بالمتطلبات والشروط والمواصفات والاحكام الواردة في وثائق المناقصة. The Ministry of Social Development (MOSD) intends to issue a tender for Annual Comprehensive Maintenance Services for Firefighting, Fire Protection, and Fire Alarm Systems across Ministry Buildings. Bidders wishing to participate must comply with the requirements and conditions contained in the tender documents.","initialBond":500.0,"tenderFees":15.0,"bidValidityDays":180,"contractDuration":"3 Year","alternateBidAllowed":false,"smeOnly":false,"detailIssue":null}
```

### Limits and caveats

- The list is the portal's published-tender window (about a year, including tenders whose closing date has passed), newest first. Use `status` `open` to skip closed ones, and read it on a schedule to keep a complete history.
- Keyword, authority and number filters are matched against the published text, so they follow the language of the source: subjects and descriptions are usually English, with Arabic where the authority wrote it.
- `status` follows the printed closing date, not a separate portal status field.
- Tender documents stay in the portal's purchase flow; the Actor delivers the public listing and detail data.
- If the source cannot be read, the run stops with a plain status message; rows already collected stay in the dataset.
- If nothing matches, the run succeeds with an empty dataset.

### FAQ

**How fresh is the data?** Every run reads the Tender Board portal live.

**How do I get only new tenders each day?** Set `onlyNewSinceLastRun` and schedule a daily run. Deduplicate across runs on `tenderNumber` if you prefer to manage the state yourself.

**Do I need an account?** No. Published tenders and their detail pages are public.

**What happens when nothing matches?** The run succeeds with 0 rows and a plain status message.

**What if one tender's page fails?** The row is still delivered with `detailIssue` set.

### Price

Each saved tender is one result. When a per-event plan applies, the start event is charged per GB of memory; the default 512 MB run is one start event. The start is charged even when a search saves nothing. See the Pricing tab for current prices.

### Custom sources and support

Need another Gulf or Saudi tender portal, a custom field, or a bulk or daily feed? Email automat42@outlook.com. For questions or issues, you can also contact us from the Actor page.

# Actor input Schema

## `keyword` (type: `string`):

Text matched in the tender subject (the list's own text; descriptions are not searched), e.g. 'maintenance' or 'صيانة'. Up to 200 characters.

## `buyer` (type: `string`):

Part of the purchasing authority name, e.g. 'Ministry of Works' or 'Bapco'. Up to 200 characters.

## `tenderNumber` (type: `string`):

Part of the tender number, e.g. 'IN/MOW/2025/559'. Searched by the portal itself, which narrows the scan. Up to 100 characters.

## `status` (type: `string`):

Keep tenders whose closing date has not passed, or only those it has.

## `publishedFrom` (type: `string`):

Earliest publication date, YYYY-MM-DD (inclusive).

## `publishedTo` (type: `string`):

Latest publication date, YYYY-MM-DD (inclusive).

## `closingFrom` (type: `string`):

Only tenders whose offer deadline falls on or after this date, YYYY-MM-DD.

## `closingTo` (type: `string`):

Only tenders whose offer deadline falls on or before this date, YYYY-MM-DD.

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

Stop after this many tenders (deduplicated on tender id).

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

Read each tender's page: description, additional notes, invitation method, fees, bid validity, contract duration and opening date. One extra read per tender. A failed page keeps the row with detailIssue set.

## `onlyNewSinceLastRun` (type: `boolean`):

Keep a ledger in your Apify account and deliver only tenders that are new, or whose dates, status or values changed since the last run. A first run delivers everything.

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

Optional name for this search's ledger, e.g. 'works-ministery'. Defaults to a key derived from the filters, so different searches keep separate ledgers. Up to 100 characters.

## Actor input object example

```json
{
  "keyword": "",
  "buyer": "",
  "tenderNumber": "",
  "status": "any",
  "publishedFrom": "",
  "publishedTo": "",
  "closingFrom": "",
  "closingTo": "",
  "maxItems": 20,
  "includeDetails": true,
  "onlyNewSinceLastRun": false,
  "stateKey": ""
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "keyword": "",
    "buyer": "",
    "tenderNumber": "",
    "status": "any",
    "publishedFrom": "",
    "publishedTo": "",
    "closingFrom": "",
    "closingTo": "",
    "maxItems": 20,
    "includeDetails": true,
    "onlyNewSinceLastRun": false,
    "stateKey": ""
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/bahrain-tenderboard-tenders-scraper").call(input);

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = {
    "keyword": "",
    "buyer": "",
    "tenderNumber": "",
    "status": "any",
    "publishedFrom": "",
    "publishedTo": "",
    "closingFrom": "",
    "closingTo": "",
    "maxItems": 20,
    "includeDetails": True,
    "onlyNewSinceLastRun": False,
    "stateKey": "",
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/bahrain-tenderboard-tenders-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "keyword": "",
  "buyer": "",
  "tenderNumber": "",
  "status": "any",
  "publishedFrom": "",
  "publishedTo": "",
  "closingFrom": "",
  "closingTo": "",
  "maxItems": 20,
  "includeDetails": true,
  "onlyNewSinceLastRun": false,
  "stateKey": ""
}' |
apify call automatic/bahrain-tenderboard-tenders-scraper --silent --output-dataset

```

## MCP server setup

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

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

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/bUs0RdR15zkTdYJ2p/builds/2C9LsDbfEz3CbqqtW/openapi.json
