Quick answer
If you are building procurement intelligence tools, automating tender monitoring, or integrating development bank data into your business workflows, you need to understand the APIs that major international organisations expose. Several of the world's largest procurement platforms provide programmatic access to their tender data, some openly, some with registration requirements, and each with its own quirks and limitations.
This guide covers the four most important procurement APIs for international development: the World Bank Procurement API, the UNGM OData API, the TED API (European Union), and the Green Climate Fund API. For each, we explain the endpoints, authentication requirements, key parameters, and practical code patterns.
World Bank Procurement Notices API
The World Bank provides one of the most accessible procurement data APIs available. It requires no authentication, returns data in JSON format, and covers all procurement notices published through the World Bank's systems.
Base Endpoint
https://search.worldbank.org/api/v2/procnoticesKey Parameters
| Parameter | Description | Example Values |
|---|---|---|
format | Response format | json (default), xml |
rows | Number of results per page | 10, 50, 100 (max varies) |
os | Offset for pagination | 0, 50, 100 |
notice_type_exact | Filter by notice type | General Procurement Notice, Specific Procurement Notice, Contract Award, Request for Expression of Interest |
project_ctry_name | Filter by country | India, Nigeria, Brazil |
procurement_method_code | Filter by procurement method | ICB, NCB, QCBS, LCS |
sector_exact | Filter by sector | Transportation, Water, Energy |
submission_date | Filter by submission deadline | Date range format |
notice_lang_exact | Filter by notice language | English, French, Spanish |
qterm | Full-text search query | solar panels, road construction |
Example Requests
Fetch the latest 20 procurement notices from India:
GET https://search.worldbank.org/api/v2/procnotices?format=json&rows=20&os=0&project_ctry_name=IndiaSearch for energy sector tenders using international competitive bidding:
GET https://search.worldbank.org/api/v2/procnotices?format=json&rows=50§or_exact=Energy&procurement_method_code=ICBFull-text search for solar energy tenders:
GET https://search.worldbank.org/api/v2/procnotices?format=json&rows=30&qterm=solar+energyPython Example
import requestsdef fetch_world_bank_notices(country=None, sector=None, rows=50, offset=0):
"""Fetch procurement notices from the World Bank API."""
base_url = "https://search.worldbank.org/api/v2/procnotices"
params = {
"format": "json",
"rows": rows,
"os": offset,
}
if country:
params["project_ctry_name"] = country
if sector:
params["sector_exact"] = sector
response = requests.get(base_url, params=params)
response.raise_for_status()
data = response.json()
notices = data.get("procnotices", {})
total = data.get("total", 0)
return {
"total": total,
"notices": [
{
"id": v.get("id", ""),
"title": v.get("notice_text", ""),
"country": v.get("project_ctry_name", ""),
"notice_type": v.get("notice_type", ""),
"submission_date": v.get("submission_date", ""),
"sector": v.get("sector", ""),
"procurement_method": v.get("procurement_method", ""),
}
for k, v in notices.items()
if isinstance(v, dict)
],
}
Fetch Indian energy tenders
results = fetch_world_bank_notices(country="India", sector="Energy")
print(f"Total results: {results['total']}")
for notice in results["notices"][:5]:
print(f" {notice['title'][:80]}...")Pagination
The World Bank API uses offset-based pagination. To retrieve all results:
def fetch_all_notices(country, sector=None, page_size=50):
"""Fetch all matching notices with pagination."""
all_notices = []
offset = 0
while True:
result = fetch_world_bank_notices(
country=country,
sector=sector,
rows=page_size,
offset=offset,
)
all_notices.extend(result["notices"])
if offset + page_size >= result["total"]:
break
offset += page_size
return all_noticesRate Limits and Best Practices
- The World Bank API does not publish formal rate limits, but be respectful, add delays between requests if you are fetching large volumes
- Cache responses where possible to avoid unnecessary repeat requests
- The API occasionally returns nested objects in unexpected formats, validate your parsing logic against real responses
- Notice data can include HTML entities in text fields, sanitise before storing or displaying
UNGM OData v4.0 API
The United Nations Global Marketplace (UNGM) provides a structured OData v4.0 API for accessing UN procurement data. Unlike the World Bank API, UNGM requires developer registration.
Getting Access
- Visit developer.ungm.org and register for a developer account
- Review the API documentation and terms of use
- Obtain your API credentials (API key or OAuth tokens, depending on the endpoint)
- UNGM may require approval for access to certain endpoints
Available Endpoints
The UNGM API exposes several key entity sets:
| Endpoint | Description | Key Fields |
|---|---|---|
/Notice | Active procurement notices from UN agencies | Title, description, deadline, UN agency, country, UNSPSC codes |
/Award | Contract award notices | Winning vendor, contract value, awarding agency |
/LTA | Long-Term Agreements | LTA holder, commodity, validity period |
/VendorSanctions | UN Ineligibility List | Sanctioned vendor name, reason, period |
OData Query Syntax
UNGM uses standard OData v4.0 query parameters:
| Parameter | Purpose | Example |
|---|---|---|
$filter | Filter results | $filter=Country eq 'Kenya' |
$select | Select specific fields | $select=Title,Deadline,Agency |
$orderby | Sort results | $orderby=Deadline desc |
$top | Limit number of results | $top=50 |
$skip | Pagination offset | $skip=100 |
$count | Include total count | $count=true |
$expand | Expand related entities | $expand=Documents |
Example Requests
Fetch the 20 most recent notices:
GET https://api.ungm.org/v4/Notice?$top=20&$orderby=PublishedDate descFilter notices by country and agency:
GET https://api.ungm.org/v4/Notice?$filter=Country eq 'Ethiopia' and Agency eq 'UNDP'&$top=50Retrieve contract awards with values above $100,000:
GET https://api.ungm.org/v4/Award?$filter=ContractValue gt 100000&$orderby=AwardDate desc&$top=30Python Example
import requestsclass UNGMClient:
"""Client for the UNGM OData v4.0 API."""
def init(self, api_key):
self.base_url = "https://api.ungm.org/v4"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
}
def get_notices(self, country=None, agency=None, top=50, skip=0):
"""Fetch procurement notices with optional filters."""
url = f"{self.base_url}/Notice"
params = {
"$top": top,
"$skip": skip,
"$orderby": "PublishedDate desc",
"$count": "true",
}
filters = []
if country:
filters.append(f"Country eq '{country}'")
if agency:
filters.append(f"Agency eq '{agency}'")
if filters:
params["$filter"] = " and ".join(filters)
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
return response.json()
def get_awards(self, min_value=None, top=50):
"""Fetch contract award notices."""
url = f"{self.base_url}/Award"
params = {
"$top": top,
"$orderby": "AwardDate desc",
}
if min_value:
params["$filter"] = f"ContractValue gt {min_value}"
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
return response.json()
def check_vendor_sanctions(self, vendor_name=None):
"""Check the UN vendor sanctions/ineligibility list."""
url = f"{self.base_url}/VendorSanctions"
params = {"$top": 100}
if vendor_name:
params["$filter"] = f"contains(VendorName, '{vendor_name}')"
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
return response.json()
Usage
client = UNGMClient(api_key="your_api_key_here")
notices = client.get_notices(country="Kenya", agency="UNICEF")
print(f"Total: {notices.get('@odata.count', 'unknown')}")Important UNGM API Notes
- The UNGM API enforces rate limits, respect the headers indicating remaining quota
- Not all UN agencies publish all their tenders through UNGM, some agencies maintain separate procurement systems
- The Vendor Sanctions endpoint provides programmatic access to the UN Ineligibility List, which is otherwise only available through the UNGM web portal
- OData filter syntax requires exact string matching for most fields, use
contains()for partial matching - Some fields may be null or missing depending on the publishing agency
TED API (Tenders Electronic Daily)
The TED API provides access to European Union public procurement data, the largest single procurement market in the world. TED publishes tenders from all EU member states, EEA countries, and some associated states.
Base Endpoint
https://api.ted.europa.eu/v3/notices/searchAuthentication
The TED API requires registration. Visit the EU Publications Office to obtain API credentials. Access is free for public use.
Key Parameters
| Parameter | Description | Example |
|---|---|---|
q | Full-text search query | water treatment plant |
fields | Fields to return | ND,TI,CY,DD,MA |
pageSize | Results per page | 50 |
page | Page number | 1 |
scope | Notice scope | ACTIVE, ALL |
sortField | Sort by field | PD (publication date) |
sortOrder | Sort direction | desc, asc |
TED Field Codes
TED uses specific codes for its fields, which can be confusing for newcomers:
| Code | Meaning |
|---|---|
ND | Notice document number |
TI | Title |
CY | Country |
DD | Deadline date |
PD | Publication date |
NC | Nature of contract (supplies, services, works) |
PR | Procedure type (open, restricted, negotiated) |
MA | Main activity |
RC | NUTS region code |
OL | Original language |
TVL | Total value (low estimate) |
TVH | Total value (high estimate) |
Python Example
import requestsclass TEDClient:
"""Client for the TED (Tenders Electronic Daily) API."""
def init(self, api_key):
self.base_url = "https://api.ted.europa.eu/v3"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
}
def search_notices(self, query=None, country=None, page=1, page_size=50):
"""Search TED procurement notices."""
url = f"{self.base_url}/notices/search"
params = {
"pageSize": page_size,
"page": page,
"scope": "ACTIVE",
"sortField": "PD",
"sortOrder": "desc",
}
# Build query string
q_parts = []
if query:
q_parts.append(query)
if country:
q_parts.append(f"CY={country}")
if q_parts:
params["q"] = " AND ".join(q_parts)
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
return response.json()
def get_notice(self, notice_id):
"""Get a specific notice by its document number."""
url = f"{self.base_url}/notices/{notice_id}"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
Search for water infrastructure tenders in Germany
client = TEDClient(api_key="your_api_key_here")
results = client.search_notices(query="water infrastructure", country="DE")TED API Considerations
- TED covers all EU member states, this is the single largest source of European public procurement data
- Historical data is available going back many years
- TED is transitioning to the eForms standard, which changes the data structure for newer notices
- Bulk data downloads are available for large-scale analysis, the API is better for targeted queries
Green Climate Fund (GCF) API
The Green Climate Fund provides API access for its climate-finance procurement data through developer.gcfund.org/apis.
Available Endpoints
| Endpoint | Description |
|---|---|
/projects | GCF-funded projects with procurement details |
/procurement | Active procurement opportunities |
/results | Project results and outcomes |
/funding | Funding proposals and approvals |
Access
- Register at developer.gcfund.org
- API documentation provides endpoint details and authentication requirements
- GCF data is particularly valuable for climate-related procurement, renewable energy, climate adaptation, environmental services
Python Example
import requests
def fetch_gcf_procurement(status="active"):
"""Fetch GCF procurement opportunities."""
base_url = "https://api.gcfund.org/v1"
response = requests.get(
f"{base_url}/procurement",
params={"status": status},
headers={"Accept": "application/json"},
)
response.raise_for_status()
return response.json()
Building a Multi-Source Procurement Monitor
The real power of procurement APIs emerges when you combine multiple sources into a unified monitoring system. Here is a practical architecture:
Architecture Overview
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ World Bank │ │ UNGM │ │ TED │ │ GCF │
│ API │ │ OData │ │ API │ │ API │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
└────────┬────────┴────────┬────────┘ │
│ │ │
┌───────▼─────────────────▼───────────────────────────▼──┐
│ Normalisation Layer │
│ (map each source to common schema) │
└──────────────────────┬─────────────────────────────────┘
│
┌──────────────────────▼─────────────────────────────────┐
│ Data Store │
│ (PostgreSQL, Elasticsearch, or similar) │
└──────────────────────┬─────────────────────────────────┘
│
┌──────────────────────▼─────────────────────────────────┐
│ Alert and Search Layer │
│ (keyword matching, country filters, notifications) │
└────────────────────────────────────────────────────────┘Common Schema Design
Normalise tender data from all sources into a consistent format:
normalised_notice = {
"source": "world_bank", # world_bank, ungm, ted, gcf
"source_id": "WB-2026-12345", # unique ID from source
"title": "Construction of...",
"description": "Full text...",
"country": "KE", # ISO 3166-1 alpha-2
"region": "Eastern Africa",
"sector": "Infrastructure",
"procurement_method": "ICB",
"notice_type": "specific", # general, specific, eoi, award
"published_date": "2026-06-15",
"deadline_date": "2026-08-15",
"estimated_value_usd": 5000000,
"currency": "USD",
"contracting_authority": "Ministry of Roads",
"url": "https://...",
"fetched_at": "2026-06-22T10:00:00Z",
}Scheduling and Incremental Fetching
import schedule
import timedef poll_world_bank():
"""Fetch new World Bank notices since last check."""
# Use date filters to get only new notices
# Store last_fetched timestamp
passdef poll_ungm():
"""Fetch new UNGM notices since last check."""
passdef poll_ted():
"""Fetch new TED notices since last check."""
passSchedule polling
schedule.every(6).hours.do(poll_world_bank)
schedule.every(6).hours.do(poll_ungm)
schedule.every(12).hours.do(poll_ted)
while True:
schedule.run_pending()
time.sleep(60)
Practical Use Cases
1. Competitive Intelligence
Monitor contract awards to understand who is winning in your sector and geography. The World Bank and UNGM both publish award data that reveals competitor activity.
2. Pipeline Building
Automated alerts when new tenders match your capability profile. Set up keyword and sector filters to receive notifications rather than manually checking portals.
3. Market Analysis
Aggregate historical data to identify procurement trends, which sectors are growing, which countries are increasing spending, and where competition is lightest.
4. Due Diligence
Use the UNGM Vendor Sanctions API to programmatically check potential partners against the UN Ineligibility List as part of your compliance workflow.
5. Proposal Preparation
When a relevant tender is detected, automatically pull project documents, related notices, and historical context to accelerate proposal preparation.
Frequently Asked Questions
Does the World Bank API require authentication or API keys?
No. The World Bank Procurement Notices API at search.worldbank.org/api/v2/procnotices is fully open and requires no authentication. You can start making requests immediately. However, be respectful of server resources, add reasonable delays between bulk requests and cache results where possible.
How frequently is the UNGM API data updated?
UNGM data is typically updated within 24 hours of notices being published on the UNGM portal. However, update frequency can vary by UN agency, some agencies publish tenders immediately upon approval, while others batch-publish periodically. For time-sensitive monitoring, check at least once every 12 hours.
Can I access historical procurement data through these APIs?
The World Bank API provides access to historical notices going back many years. TED similarly maintains a deep archive. UNGM's API access to historical data may be more limited depending on your access level. For large-scale historical analysis, consider using the bulk download options that some platforms offer rather than paginating through the API.
Are there rate limits on these procurement APIs?
The World Bank API does not publish formal rate limits but will throttle or block excessive requests. UNGM enforces rate limits and returns appropriate HTTP headers. TED has documented rate limits in its API terms of use. As a general best practice, limit yourself to no more than 1-2 requests per second for any of these APIs, and implement exponential backoff for error responses.
Can I redistribute or publish data obtained from these APIs?
Terms of use vary by source. World Bank data is generally available under open data licences. UNGM and TED have specific terms governing data reuse. Review each platform's terms of service before publishing or commercially redistributing their data. Attribution requirements also differ, some sources require you to credit them when displaying their data.
Procurement APIs unlock powerful capabilities for businesses that compete in international development markets. But building and maintaining integrations across multiple sources is complex, time-consuming work. Bidovate does the heavy lifting for you, we aggregate tender data from the World Bank, UN agencies, development banks, and dozens of other sources into a single, searchable platform with intelligent alerts. Stop building scrapers and start winning tenders, try Bidovate today.
Ready to win more tenders?
Bidovate scans 1000+ procurement portals and matches opportunities to your company profile.