Building Custom Internal Procurement Dashboards with the Tenders-SA API
A practical architecture for a procurement dashboard: choose the metrics, pull data from the Developer API v2, exports or a CRM, model the warehouse, and visualise the pipeline without inventing a dashboard builder.
Off-the-shelf dashboards answer the questions the vendor thought were important. A custom internal dashboard answers the questions your bid desk actually asks: which opportunities are closing, which buyers are active, where the pipeline is thin, and whether the team is spending its time on winnable work.
Tenders-SA does not provide a drag-and-drop dashboard builder. It provides the data layer: the Developer API v2, exports, CRM webhooks and the platform's own reporting views. This article explains how to compose those into a dashboard using your own BI tool, spreadsheet or application.
Start With the Decisions, Not the Charts
A dashboard is only useful if it changes a decision. Before choosing a tool, write down the five decisions the dashboard must support.
| Decision | Question | Metric |
|---|---|---|
| What to bid | Which opportunities match our capability and deadline? | Match band, closing runway |
| Where to focus | Which buyers and provinces are active in our categories? | Award volume and value by buyer/province |
| Whether we are competitive | What values are being awarded to whom? | Award value bands and supplier concentration |
| Whether the team is productive | How many qualified bids are we producing? | Bids submitted, cycle time, win rate |
| Whether we are ready | Are our compliance documents current? | Expiry status and gaps |
If a chart does not support one of those decisions, it is decoration. Delete it.
The Data Sources
| Source | What it provides | Best use |
|---|---|---|
| Developer API v2 | Tender, award, company, organisation and document resources via REST JSON | Scheduled sync into a warehouse or app |
| File exports | Excel, CSV, JSON, XML and PDF extracts up to 5,000 records | Periodic analysis and offline reporting |
| CRM webhook | Push of selected data to monday.com or a generic webhook | Workflow and pipeline tracking |
| Platform reporting | Built-in reports and analytics | Operational review |
| Internal data | Bid/no-bid decisions, prices, outcomes and team activity | The context the public data lacks |
The developer portal documents API v2, managed API keys, SDKs and the CLI. The existing guides to the Developer API and what the API gives you explain the resource model and authentication. API access may be plan-gated; check the pricing page and the developer dashboard for your entitlement.
Choose the source by the question, not by habit. The API is better for a dashboard that must reflect today's pipeline. A file export is better for a one-off historical analysis or a board pack that needs a fixed snapshot. The CRM webhook is better for pushing a qualified opportunity into a workflow the sales team already uses. Most teams need two of the three, not all of them.
Record the trade-off in the dashboard itself: source, refresh time and coverage. An API-fed view that refreshes every morning is not the same as a live view, and a view built from a monthly export is not a monitoring tool. Users forgive a known limitation; they do not forgive a number that pretends to be current when it is not.
"
A Reference Architecture
- Extract. Pull tender, award and company data from the API on a schedule. Use cursor-based pagination and store the last cursor so the sync is incremental.
- Land. Write the raw JSON to a landing table or object store. Do not transform on arrival; keep the source intact for audit.
- Model. Build dimension tables (buyer, supplier, category, province, date) and fact tables (tender, award, bid, submission).
- Enrich. Join your internal bid data: go/no-go decision, owner, price, outcome and reason.
- Serve. Expose curated views to the BI tool, spreadsheet or internal app.
- Govern. Control access, document definitions and monitor freshness.
For a small team, the same architecture can run in a spreadsheet: a daily CSV export, a pivot table and a one-page dashboard. The principles are the same; the tooling is lighter.
The Core Metrics
| Metric | Definition | Why it matters |
|---|---|---|
| Active pipeline value | Sum of estimated or historical values for active opportunities | Shows the size of the opportunity set |
| Closing runway | Days from today to the closing date | Shows urgency and capacity |
| Match quality mix | Count of opportunities by match band | Shows whether discovery is precise |
| Bid throughput | Qualified bids submitted per month | Shows team capacity |
| Win rate | Awards divided by submissions, by category | Shows competitiveness |
| Cycle time | Days from discovery to submission | Shows process speed |
| Buyer concentration | Share of pipeline from the top buyers | Shows dependency risk |
| Category mix | Share of pipeline by category | Shows focus |
| Compliance readiness | Count of documents expiring within 60 days | Shows bid risk |
| Award benchmark | Median award value for comparable work | Shows whether the pipeline is realistic |
Be precise about definitions. A win rate calculated on awards divided by submissions is not the same as awards divided by opportunities discovered. A pipeline value based on estimated values is not the same as one based on historical awards. Write the definition next to the metric so the team does not argue about the number.
A Minimal Data Model
| Table | Key fields | Source |
|---|---|---|
| dim_buyer | buyer_id, name, province, type | API / awards |
| dim_supplier | supplier_id, name, registration, province | API / company data |
| dim_category | category_id, name, parent | API / taxonomy |
| dim_date | date_key, year, month, week | Generated |
| fact_tender | tender_id, buyer_id, category_id, province, closing_date, value | API |
| fact_award | award_id, tender_id, supplier_id, value, award_date | API |
| fact_bid | bid_id, tender_id, owner, decision, price, outcome | Internal |
| fact_compliance | document_id, entity, type, expiry_date | Internal |
The internal fact tables are what make the dashboard useful. Public tender data shows the market; your bid data shows your performance. Without the internal join, the dashboard is a news feed.
A Worked Example: Weekly Bid Desk Dashboard
A ten-person engineering firm wants a Monday dashboard for its bid desk. It pulls tender and award data from the API, imports its bid log from a spreadsheet, and builds four views.
- Opportunity view: Active tenders in the firm's categories and provinces, with closing runway and match band. Filter: closing in the next 21 days.
- Pipeline view: Count and value of bids by stage (identified, qualified, preparing, submitted, awarded, lost) and owner.
- Market view: Award value by buyer and category for the last 12 months, with the firm's win rate overlaid.
- Readiness view: Compliance documents expiring within 60 days, by entity and owner.
The dashboard answers four questions in one screen: what is closing, what are we working on, where is the market, and what could stop us. That is enough for a Monday meeting. More charts would not improve the decision.
A One-Page Dashboard Specification
Write the specification before you open the BI tool. One page is enough, and it prevents the most expensive mistake: building a view that nobody can define. The template below is deliberately generic so it works for a spreadsheet, a BI tool or an internal app.
| Field | What to write |
|---|---|
| Audience | The role that opens the page and the decision they own |
| Question | The single question the view answers, phrased as a decision |
| Metric and formula | The exact numerator, denominator, date window and filters |
| Source | The API endpoint, export or internal table, with the field mapping |
| Refresh | Daily, weekly or monthly, plus the time of day and timezone |
| Owner | The person accountable for accuracy and definitions |
| Stale state | What the page shows when the last sync failed |
| Action | What the audience should do differently when the number moves |
If you cannot fill in the action row, the view is probably a report rather than a dashboard. That is not a failure; it may still be useful. But it belongs in a scheduled report, not on a page that competes for daily attention.
API Practicalities
API v2 lives at https://api.tenders-sa.org/v2 and uses a Bearer token in the Authorization header. The endpoints below cover the data a procurement dashboard normally needs. Confirm the current parameter names on the developer documentation before you build the first sync.
| Endpoint | What it gives you | Pagination |
|---|---|---|
| GET /v2/tenders | Current tenders with enriched fields and links to sub-resources | limit and cursor |
| GET /v2/tenders/search?q= | Full-text search across tender titles and descriptions | limit and cursor |
| GET /v2/tenders/{id} | One tender with awards and sub-resource references | Single record |
| GET /v2/awards | Contract awards with supplier context and analytics resources | limit and cursor |
| GET /v2/companies/{name} | Company profile with award history, directors and compliance data | Single record |
| GET /v2/forensic/restricted-suppliers/check?q= | Screens a supplier against the restricted suppliers registry | Single record |
| GET /v2/meta/status | API health, entity counts and last cron sync status | No auth required |
Pagination is cursor-based. Ask for limit=100, read meta.nextCursor from the response, and pass it back as cursor on the next request. There is no page number to lose. Persist the last cursor per resource so the next sync continues where the previous one stopped instead of re-downloading the full dataset.
1const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) 2let cursor = await store.get('tenders_cursor') 3 4do { 5 const url = new URL('https://api.tenders-sa.org/v2/tenders') 6 url.searchParams.set('limit', '100') 7 if (cursor) url.searchParams.set('cursor', cursor) 8 9 const res = await fetch(url, { 10 headers: { Authorization: `Bearer ${process.env.TENDERS_API_KEY}` } 11 }) 12 13 if (res.status === 429) { 14 const wait = Number(res.headers.get('Retry-After') ?? 60) 15 await sleep(wait * 1000) 16// ... (truncated)JAVASCRIPT
Three operational rules matter more than the code. First, read the rate-limit metadata returned with each response and schedule around it; a 429 response includes a Retry-After header, so wait for that period instead of retrying immediately. Second, upsert by the resource ID so a repeated sync does not duplicate rows. Third, record the last successful sync time and display it on the dashboard. A stale number with no timestamp is worse than no number.
Treat schema changes as a normal event, not an exception. Version your transformations, monitor for new or missing fields, and test a small sample after any change to the API. The existing guide to connecting procurement data to a CRM covers the push-only CRM route, and the guide to procurement reports and exports covers the file-based route. A dashboard can combine both: the API for scheduled data, the export for a point-in-time report.
Security and Governance
A procurement dashboard concentrates commercially sensitive information: your pipeline, your prices, your win rate and your compliance gaps. It also contains personal information about directors and supplier contacts. Treat it as an internal system with a named owner, not as a spreadsheet that happens to have a URL.
- One key per integration. Give the sync job its own API key, store it in a secret manager, and never place it in front-end code or a notebook that gets shared.
- Read-only warehouse roles. The ETL job writes; the dashboard reads. Do not let a BI tool connect with an owner account.
- Row-level security. If the dashboard serves more than one business unit, restrict rows by unit or region rather than relying on a filter in the chart.
- POPIA. Collect only the personal information you need, document the purpose, restrict access and set a retention period for director and contact data.
- Audit trail. Log who runs queries, who exports data and who changes a metric definition.
- Safe embedding. If the dashboard is embedded in another system, use authenticated sessions or signed URLs, not a public link.
- Vendor access. If a consultant built the dashboard, remove their access when the engagement ends.
Key rotation deserves a calendar entry, not good intentions. Rotate the API key on a fixed schedule and immediately after any staff change. Keep the old key alive only long enough for the next successful sync, then revoke it. A key that is never rotated is a key that is eventually leaked.
Governance also means defining who owns each number. A metric without an owner is a metric that will drift. Put the owner's name and the definition in the metric dictionary, and review both when the dashboard is revised.
Common Dashboard Mistakes
- Starting with charts. Start with decisions and metrics.
- Mixing definitions. Two "win rates" with different denominators destroy trust.
- No internal data. Public data alone cannot show your performance.
- No freshness indicator. A stale dashboard is worse than no dashboard.
- No owner. A dashboard without an owner decays.
- Overbuilding. A one-page view that is used beats a twenty-page view that is ignored.
Most of these mistakes show up as symptoms before anyone names the cause. Use the table below when the team stops trusting the page.
| Symptom | Likely cause | Fix |
|---|---|---|
| Two people quote different win rates | Different denominators or date windows | Publish one definition and calculate it in one place |
| The page is opened only after a meeting | It reports history but does not drive a weekly decision | Remove views and add the one question the meeting asks |
| Numbers look stale or inconsistent with the portal | The sync failed or the cursor was reset | Show last successful sync time and alert on failure |
| Nobody can explain a spike | Source records were duplicated or values were not normalised | Deduplicate by resource ID and normalise currency and VAT |
| The dashboard grows every month | No owner and no deletion rule | Assign an owner and review views quarterly |
Four Dashboard Patterns
Most procurement teams need four views, not one. Each serves a different audience and a different refresh rhythm.
| Pattern | Audience | Refresh | Core metrics |
|---|---|---|---|
| Executive pipeline | Owner or director | Weekly | Pipeline value, win rate, forecast, concentration |
| Operational bid desk | Bid lead and team | Daily | Closing runway, match band, tasks, gaps |
| Market intelligence | Strategy and business development | Monthly | Award value by buyer, category and province |
| Readiness and risk | Compliance and finance | Daily | Expiring documents, restricted suppliers, guarantee exposure |
A common mistake is to put all four into one screen. The result is a dashboard that is too dense for the bid desk and too operational for the director. Build separate views on the same model, and let each audience see the decisions it owns.
A Sample Query
The exact SQL depends on your warehouse, but the shape of the pipeline query is usually the same: group active opportunities by category and month, count them, sum the value and calculate the closing runway. The column names below are warehouse names from the model above, not API field names. Map them in your staging layer.
1SELECT 2 c.name AS category, 3 DATE_TRUNC('month', t.closing_date) AS closing_month, 4 COUNT(*) AS opportunities, 5 SUM(t.value) AS pipeline_value, 6 AVG(t.closing_date - CURRENT_DATE) AS avg_days_to_close 7FROM fact_tender t 8JOIN dim_category c ON c.category_id = t.category_id 9WHERE t.closing_date >= CURRENT_DATE 10 AND t.province IN ('Gauteng', 'North West') 11GROUP BY 1, 2 12ORDER BY 2, 4 DESC;SQL
The query is deliberately simple. The value is in the definitions: what counts as active, whether the value is estimated or historical, how the province is normalised and how the runway handles a closing date that has passed. Document those choices next to the query, because the number is only meaningful if everyone uses the same definition.
Data Quality Checks
- Completeness. Are there tenders with no category, province or closing date? Those rows will distort the pipeline.
- Duplicates. The same tender may appear from more than one source. Deduplicate by a stable identifier, not by title.
- Currency and VAT. Normalise everything to the same basis before summing.
- Freshness. If the last sync failed, show the last successful time rather than a stale number.
- Definitions. Keep a metric dictionary so two teams do not calculate different win rates.
Adoption and Trust
A dashboard that looks impressive but is opened once is a cost, not a control. Adoption comes from answering one question the team already asks every week, answering it accurately, and putting the page where the team already works. Start there. Add views only when someone requests them, and delete views nobody opens.
- Name an owner. One person is accountable for definitions, freshness and the review cadence.
- Show the source time. Every page should say when the data was last synced.
- Explain changes. When a number moves unexpectedly, publish the reason before someone asks.
- Review quarterly. Keep the views that are used, fix the ones that are wrong, remove the rest.
Trust is the real product. If a number is wrong once and nobody explains why, the team stops relying on the page. Accuracy first, adoption second, elegance last.
If the dashboard is built for investigation rather than monitoring, start from the method in forensic procurement analysis and purchasing patterns. The questions are different, and the design should reflect that.
Conclusion: Build Only What the Team Uses
A custom procurement dashboard is not a chart collection. It is a decision layer built from public tender data, award data and your own bid record. The API, exports and CRM webhooks provide the raw material; your model provides the meaning.
Start with five decisions, ten metrics and one page. Pull the data from the Developer API or an export, join it to your bid log, and show the freshness. Then iterate. A dashboard that is used every Monday is worth more than a perfect one that is never opened.
Tags
Based on this article's topics, here are some current tenders that might interest you
register on the National Treasury Central Supplier Database online - Science &
SUPPLY, INSTALLATION AND MANAGEMENT OF A STS COMPLIANT PREPAYMENT ELECTRICITY VENDING, DATA MANAGEMENT, REVENUE PROTECTION and AUTOMATIC READING(AMR) FOR THE PERIOD 01 JULY 2027 TO 30 JUNE 2032.
SUPPLY AND DELIVERY OF TOOLS OF TRADE - KOUKAMMA MUNICIPAL COUNCILLORS (LAPTOPS AND MOBILE DATA SOLUTION) FOR A PERIOD OF 3 YEARS
Accreditation of service provider (Shops and retailers) to be included on the SASSA database for issuing of Social Relief of Distress(SRD) groceries to approved SASSA beneficiaries using the redemption of SASSA vouchers for a period of 36 months in the Western Cape Region
CALL FOR PROPOSAL: SUBMISSION OF PROPOSAL FOR INCLUSION IN THE NORTH WEST DEPARTMENT OF HEALTH DATABASE FOR INFRASTRUCTURE RELATED PROFESSIONAL SERVICE PROVIDERS (PSP'S) FOR A PERIOD OF FOUR (04) YEARS.
The appointment of 100 qualifying panel contractors for the installation of smart meters, data concentrators, ready board retrofit, access covers, insulation piercing connector (IPC), pole top split metering kiosks, secure pole mount DC kiosk and all associated accessories including the decommissioning, scrapping and disposal of existing meters for the Distribution Division in the LimLanga cluster for a period of 2 years.
Want to see all available tenders?
Browse All Tenders →Share this article
Building Custom Internal Procurement Dashboards with the Tenders-SA API
A practical architecture for a procurement dashboard: choose the metrics, pull data from the Developer API v2, exports or a CRM, model the warehouse, and visualise the pipeline without inventing a dashboard builder.