Skip to main content
Technology

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:

  1. 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.
  2. 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 since and cursor parameters. Only rows with updatedAt newer than the last cursor are fetched — not full-table scans.
  3. 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_key
TEXT

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: daily
TEXT

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

EndpointDescription
GET /v2/tendersCursor-paginated tender list with limit and cursor params
GET /v2/tenders/search?q=Full-text search across tenders
GET /v2/tenders/closing-soonTenders approaching closing date
GET /v2/tenders/newMost recently published tenders
GET /v2/tenders/bbbee-requiredTenders with B-BBEE requirements
GET /v2/tenders/value-range?min=&max=Filter by estimated value range
GET /v2/tenders/counts/provinceTender counts grouped by province
GET /v2/tenders/counts/categoryTender counts grouped by category
GET /v2/tenders/counts/organizationTender counts grouped by issuing org
GET /v2/tenders/counts/statusTender 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}/awardsAwards linked to this tender
GET /v2/tenders/{id}/contractsContracts linked to this tender
GET /v2/tenders/{id}/milestonesProcurement milestones
GET /v2/tenders/{id}/documentsAssociated documents with metadata
GET /v2/tenders/{id}/biddersList of bidders who responded
GET /v2/tenders/{id}/submission-requirementsRequired submission documentation
GET /v2/tenders/{id}/timelineFull procurement timeline events
GET /v2/tenders/{id}/analysisAI-generated analysis (summary, requirements, criteria)
GET /v2/tenders/{id}/value-estimateEstimated value range with methodology
GET /v2/tenders/{id}/seoSEO metadata for the tender detail page
GET /v2/tenders/{id}/slugURL slug mapping for the tender
GET /v2/tenders/{id}/relatedRelated tenders by category or organisation

Awards — 12 endpoints

EndpointDescription
GET /v2/awardsCursor-paginated award list
GET /v2/awards/{id}Full award detail with supplier and contract data
GET /v2/awards/{id}/subcontractorsSubcontractors for this award with B-BBEE demographics
GET /v2/awards/analyticsAggregated market analytics
GET /v2/awards/analytics/provinceAnalytics grouped by province
GET /v2/awards/analytics/categoryAnalytics grouped by procurement category
GET /v2/awards/analytics/bee-levelAnalytics grouped by B-BBEE contributor level
GET /v2/awards/analytics/enterprise-typeAnalytics 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

EndpointDescription
GET /v2/organizationsCursor-paginated organisation list
GET /v2/organizations/search?q=Search organisations by name
GET /v2/organizations/counts-by-typeOrganisation counts grouped by type
GET /v2/organizations/{id}Full organisation profile with enrichment data
GET /v2/organizations/{id}/tendersTenders issued by this organisation
GET /v2/organizations/{id}/directorsDirectors 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

EndpointDescription
GET /v2/companiesCursor-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}/awardsAwards received by this company
GET /v2/companies/{name}/contractsContracts held by this company
GET /v2/companies/{name}/tendersTenders this company bid on
GET /v2/companies/{name}/directorsDirectors linked to this company
GET /v2/companies/by-registration/{reg}Lookup by registration number

Directors, Categories, Provinces — 10 endpoints

EndpointDescription
GET /v2/directorsCursor-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/categoriesFull category list with tender counts
GET /v2/categories/{id}Category detail
GET /v2/categories/by-slug/{slug}Category lookup by URL slug
GET /v2/provincesProvince list
GET /v2/provinces/{id}Province detail
GET /v2/provinces/{id}/health-scoresProvince procurement health scores

SEO, Content, Industry, Services, OCDS — 14 endpoints

EndpointDescription
GET /v2/seo/category/{slug}SEO metadata for a category page
GET /v2/seo/province/{slug}SEO metadata for a province page
GET /v2/articlesCursor-paginated article list
GET /v2/articles/{id}Full article with content
GET /v2/authors/{id}Author profile
GET /v2/industry/benchmarksIndustry value benchmarks with sample sizes
GET /v2/industry/benchmarks/{id}Single benchmark detail
GET /v2/servicesService type classification list
GET /v2/services/{id}Service type detail
GET /v2/ocds/partiesCursor-paginated OCDS party list
GET /v2/ocds/parties/{id}OCDS party detail

Intelligence, Forensic, CIPC, Newsletters, Documents, Meta — remainder

GroupEndpointsKey Endpoint
Intelligence4GET /v2/intel/items — market alerts and sector insights
Forensic4GET /v2/forensic/restricted-suppliers/check?q= — supplier screening
CIPC4GET /v2/cipc/enrichments — company registry data
Newsletters2GET /v2/newsletters — newsletter edition archive
Documents2GET /v2/documents/{id}/download-url — document download link
Meta (auth)2GET /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:

