← Back to Blog
EN FR

Canadian Government Procurement API for AI & LLM Builders

ProcureData aggregates 2,105,430 Canadian government procurement records across 9 sources into a single REST API: CanadaBuys federal contracts and tenders, TBS proactive disclosures, SEAO Quebec tenders and contracts, Nova Scotia awarded contracts, Alberta sole-source records, and PSPC standing offers. All sources are normalized to consistent field names, cursor-based pagination, and response times under 200 ms.

The raw sources are fragmented by design. CanadaBuys uses inconsistent department names and no stable record IDs. SEAO’s OCDS feed requires non-trivial parsing. Nova Scotia, Alberta, and other provinces each publish separately, in different formats. ProcureData handles all of that before your first API call. What follows is the developer reference for building LLM agents, reporting tools, or RAG pipelines on top of that data.

What’s in the database

Every record maps to one of five entity types: contract, tender, award, disclosure, or standing_offer. The source breakdown:

Records by Source (2,105,430 total)
All counts as of April 2026. Federal sources refresh weekly (Monday). Provincial sources refresh on the same schedule. Nova Scotia and Alberta sole-source data refresh weekly.
Source Entity type Records Level Coverage
CanadaBuys contracts contract 591,762 federal 2009–present
TBS proactive disclosures disclosure 499,868 federal 2004–present
SEAO Quebec tenders tender 368,149 provincial 2021–present
SEAO Quebec contracts contract 352,972 provincial 2021–present
CanadaBuys awards award 143,129 federal 2012–present
CanadaBuys tenders tender 99,182 federal 2009–present
Nova Scotia awarded contracts contract 30,788 provincial 2010–present
Alberta sole-source registry contract 13,596 provincial 1975–present
PSPC standing offers standing_offer 5,984 federal current snapshot

Federal records use government_level=federal. Quebec, Nova Scotia, and Alberta records use government_level=provincial. The municipal level is reserved for future use. Alberta sole-source coverage begins in 1975, though pre-2000 records are sparse; most practical queries will use issued_after=2010-01-01.

Authentication

All endpoints return JSON and are accessed via RapidAPI. Two headers are required on every request:

bash
# Required headers for every request
X-RapidAPI-Key: YOUR_API_KEY
X-RapidAPI-Host: procuredata-canadian-government-procurement-api.p.rapidapi.com

Free tier: 100 requests/day. Pro tier: 1,000 requests/day at $29/month. Ultra tier: ~127,000 requests/day at $99/month.

Endpoints

The API surface is intentionally small. Ten endpoints cover all entity types, aggregation, and lifecycle views. The full OpenAPI 3.0.3 spec is at procuredata.ca/openapi.json and imports directly into Postman, Insomnia, or any LLM tool-use framework.

API Surface
  • GET
    /contract
    Awarded contracts. Filter by department, vendor, category, value range, date range, government level.
  • GET
    /tender
    Open and historical tender notices (CanadaBuys + SEAO). Filter by notice_type, security_clearance, department.
  • GET
    /award
    Award notices from CanadaBuys. Links to tenders via solicitation_number.
  • GET
    /disclosure
    TBS proactive disclosure (federal contracts over $10,000), 2004–present.
  • GET
    /standing_offer
    PSPC standing offers and supply arrangements (SOSA snapshot).
  • GET
    /{entity_type}/stats
    Aggregated stats. group_by: department, category, vendor_province. Approximate counts via TABLESAMPLE.
  • GET
    /procurement/{solicitation_number}
    Full lifecycle view: tender + awards + contracts linked by solicitation number.
  • GET
    /department/{name}
    Department profile: total records, total value, breakdown by entity type, top categories.
  • GET
    /vendor/{name}
    Vendor profile: all contracts and awards, top departments, value breakdown.
  • GET
    /sources
    List of active data sources with record counts and last updated timestamps.

Query examples

Contracts by department, last 12 months

curl
curl -G "https://procuredata-canadian-government-procurement-api.p.rapidapi.com/contract" \
  --data-urlencode "department=National Defence" \
  --data-urlencode "issued_after=2025-04-01" \
  --data-urlencode "sort_by=value" \
  --data-urlencode "limit=20" \
  -H "X-RapidAPI-Key: YOUR_API_KEY" \
  -H "X-RapidAPI-Host: procuredata-canadian-government-procurement-api.p.rapidapi.com"

Full-text search across tenders

curl
curl -G "https://procuredata-canadian-government-procurement-api.p.rapidapi.com/tender" \
  --data-urlencode "q=cybersecurity" \
  --data-urlencode "government_level=federal" \
  --data-urlencode "issued_after=2025-01-01" \
  --data-urlencode "limit=10" \
  -H "X-RapidAPI-Key: YOUR_API_KEY" \
  -H "X-RapidAPI-Host: procuredata-canadian-government-procurement-api.p.rapidapi.com"

Cursor-based pagination (for agents)

