# South Africa Government Tenders & Contract Awards (eTenders) (`mzansi-dataworks/sa-government-tenders`) Actor

Open tenders, contract awards (winner, rand amount, B-BBEE enterprise type), closed tenders with bidder lists, and cancellations from South Africa's National Treasury eTenders portal. Filter by keyword, province, category and organ of state, or run on a schedule to get only new tenders.

- **URL**: https://apify.com/mzansi-dataworks/sa-government-tenders.md
- **Developed by:** [Norton Claude](https://apify.com/mzansi-dataworks) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## South Africa Government Tenders & Contract Awards (eTenders)

Get every South African government tender from the National Treasury **eTenders portal** as clean JSON, CSV or Excel: open tenders with closing dates, compulsory briefings and document download links, plus **contract awards with the winning company, the rand amount and the winner's B-BBEE enterprise type**, closed tenders with the list of bidders, and cancellations.

All organs of state publish here: national and provincial departments, all 257 municipalities, SOEs such as Eskom, Transnet, PRASA and SANRAL, water boards, universities and TVET colleges. On a typical day the portal carries about 2,200 open tenders.

### What makes this actor different

- **Contract awards, not just adverts.** See who won, for how much, and whether the winner is an EME, QSE or large enterprise (GEN). Track competitors, price your next bid, or find newly funded contractors to sell to.
- **Bidder lists for closed tenders**, where the buyer published one.
- **Tender alerts.** Switch on *Only new since my last run*, schedule it daily, and connect Slack, email or Google Sheets. You only pay for tenders you haven't seen before.
- **Filters that match how people search for tenders:** keywords, province, category, buyer (organ of state), tender type, closing within N days, compulsory briefing only, e-submission only, minimum award value.
- **Direct document links** to the advert and bid pack PDFs.
- **Clean fields:** SA dates with the +02:00 offset, award amounts parsed from "R551 189,00" into numbers, days left until closing.

### Who uses it

- Suppliers, contractors and consultants who bid on government work and want a daily feed of relevant tenders.
- Tender alert services, bid offices and B-BBEE consultants who need structured data instead of the portal's paged table.
- Sales and BD teams selling to companies that just won government contracts (finance, staffing, equipment hire, insurance).
- Journalists, researchers and civic tech projects analysing public procurement.

### How to use it

1. Pick **What to fetch**: open tenders, contract awards, closed tenders or cancelled tenders.
2. Add filters if you want them (all optional). Within a list, any match counts; different filters must all match.
3. Click **Start**. Download the results as JSON, CSV, Excel or HTML, or read them through the Apify API.

For alerts, create a **Task** with your filters, tick **Only new since my last run**, give the alert a name, and add a daily **Schedule**. Use a different alert name for each saved search.

#### Example inputs

Security and cleaning tenders in the Western Cape closing in the next 14 days:

```json
{
  "mode": "open",
  "keywords": ["security", "cleaning"],
  "provinces": ["Western Cape"],
  "closingWithinDays": 14
}
```

Construction awards over R1 million posted in the last 30 days:

```json
{
  "mode": "awarded",
  "categories": ["Construction"],
  "minAwardAmountZar": 1000000,
  "awardedWithinDays": 30
}
```

Daily alert for City of Cape Town and Eskom tenders:

```json
{
  "mode": "open",
  "organsOfState": ["City of Cape Town", "Eskom"],
  "onlyNew": true,
  "stateKey": "cpt-eskom-daily"
}
```

### Output

One row per tender. Open tender example (shortened):

```json
{
  "tenderId": 174247,
  "tenderNumber": "TDR31/2026/2027",
  "description": "PROVISION OF BANKING SERVICES",
  "status": "Published",
  "category": "Financial service activities, except insurance and pension funding",
  "tenderType": "Request for Bid(Open-Tender)",
  "organOfState": "Mossel Bay Municipality",
  "province": "Western Cape",
  "datePublished": "2026-10-16",
  "closingDate": "2026-12-18T12:00:00+02:00",
  "daysUntilClosing": 62,
  "briefingSession": true,
  "briefingCompulsory": true,
  "briefingDate": "2026-11-02T12:00:00+02:00",
  "briefingVenue": "A compulsory online briefing meeting via Microsoft Teams.",
  "deliveryAddress": "101 Marsh Street-Mossel Bay municipality-Mossel Bay-6500",
  "eSubmission": false,
  "bidValidityDays": 120,
  "enquiriesContactPerson": "Mr Hennie le Roux",
  "enquiriesEmail": "hleroux@mosselbay.gov.za",
  "enquiriesPhone": "044-606-5267",
  "documentCount": 2,
  "documents": [
    { "name": "31-2026-2027 ADVERT.pdf", "url": "https://www.etenders.gov.za/home/Download?blobName=..." }
  ],
  "source": "eTenders Portal, National Treasury (etenders.gov.za)",
  "scrapedAt": "2026-10-16T18:04:29.983Z"
}
```

Award rows add:

```json
{
  "awardedTo": "AUMANAT",
  "totalAwardAmountZar": 551189,
  "awardDate": "2026-10-14",
  "awardPostedDate": "2026-10-16",
  "awards": [
    { "awardedTo": "AUMANAT", "enterpriseType": "EME", "awardAmountZar": 551189, "awardAmountText": "R551 189,00", "awardDate": "2026-10-14", "awardPostedDate": "2026-10-16" }
  ]
}
```

Closed tender rows add `bidders` (company names), `bidderCount` and `closedReason`. Cancelled rows add `cancellationReason` and `cancelledDate`.

| Field | Meaning |
|---|---|
| `tenderNumber` | Buyer's tender reference |
| `organOfState` | The department, municipality or entity buying |
| `province` | Province, or "National" for national bodies |
| `closingDate` | Bid deadline, SA time |
| `daysUntilClosing` | Whole days left (open tenders) |
| `briefingCompulsory`, `briefingDate`, `briefingVenue` | Briefing session details |
| `enquiriesContactPerson`, `enquiriesEmail`, `enquiriesPhone` | The buyer's official enquiries contact, as published |
| `documents` | Advert and bid document download links |
| `awardedTo`, `totalAwardAmountZar`, `awards[]` | Winner(s), amount in rand, enterprise type (awards mode) |
| `bidders` | Bidding companies (closed mode, where published) |

### Pricing

Pay per result, no subscription:

- **$0.002 per tender** (open, closed or cancelled), so all ~2,200 open tenders cost about $4.40 and a filtered daily alert usually costs cents.
- **$0.003 per contract award.**

Set **Max results** or a maximum cost per run to cap spend. With *Only new since my last run*, repeat tenders are free.

### Good to know

- Open tenders are fetched in full every run (about 2,200 records, a minute or two). Award runs look back through tenders advertised in the last ~10 months to find awards posted recently, which takes longer.
- The eTenders portal returns a server error for a small share of award records. The actor isolates and skips those records instead of failing, and logs how many it skipped.
- Dates on the portal are South African time; the actor adds the +02:00 offset.
- Awards appear on eTenders when the buyer posts them, often weeks after the closing date, and not every buyer posts awards.

### Data source and privacy

Data comes from the public eTenders Portal run by National Treasury (etenders.gov.za); National Treasury publishes the same data as open data under the Open Data Commons Public Domain Dedication and License. The actor outputs only what tender notices publish. For personal information it keeps only the buyer's official enquiries contact as published on the notice. It does not output portal user accounts, staff upload addresses, or the winning bidder's contact person and phone number. If you process the data, you are responsible for your own compliance with POPIA, including its rules on direct marketing.

This actor is independent and is not affiliated with or endorsed by National Treasury.

### Feedback

Missing a field, a filter or a source (for example Eskom's or a metro's own tender pages)? Open an issue on the actor's Issues tab and it will be looked at.

### Changelog

- 1.0: First release. Open, awarded, closed and cancelled tenders; filters; only-new alerts; document links.

# Actor input Schema

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

Open tenders that are accepting bids, contract awards (who won and for how much), closed tenders (with the bidder list where the buyer published one), or cancelled tenders.

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

Keep tenders whose number, description, category, buyer or special conditions contain ANY of these words (case-insensitive). Leave empty for everything.

## `excludeKeywords` (type: `array`):

Drop tenders containing any of these words.

## `provinces` (type: `array`):

Keep only these provinces. 'National' covers national departments and entities.

## `categories` (type: `array`):

Keep tenders whose eTenders category contains any of these phrases, e.g. 'Construction', 'Computer programming', 'Security'.

## `organsOfState` (type: `array`):

Keep tenders from buyers whose name contains any of these, e.g. 'City of Cape Town', 'Eskom', 'Transnet', 'Department of Health'.

## `tenderTypes` (type: `array`):

e.g. 'Request for Bid', 'Request for Quotation', 'Request for Proposal', 'Expression of interest'.

## `publishedWithinDays` (type: `integer`):

Only tenders advertised in the last N days. 0 means no limit. If left empty: no limit for open tenders, 30 days for closed and cancelled, and for awards 270 days more than 'Awarded within' (awards are posted long after the advert).

## `closingWithinDays` (type: `integer`):

Open tenders only: keep tenders closing within the next N days. 0 means no limit.

## `onlyCompulsoryBriefing` (type: `boolean`):

Keep only tenders with a compulsory briefing session (useful for planning site visits).

## `onlyESubmission` (type: `boolean`):

Keep only tenders that accept electronic bid submission on eTenders.

## `awardedWithinDays` (type: `integer`):

Keep awards posted on eTenders in the last N days (default 30). 0 means no limit. Awards appear weeks or months after the tender was advertised, so this is the filter most people want for award tracking.

## `enterpriseTypes` (type: `array`):

Keep awards won by these enterprise types, e.g. EME, QSE, Large.

## `minAwardAmountZar` (type: `integer`):

Keep awards whose total value is at least this many rand. 0 means no minimum.

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

Remembers what earlier runs returned (in a named key-value store on your account) and outputs only tenders you haven't seen. Ideal with a daily schedule for tender alerts. You pay only for new records.

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

Use a different name for each saved search you monitor, so their memories don't mix. Defaults to one memory per mode.

## `includeDocuments` (type: `boolean`):

Adds the tender advert and bid documents with direct download links.

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

Stop after this many results. You are charged per result.

## Actor input object example

```json
{
  "mode": "open",
  "keywords": [
    "security"
  ],
  "closingWithinDays": 0,
  "onlyCompulsoryBriefing": false,
  "onlyESubmission": false,
  "awardedWithinDays": 30,
  "minAwardAmountZar": 0,
  "onlyNew": false,
  "includeDocuments": true,
  "maxItems": 100
}
```

# 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 = {
    "keywords": [
        "security"
    ],
    "maxItems": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("mzansi-dataworks/sa-government-tenders").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": ["security"],
    "maxItems": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("mzansi-dataworks/sa-government-tenders").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": [
    "security"
  ],
  "maxItems": 100
}' |
apify call mzansi-dataworks/sa-government-tenders --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mzansi-dataworks/sa-government-tenders"
        }
    }
}
```

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/mNcbbDKSuYNgQXyID/builds/CentsWYUUM2kEkVhr/openapi.json
