Gov APIetenders.gov.za ↗
Search South African public-sector tenders and categories from the National Treasury eTenders portal. Filter by province, status, tender type, and more.
What is the Gov API?
The eTenders South Africa API exposes 2 endpoints covering the National Treasury eTenders portal at etenders.gov.za. Use list_categories to retrieve all available tender categories and provinces, then pass those IDs to search_tenders to query live public-sector procurement notices. Each tender record returns up to 15 fields including tender number, organ of state, closing date, briefing details, procurement contact, and attached document names.
No input parameters required.
curl -X GET 'https://api.parse.bot/scraper/f4e437b0-748b-4882-a71c-bac295859254/list_categories' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace etenders-gov-za-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: South Africa eTenders — browse categories, then search tenders."""
from parse_apis.etenders_gov_za_api import ETenders, TenderStatus, InputFormatInvalid
client = ETenders()
# Fetch available tender categories from the portal.
category = client.categories.list(limit=3).first()
if category is not None:
print(f"Using category: {category.name} (id={category.category_id})")
# Search advertised tenders in that category, capped at 5 results.
try:
for tender in client.tenders.search(
category=category.category_id,
status=TenderStatus.ADVERTISED,
limit=5,
):
print(
tender.tender_number,
tender.description[:80],
tender.closing_date,
)
# Show briefing info when a session is scheduled.
if tender.briefing_session and tender.briefing_date is not None:
print(f" Briefing: {tender.briefing_date} — {tender.briefing_venue}")
except InputFormatInvalid as e:
print(f"Invalid search input: {e.message}")
print("exercised: categories.list / tenders.search")
Returns the full list of tender categories (category_id and name) and provinces (province_id and name) that the portal's Advanced Search offers. One round trip, no parameters. Use category_id values from this list for search_tenders.category and province_id values for search_tenders.province.
No input parameters required.
{
"type": "object",
"fields": {
"provinces": "array of {province_id (string numeric ID), name (province label; includes 'National')}",
"categories": "array of {category_id (string numeric ID), name (category label)}"
},
"sample": {
"data": {
"provinces": [
{
"name": "Gauteng",
"province_id": "8"
},
{
"name": "National",
"province_id": "7"
}
],
"categories": [
{
"name": "Accommodation",
"category_id": "30"
},
{
"name": "Education",
"category_id": "4"
}
]
},
"status": "success"
}
}About the Gov API
Endpoints Overview
The API has two endpoints. list_categories takes no parameters and returns two arrays: categories (each with a category_id and name) and provinces (each with a province_id and name, including a 'National' entry). These IDs feed directly into search_tenders — you must supply at least one category value to run a search.
Searching Tenders
search_tenders accepts comma-separated category and province IDs, an optional status filter (advertised, awarded, closed, or cancelled), a tender_type string matching portal labels exactly, a partial tender_number string for contains-matching, and an esubmission boolean to isolate tenders that accept electronic submissions. Results are paginated via limit (capped at 100) and start (0-based offset). The has_more field in the response tells you whether further pages exist.
Response Fields
Each tender object in the tenders array includes tender_id, tender_number, description, category_id, category, tender_type, status, organ_of_state, province, publish and closing dates, briefing date and location, delivery address, procurement contact details, and the names of any attached documents. The top-level total field reflects the portal's full matched count, not just the current page.
Coverage and Scope
Data covers the public-sector procurement notices published on the South African National Treasury eTenders portal. Tenders span all nine provinces plus national-level organs of state, across the full range of category types the portal's Advanced Search offers. The status filter maps directly to the portal's own lifecycle buckets, so awarded and cancelled tenders are retrievable alongside currently advertised ones.
The Gov API is a managed, monitored endpoint for etenders.gov.za — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when etenders.gov.za changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official etenders.gov.za API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Monitor newly advertised tenders in a specific province by polling
search_tenderswith aprovinceID andstatus=advertised. - Build a procurement alert system that filters by
categoryandesubmission=truefor organisations that only bid electronically. - Aggregate awarded tender data by
organ_of_stateto analyse public-sector spending patterns over time. - Match
tender_numberfragments against an internal watchlist to track specific procurement references as they move through lifecycle stages. - Compile a dataset of all cancelled tenders in a given category for compliance or audit reporting.
- Extract procurement contact details from tender records to build a directory of government buyer contacts per category.
- Cross-reference tender closing dates and briefing schedules to prioritise bid preparation workloads.
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does etenders.gov.za have an official developer API?+
What does `list_categories` return and why do I need it before searching?+
list_categories returns two arrays: all tender categories (each with a category_id and name) and all provinces (each with a province_id and name). The search_tenders endpoint requires at least one category value and accepts province values in the same numeric-ID format, so a call to list_categories is the correct way to discover valid IDs before filtering.How does pagination work in `search_tenders`?+
limit (maximum 100, values above that are clamped) and start (0-based offset). The response includes a total field with the portal's full match count and a has_more boolean. Increment start by your limit value on each subsequent call to walk through all matching tenders.Does the API expose the actual tender document files, not just their names?+
tender_id.Can I retrieve tenders for a specific organ of state directly?+
search_tenders filters by category, province, status, tender_type, esubmission, and tender_number. The organ_of_state field is present in each returned tender record, so you can filter client-side after fetching. You can fork this API on Parse and revise it to add an organ_of_state parameter if server-side filtering is needed.