The Tenders-SA Developer API: Programmatic Access to South African Procurement Data
A technical guide to the Tenders-SA Developer API — authentication, endpoints, pagination, rate limiting, error handling, and sparse field support. For developers building on top of SA public procurement data.
Programmatic Access to South African Procurement Data
Tenders-SA.org aggregates tenders from national, provincial, and municipal government departments, SOEs (Eskom, Transnet, SANRAL), and public entities across South Africa. The data is sourced directly from official OCDS (Open Contracting Data Standard) feeds and then enriched through AI pipelines for summarisation, requirement extraction, value estimation, and classification.
The Tenders-SA Developer API exposes this enriched procurement data through a set of RESTful endpoints. It runs on a dedicated infrastructure layer with its own database — synced from the main platform — so the API remains fast and available independently of the web application. This guide covers the API architecture, authentication, available endpoints, and usage patterns.
API Base URL and Authentication
All API requests are made to the following base URL:
1https://api.tenders-sa.org/v1TEXT
Authentication is handled via Bearer tokens passed in the Authorization header. API keys use the format tsa_prod_ followed by a unique generated string:
1Authorization: Bearer tsa_prod_your_api_keyTEXT
API keys are generated through the Developer Portal. Access requires a Professional or Enterprise subscription with the following rate limits:
| Plan | Max API Keys | Daily Limit | Monthly Limit |
|---|---|---|---|
| Professional | 3 | 500 | 15,000 |
| Enterprise | 25 | 10,000 | 300,000 |
Response Format
Every API response follows a consistent envelope structure. A successful response contains a success boolean, a data payload, and a meta object with pagination and rate limit information:
1{ 2 "success": true, 3 "data": { ... }, 4 "meta": { 5 "requestId": "req_uuid", 6 "timestamp": "2026-01-01T00:00:00Z", 7 "apiVersion": "v1", 8 "page": 1, 9 "pageSize": 20, 10 "totalCount": 142, 11 "totalPages": 8, 12 "hasNext": true, 13 "hasPrev": false, 14 "rateLimit": { 15 "limit": 500, 16// ... (truncated)JSON
Error responses follow a similar pattern with an error string, a machine-readable code, a human-readable message, and a docs URL pointing to the relevant error documentation:
1{ 2 "success": false, 3 "error": "Not found", 4 "code": "NOT_FOUND", 5 "message": "The requested resource was not found", 6 "requestId": "req_xxx", 7 "docs": "https://tenders-sa.org/developers/docs/errors#NOT_FOUND", 8 "timestamp": "2026-01-01T00:00:00Z" 9}JSON
Available Endpoints
The API is organised into five resource groups. Here is every available endpoint with its purpose:
Tenders
| Endpoint | Method | Description |
|---|---|---|
| /v1/tenders | GET | List tenders with filters (status, province, category, value range, closing date) |
| /v1/tenders/{id} | GET | Get a single tender with all enriched fields |
| /v1/tenders/search | GET | AI-powered semantic search across all active tenders |
| /v1/tenders/{id}/documents | GET | List associated tender documents with download URLs |
| /v1/tenders/{id}/awards | GET | Get award history for a specific tender |
| /v1/tenders/{id}/timeline | GET | Full procurement timeline from publication to award |
| /v1/tenders/{id}/analysis | GET | AI-generated analysis including summary, requirements, and scoring |
| /v1/tenders/{id}/value-estimate | GET | Estimated value range based on document content analysis |
Awards
| Endpoint | Method | Description |
|---|---|---|
| /v1/awards | GET | List awards with filters (supplier, province, category, value range, date range) |
| /v1/awards/{id} | GET | Get a single award with full supplier and contract details |
| /v1/awards/analytics | GET | Aggregated award analytics grouped by province, category, enterprise type, or BEE level |
Companies
| Endpoint | Method | Description |
|---|---|---|
| /v1/companies/{name} | GET | Company intelligence profile with award history, contract values, and compliance data |
| /v1/companies/search | GET | Search companies by name, BEE level, province, or industry |
Organisations (Procurement Bodies)
| Endpoint | Method | Description |
|---|---|---|
| /v1/organizations/{id} | GET | Procurement body profile with contact information |
| /v1/organizations/{id}/tenders | GET | Tenders issued by a specific procurement body |
Meta
| Endpoint | Method | Description |
|---|---|---|
| /v1/meta/status | GET | API health and data freshness information |
| /v1/meta/provinces | GET | List of provinces with active tender counts |
| /v1/meta/categories | GET | List of procurement categories with tender counts |
| /v1/meta/usage | GET | Your API usage statistics across all keys |
Pagination
All list endpoints are paginated with a page-based cursor system. You control pagination with page and pageSize query parameters. The response meta includes everything you need to navigate: page, pageSize, totalCount, totalPages, hasNext, and hasPrev.
1GET /v1/tenders?status=OPEN&page=2&pageSize=50TEXT
Page size defaults to 20 items and can be set up to 100. All three official SDKs implement pagination iterators that handle page traversal automatically.
Rate Limiting
Rate limit status is returned in both HTTP response headers and the response body's meta.rateLimit object:
- Headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,X-RateLimit-Policy - Body:
meta.rateLimit.limit,meta.rateLimit.remaining,meta.rateLimit.reset,meta.rateLimit.policy
When a rate limit is exceeded, the API returns a 429 status with either RATE_LIMIT_DAILY_EXCEEDED or RATE_LIMIT_MONTHLY_EXCEEDED error code.
Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request parameters or malformed input |
| 401 | UNAUTHORIZED | Missing or invalid API key |
| 403 | FORBIDDEN / KEY_NOT_ACTIVE / KEY_EXPIRED | Key not active, expired, or lacks permissions |
| 404 | NOT_FOUND | The requested resource does not exist |
| 409 | CONFLICT / KEY_LIMIT_REACHED | Key limit reached for your plan tier |
| 429 | RATE_LIMIT_DAILY_EXCEEDED / RATE_LIMIT_MONTHLY_EXCEEDED | Rate limit exceeded |
| 500 | INTERNAL_ERROR | Server error — please retry with exponential backoff |
| 502 | SERVICE_UNAVAILABLE | Service temporarily unavailable |
Sparse Fields
Reduce response payload size by specifying only the fields you need with the fields query parameter:
1GET /v1/tenders?fields=tenderId,title,status,closingDate,estimatedValueTEXT
This is particularly useful for list endpoints where you may only need a subset of fields for display purposes — for example, populating a table or a dropdown selector without loading the full tender object.
Filtering Parameters
List endpoints support a common set of filter parameters. The exact set varies by resource, but the most widely supported are:
| Parameter | Type | Applies To |
|---|---|---|
| status | string | Filter by tender status: OPEN, CLOSED, AWARDED, CANCELLED |
| province | string | Filter by province name (e.g. 'Gauteng', 'Western Cape') |
| category | string | Filter by procurement category slug |
| minAmount / maxAmount | number | Filter by contract value range (awards) |
| minValue / maxValue | number | Filter by estimated value range (tenders) |
| beeLevel | string | Filter by B-BBEE contributor level |
| sort | string | Sort field with optional '-' prefix for descending order |
| q | string | Free-text search query |
Getting Started
The quickest way to start exploring the API is through the official SDKs, which handle authentication, pagination, error handling, and retry logic:
- JavaScript / TypeScript SDK — github.com/Tenders-SA/js (
@tenders-sa-org/sdk-json npm) - Python SDK — github.com/Tenders-SA/python (
tendersa-sdkon PyPI) - CLI — github.com/Tenders-SA/cli (
@tenders-sa-org/clion npm)
For direct API access, the full API reference is available at tenders-sa.org/developers/docs. API keys can be generated at tenders-sa.org/developers/api-keys.
Tags
Based on this article's topics, here are some current tenders that might interest you
Request for Proposals (RFP) The Provision of Underwater Measurement Capabilities and Technical Expertise Related to the Development and Testing of Maritime Technology and Underwater Sensors with the CSIR for a Period of 5 Years.
The request - technology innovation
Want to see all available tenders?
Browse All Tenders →Share this article
The Tenders-SA Developer API: Programmatic Access to South African Procurement Data
A technical guide to the Tenders-SA Developer API — authentication, endpoints, pagination, rate limiting, error handling, and sparse field support. For developers building on top of SA public procurement data.