Org APIzppa.org.zm ↗
Access Zambia's public procurement data via ZPPA OCDS. Browse tenders, search by OCID, and retrieve buyer details, contract values, and tenderer info.
What is the Org API?
This API provides structured access to Zambia Public Procurement Authority (ZPPA) procurement records across 3 endpoints. Use get_current_tenders to retrieve a paginated list of live and historical procurement records sorted by most recent process date, each carrying an OCID and status field. From there, pass the returned MongoDB ObjectId into get_tender_details to pull the full OCDS compiled release including procuring entity, procurement method, budget, tenderers, and contract details.
curl -X GET 'https://api.parse.bot/scraper/9194eb38-d322-4509-9d51-933120aa8121/get_current_tenders?page=1' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace zppa-org-zm-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""
Zambia Public Procurement Authority (ZPPA) OCDS API Client
Access Zambia's public procurement data through the ZPPA Open Contracting Data Standard (OCDS) system.
Get your API key from: https://parse.bot/settings
"""
import os
import requests
from datetime import datetime
from typing import Optional, Dict, Any, List
class ParseClient:
"""Client for interacting with the ZPPA OCDS API via Parse."""
def __init__(self, api_key: Optional[str] = None):
"""
Initialize the Parse client.
Args:
api_key: API key for authentication. If not provided, reads from PARSE_API_KEY env var.
"""
self.base_url = "https://api.parse.bot"
self.scraper_id = "9194eb38-d322-4509-9d51-933120aa8121"
self.api_key = api_key or os.getenv("PARSE_API_KEY")
if not self.api_key:
raise ValueError("API key must be provided or set in PARSE_API_KEY environment variable")
def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
"""
Make an API call to the Parse endpoint.
Args:
endpoint: The endpoint name (e.g., 'get_current_tenders')
method: HTTP method ('GET' or 'POST')
**params: Parameters to pass to the endpoint
Returns:
Dictionary containing the API response data
"""
url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
headers = {
"X-API-Key": self.api_key,
"Content-Type": "application/json"
}
if method.upper() == "GET":
response = requests.get(url, headers=headers, params=params)
else: # POST
response = requests.post(url, headers=headers, json=params)
response.raise_for_status()
result = response.json()
if result.get("status") != "success":
raise Exception(f"API error: {result}")
return result.get("data", {})
def get_current_tenders(self, page: int = 1) -> Dict[str, Any]:
"""
Fetch a paginated list of procurement records from the ZPPA OCDS system.
Args:
page: Page number to fetch (default 1). Each page contains 20 records.
Returns:
Dictionary with 'tenders', 'page', 'total_pages', and 'total_records'
"""
return self._call("get_current_tenders", method="GET", page=page)
def get_tender_details(self, record_id: str) -> Dict[str, Any]:
"""
Fetch full OCDS compiled release details for a specific procurement record.
Args:
record_id: MongoDB ObjectId of the record (e.g., '69f3df4ab4275d46715e26b5')
Returns:
Dictionary with 'details' containing full OCDS compiled release data
"""
return self._call("get_tender_details", method="GET", record_id=record_id)
def search_tenders(self, query: str, page: int = 1) -> Dict[str, Any]:
"""
Search OCDS procurement records by OCID.
Args:
query: OCID search term (matches numeric portion, e.g., '10000137')
page: Page number to fetch (default 1). Each page contains 20 records.
Returns:
Dictionary with 'results', 'query', 'page', 'total_pages', and 'total_results'
"""
return self._call("search_tenders", method="GET", query=query, page=page)
def format_timestamp(epoch_ms: int) -> str:
"""Convert epoch milliseconds to readable date string."""
return datetime.fromtimestamp(epoch_ms / 1000).strftime("%Y-%m-%d %H:%M:%S")
def main():
"""
Practical workflow: Search for a tender, retrieve its details,
and analyze procurement information.
"""
# Initialize the client
client = ParseClient()
print("=" * 70)
print("ZPPA OCDS Procurement Data Analysis")
print("=" * 70)
# Step 1: Search for a specific tender by OCID
print("\n[1] Searching for tender with OCID containing '10000137'...")
search_results = client.search_tenders(query="10000137")
total_results = search_results.get("total_results", 0)
print(f" Found {total_results} matching record(s)")
results = search_results.get("results", [])
if not results:
print(" No records found. Fetching recent tenders instead...")
# Step 2: If search yields nothing, get current/recent tenders
print("\n[2] Fetching most recent procurement records (page 1)...")
recent = client.get_current_tenders(page=1)
tenders = recent.get("tenders", [])
total_records = recent.get("total_records", 0)
print(f" Total records in system: {total_records}")
print(f" Records on this page: {len(tenders)}")
# Use first few records for analysis
results = tenders[:3]
# Step 3: Retrieve and analyze details for each result
print(f"\n[3] Retrieving detailed information for {len(results)} tender(s)...")
for idx, tender_summary in enumerate(results, 1):
tender_id = tender_summary.get("id")
ocid = tender_summary.get("ocid")
status = tender_summary.get("status")
creation_date = format_timestamp(tender_summary.get("creation_date", 0))
process_date = format_timestamp(tender_summary.get("process_date", 0))
print(f"\n --- Tender {idx} ---")
print(f" OCID: {ocid}")
print(f" Status: {status}")
print(f" Created: {creation_date}")
print(f" Processed: {process_date}")
# Fetch full details
details_response = client.get_tender_details(record_id=tender_id)
details = details_response.get("details", {})
# Extract key procurement information
tender_info = details.get("tender", {})
title = tender_info.get("title", "N/A")
description = tender_info.get("description", "N/A")
procurement_method = tender_info.get("procurement_method", "N/A")
number_of_tenderers = tender_info.get("number_of_tenderers", 0)
value = tender_info.get("value", {})
amount = value.get("amount", "N/A")
currency = value.get("currency", "N/A")
procuring_entity = tender_info.get("procuring_entity", {})
entity_name = procuring_entity.get("name", "N/A")
buyer = details.get("buyer", {})
buyer_name = buyer.get("name", "N/A")
# Print procurement details
print(f"\n Title: {title}")
print(f" Description: {description}")
print(f" Procuring Entity: {entity_name}")
print(f" Buyer: {buyer_name}")
print(f" Procurement Method: {procurement_method}")
print(f" Budget: {amount:,} {currency}" if isinstance(amount, (int, float)) else f" Budget: {amount} {currency}")
print(f" Number of Tenderers: {number_of_tenderers}")
# Extract and display items
items = tender_info.get("items", [])
if items:
print(f" Items ({len(items)}):")
for item in items[:2]: # Show first 2 items
item_desc = item.get("description", "N/A")
item_id = item.get("id", "N/A")
print(f" - {item_desc} (ID: {item_id})")
if len(items) > 2:
print(f" ... and {len(items) - 2} more")
# Display tenderers
tenderers = tender_info.get("tenderers", [])
if tenderers:
print(f" Tenderers ({len(tenderers)}):")
for tenderer in tenderers[:3]: # Show first 3 tenderers
tenderer_name = tenderer.get("name", "N/A")
print(f" - {tenderer_name}")
if len(tenderers) > 3:
print(f" ... and {len(tenderers) - 3} more")
print("\n" + "=" * 70)
print("Analysis complete!")
print("=" * 70)
if __name__ == "__main__":
main()Fetch a paginated list of procurement records from the ZPPA OCDS system, sorted by most recent process date. Returns record identifiers that can be used with get_tender_details to retrieve full information.
| Param | Type | Description |
|---|---|---|
| page | integer | Page number to fetch. Each page contains 20 records. |
{
"type": "object",
"fields": {
"page": "integer, current page number",
"tenders": "array of record summary objects, each containing id, ocid, status, creation_date (epoch ms), and process_date (epoch ms)",
"total_pages": "integer, total number of pages available",
"total_records": "integer, total number of records in the system"
},
"sample": {
"data": {
"page": 1,
"tenders": [
{
"id": "69f3df4ab4275d46715e26b5",
"ocid": "ocds-23g63a01-25844731",
"status": "NEW",
"process_date": 1777676418609,
"creation_date": 1777563602000
}
],
"total_pages": 8533,
"total_records": 170656
},
"status": "success"
}
}About the Org API
Browsing and Searching Tenders
The get_current_tenders endpoint returns a paginated list of procurement records from the ZPPA OCDS system. Each page contains 20 records, and each record in the tenders array includes an id (MongoDB ObjectId), ocid, status, creation_date, and process_date (both as epoch milliseconds). The total_records and total_pages fields let you walk the full dataset systematically. For targeted lookups, search_tenders accepts a query string matched against the numeric portion of an OCID — for example, querying 10000001 will match ocds-23g63a01-10000001. Results are paginated identically to the browse endpoint.
Retrieving Full Procurement Details
Once you have a record_id from either listing or search, pass it to get_tender_details to retrieve the full OCDS compiled release. The details object follows the Open Contracting Data Standard and includes the ocid, a tender block with title, description, status, procurement method, and procuring entity, plus tenderer information and budget and contract values where available. This makes it possible to reconstruct a complete picture of a procurement process from notice through award.
Data Coverage and Standards
All records conform to the OCDS format (prefix ocds-23g63a01), the international open contracting standard used by governments to publish procurement data. The API covers procurements managed through ZPPA, Zambia's central procurement authority. Dates are provided as epoch milliseconds, so convert to ISO 8601 before display. Pagination is fixed at 20 records per page across all three endpoints.
The Org API is a managed, monitored endpoint for zppa.org.zm — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when zppa.org.zm changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official zppa.org.zm API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Monitor new Zambian government tenders by polling
get_current_tendersand filtering onprocess_date - Build a tender alert system that notifies vendors when procurement records matching their sector appear
- Cross-reference OCID values from external sources against ZPPA records using
search_tenders - Aggregate contract values and procurement methods from
get_tender_detailsfor public spending analysis - Track procuring entities across multiple tender records to identify active buyers in the Zambian government
- Feed ZPPA OCDS data into a compliance or due-diligence workflow for tenderers listed in procurement records
- Archive historical procurement records for auditing or research on Zambian public sector contracting trends
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 100 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does ZPPA provide an official developer API?+
What does `get_tender_details` actually return beyond the basic record summary?+
tender block (title, description, status, procurement method, procuring entity), tenderer details, budget figures, and contract values where the record has progressed to award stage. The record summary from get_current_tenders only includes id, ocid, status, and two date fields — the detail endpoint is required for all substantive procurement information.Can I search by tender title, procuring entity name, or procurement method?+
search_tenders only matches against the numeric portion of an OCID. Title, entity name, and procurement method are returned inside get_tender_details but are not filterable query parameters at this time. You can fork this API on Parse and revise it to add a full-text or field-specific search endpoint.Are procurement records from all Zambian public entities included, or only central government?+
How are dates represented and is there a way to filter by date range?+
creation_date and process_date are both returned as epoch milliseconds in the record summary objects. Date-range filtering is not a supported parameter on any of the three endpoints. get_current_tenders is sorted by most recent process_date descending, so paginating from page 1 gives the newest records first.