EndpointReturns
GET /v2/meta/statusPer-table record counts (tenders, awards, organisations, etc.), last cron run status and timestamp, worker version. Useful for health-check dashboards.
GET /v2/meta/provincesAll provinces with live tender counts. Useful for populating dropdown filters and geographic selectors in UIs.
GET /v2/meta/categoriesAll procurement categories with live tender counts. Useful for category navigation and filter components.
GET /v2/openapi.jsonComplete 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-level with /v2/awards/analytics/province to 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-soon and /v2/tenders/new on a schedule to populate opportunity feeds, email alerts, or internal CRM pipelines.
  • Competitor intelligence — Use /v2/companies/{name}/awards to understand which contracts a competitor has won, at what values, and in which categories. Cross-reference with /v2/companies/{name}/tenders to 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}/tenders and /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-js on npm. Published via semantic-release on push to main. Uses the resource pattern: client.tenders.list(), client.awards.get(id).
  • Pythontendersa-sdk on PyPI (alpha). Built with hatchling. Automatically converts camelCase API fields to snake_case Python attributes.
  • CLI@tenders-sa-org/cli on npm. Wraps the JS SDK. Supports piping output to jq and 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 StatusCodeMeaning
400INVALID_REQUEST_BODYRequest body is not valid JSON or missing required fields
401MISSING_API_KEYNo Authorization header present
401INVALID_AUTH_FORMATAuthorization header does not use Bearer scheme
401INVALID_API_KEYKey not found in database or SHA-256 hash mismatch
403KEY_NOT_ACTIVEKey exists but status is not ACTIVE
403KEY_EXPIREDKey has passed its expiry date
403FORBIDDENValid credentials but insufficient permissions for this resource
404NOT_FOUNDThe requested endpoint or resource does not exist
429RATE_LIMIT_DAILY_EXCEEDEDDaily call quota reached
429RATE_LIMIT_MONTHLY_EXCEEDEDMonthly call quota reached
500INTERNAL_ERRORUnexpected 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.

Tags

APIDeveloper ToolsREST APIv2Cloudflare WorkersOpenAPIProcurement DataGovernment DataOpen DataIntegration
Relevant Tender Opportunities

Based on this article's topics, here are some current tenders that might interest you

Supplies: Computer Equipment

THE APPOINTMENT OF A SERVICE PROVIDER TO SUPPLY AND DELIVER INFORMATION TECHNOLOGY (IT) EQUIPMENT FROM SUPPLIERS LISTED ON THE SITA RFB 740 TRANSVERSAL CONTRACT- IN THE INLAND REGION FOR THE DEPARTMENT OF FORESTRY, FISHERIES, AND THE ENVIRONMENT (DFFE).

National - Environment, Forestry and Fisheries
National
17 Aug 2026
10d left
Services: Professional

This Request for Information (RFI) invites qualified vendors, advisors and technology providers to submit information on tools, models, workflows and enablement services required to close these identified gaps and integrate with the existing Enerweb ETRM, phased across Day-1 go-live (Phase 1), controlled expansion (Phase 2, Year 1–2) and a mature state (Phase 3, Year 3+).

Eskom
Gauteng
17 Aug 2026
10d left
Professional, Scientific and Technical Activities

Feasibility Study on Hydrogen-Powered Mining Equipment Conversion using Hydrogen Combustion Technology for Surface and Underground Mining.

South African National Energy Development Institute
Gauteng
02 Sept 2026
26d left
Administrative and Support Activities

VIRTUALIZATION TECHNOLOGY REQUIREMENTS AND CAPABILITIES FOR SABC

South African Broadcasting Corporation Limited
National
28 Aug 2026
21d left
Computer Programming, Consultancy and Related Activities

REQUEST FOR INFORMATION: CLOUD-HOSTED OBSERVABILITY SOLUTION FOR THE ROAD ACCIDENT FUND (RAF) TO SUPPORT ENTERPRISE-WIDE MONITORING, ANALYTICS AND OPERATIONAL INTELLIGENCE ACROSS ITS HYBRID TECHNOLOGY ENVIRONMENT

Road Accident Fund (RAF)
Gauteng
26 Aug 2026
19d left
Services: Professional

APPOINTMENT OF A SERVICE PROVIDER TO ROLL OUT AND IMPLEMENT A FOUNDRY TRAINING AND FURNACE REPAIRS PROJECT AT THE DOWNSTREAM ALUMINIUM CENTRE FOR TECHNOLOGY (DACT) TO IMPROVE METAL BENEFICIATION SKILLS FOR A PERIOD OF SIX MONTHS.

Kwazulu Natal - Economic Development, Tourism and Environ Affairs
KwaZulu-Natal
25 Aug 2026
18d left

Want to see all available tenders?

Browse All Tenders →
AI-Powered Matching
Never Miss a Perfect Tender Again
Our AI analyzes thousands of tenders and finds the ones YOUR company can actually win
AI Match Scoring for every tender
Instant alerts for 85%+ matches
B-BBEE level optimization
Document readiness checks

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.

https://www.tenders-sa.org/blog/developer-api-v2-architecture-and-usage