Every list response includes a next_cursor field. Pass it as the cursor parameter on the next request. Cursors are stable and safe for sequential tool calls.

python
import requests

BASE = "https://procuredata-canadian-government-procurement-api.p.rapidapi.com"
HEADERS = {
    "X-RapidAPI-Key": "YOUR_API_KEY",
    "X-RapidAPI-Host": "procuredata-canadian-government-procurement-api.p.rapidapi.com",
}

params = {"department": "Health Canada", "category": "SRV", "limit": 100}
records = []

while True:
    r = requests.get(BASE + "/contract", headers=HEADERS, params=params)
    data = r.json()
    records.extend(data["results"])
    if not data.get("next_cursor"):
        break
    params["cursor"] = data["next_cursor"]

print(f"{len(records)} contracts fetched")

LLM function-call definition

The API is a natural fit for tool use in LLM agents. Below is an OpenAI-compatible function definition you can drop into any agent that needs to query Canadian procurement data. Anthropic tool use follows the same structure.

json (OpenAI tool)
{
  "type": "function",
  "function": {
    "name": "query_canadian_procurement",
    "description": "Search Canadian government contracts, tenders, and awards from federal and provincial sources. Returns structured procurement records including department, vendor, value, category, and date.",
    "parameters": {
      "type": "object",
      "properties": {
        "entity_type": {
          "type": "string",
          "enum": ["contract", "tender", "award", "disclosure", "standing_offer"],
          "description": "Type of procurement record to query"
        },
        "department": {
          "type": "string",
          "description": "Federal or provincial department name (partial match supported)"
        },
        "vendor": {
          "type": "string",
          "description": "Vendor or supplier name (partial match)"
        },
        "q": {
          "type": "string",
          "description": "Full-text search query across title and description fields"
        },
        "category": {
          "type": "string",
          "enum": ["SRV", "GD", "CNST", "SRVTGD"],
          "description": "Procurement category: SRV=Services, GD=Goods, CNST=Construction, SRVTGD=Services+Goods"
        },
        "government_level": {
          "type": "string",
          "enum": ["federal", "provincial"],
          "description": "Filter by level of government"
        },
        "value_min": {
          "type": "number",
          "description": "Minimum contract value in CAD"
        },
        "value_max": {
          "type": "number",
          "description": "Maximum contract value in CAD"
        },
        "issued_after": {
          "type": "string",
          "description": "Return records issued on or after this date (YYYY-MM-DD)"
        },
        "issued_before": {
          "type": "string",
          "description": "Return records issued on or before this date (YYYY-MM-DD)"
        },
        "sort_by": {
          "type": "string",
          "enum": ["value", "date"],
          "description": "Sort results by contract value or date (default: date desc)"
        },
        "limit": {
          "type": "integer",
          "minimum": 1,
          "maximum": 500,
          "description": "Number of records to return (default 100)"
        },
        "cursor": {
          "type": "string",
          "description": "Pagination cursor from next_cursor field of a previous response"
        }
      },
      "required": ["entity_type"]
    }
  }
}

Example: agent reasoning trace

A user asks: “Which IT vendors received the most federal contracts from Health Canada in the last two years?” The agent decomposes this into two tool calls:

tool call #1
{
  "entity_type": "contract",
  "department": "Health Canada",
  "category": "SRV",
  "issued_after": "2024-04-22",
  "sort_by": "value",
  "limit": 100
}
tool call #2 (stats, for aggregate view)
GET /contract/stats?department=Health+Canada&category=SRV&group_by=vendor&period=1y

The stats endpoint returns approximate group counts and sums via database sampling. For large questions about total spend or category distribution, it is faster than paging through all records.

Record schema

A typical contract record:

json (contract record)
{
  "record_id": "canada_contracts|EC107-200001|C",
  "entity_type": "contract",
  "source_id": "canada_contracts",
  "event_date": "2025-03-15",
  "department": "Health Canada",
  "vendor": "Deloitte Inc",
  "contract_value": 2400000.00,
  "category": "SRV",
  "government_level": "federal",
  "data": {
    "solicitation_number": "EC107-200001",
    "procurement_method": "Competitive",
    "contract_period_start": "2025-03-15",
    "contract_period_end": "2026-03-14",
    "description": "Management consulting services",
    "vendor_province": "Ontario"
  }
}

Tender record (CanadaBuys)

json (tender record)
{
  "record_id": "canada_tenders|W8486-226285",
  "entity_type": "tender",
  "source_id": "canada_tenders",
  "event_date": "2025-11-03",
  "department": "National Defence",
  "data": {
    "solicitation_number": "W8486-226285",
    "notice_type": "Request for Proposal",
    "closing_date": "2025-12-15",
    "security_clearance": "secret",
    "title": "Tactical Communications Equipment"
  }
}

