From Data to Intelligence: What the Tenders-SA API Actually Gives You
Raw government procurement feeds are fragmented and incomplete. The Tenders-SA Developer API layers AI enrichment, consistent schemas, multi-language SDKs, and award analytics over these feeds. Here is what that means for your platform.
What You Actually Get When You Call the API
The Tenders-SA Developer API sits between raw government procurement feeds and your application. It does not just proxy data — it transforms it. Every endpoint returns enriched, structured, and documented data that is ready to display, analyse, or store. This article is a transparent look at what each endpoint actually gives you, what the enrichment pipeline does, and how the SDKs make it accessible.
The API serves from a dedicated infrastructure at https://api.tenders-sa.org/v1. It has its own database, synced from the main platform, so it remains fast and available independently of the Tenders-SA web application.
The Data Pipeline: From Government Feed to API Response
Understanding what you are getting means understanding where it comes from. Here is the pipeline that every tender passes through before it reaches an API response:
- Ingestion — The platform fetches OCDS data from government feeds: National Treasury eTenders, Eskom, Transnet, SANRAL, provincial portals, and municipal systems. Raw OCDS JSON arrives with inconsistent schemas, missing fields, and varying naming conventions.
- Normalisation — Province names are standardised (e.g. "GP" → "Gauteng"), status values are mapped to a consistent set (OPEN, CLOSED, AWARDED, CANCELLED), and procurement categories are classified against a unified taxonomy.
- AI Enrichment — Every tender is processed through AI pipelines. This generates an executive summary, extracts key requirements and evaluation criteria, estimates the tender value range based on document content analysis, and classifies the tender into its primary and secondary categories.
- Document Processing — Associated documents are fetched and stored in Cloudflare R2 with metadata tracked in D1. Document URLs are repaired and made accessible through the document endpoint.
- Synchronisation — The API database is synced from the main platform, making enriched data available through REST endpoints.
The result is a dataset that is significantly more useful than the raw OCDS source. A tender that arrives from a government feed with a title, an organisation name, and a cryptic status code comes out the other end with a human-readable summary, an estimated value range, a classified category, extracted requirements, and linked documents.
The Enrichment Layer: What AI Actually Adds
The AI enrichment layer is the core differentiator of the Tenders-SA API. Here is what it produces for every tender:
| Field | Source | What It Contains |
|---|---|---|
| aiSummary | AI-generated | 2-3 paragraph executive summary of the tender, extracted from document content and notice text |
| aiRequirements | AI-extracted | Key submission requirements, evaluation criteria, and mandatory documentation |
| estimatedValueMin / estimatedValueMax | AI-estimated | Estimated value range based on historical award data for similar tenders and document analysis |
| category | AI-classified | Primary procurement category matched against the unified category taxonomy |
| subcategory | AI-classified | Secondary classification for finer-grained filtering |
| status | Normalised | Mapped to OPEN, CLOSED, AWARDED, CANCELLED — consistent across all sources |
| province | Normalised | Geography resolved to standardised province name |
| issueDate / closingDate | Parsed | Normalised ISO 8601 date strings |
This enrichment does not depend on the source feed quality. Even a sparse OCDS release with only a title and organisation name will receive a summary, value estimate, and category assignment through the AI pipeline. That means you get usable data even for tenders where the government feed was incomplete.
Every Endpoint and What It Returns
The API exposes five resource groups. Here is a detailed walkthrough of each.
Tenders — The Core Dataset
The tenders resource is the primary interface for all procurement opportunities in the system.
| Endpoint | What It Returns |
|---|---|
| GET /v1/tenders | Paginated list of tenders with summary fields. Supports 10+ filter parameters: status, province, category, value range, closing date range, sort order, and sparse field selection. |
| GET /v1/tenders/{id} | Full tender object with all enriched fields: summary, requirements, value estimate, category, timeline dates, linked organisation profile, and related awards. |
| GET /v1/tenders/search | Semantic search results across all active tenders. Uses AI-powered matching rather than keyword matching, so a search for 'road construction' will surface tenders about 'national road maintenance' and 'bridge repairs'. |
| GET /v1/tenders/{id}/documents | List of associated documents with download URLs. Documents that have been cached in R2 return a reliable public URL. The document metadata includes file name, type, and size. |
| GET /v1/tenders/{id}/awards | Award history for a specific tender, including winning supplier, contract value, BEE level, and enterprise type. |
| GET /v1/tenders/{id}/timeline | Full procurement timeline: publication, briefing session, deadline, award date, contract start and end. |
| GET /v1/tenders/{id}/analysis | AI-generated analysis with structured sections: summary, requirements, compliance checklist, and evaluation criteria. |
| GET /v1/tenders/{id}/value-estimate | Estimated value range with confidence indicators and methodology notes. |
Awards — Contract Intelligence
The awards resource surfaces who won what, for how much, and under what conditions.
| Endpoint | What It Returns |
|---|---|
| GET /v1/awards | Paginated list of awarded contracts with filterable fields: supplier name, province, category, value range, BEE level, enterprise type, and award date range. |
| GET /v1/awards/{id} | Full award detail including supplier profile, contract value, BEE level, enterprise type (SMME, QSE, etc.), issuing organisation, and applicable legislation. |
| GET /v1/awards/analytics | Aggregated award analytics grouped by province, category, enterprise type, or BEE level. Returns totals, counts, and percentage distributions for the selected dimension. |
The analytics endpoint is particularly useful for market research and reporting. For example, you can see the distribution of award value by BEE level across all tenders in the Western Cape, or compare contract volumes between construction and IT over a specific period.
Companies and Organisations — Entity Profiles
| Endpoint | What It Returns |
|---|---|
| GET /v1/companies/{name} | Company intelligence profile: award history, total contract value, enterprise type, BEE level, and compliance indicators aggregated from public procurement records. |
| GET /v1/companies/search | Company search by name, BEE level, province, or industry sector. |
| GET /v1/organizations/{id} | Procurement body profile: name, contact details, and sourcing category focus. |
| GET /v1/organizations/{id}/tenders | All tenders issued by a specific procurement body. |
Meta — Platform Status and Reference Data
| Endpoint | What It Returns |
|---|---|
| GET /v1/meta/status | API health check with data freshness timestamp — confirms the API is operational and how recently data was synchronised. |
| GET /v1/meta/provinces | All nine provinces with active tender counts. Useful for powering dropdown filters and geographic selectors. |
| GET /v1/meta/categories | All procurement categories with tender counts. Useful for filter UIs and category browsing. |
| GET /v1/meta/usage | Your API usage statistics across all registered keys: requests today, this month, remaining quota, and reset times. |
Working with the API in Practice
All API responses follow a consistent envelope format. A successful response looks like this:
1{ 2 "success": true, 3 "data": { ... }, 4 "meta": { 5 "requestId": "req_uuid", 6 "timestamp": "2026-05-28T10: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
Key design decisions in the response format:
- The
successboolean lets you check the outcome without parsing status codes. - Every response includes a
requestId— useful for debugging and support. - Pagination metadata is always present on list endpoints, even for single-page results.
- Rate limit information is in both HTTP headers and the body
meta.rateLimitso you never need to parse headers if you prefer the body. - The
fieldsquery parameter lets you request subsets of fields —GET /v1/tenders?fields=tenderId,title,status— reducing payload size significantly on list endpoints.
SDK Design: What the Client Handles for You
The three official SDKs share a consistent design philosophy. Each one hides HTTP mechanics behind a resource-oriented interface:
1client.tenders.list(filters) // → ListResponse<Tender> 2client.tenders.get(id) // → ApiResponse<Tender> 3client.tenders.search(query) // → ListResponse<Tender> 4client.tenders.documents(id) // → ListResponse<Document> 5client.tenders.awards(id) // → ListResponse<Award> 6client.tenders.analysis(id) // → ApiResponse<Analysis> 7client.tenders.valueEstimate(id) // → ApiResponse<ValueEstimate> 8client.tenders.timeline(id) // → ApiResponse<Timeline> 9client.tenders.listPages(filters) // → AsyncIterator<Tender[]>TEXT
What the SDK handles transparently:
- Authentication — You pass the API key once in the constructor. The SDK attaches the
Authorizationheader to every request. - Pagination — List endpoints return paginated results. The
listPages()method (JavaScript) andpaginated()method (Python) provide async iterators that automatically fetch subsequent pages. - Error mapping — Every HTTP status maps to a typed error class. A 401 becomes
AuthError, a 404 becomesNotFoundError, a 429 becomesRateLimitError. Each error exposesstatus,code,message, andrequestId. - Rate limit tracking — The client stores the most recent rate limit snapshot, accessible via
client.lastRateLimit. - Retry with backoff — Transient failures (5xx, network errors) are retried with exponential backoff. Configurable via the
retryoption in the constructor. - Key conversion (Python SDK only) — The Python SDK automatically converts camelCase API fields to snake_case Python attributes.
Error Handling: The Complete Picture
Every API error returns a structured response:
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-05-28T10:00:00Z" 9}JSON
Each error response includes a docs URL pointing to documentation for that specific error code. The SDKs throw typed exceptions that expose all of these fields so you can handle errors programmatically:
1try { 2 const result = await client.tenders.get('nonexistent') 3} catch (err) { 4 if (err instanceof AuthError) { 5 console.error('Invalid API key. Get one at https://tenders-sa.org/developers/api-keys') 6 } else if (err instanceof NotFoundError) { 7 console.error('Tender not found') 8 } else if (err instanceof RateLimitError) { 9 console.error(`Rate limited: ${err.used}/${err.limit}`) 10 } else if (err instanceof TendersaError) { 11 console.error(`API error [${err.code}]: ${err.message} (request: ${err.requestId})`) 12 } 13}TYPESCRIPT
What You Cannot Get from the Public Widget API (But Can Get from the Developer API)
The public widget endpoints at /api/widgets/* are open-access and do not require authentication. They are designed for the four embeddable widgets and expose aggregated or limited views of the data. The Developer API provides everything the widget API does — plus the following:
- Full tender detail with AI analysis, requirements, value estimates, and document lists.
- Award records with supplier names, BEE levels, contract values, and enterprise types.
- Company intelligence profiles with aggregated award history.
- Organisation (procurement body) profiles and their tender history.
- Award analytics with grouping by province, category, BEE level, or enterprise type.
- Semantic search across all active tenders.
- Sparse field support for payload size optimisation.
Source Code and Documentation
Everything described in this article is available today. Here are the key links:
| Resource | Link |
|---|---|
| Developer Portal (API keys) | tenders-sa.org/developers/api-keys |
| API Documentation | tenders-sa.org/publishers/developers |
| JavaScript/TypeScript SDK | github.com/Tenders-SA/js |
| Python SDK | github.com/Tenders-SA/python |
| CLI | github.com/Tenders-SA/cli |
| Widget: Heatmap | github.com/Tenders-SA/widget-heatmap |
| Widget: Sector Trends | github.com/Tenders-SA/widget-sector-trends |
| Widget: Top Companies | github.com/Tenders-SA/widget-top-companies |
| Widget: Winners Feed | github.com/Tenders-SA/widget-winners-feed |
| Publishers Portal | tenders-sa.org/publishers |
| Platform Home | tenders-sa.org |
All SDKs and widgets are open source under the MIT license. Issues and pull requests are welcome on any of the GitHub repositories.
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
From Data to Intelligence: What the Tenders-SA API Actually Gives You
Raw government procurement feeds are fragmented and incomplete. The Tenders-SA Developer API layers AI enrichment, consistent schemas, multi-language SDKs, and award analytics over these feeds. Here is what that means for your platform.