Tenders-SA Developer API v2: Architecture, Usage, and Integration Guide
The v2 Developer API serves 80+ endpoints across 17 resource groups from a dedicated Cloudflare Worker at api.tenders-sa.org. This guide covers the infrastructure design, authentication model, endpoint catalogue, pagination mechanics, rate limiting, and integration patterns for developers and technical decision-makers.
Developer API v2: What Shipped and How to Use It
The Tenders-SA Developer API v2 runs on a dedicated Cloudflare Worker at api.tenders-sa.org. It exposes 80+ endpoints across 17 resource groups — tenders, awards, organisations, suppliers, directors, categories, provinces, SEO content, industry benchmarks, services, OCDS parties, procurement intelligence, restricted supplier forensics, CIPC company data, newsletter editions, and document metadata. Every endpoint returns structured JSON with a consistent envelope, cursor-based pagination, and per-key rate limiting.
This is not a thin proxy over the main application database. The API runs against its own dedicated D1 database, synced from the main platform every five minutes via shared-secret authenticated cursor-based export endpoints. This separation means API traffic never competes with the user-facing website for database connections, and the Worker's global edge distribution provides sub-50ms cold starts regardless of where the caller is located.
Infrastructure Design
Understanding the infrastructure helps you reason about data freshness, reliability, and failure modes. The pipeline has three stages:
- Main application (Next.js on AWS, PostgreSQL) — ingests tenders from National Treasury eTenders, Eskom, Transnet, SANRAL, provincial portals, and municipal systems. AI pipelines enrich each tender with summaries, requirement extraction, value estimation, and category classification.
- Sync layer — 33 internal sync-export endpoints expose each entity as a paginated cursor-based stream. The Worker pulls delta changes every five minutes using the
sinceandcursorparameters. Only rows withupdatedAtnewer than the last cursor are fetched — not full-table scans. - API Worker (Cloudflare, D1) — serves all public and authenticated endpoints from its own database. Validates API keys against SHA-256 hashes, enforces per-key daily and monthly rate limits, logs usage, and caches responses at the edge with TTLs ranging from 5 minutes (tender lists) to 6 hours (analytics).
The Worker exposes two health-check endpoints without authentication — /v2/meta/status and /v2/meta/health — that return per-table record counts and the most recent cron sync status. These are useful for monitoring dashboards and integration health checks.
Base URL and API Versioning
All API requests target the /v2/ prefix:
1https://api.tenders-sa.org/v2/{resource}TEXT
The /v1/ prefix is not active on this Worker. If you are migrating from the previous API surface, all endpoint paths have changed. The v2 API uses cursor-based pagination instead of page-based, and resource paths follow a RESTful-by-resource convention.
Authentication
All data endpoints require a Bearer token in the Authorization header. Keys use the tsa_prod_ prefix:
1Authorization: Bearer tsa_prod_your_api_keyTEXT
Keys are SHA-256 hashed at rest in D1. The authentication flow validates the hash, checks the key status (must be ACTIVE), confirms the key has not expired, then evaluates per-key daily and monthly usage counters against the key's configured limits. Failed authentications return structured error responses with the specific failure reason (MISSING_API_KEY, INVALID_API_KEY, KEY_NOT_ACTIVE, or KEY_EXPIRED).
Rate Limiting
Every authenticated response returns rate limit information in both HTTP headers and the response body:
1X-RateLimit-Limit: 10000 2X-RateLimit-Remaining: 9847 3X-RateLimit-Reset: 2026-06-16T00:00:00.000Z 4X-RateLimit-Policy: dailyTEXT
The same data appears in the JSON response body under meta.rateLimit, so integrators that do not parse HTTP headers can read limit status from the envelope. When a limit is exceeded, the API returns HTTP 429 with either RATE_LIMIT_DAILY_EXCEEDED or RATE_LIMIT_MONTHLY_EXCEEDED, plus a Retry-After header and a resetsAt ISO timestamp in the response body.
Response Envelope
Every successful response follows this structure:
1{ 2 "success": true, 3 "data": [ ], 4 "meta": { 5 "requestId": "req_67af5c2e", 6 "timestamp": "2026-06-15T10:04:14.318Z", 7 "apiVersion": "v2", 8 "totalCount": 5916, 9 "pageSize": 20, 10 "totalPages": 296, 11 "hasNext": true, 12 "hasPrev": false, 13 "rateLimit": { 14 "limit": 10000, 15 "remaining": 9847, 16// ... (truncated)JSON
Error responses use the same envelope shape but with success: false, an error message, a machine-readable code, and a docs URL pointing to the relevant error documentation:
1{ 2 "success": false, 3 "error": "The provided API key is invalid or has been revoked", 4 "code": "INVALID_API_KEY", 5 "action": "API_KEY_REQUIRED", 6 "requestId": "req_a1b2c3d4", 7 "docs": "https://tenders-sa.org/developers/docs", 8 "timestamp": "2026-06-15T10:04:14.318Z" 9}JSON
Pagination
List endpoints use cursor-based pagination. Every list response includes a nextCursor value in the meta. To retrieve the next page, pass this value as the cursor query parameter:
1GET /v2/tenders?limit=50&cursor=eyJpZCI6ImNs...TEXT
Cursor-based pagination is stable under concurrent writes — unlike offset-based pagination, a cursor points to a specific row and will not skip or duplicate records when new data is inserted between requests. The totalCount field in the response meta gives the full dataset size, and hasNext / hasPrev booleans simplify client-side navigation logic.
Endpoint Catalogue
The full API surface is documented in an auto-generated OpenAPI 3.0 specification at /v2/openapi.json (no auth required) with an interactive Redocly viewer at /v2/docs. Below is a functional summary of every resource group.
Tenders — 27 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/tenders | Cursor-paginated tender list with limit and cursor params |
| GET /v2/tenders/search?q= | Full-text search across tenders |
| GET /v2/tenders/closing-soon | Tenders approaching closing date |
| GET /v2/tenders/new | Most recently published tenders |
| GET /v2/tenders/bbbee-required | Tenders with B-BBEE requirements |
| GET /v2/tenders/value-range?min=&max= | Filter by estimated value range |
| GET /v2/tenders/counts/province | Tender counts grouped by province |
| GET /v2/tenders/counts/category | Tender counts grouped by category |
| GET /v2/tenders/counts/organization | Tender counts grouped by issuing org |
| GET /v2/tenders/counts/status | Tender counts grouped by status |
| GET /v2/tenders/by-province/{province} | Tenders filtered by province |
| GET /v2/tenders/by-organization/{orgId} | Tenders filtered by issuing organisation |
| GET /v2/tenders/by-publication-type/{type} | Filter by publication type (TENDER_NOTICE, AWARD_NOTICE, etc.) |
| GET /v2/tenders/by-category/{category} | Tenders filtered by procurement category |
| GET /v2/tenders/{id} | Full tender detail with all enriched fields |
| GET /v2/tenders/{id}/awards | Awards linked to this tender |
| GET /v2/tenders/{id}/contracts | Contracts linked to this tender |
| GET /v2/tenders/{id}/milestones | Procurement milestones |
| GET /v2/tenders/{id}/documents | Associated documents with metadata |
| GET /v2/tenders/{id}/bidders | List of bidders who responded |
| GET /v2/tenders/{id}/submission-requirements | Required submission documentation |
| GET /v2/tenders/{id}/timeline | Full procurement timeline events |
| GET /v2/tenders/{id}/analysis | AI-generated analysis (summary, requirements, criteria) |
| GET /v2/tenders/{id}/value-estimate | Estimated value range with methodology |
| GET /v2/tenders/{id}/seo | SEO metadata for the tender detail page |
| GET /v2/tenders/{id}/slug | URL slug mapping for the tender |
| GET /v2/tenders/{id}/related | Related tenders by category or organisation |
Awards — 12 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/awards | Cursor-paginated award list |
| GET /v2/awards/{id} | Full award detail with supplier and contract data |
| GET /v2/awards/{id}/subcontractors | Subcontractors for this award with B-BBEE demographics |
| GET /v2/awards/analytics | Aggregated market analytics |
| GET /v2/awards/analytics/province | Analytics grouped by province |
| GET /v2/awards/analytics/category | Analytics grouped by procurement category |
| GET /v2/awards/analytics/bee-level | Analytics grouped by B-BBEE contributor level |
| GET /v2/awards/analytics/enterprise-type | Analytics grouped by enterprise type (EME, QSE, Large) |
| GET /v2/awards/by-tender/{tenderId} | Awards for a specific tender |
| GET /v2/awards/by-supplier/{name} | Awards received by a supplier |
| GET /v2/awards/by-supplier-party/{partyId} | Awards by OCDS supplier party identifier |
| GET /v2/awards/by-date-range?from=&to= | Awards within a date range |
Organisations (Procurement Bodies) — 8 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/organizations | Cursor-paginated organisation list |
| GET /v2/organizations/search?q= | Search organisations by name |
| GET /v2/organizations/counts-by-type | Organisation counts grouped by type |
| GET /v2/organizations/{id} | Full organisation profile with enrichment data |
| GET /v2/organizations/{id}/tenders | Tenders issued by this organisation |
| GET /v2/organizations/{id}/directors | Directors of this organisation |
| GET /v2/organizations/by-registration/{reg} | Lookup by CIPC registration number |
| GET /v2/organizations/by-slug/{slug} | Lookup by URL slug |
Suppliers (Companies) — 9 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/companies | Cursor-paginated company list |
| GET /v2/companies/search?q= | Search companies by name |
| GET /v2/companies/top?limit= | Top companies by award value or count |
| GET /v2/companies/{name} | Company intelligence profile with aggregated award history |
| GET /v2/companies/{name}/awards | Awards received by this company |
| GET /v2/companies/{name}/contracts | Contracts held by this company |
| GET /v2/companies/{name}/tenders | Tenders this company bid on |
| GET /v2/companies/{name}/directors | Directors linked to this company |
| GET /v2/companies/by-registration/{reg} | Lookup by registration number |
Directors, Categories, Provinces — 10 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/directors | Cursor-paginated director list |
| GET /v2/directors/search?q= | Search directors by name |
| GET /v2/directors/{id} | Director detail |
| GET /v2/directors/by-organization/{orgId} | Directors of a specific organisation |
| GET /v2/categories | Full category list with tender counts |
| GET /v2/categories/{id} | Category detail |
| GET /v2/categories/by-slug/{slug} | Category lookup by URL slug |
| GET /v2/provinces | Province list |
| GET /v2/provinces/{id} | Province detail |
| GET /v2/provinces/{id}/health-scores | Province procurement health scores |
SEO, Content, Industry, Services, OCDS — 14 endpoints
| Endpoint | Description |
|---|---|
| GET /v2/seo/category/{slug} | SEO metadata for a category page |
| GET /v2/seo/province/{slug} | SEO metadata for a province page |
| GET /v2/articles | Cursor-paginated article list |
| GET /v2/articles/{id} | Full article with content |
| GET /v2/authors/{id} | Author profile |
| GET /v2/industry/benchmarks | Industry value benchmarks with sample sizes |
| GET /v2/industry/benchmarks/{id} | Single benchmark detail |
| GET /v2/services | Service type classification list |
| GET /v2/services/{id} | Service type detail |
| GET /v2/ocds/parties | Cursor-paginated OCDS party list |
| GET /v2/ocds/parties/{id} | OCDS party detail |
Intelligence, Forensic, CIPC, Newsletters, Documents, Meta — remainder
| Group | Endpoints | Key Endpoint |
|---|---|---|
| Intelligence | 4 | GET /v2/intel/items — market alerts and sector insights |
| Forensic | 4 | GET /v2/forensic/restricted-suppliers/check?q= — supplier screening |
| CIPC | 4 | GET /v2/cipc/enrichments — company registry data |
| Newsletters | 2 | GET /v2/newsletters — newsletter edition archive |
| Documents | 2 | GET /v2/documents/{id}/download-url — document download link |
| Meta (auth) | 2 | GET /v2/meta/usage — your current API key consumption |
Public Endpoints (No Authentication)
Four endpoints are available without an API key. These serve monitoring, reference data, and documentation needs:
| Endpoint | Returns |
|---|---|
| GET /v2/meta/status | Per-table record counts (tenders, awards, organisations, etc.), last cron run status and timestamp, worker version. Useful for health-check dashboards. |
| GET /v2/meta/provinces | All provinces with live tender counts. Useful for populating dropdown filters and geographic selectors in UIs. |
| GET /v2/meta/categories | All procurement categories with live tender counts. Useful for category navigation and filter components. |
| GET /v2/openapi.json | Complete OpenAPI 3.0 specification auto-generated from the Worker's D1 schema. Import this into Postman, Insomnia, or any OpenAPI-compatible tool to get a full interactive API client. |
Working with the API: Key Patterns
Listing tenders with pagination
1# First page — no cursor 2curl -s "https://api.tenders-sa.org/v2/tenders?limit=10" \ 3 -H "Authorization: Bearer tsa_prod_<key>" | jq '{count: .meta.totalCount, next: .meta.nextCursor, first: .data[0].title}' 4 5# Second page — pass the cursor from the first response 6curl -s "https://api.tenders-sa.org/v2/tenders?limit=10&cursor=eyJpZCI6ImNs..." \ 7 -H "Authorization: Bearer tsa_prod_<key>" | jq '.data[].title'BASH
Searching tenders
1curl -s "https://api.tenders-sa.org/v2/tenders/search?q=construction+western+cape" \ 2 -H "Authorization: Bearer tsa_prod_<key>" | jq '.data[] | {title, province, closingDate}'BASH
Supplier due diligence: forensic check
1# Check if a supplier appears on any restricted list 2curl -s "https://api.tenders-sa.org/v2/forensic/restricted-suppliers/check?q=ACME+Construction+Pty+Ltd" \ 3 -H "Authorization: Bearer tsa_prod_<key>" | jq .BASH
Award analytics by B-BBEE level
1curl -s "https://api.tenders-sa.org/v2/awards/analytics/bee-level" \ 2 -H "Authorization: Bearer tsa_prod_<key>" | jq '.data'BASH
Monitoring your API usage
1curl -s "https://api.tenders-sa.org/v2/meta/usage" \ 2 -H "Authorization: Bearer tsa_prod_<key>" | jq .BASH
Integration Use Cases
The endpoint catalogue supports several integration patterns that go beyond simple data retrieval:
- Compliance dashboards — Combine
/v2/awards/analytics/bee-levelwith/v2/awards/analytics/provinceto build real-time B-BBEE spend visualisations. Filter by date range to track procurement transformation over time. - Supplier risk screening — Before onboarding a new subcontractor or responding to a tender that requires joint venture partners, call
/v2/forensic/restricted-suppliers/check?q={name}to verify the entity does not appear on government restricted supplier lists. - Market opportunity tracking — Poll
/v2/tenders/closing-soonand/v2/tenders/newon a schedule to populate opportunity feeds, email alerts, or internal CRM pipelines. - Competitor intelligence — Use
/v2/companies/{name}/awardsto understand which contracts a competitor has won, at what values, and in which categories. Cross-reference with/v2/companies/{name}/tendersto see what they are bidding on. - Content enrichment — Pull tender data into external websites, industry blogs, or newsletters. Use the
/v2/tenders/{id}endpoint to get AI-summarised descriptions and structured requirement lists without parsing raw government PDFs. - Procurement transparency reporting — Use
/v2/organizations/{id}/tendersand/v2/awards/by-tender/{id}to audit procurement activity by department, track award concentration, or verify publication compliance.
OpenAPI Specification and Tooling
The API serves its own OpenAPI 3.0 specification at /v2/openapi.json. This spec is auto-generated from the Worker's D1 schema, which means it always reflects the actual deployed data surface — there is no separate hand-maintained spec file. The spec includes:
- All 17 tag groups with descriptions
- Every path, HTTP method, and parameter definition
- Response schemas derived from the D1 table columns
- Security scheme definition for Bearer token authentication
- A Redocly HTML viewer at
/v2/docs
To use the spec with external tooling, point Postman, Insomnia, or any OpenAPI-compatible client at https://api.tenders-sa.org/v2/openapi.json. The spec includes the production server URL, so imported collections are immediately callable after setting an API key.
SDKs
Three official SDKs are available in the monorepo under api-sdk-packages/:
- JavaScript / TypeScript —
@tenders-sa-org/sdk-json npm. Published via semantic-release on push to main. Uses the resource pattern:client.tenders.list(),client.awards.get(id). - Python —
tendersa-sdkon PyPI (alpha). Built with hatchling. Automatically converts camelCase API fields to snake_case Python attributes. - CLI —
@tenders-sa-org/clion npm. Wraps the JS SDK. Supports piping output tojqand file redirection for scripting.
The SDKs handle authentication, pagination, error mapping to typed exceptions, rate limit tracking, and retry with exponential backoff for transient failures. The HTTP API is the stable surface — SDKs are convenience layers that reduce boilerplate.
Error Handling
| HTTP Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_REQUEST_BODY | Request body is not valid JSON or missing required fields |
| 401 | MISSING_API_KEY | No Authorization header present |
| 401 | INVALID_AUTH_FORMAT | Authorization header does not use Bearer scheme |
| 401 | INVALID_API_KEY | Key not found in database or SHA-256 hash mismatch |
| 403 | KEY_NOT_ACTIVE | Key exists but status is not ACTIVE |
| 403 | KEY_EXPIRED | Key has passed its expiry date |
| 403 | FORBIDDEN | Valid credentials but insufficient permissions for this resource |
| 404 | NOT_FOUND | The requested endpoint or resource does not exist |
| 429 | RATE_LIMIT_DAILY_EXCEEDED | Daily call quota reached |
| 429 | RATE_LIMIT_MONTHLY_EXCEEDED | Monthly call quota reached |
| 500 | INTERNAL_ERROR | Unexpected server error — retry with exponential backoff |
Getting an API Key
API keys are generated through the main application at https://tenders-sa.org/developers/api-keys. Each key is configurable with:
- A descriptive name for identifying the key's purpose (e.g. "CRM integration", "Analytics dashboard")
- A tier (Professional or Enterprise) that determines default rate limits
- Custom daily and monthly limits if the tier defaults need adjustment
- An optional expiry date for time-limited access
Keys can be revoked at any time from the same dashboard. Revocation takes effect immediately on the Worker — the key cache has a 5-minute TTL, after which the revocation propagates to all edge locations.
Data Freshness and Monitoring
Data is synced from the main application to the API Worker's D1 database every 5 minutes via a cron trigger. Each sync cycle processes up to 10 entities in priority order — entities that have never been synced or have unfinished cursors are prioritised. The sync uses the shared-secret-protected internal export endpoints and only fetches rows with updatedAt timestamps newer than the last completed cursor.
You can inspect the current state of the API database at any time through the public health endpoint:
1curl -s "https://api.tenders-sa.org/v2/meta/status" | jq '.data.entities' 2# Returns: { "tenders": 5916, "awards": 1800, "organizations": 764, ... }BASH
The response includes the most recent cron run timestamp, its status, and rows synced in that run. For production integrations, poll this endpoint on a regular interval and alert if the last successful sync is older than your freshness tolerance.
Links
| Resource | URL |
|---|---|
| API Base URL | https://api.tenders-sa.org |
| OpenAPI Spec | https://api.tenders-sa.org/v2/openapi.json |
| Interactive Docs | https://api.tenders-sa.org/v2/docs |
| Health / Status | https://api.tenders-sa.org/v2/meta/status |
| API Keys Dashboard | https://tenders-sa.org/developers/api-keys |
| JS/TS SDK (npm) | https://www.npmjs.com/package/@tenders-sa-org/sdk-js |
| Python SDK (PyPI) | https://pypi.org/project/tendersa-sdk/ |
| CLI (npm) | https://www.npmjs.com/package/@tenders-sa-org/cli |
Tags
Based on this article's topics, here are some current tenders that might interest you
FOR THE APPOINTMENT OF A SERVICE PROVIDER TO SUPPLY AND DELIVER ONCE OFF PROCUREMENT OF INFORMATION TECHNOLOGY (IT) EQUIPMENT FROM SUPPLIERS LISTED ON THE SITA RFB 740 TRANSVERSAL CONTRACT- FOR THE WESTERN CAPE (COASTAL REGION) FOR DEPARTMENT OF FORESTRY, FISHERIES, AND THE ENVIRONMENT (DFFE).
Request for Supply and Delivery of PAT Materials to appropriate workshop clothing to Technical, Comprehensive and Agricultural Schools: Lot1 - Supply, delivery and installation of Equipment and Tools for Civil, Electrical, Mechanical Technology Specialisations and Engineering Graphics and Design (EGD): Lot2 - Supply and Delivery of Practical Assessment Task (PAT) Materials to Technical, Comprehensive and Agricultural Schools: Lot3 - Supply and Delivery of Personal Protective Clothing for Teachers to Technical and Comprehensive Schools in the Free State for the period of Three Financial Years from the date of approval
Request for information on commercially available and/or proven technology for re-engineering of the OPC DA (Data Access) communication protocol for control systems that are impacted by the Microsoft security changes for systems using the Windows operating system and have the OPC classic architecture infrastructure based on DCOM based communication (where OEM support is no longer available as they have exited the business).
THE APPOINTMENT OF ONE SERVICE PROVIDER TO SUPPLY, INSTALL, CONFIGURE AND PROVIDE TECHNICAL TRAINING OF INFORMATION TECHNOLOGY (IT) SERVICE HELPDESK SYSTEM FOR THE DEPARTMENT OF FORESTRY, FISHERIES, AND THE ENVIRONMENT (DFFE) FOR A PERIOD OF TWO (2) YEARS.
THE APPOINTMENT OF ONE SERVICE PROVIDER TO SUPPLY, INSTALL, CONFIGURE AND PROVIDE TECHNICAL TRAINING OF INFORMATION TECHNOLOGY (IT) SERVICE HELPDESK SYSTEM FOR THE DEPARTMENT OF FORESTRY, FISHERIES, AND THE ENVIRONMENT (DFFE) FOR A PERIOD OF TWO (2) YEARS.
REPLACEMENT OF EXISTING PIPE SYSTEMS THROUGH TRENCHLESS TECHNOLOGY FOR A PERIOD ENDING 30 JUNE 2029
Want to see all available tenders?
Browse All Tenders →Share this article
Tenders-SA Developer API v2: Architecture, Usage, and Integration Guide
The v2 Developer API serves 80+ endpoints across 17 resource groups from a dedicated Cloudflare Worker at api.tenders-sa.org. This guide covers the infrastructure design, authentication model, endpoint catalogue, pagination mechanics, rate limiting, and integration patterns for developers and technical decision-makers.