Security clearance field: federal tenders from CanadaBuys include a security_clearance field extracted from the solicitation PDF. Values: reliability, enhanced_reliability, secret, top_secret, none, or null when no document was available. Useful for filtering tenders accessible to uncleared vendors.

Design decisions for LLM agents

Use cursor pagination, not offset

Offset pagination breaks when records are added between requests. If your agent pages through results over multiple turns or tool calls, use the cursor parameter. Pass next_cursor from the previous response. There is no need to track a page number or recalculate offsets.

Use stats for aggregate questions

If the user asks “how much did DND spend on IT in 2024,” do not page through all contracts. Call /contract/stats?department=National+Defence&category=SRV&sum_field=contract_value&period=1y. The stats endpoint uses database sampling and returns in under a second even on large filtered sets.

Use department profiles for vendor discovery

The /department/{name} endpoint returns the top vendors, total value, and category breakdown for a department without any pagination. One tool call answers most “who does X buy from” questions.

Normalize department names

The raw CanadaBuys data has approximately 90 department name variants normalized to about 20 canonical names in the API. The department filter supports partial match, but using canonical names returns faster results and avoids ambiguous matches. Common canonical names:

canonical department names (sample)
# Canonical names return the fastest, most precise results
"National Defence"
"Public Services and Procurement Canada"
"Health Canada"
"Shared Services Canada"
"Royal Canadian Mounted Police"
"Canada Revenue Agency"
"Transport Canada"
"Environment and Climate Change Canada"
"Indigenous Services Canada"
"Fisheries and Oceans Canada"

# Or use the /departments endpoint to list all canonical names
GET /departments

Rate limits and retry

The API returns HTTP 429 when you exceed your plan’s daily limit. RapidAPI sends a Retry-After header indicating when the limit resets (UTC midnight). For agents running overnight pipelines, stay under 80% of your daily quota to leave room for retries.

Building a RAG pipeline

If you are embedding procurement records for retrieval-augmented generation, a few practical notes:

  • The description field in the data object is the richest text for embedding. It is populated for most CanadaBuys records and all disclosure records.
  • For tender records, embed title + notice_type + department together. The title alone is often ambiguous.
  • Chunk at the record level. Each record is 200–800 tokens. No splitting needed.
  • Use event_date as a metadata filter when users ask time-scoped questions. This avoids embedding stale records into answers about current spending.
  • Filter government_level in metadata before retrieval if the user’s question is explicitly federal or provincial.

With records at 200–800 tokens each, no chunking logic is needed. Pipe records directly from the API into your embedding endpoint and index by record_id for deduplication on re-ingestion. For background on how CanadaBuys tenders are structured, see Federal Tenders on CanadaBuys: A Technical Overview.

Frequently asked questions

What data sources does ProcureData cover?
9 sources: CanadaBuys contracts (591,762), TBS proactive disclosures (499,868), SEAO Quebec tenders (368,149), SEAO Quebec contracts (352,972), CanadaBuys awards (143,129), CanadaBuys tenders (99,182), Nova Scotia awarded contracts (30,788), Alberta sole-source registry (13,596), and PSPC standing offers (5,984). Total: 2,105,430 records.
Does the API support cursor pagination?
Yes. Every list endpoint returns a next_cursor field. Pass cursor=<value> on subsequent requests. Cursors are stable and safe for agents making sequential calls across turns.
Can I filter by government level?
Yes. Use ?government_level=federal for CanadaBuys and TBS data, or ?government_level=provincial for SEAO Quebec, Nova Scotia, and Alberta data. The field is indexed; responses are under 200 ms for typical queries.
How fresh is the data?
Federal contract, tender, and award data refreshes weekly (Monday 2:00–6:00 AM UTC). TBS proactive disclosures refresh monthly. SEAO Quebec refreshes weekly. Nova Scotia and Alberta refresh weekly. You can check the /sources endpoint for the last updated timestamp per source.
Is there a free tier?
Yes. Basic plan: free, 100 requests/day. Pro plan: $29/month, 1,000 requests/day. Ultra plan: $99/month, approximately 127,000 requests/day.
What does the OpenAPI spec look like?
The full OpenAPI 3.0.3 spec is available at procuredata.ca/openapi.json. It includes all endpoints, parameters, and response schemas. You can import it directly into tools like Postman, Insomnia, or an LLM tool-use framework.
Data notes: Record counts as of April 2026. entity_type values: contract, tender, award, disclosure, standing_offer. Department names are normalized from raw source data (approximately 90 variants to about 20 canonical forms; use the /departments endpoint for the full list). category values: SRV (Services), GD (Goods), CNST (Construction), SRVTGD (Services and Goods). security_clearance is extracted from solicitation PDFs via pypdf for CanadaBuys tenders only. Stats endpoints use TABLESAMPLE SYSTEM for approximate aggregation; exact counts can be obtained by paging through results.

Get your API key and start querying 2.1M procurement records today.

Get API Key on RapidAPI View OpenAPI Spec