# NUPCO & Saudi Health Tender Awards — Winners, Bids, Prices (`dottti/nupco-awards`) Actor

Who won Saudi health-sector tenders. NUPCO result notices with the winning supplier, manufacturer and model per item, plus Etimad awards of the Ministry of Health, health clusters and hospitals with every bid and the award value in SAR.

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

## Pricing

from $25.00 / 1,000 awards

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/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

## NUPCO & Saudi Health Tender Awards — Winners, Bids, Prices

See who wins Saudi health-sector tenders. The Actor reads two public sources and returns one row per tender with published results:

- **NUPCO** ([nupco.com](https://www.nupco.com)), the national unified procurement company for health. NUPCO publishes its tender results as PDF tables. The Actor reads each table and returns every item: NUPCO item code, description, winning supplier with its vendor number, manufacturer, country and model or trade name.
- **Etimad** ([tenders.etimad.sa](https://tenders.etimad.sa)) awards of health-sector buyers: the Ministry of Health and its regional directorates, health clusters, hospitals and medical cities, National Guard, Armed Forces and Security Forces health services, health universities and health authorities. Each row lists every bidder, every financial offer, the winner and the award value in SAR.

### Who uses this

- **Medical suppliers and distributors** see which competitor won each NUPCO item and with which manufacturer.
- **Pharma and device sales teams** search awarded items for their molecules or product lines, for example `PACLITAXEL` or `PHACO`.
- **Bid teams** price their next offer against what hospitals and health clusters actually paid on Etimad.
- **Market analysts** count wins per supplier across a whole framework tender.

### What each source publishes

| | NUPCO | Etimad |
|---|---|---|
| Winning supplier | Yes, per item | Yes, per tender |
| Award value in SAR | NUPCO does not publish it | Yes |
| Every bidder and offer | NUPCO does not publish it | Yes |
| Manufacturer, model, country | Yes | No |
| Dates | Date only (YYYY-MM-DD) | Riyadh time with +03:00 |

Fields a source does not publish come back as `null`.

### Input examples

Empty input gives a quick sample in about a minute: the newest NUPCO result and 3 Etimad health awards from 30 to 120 days ago. For a full sweep, raise **Maximum NUPCO awards** (100 reads all of NUPCO) and **Maximum Etimad awards**.

#### Every NUPCO award for your molecules

```json
{
  "sources": ["nupco"],
  "keywords": ["PACLITAXEL", "FILGRASTIM"],
  "maxNupcoAwards": 100
}
```

When only items match, the row keeps just the matching items.

#### What a competitor won at NUPCO (final results only)

```json
{
  "sources": ["nupco"],
  "supplierContains": "ALGOSAIBI",
  "includePreliminary": false,
  "maxNupcoAwards": 100
}
```

#### Health cluster and Ministry of Health awards on Etimad for one month

```json
{
  "sources": ["etimad"],
  "healthGroups": ["health-cluster", "ministry-of-health"],
  "offerDeadlineFrom": "2026-08-01",
  "offerDeadlineTo": "2026-08-31",
  "maxItems": 500
}
```

### Output

Both sources share one schema. These rows come from a local run on 3 November 2026.

#### Etimad row

```json
{
  "source": "etimad",
  "tenderName": "توريد قطع غيار لمجموعة عربات ( 381 )",
  "referenceNumber": "260939008853",
  "agencyName": "مستشفى القوات المسلحة بالرياض",
  "branchName": "إدارةالمشتريات/ النقل وصيانة السيارات",
  "healthGroup": "military-security-health",
  "tenderTypeName": "شراء مباشر",
  "publishedAt": "2026-10-02T13:45:00+03:00",
  "offerDeadline": "2026-10-04T09:59:00+03:00",
  "awardStatus": "awarded",
  "resultPhase": "final",
  "awardType": "ترسية كاملة",
  "winnerCount": 1,
  "winners": [
    { "supplierName": "شركة تقنية السيور للتجارة", "supplierCode": null, "offerSar": 71873, "awardSar": 71873 }
  ],
  "totalAwardSar": 71873,
  "currency": "SAR",
  "bidderCount": 4,
  "bidders": [
    { "supplierName": "مؤسسة جودة التفاصيل التجارية", "offerSar": 166402.52, "technicalResult": "مطابق" },
    { "supplierName": "شركة تقنية السيور للتجارة", "offerSar": 71873, "technicalResult": "مطابق" },
    { "supplierName": "مؤسسة كشف لقطع الغيار", "offerSar": 94944, "technicalResult": "مطابق" },
    { "supplierName": "شركة الفيضة المحدودة", "offerSar": 97566, "technicalResult": "مطابق" }
  ],
  "lowestOfferSar": 71873,
  "winnerIsLowestBidder": true,
  "url": "https://tenders.etimad.sa/Tender/DetailsForVisitor?STenderId=MCPOKWqE3C1hXY3hgsnYoQ%3D%3D"
}
```

#### NUPCO row

The `items` array is cut to 1 of its 388 rows and `winners` to 2 of 10 here.

```json
{
  "source": "nupco",
  "tenderName": "منافسة عامة لتوريد وتجهيز مشروع مختبر الكشف عن المنشطات",
  "tenderNamePrinted": "GENERAL TENDER FOR ANTI-DOPING LABORATORY",
  "referenceNumber": "NPT0064/25",
  "agencyName": "الشركة الوطنية للشراء الموحد (نوبكو)",
  "healthGroup": "nupco",
  "tenderTypeName": "Public tender",
  "publishedAt": "2025-12-30",
  "offerDeadline": "2026-02-01",
  "offersOpeningAt": "2026-02-02",
  "awardStatus": "awarded",
  "resultPhase": "final",
  "resultPhasePrinted": "FINAL RESULT",
  "winnerCount": 10,
  "winners": [
    { "supplierName": "LAB TECHNOLOGIES TRADING COMPANY", "supplierCode": "400978", "itemsWon": 192, "offerSar": null, "awardSar": null },
    { "supplierName": "SECOND ADVANCE MEDICAL COPANY", "supplierCode": "400418", "itemsWon": 36, "offerSar": null, "awardSar": null }
  ],
  "totalAwardSar": null,
  "bidders": null,
  "itemCount": 388,
  "awardedItemCount": 335,
  "items": [
    {
      "sn": 1,
      "nupcoCode": "4111150200300",
      "itemNo": "MLB110261",
      "description": "BALANCE LABORATORY PREMIUM",
      "group": "GENERAL",
      "awarded": true,
      "supplierName": "LAB TECHNOLOGIES TRADING COMPANY",
      "supplierCode": "400978",
      "manufacturer": "OHAUS",
      "country": "USA",
      "model": "30852312",
      "page": 1
    }
  ],
  "resultPdfUrl": "https://www.nupco.com/wp-content/uploads/2026/06/NPT0064-25-FINAL-RESULT-WEBSITE-ANNOUNCEMENT.pdf",
  "url": "https://www.nupco.com/tender/..."
}
```

Each item keeps its PDF `page`, so you can check any row against the source PDF in seconds. Supplier names stay as NUPCO prints them, including markers such as `-Orig-` and `-Alt-`. Items with no winner (for example `CANCELLED - NO BID ACCEPTED` or `NO QUOTE`) stay in `items` with `awarded: false` and the reason in `remarks`.

### Schedule it

1. Set your filters and click **Save as a new task**.
2. Add a schedule, for example weekly on Sunday 07:00 Riyadh time.
3. Connect the task to email, Slack or Google Sheets from the **Integrations** tab.

### Pricing

- **Award result:** $0.025 per tender with published results delivered to your dataset ($25 per 1,000). One NUPCO row can hold thousands of awarded items.
- **Tender checked:** $0.0003 per tender looked up (one Etimad award page or one NUPCO result PDF). This covers lookups that find no results yet.

Platform usage is included in the price. `maxNupcoAwards`, `maxItems` and `maxTendersChecked` cap the cost of any run.

### How it works and limits

- NUPCO lists about 125 tenders on its site. On 3 November 2026, 53 carried results and 51 had a result PDF. The Actor read a table from 50 of them. One was an Arabic text letter with no table, and the Actor skips it with a log line.
- NUPCO result PDFs reach 6 MB and 300 pages. The Actor downloads three at a time, so a full NUPCO run takes a few minutes.
- Etimad allows about 10 award lookups per minute per IP address. The Actor runs several lookups in parallel, each on its own Apify Proxy IP.
- Etimad has no sector field. The Actor picks health buyers by agency and branch name. Use **Etimad health groups** and **Etimad agency contains** to narrow it.
- Data comes from public visitor pages and public PDFs. No login, no credentials.

### Development

```bash
npm install
npm test          # parser tests on real NUPCO PDFs and Etimad responses
node src/main.js  # local run, reads storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

## `sources` (type: `array`):

nupco: result notices on nupco.com (winning supplier, manufacturer and model per item; NUPCO publishes no prices). etimad: award results of health-sector agencies on Etimad (every bidder, every offer and the award value in SAR). Leave both for the full health picture.

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

Words to match. NUPCO: tender title and each awarded item (description, NUPCO code, manufacturer, trade name); when only items match, the row keeps just the matching items. Etimad: tender name, agency, branch and activity. Arabic is matched after normalisation (alef/hamza, ة/ه, ى/ي). Leave empty for everything.

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

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

## `supplierContains` (type: `string`):

Keep only awards where a winner (NUPCO) or a bidder or winner (Etimad) name contains this text. NUPCO rows then keep only that supplier's items. Example: ALGOSAIBI or شركة

## `includePreliminary` (type: `boolean`):

NUPCO first publishes preliminary results, then final ones. On: a tender with only preliminary results is included (resultPhase preliminary). Off: final results only.

## `offerDeadlineFrom` (type: `string`):

Etimad only: tenders whose offer deadline (آخر موعد لتقديم العروض) falls on or after this date. Format YYYY-MM-DD. Leave empty for 120 days ago. Awards are usually published a few weeks after the deadline.

## `offerDeadlineTo` (type: `string`):

Etimad only: tenders whose offer deadline falls on or before this date. Format YYYY-MM-DD. Leave empty for 30 days ago.

## `healthGroups` (type: `array`):

Which Etimad buyers count as health sector, by agency and branch name. Leave empty for all six groups.

## `agencyContains` (type: `string`):

Keep only Etimad tenders whose agency or branch name contains this text, on top of the health groups. Example: تجمع الرياض الصحي الأول

## `includeNotAnnounced` (type: `boolean`):

Add Etimad health tenders whose award has not been announced, with awardStatus not_announced. These rows are free.

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

Hard cap on delivered Etimad awards (one row per tender). You are charged per award, so this is also your cost cap. The default of 3 is a quick sample; raise it for a full sweep.

## `maxNupcoAwards` (type: `integer`):

Hard cap on delivered NUPCO results (one row per tender, newest first). You are charged per award. The default of 1 is a quick sample, because NUPCO result PDFs are large and download slowly. NUPCO lists about 50 tenders with results, so 100 reads all of them.

## `maxTendersChecked` (type: `integer`):

Per source. Each tender needs its own lookup (an Etimad award page or a NUPCO result PDF). Each check costs $0.0003. This caps run time and cost when few tenders match.

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

Pause between requests on each parallel worker. Etimad returns HTTP 429 on bursts; NUPCO requests use at most 500 ms.

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

Retries with exponential backoff when Etimad answers 429 or 5xx.

## `concurrency` (type: `integer`):

How many Etimad award lookups run at the same time, each on its own proxy IP, so Etimad's per-IP throttling slows one worker only. Needs a proxy; with the proxy off Etimad uses one worker. NUPCO always fetches three result PDFs at a time.

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

Used for Etimad only. Apify Proxy (automatic datacenter IPs) by default; each worker gets its own IP and switches to a new one when Etimad throttles it. NUPCO is read directly.

## Actor input object example

```json
{
  "sources": [
    "nupco",
    "etimad"
  ],
  "matchMode": "any",
  "includePreliminary": true,
  "includeNotAnnounced": false,
  "maxItems": 3,
  "maxNupcoAwards": 1,
  "maxTendersChecked": 3000,
  "requestDelayMs": 900,
  "maxRetries": 6,
  "concurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `awards` (type: `string`):

Tender, agency, winners, award value in SAR and every bid (Etimad), or every awarded item with supplier, manufacturer and model (NUPCO).

# 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 = {
    "sources": [
        "nupco",
        "etimad"
    ],
    "maxItems": 3,
    "maxNupcoAwards": 1,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dottti/nupco-awards").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 = {
    "sources": [
        "nupco",
        "etimad",
    ],
    "maxItems": 3,
    "maxNupcoAwards": 1,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("dottti/nupco-awards").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 '{
  "sources": [
    "nupco",
    "etimad"
  ],
  "maxItems": 3,
  "maxNupcoAwards": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call dottti/nupco-awards --silent --output-dataset

```

## MCP server setup

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

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/WEXbdHyfMRdDDzrll/builds/qQFG0gk909BYVy5aF/openapi.json
