Public API
The two endpoints behind this site: a live feed of Canadian procurement opportunities, and the catalog of sources monitored to build it. Plain JSON over HTTPS — no key, no signup, no SDK.
no key, no signup1,000 requests/day per IPJSON over HTTPSstable response shapes
Documented as deployed 2026-10-10 (commit 05870d2).
The API is free — no key, no signup, 1,000 requests a day — where comparable Canadian procurement APIs charge for the same access. That's the whole pitch. The terms are fair use:
- Base URL — https://pubsec.pro. Both endpoints live there, not on this domain; every response carries a meta.documented link back to this page.
- Rate limit — 1,000 requests per day per IP. Polling both endpoints once a day costs two of them.
- Rate-limit headers — every response ships x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset. Cross the line and you get HTTP 429 with a plain-English message.
- Stability — the shapes below are the contract. Every field in data is always present on every record, null where unknown, so your code never branches on missing keys.
The monitored-source catalog: every source tracked, its jurisdiction, whether monitoring is on, and when it last synced successfully. One request returns the whole catalog — 24 sources today. This is the endpoint the source monitor watches.
Response fields
| Field | What it is |
|---|---|
| meta.count | Number of sources in data. |
| meta.documented | URL of this documentation. |
| source_id | Stable identifier for the source; tender records carry the same value in their own source_id field. |
| name | Display name of the source. |
| jurisdiction | Province or territory code the source covers. |
| enabled | Whether the source is currently monitored. |
| last_success | Timestamp of the source's last successful sync, UTC — e.g. 2026-10-05 17:26:20.101271+00. |
Example response
{
"meta": {
"count": 24,
"documented": "https://pubsecdata.org/api"
},
"data": [
{
"source_id": "apc",
"name": "Alberta Purchasing Connection",
"jurisdiction": "AB",
"enabled": true,
"last_success": "2026-10-05 17:26:20.101271+00"
}
]
}
One of 24 sources shown.
The live feed: current Canadian procurement opportunities from every monitored source. Filter with query parameters, page with offset, and follow meta.next_offset until it returns null.
Parameters
| Parameter | What it does | Values / bounds |
|---|---|---|
| q | Text match on tender title or buyer. | Case-insensitive free text, e.g. electrical. |
| prov | Province or territory filter. | on qc bc ab sk mb nb ns pe nl yt nt nu |
| trade | Category filter. | A category slug, e.g. electrical, roofing, it-infrastructure. |
| sort | Result order. | closing (default) or published. |
| limit | Page size. | 1–500; default 50. |
| offset | Pagination cursor. | 0–9500; default 0. |
Response fields
| Field | What it is |
|---|---|
| meta.count | Records in this response's data. |
| meta.limit / meta.offset | The page size and cursor in effect. |
| meta.next_offset | Offset for the next page — null on the last page. |
| meta.sort | Order applied: closing or published. |
| meta.documented | URL of this documentation. |
| id | Stable identifier for the tender. Dedupe on this. |
| title | Tender title. |
| buyer | Buying organization. |
| province_code | Two-letter province or territory code. |
| region | Sub-provincial region, or null. |
| level | Level of government, e.g. municipal. |
| trade_category | Category slug, or null. |
| closing_at | Bid deadline, ISO 8601 UTC. |
| posted_at | Publication timestamp, ISO 8601 UTC. |
| value_cad | Contract value in Canadian dollars, or null. |
| value_est_cad | Estimated value in Canadian dollars, or null. |
| url | The tender's page at its source. |
| source_id | Identifier of the monitored source that published it. |
Example response
{
"meta": {
"count": 50,
"limit": 50,
"offset": 0,
"next_offset": 50,
"sort": "closing",
"documented": "https://pubsecdata.org/api"
},
"data": [
{
"id": "0d1f6c50-9db2-4b1e-a14f-2f7c8d3a5b69",
"title": "Electrical upgrades \u2014 arena compressor replacement",
"buyer": "Town of Gananoque",
"province_code": "ON",
"region": null,
"level": "municipal",
"trade_category": "electrical",
"closing_at": "2026-10-10T17:00:00Z",
"posted_at": "2026-09-29T13:42:00Z",
"value_cad": null,
"value_est_cad": 120000,
"url": "\u2026",
"source_id": "bandt"
}
]
}
One of 50 records shown — every field ships on every record, null where unknown.
Quick start
Three ways to make the same call — the ten most recently published electrical tenders in Ontario. Copy, paste, run.
curl
curl "https://pubsec.pro/api/v1/tenders?q=electrical&prov=on&sort=published&limit=10"
Python
import json
import urllib.request
url = ("https://pubsec.pro/api/v1/tenders"
"?q=electrical&prov=on&sort=published&limit=10")
with urllib.request.urlopen(url) as response:
feed = json.load(response)
for tender in feed["data"]:
print(tender["closing_at"], tender["buyer"], "-", tender["title"])
JavaScript
const url =
"https://pubsec.pro/api/v1/tenders?q=electrical&prov=on&sort=published&limit=10";
const response = await fetch(url);
const feed = await response.json();
for (const tender of feed.data) {
console.log(tender.closing_at, tender.buyer, "-", tender.title);
}
Cookbook
Two complete recipes built on these endpoints — standard library only, cron-ready:
Citation format
pubsec/data 2026.10, https://pubsecdata.org/api