# Oman Tender Radar — Tender Board Government Tenders (`dottti/oman-tender-radar`) Actor

Live Oman government tenders from the Tender Board e-tendering portal as clean JSON: tender number, title, entity, category and grades, type, sales end and closing dates, fee and bank guarantee in OMR, tender notice details, and bid-opening results.

- **URL**: https://apify.com/dottti/oman-tender-radar.md
- **Developed by:** [Dottti](https://apify.com/dottti) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 tender delivereds

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

## Oman Tender Radar — Tender Board Government Tenders

Get live Oman government tenders from the [Tender Board e-tendering portal](https://etendering.tenderboard.gov.om) as clean JSON, CSV or Excel.

Filter by keyword, government entity and procurement category. Schedule it every morning and you have a tender alert feed for your sector.

The Actor reads two public lists:

- **New tenders.** Tenders open for document purchase and bidding.
- **In process.** Tenders whose bids were opened and are now under evaluation. Each record can carry the bid-opening report: every company that bid, the offer type and the total price in OMR.

### Who uses this

- **Suppliers and contractors** get a daily list of new tenders in their category.
- **Bid teams** see the closing date, the fee and the bank guarantee before they buy the documents.
- **Sales and market analysts** see who bid on each tender and at what price, from the opening reports.
- **Consultancies** watch several entities or sectors for their clients.

Working in Saudi Arabia too? See [Etimad Tender Radar](https://apify.com/dottti/etimad-tender-radar) for Saudi government tenders.

### Daily tender alert in 3 steps

1. Set your filters. For example, `categoryContains` = `خدمات تقنية المعلومات` and `onlyOpen` = `true`.
2. Click **Save as a new task**, then add a daily schedule to the task.
3. Connect the task to email, Slack or Google Sheets from the **Integrations** tab. New tenders then arrive every morning.

### What you get

One record per tender. This record comes from a real run on 20 October 2026 (the long `description` and the `calendar` list are shortened here):

```json
{
  "tenderNumber": "2026/146/و ت س/ م ر -25",
  "internalId": "99301",
  "title": "توريد مواد لمصنع الصاروج التابع لوزارة التراث والسياحة",
  "titleEnglish": "Supply of Materials for the Sarooj Factory of the Ministry of Heritage and Tourism",
  "description": "المشروع عبارة عن توفير مواد خاصة بإنتاج مادة الصاروج ...",
  "entity": "وزارة التراث والسياحة",
  "category": "التوريدات",
  "grades": ["الثالثة", "الرابعة"],
  "tenderType": "مؤسسات صغيرة و المتوسطة",
  "companyOrigin": "Local",
  "view": "NewTenders",
  "status": "New tenders (open for purchase and bidding)",
  "isOpen": true,
  "salesEndDate": "2026-11-10",
  "bidClosingDate": "2026-11-30",
  "actualOpeningDate": null,
  "datesText": "Sales EndDate:10-11-2026-Bid Closing Date:30-11-2026",
  "currency": "OMR",
  "feeOmr": 25,
  "bankGuarantee": { "amount": null, "unit": null, "electronic": null, "text": null },
  "details": {
    "floatingDate": "2026-10-20",
    "evaluationType": "حسب الجدول الزمني",
    "tenderScope": "محلية",
    "subCategories": ["توريد مواد ومعدات البناء"],
    "governorate": "محافظة مسقط",
    "wilayats": ["ولاية مسقط"],
    "envelopeType": "مظروف واحد",
    "feePaymentMethod": "إلكتروني",
    "dates": {
      "documentSalesEnd": "2026-11-10T14:30:00+04:00",
      "queriesEnd": "2026-11-19T14:30:00+04:00",
      "bidSubmissionDeadline": "2026-11-30T10:00:00+04:00",
      "financialBidOpening": "2026-11-30T11:00:00+04:00"
    }
  },
  "openingReport": null,
  "raw": {
    "title": "توريد مواد لمصنع الصاروج التابع  لوزارة التراث والسياحة",
    "category": "التوريدات [الثالثة,الرابعة]",
    "tenderType": "مؤسسات صغيرة و المتوسطة [ Local]",
    "fee": "25"
  },
  "url": "https://etendering.tenderboard.gov.om/product/nitParameterView?mode=public&tenderNo=99301&..."
}
```

In the **In process** list, `openingReport` holds the opened bids. From the same day's run, tender `2026/4109/و ص/م ع خ ص ش ب م-216` (first 2 of 6 bids shown):

```json
"openingReport": [
  { "bidSerial": "1-[1]", "company": "التقنيات الناشئة المتميزة", "offerType": "العرض الأساسي", "totalPriceOmr": 1706.25, "totalPriceRaw": "1706.250", "status": "تم فتح العطاء المالي" },
  { "bidSerial": "1-[2]", "company": "التقنيات الناشئة المتميزة", "offerType": "البديل1", "totalPriceOmr": 1942.5, "totalPriceRaw": "1942.500", "status": "تم فتح العطاء المالي" }
]
```

#### Notes on the fields

- **Money.** All amounts are Omani rials. The `currency` field says `OMR` and the field names end in `Omr`.
- **Bank guarantee.** The portal's list shows a bare number. That number is sometimes an OMR amount and sometimes a percent of the bid. The unit is only stated on the tender notice. With `includeDetails` on, `bankGuarantee.unit` is `OMR` or `PERCENT_OF_BID`. With it off, `unit` stays `null`.
- **Dates.** The list gives dates only, as ISO dates (`2026-11-30`). The notice gives exact times in Oman time, as ISO with `+04:00`. The original text is kept in `datesText` and `details.calendar`.
- **Titles.** `title` is the Arabic title. `titleEnglish` is filled when the notice's main title is in English. `description` is the longer description the portal attaches to the tender.
- **Raw text.** Every record has a `raw` object with the text exactly as the portal published it, so you can search the portal for it.
- **Missing values** are `null`. The Actor never fills a gap with a guess.
- **`url`** opens the tender notice on the portal. It works without logging in.
- **`openingReportUrl`** opens the bid-opening report. The portal serves it only with its session cookie. Open any portal page first, then the link.

### Input

| Field | What it does |
| --- | --- |
| `view` | `NewTenders` (default) or `InProcessTenders`. |
| `keywords` | Words matched against title, description, entity, category and tender number. Arabic and English both work. Empty returns everything the other filters allow. |
| `matchMode` | `any` (default) or `all`. |
| `entityContains` | Keep tenders whose entity name contains this text, for example `وزارة الصحة`. |
| `categoryContains` | Keep tenders whose category contains this text, for example `التوريدات` or `خدمات تقنية المعلومات`. |
| `onlyOpen` | New tenders only. Drops tenders whose bid closing date has passed. On by default. |
| `includeDetails` | Adds the tender notice fields: English title, governorate, wilayat, sub-categories, exact times, guarantee unit. On by default. |
| `includeOpeningReport` | In process only. Adds the opened bids. On by default. |
| `maxItems` | Hard cap on delivered tenders, and so on cost. Default 100. |
| `maxPages` | How many list pages of 50 to read at most. Default 50. |

Arabic keywords are matched after normalisation. `اعمال` matches `أعمال`, `صيانه` matches `صيانة`, and `مستشفي` matches `مستشفى`. Diacritics, tatweel and Arabic-Indic digits are handled.

#### Example: open IT tenders

```json
{
  "view": "NewTenders",
  "categoryContains": "خدمات تقنية المعلومات",
  "onlyOpen": true,
  "maxItems": 200
}
```

#### Example: latest bid openings for Ministry of Health supply tenders

```json
{
  "view": "InProcessTenders",
  "categoryContains": "التوريدات",
  "entityContains": "وزارة الصحة",
  "includeOpeningReport": true,
  "maxItems": 100,
  "maxPages": 10
}
```

### Pricing

- **Tender delivered:** $0.005 per tender that passes your filters and is stored in your dataset ($5 per 1,000).
- **Bid report:** $0.02 extra per tender that carries a bid-opening report with every bidder and price ($20 per 1,000). Only In process tenders can have one. Turn off `includeOpeningReport` to skip it.

`maxItems` caps the cost of any run.

### Limits

- The portal shows 50 tenders per page. On 20 October 2026 the New tenders list had 46 pages (2,300 rows) and the In process list had 412 pages (about 20,550 rows).
- The New tenders list still includes tenders from earlier years. On 20 October 2026, 912 of its 2,290 distinct tenders had a closing date today or later. Keep `onlyOpen` on to receive only those.
- Keyword, entity and category filters run on the Actor's side. The portal's own search needs a signed browser form, so the Actor reads list pages and filters them. A narrow filter on the In process list may read many pages. Use `maxPages` to bound the run.
- The order of the list can shift while a run is paging. The Actor removes duplicates by the portal's internal tender ID. In one full scan, 10 of 2,300 rows were repeats.
- The Actor waits at least 1 second between requests. It retries with backoff on HTTP 429, 5xx and network errors. Each delivered tender costs one extra request with `includeDetails`, and one more with `includeOpeningReport`.
- Tender documents are sold on the portal and are not downloaded. The record links to the tender notice.
- Data comes from the public pages of the Tender Board portal. No login and no credentials are used.

### Development

```bash
npm install
npm test                 # parser tests on saved live pages
apify run --purge        # local run, reads storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

## `view` (type: `string`):

Which public list of the Tender Board to read. New tenders: open for document purchase and bidding (about 2,300 rows, including some old tenders the portal still lists). In process: bids already opened and under evaluation, newest first (about 20,000 rows), with the bid-opening report.

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

Words matched against tender title, description, entity, category and tender number. Arabic and English both work. Arabic is matched after normalisation, so alef/hamza, ة/ه and ى/ي spelling variants all match. Leave empty to return everything the other filters allow.

## `matchMode` (type: `string`):

any: keep a tender when at least one keyword matches. all: keep it only when every keyword matches.

## `entityContains` (type: `string`):

Keep only tenders whose entity name contains this text, for example وزارة الصحة. Normalised the same way as keywords.

## `categoryContains` (type: `string`):

Keep only tenders whose procurement category contains this text. Categories seen on the portal include التوريدات, الخدمات, خدمات تقنية المعلومات, المقاولات العمرانيه والصيانة and المكاتب الإستــشــارية.

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

New tenders list only. Drop tenders whose bid closing date is before today (Oman time). The portal still lists some tenders from earlier years in this list.

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

Open each tender's notice page and add the English title, publish date, governorate and wilayat, sub-categories, exact sales, query, closing and opening times, fee payment method, and the bank guarantee unit (OMR amount or percent of the bid). Adds one request per delivered tender.

## `includeOpeningReport` (type: `boolean`):

In process list only. Add the opened bids for each tender: company, offer type, total price in OMR and status. Adds one request per delivered tender.

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

Hard cap on delivered tenders. You are charged per tender delivered, so this is also your cost cap.

## `maxPages` (type: `integer`):

The portal shows 50 tenders per page. New tenders has about 46 pages and In process about 412. This caps how far a run reads when filters match few tenders.

## `requestDelayMs` (type: `integer`):

Pause before each request to the government portal. Never lower than 1000 ms; lower values are raised to 1000.

## `maxRetries` (type: `integer`):

Retries with exponential backoff when the portal answers 429 or 5xx, or the network fails.

## Actor input object example

```json
{
  "view": "NewTenders",
  "keywords": [
    "توريد"
  ],
  "matchMode": "any",
  "onlyOpen": true,
  "includeDetails": true,
  "includeOpeningReport": true,
  "maxItems": 20,
  "maxPages": 50,
  "requestDelayMs": 1000,
  "maxRetries": 5
}
```

# Actor output Schema

## `tenders` (type: `string`):

Matching tenders: number, title, entity, category and grades, tender type, sales end and closing dates, fee and bank guarantee in OMR, notice details and, for opened tenders, the bids opened.

# 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 = {
    "keywords": [
        "توريد"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("dottti/oman-tender-radar").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 = {
    "keywords": ["توريد"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("dottti/oman-tender-radar").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 '{
  "keywords": [
    "توريد"
  ],
  "maxItems": 20
}' |
apify call dottti/oman-tender-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dottti/oman-tender-radar"
        }
    }
}
```

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/Ap2VwBE4AyJfKYpEK/builds/hGNtU4rHCaVaVkxQ2/openapi.json
