API reference
Two open GET endpoints and an MCP server, over the same live corpus behind the app. No key to start.
Overview
The API is served from the same origin as the site — https://tenderwave.io. There is no /v1/ prefix and no separate host. Responses are JSON. Every record carries a provenance object so you can audit exactly where and when it came from.
Authentication
None to start. The public GET endpoints are open — no API key, no bearer token. Just make the request. Higher volume and account-level features come with a paid plan (see pricing).
GET /api/tenders
Search and filter the tender corpus. Returns { tenders, total, sources } — the matching page of tenders, the total match count, and a per-source count map over the whole corpus.
Query parameters
qstringFree-text query over title, agency, sector and summary.
countrystringISO code of the beneficiary country — e.g. PH, TH, MY, SG. Comma-separate for multiple.
sourcestringOrigin feed — WB · ADB · TH · PH · MY · SG. Comma-separate for multiple.
sectorstringSector keyword, matched as a substring — e.g. Infrastructure, Health, IT.
minValueUsdnumberOnly tenders at or above this USD value.
maxValueUsdnumberOnly tenders at or below this USD value.
statusenumopen · closed · all. Default all. open = not awarded and still within its window.
sortenumpublishedAt · closesAt · valueUsd. Default publishedAt.
orderenumasc · desc. Default desc.
limitnumberPage size, 1–100. Default 20.
offsetnumberRow offset for pagination. Default 0.
Example request
curl "https://tenderwave.io/api/tenders\
?q=rail\
&country=MY\
§or=Transport\
&minValueUsd=1000000\
&maxValueUsd=50000000\
&status=open\
&sort=closesAt\
&order=asc\
&limit=20\
&offset=0"Example response
{
"total": 128,
"sources": { "PH": 5120, "TH": 3980, "SG": 2110, "WB": 1670, "MY": 940, "ADB": 610 },
"tenders": [
{
"id": "my_884120",
"source": "MY",
"title": "Upgrading of the Kuala Lumpur light rail depot",
"country": "MY",
"agency": "Prasarana Malaysia Berhad",
"sector": "Transport",
"valueUsd": 12400000,
"publishedAt": "2026-07-02T00:00:00.000Z",
"closesAt": "2026-07-30T09:00:00.000Z",
"url": "https://www.dbkl.gov.my/...",
"winner": null,
"provenance": {
"sourceUrl": "https://www.dbkl.gov.my/...",
"fetchedAt": "2026-07-07T01:00:00.000Z",
"parserVersion": "my-v1"
}
}
]
}The tender object
Each record in tenders[] has this shape:
idstringStable unique id, e.g. "ph_1024887".
sourceenumWB · ADB · TH · ID · VN · PH · MY · SG.
titlestringEnglish title (machine-translated where the source is non-English).
titleOriginalstring | nullOriginal-language title when translated.
countrystringBeneficiary country ISO code (or WB/ADB for multilateral).
agencystringBuyer / procuring agency.
sectorstring | nullProcurement category, when known.
valueUsdnumber | nullValue normalized to USD; null if undisclosed.
publishedAtstringISO 8601 publication date.
closesAtstring | nullISO 8601 submission deadline; null for award/metadata rows.
urlstringLink back to the official notice.
winnerstring | nullAwarded supplier name; null for open tenders.
aiSummarystring | nullOne-to-two sentence summary, when available.
provenanceobject{ sourceUrl, fetchedAt, parserVersion } — auditable origin of the record.
GET /api/tenders/stats
A corpus-wide roll-up — no parameters. Useful for dashboards and freshness checks.
curl "https://tenderwave.io/api/tenders/stats"totalTendersTotal records in the corpus.
bySourceRecord count per source code.
byCountryTop-10 countries by record count.
publishedLast7dRecords published in the last 7 days.
closingNext7dOpen tenders closing within 7 days.
withValueCountRecords that carry a USD value.
totalValueUsdSum of all disclosed values.
avgValueUsdMean disclosed value.
openCountRecords currently open.
dataUpdatedAtMost recent fetch timestamp across the corpus.
Example response
{
"totalTenders": 34699,
"bySource": { "SG": 18464, "TH": 16000, "WB": 1500, "MY": 148, "PH": 20, "ADB": 12 },
"byCountry": { "TH": 16000, "SG": 18464, "…": 0 },
"openCount": 1012,
"totalValueUsd": 97728262589,
"dataUpdatedAt": "2026-07-07T10:42:00.000Z"
}Errors
On success both endpoints return 200 OK with a JSON body. If the corpus can't be loaded, the API returns 503 with { "error": "…" }. Unknown query params are ignored rather than rejected, so you can add filters incrementally without breaking a call.
MCP server
The MCP server (tenderwave, v0.2.0) speaks the Model Context Protocol over stdio, so any MCP-capable client — Claude, Cursor, your own agent — can call the corpus as native tools.
Build
# From the repo
cd mcp-server
npm install
npm run build # emits dist/index.jsClient config
{
"mcpServers": {
"tenderwave": {
"command": "node",
"args": ["/absolute/path/to/mcp-server/dist/index.js"],
"env": {
"TENDERWAVE_API_URL": "https://tenderwave.io"
}
}
}
}Point args at the built dist/index.js. The server reads the live corpus via TENDERWAVE_API_URL.
Tools
search_tendersreq: queryFree-text search with optional country, sector and minValueUsd filters. Up to 25 results by relevance.
get_tender_detailsreq: idFetch the full record for one tender by id, including provenance metadata.
list_sourcesreq: —List the active data sources loaded by the server with their tender counts.
find_agenciesreq: —List unique procuring agencies, optionally filtered by country or a name substring.
Rate limits
The open API is generous to start and unmetered for normal exploration. Sustained high-volume use and account-level features are part of the paid plans — see pricing. Be a good citizen: page with limit/offset rather than pulling the whole corpus in a loop.
Ready to build?
Make your first open call now, or create a free account for scoring, alerts and higher limits.