# South Africa eTenders Scraper: Tenders & Awards (`openprocure/south-africa-etenders-ocds`) Actor

Get South African government tenders and contract awards from National Treasury's official eTenders OCDS open-data API. Filter by keyword, buyer, category, status and dates. Clean rows with award values, suppliers and document links; no personal contact data.

- **URL**: https://apify.com/openprocure/south-africa-etenders-ocds.md
- **Developed by:** [OpenProcure Data](https://apify.com/openprocure) (community)
- **Categories:** Lead generation, Developer tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 eTenders Scraper: Government Tenders & Contract Awards API

Extract **South African government tenders and contract awards** from the official **eTenders OCDS open-data API** published by National Treasury. Filter by keyword, buyer, procurement category, open/closed status and date range, and get clean JSON, CSV or Excel rows with closing dates, award values, awarded suppliers and tender document links. It uses only HTTP calls to the official API: no browser and no proxies. It does not collect personal contact data.

### What does this Actor do?

- Reads releases from `https://ocds-api.etenders.gov.za` (Open Contracting Data Standard format) for the date window you choose.
- Flattens each release into **one row per tender**: buyer, title, category, procurement method, opening and closing dates, days left, value, awards and documents.
- Parses **contract awards**: the number of awards, the total award value in ZAR, supplier names, and a per-award breakdown.
- Works around the API's known server errors. The official list endpoint returns HTTP 500 for some records. In our tests these included every record that carried award data. The Actor isolates the failing records and re-fetches them one by one from the single-release endpoint.
- Removes contact-person names, emails and phone numbers.

### Use cases

- **Tender alerts**: run it daily on a schedule with your keywords and send new open tenders to email, Slack, Google Sheets, n8n, Make or Zapier.
- **Bid research**: see which departments and state-owned entities buy what you sell, and read the tender documents before you bid.
- **Procurement analytics and competitor research**: use `onlyAwarded` to see who won contracts, for how much, and from which buyers.
- **Data journalism and transparency research** on South African public spending.

### Input

| Field | Type | Description |
|---|---|---|
| `dateFrom` / `dateTo` | string (YYYY-MM-DD) | Release-date window. Defaults: 7 days ago and today. |
| `keywords` | string\[] | Keep tenders whose title or description contains any of these words (case-insensitive). |
| `excludeKeywords` | string\[] | Drop tenders that contain any of these words. |
| `buyerContains` | string | Buyer name filter, e.g. `Eskom`, `Transnet`, `City of Cape Town`. |
| `categories` | string\[] | `goods`, `services`, `works`. |
| `onlyOpen` | boolean | Only tenders that close today or later. |
| `onlyAwarded` | boolean | Only releases that include contract awards. |
| `includeDocuments` | boolean | Include tender document download links (default on). |
| `includeRaw` | boolean | Attach the full OCDS release, with contact persons removed. |
| `recoverFailedRecords` | boolean | Re-fetch records the list endpoint fails on (default on; needed for award data). |
| `maxItems` | integer | Stop after N rows (0 = no limit). |

Example: open ICT tenders from the last 7 days.

```json
{"keywords": ["software", "ICT", "network", "cloud"], "onlyOpen": true, "maxItems": 50}
```

### Output fields

`ocid`, `tenderId`, `title`, `description`, `buyer`, `category`, `procurementMethod`, `status`, `releaseDate`, `openingDate`, `closingDate`, `daysToClose`, `isOpen`, `valueZar`, `awardCount`, `awardTotalZar`, `awardedSuppliers`, `awards[]` (`awardId`, `supplier`, `status`, `valueZar`, `currency`, `description`), `documentCount`, `documents[]` (`title`, `url`, `published`), `sourceUrl`, `apiReleaseId`, and optionally `raw`.

A `RUN_SUMMARY` record in the default key-value store shows how many releases were scanned, delivered, skipped because of API errors, and recovered.

#### Sample output (a real record, shortened)

```json
{
  "ocid": "ocds-9t57fa-145714",
  "tenderId": "145714",
  "title": "IHLM/126/2025-26/COMM",
  "description": "SUPPLY, DELIVERY AND INSTALLATION OF 80 STREET LITTER BINS",
  "buyer": "Ingquza Hill Local Municipality",
  "category": "goods",
  "procurementMethod": "Request for Bid(Open-Tender)",
  "status": null,
  "releaseDate": "2026-01-30T00:00:00Z",
  "openingDate": "2026-01-30T00:00:00Z",
  "closingDate": "2026-02-17T12:00:00+00:00",
  "daysToClose": -253,
  "isOpen": false,
  "valueZar": null,
  "awardCount": 1,
  "awardTotalZar": 8774000.0,
  "awardedSuppliers": [
    "WE ARE THE HYGIENE HUB"
  ],
  "awards": [
    {
      "awardId": "68835",
      "supplier": "WE ARE THE HYGIENE HUB",
      "status": "active",
      "valueZar": 8774000.0,
      "currency": "ZAR",
      "description": "EME"
    }
  ],
  "documentCount": 1,
  "sourceUrl": "https://www.etenders.gov.za/Home/opportunities?id=145714",
  "apiReleaseId": "ocds-9t57fa-145714-2026-01-30",
  "documents": [
    {
      "title": "BID DOCUMENT FOR SUPPLY, DELIVERY AND INSTALLATION OF 80 STREET LITTER BINS.pdf",
      "url": "https://www.etenders.gov.za/home/Download?blobName=c7db0eff-8caf-43e2-b5bb-a88b5f0a2207.pdf&downloadedFileName=BID%20DOCUMENT%20FOR%20SUPPLY%2C%20DELIVERY%20AND%20INSTALLATION%20OF%2080%20STREET%20LITTER%20BINS.pdf",
      "published": "2026-01-30T08:37:22Z"
    }
  ]
}
```

### Pricing

This Actor uses **pay-per-event** pricing:

- **$0.002 per result** (one tender row in the dataset), which is $2 per 1,000 tenders.
- **$0.005 per run start.**

You pay only for rows you receive. Set a maximum cost per run in the run options, and the Actor stops when it reaches that limit.

### FAQ

**Is this legal? Where does the data come from?**\
All data comes from National Treasury's public eTenders OCDS API, which is published under the Open Data Commons PDDL licence. This Actor is independent and is not affiliated with National Treasury.

**Why do some tenders have no award data?**\
Awards appear only after the buying entity publishes them on eTenders. Most recent releases are still open tenders.

**Why is a run with awards slower?**\
The official list endpoint fails on many award records. The Actor has to fetch them one at a time, which takes longer.

**Are some records still missing?**\
Yes, a few. Some records also fail on the single-release endpoint. Their count is in `RUN_SUMMARY.recordsSkippedByApiErrors` minus `recordsRecoveredViaSingleLookup`.

**Can I get the data in Excel or CSV?**\
Yes. Download the dataset as JSON, CSV, Excel, XML or HTML, or use the Apify API and integrations.

**Can I schedule daily tender alerts?**\
Yes. Create a Schedule in Apify Console with `dateFrom` left empty. To get only new items each day, set the window to 1–2 days.

**Do you include personal contact details?**\
No. Contact names, emails, phone and fax numbers are removed.

Always confirm deadlines and requirements in the official tender documents before you bid.

# Actor input Schema

## `dateFrom` (type: `string`):

Start of the release-date window. Empty = 7 days ago.

## `dateTo` (type: `string`):

End of the release-date window. Empty = today.

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

Keep tenders whose title or description contains ANY of these words (case-insensitive). Empty = all.

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

Drop tenders containing any of these words.

## `buyerContains` (type: `string`):

e.g. 'Eskom', 'Transnet', 'City of Cape Town'.

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

Empty = all.

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

Keep only tenders whose closing date is today or later.

## `onlyAwarded` (type: `boolean`):

Keep only releases that include contract awards (for market and competitor research).

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

Add the tender document download links.

## `includeRaw` (type: `boolean`):

Attach the full original OCDS JSON (contact persons removed).

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

Stop after this many rows. 0 = no limit.

## `recoverFailedRecords` (type: `boolean`):

The official list endpoint returns server errors for some records (in our tests, the ones carrying contract awards). When on, the Actor fetches those records one by one from the single-release endpoint. Slower, but needed for award data.

## Actor input object example

```json
{
  "dateFrom": "",
  "dateTo": "",
  "keywords": [],
  "excludeKeywords": [],
  "buyerContains": "",
  "categories": [],
  "onlyOpen": false,
  "onlyAwarded": false,
  "includeDocuments": true,
  "includeRaw": false,
  "maxItems": 50,
  "recoverFailedRecords": true
}
```

# Actor output Schema

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

One row per tender.

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

Award-focused view of the same rows.

## `runSummary` (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 = {
    "dateFrom": "",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("openprocure/south-africa-etenders-ocds").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 = {
    "dateFrom": "",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("openprocure/south-africa-etenders-ocds").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 '{
  "dateFrom": "",
  "maxItems": 50
}' |
apify call openprocure/south-africa-etenders-ocds --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,openprocure/south-africa-etenders-ocds"
        }
    }
}
```

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/IT37qI1Af65QM7oxe/builds/l4TEEhlSssl204Awk/openapi.json
