Release 2026.10

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:

GEThttps://pubsec.pro/api/v1/sources

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

FieldWhat it is
meta.countNumber of sources in data.
meta.documentedURL of this documentation.
source_idStable identifier for the source; tender records carry the same value in their own source_id field.
nameDisplay name of the source.
jurisdictionProvince or territory code the source covers.
enabledWhether the source is currently monitored.
last_successTimestamp 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.

GEThttps://pubsec.pro/api/v1/tenders

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

ParameterWhat it doesValues / bounds
qText match on tender title or buyer.Case-insensitive free text, e.g. electrical.
provProvince or territory filter.on qc bc ab sk mb nb ns pe nl yt nt nu
tradeCategory filter.A category slug, e.g. electrical, roofing, it-infrastructure.
sortResult order.closing (default) or published.
limitPage size.1–500; default 50.
offsetPagination cursor.0–9500; default 0.

Response fields

FieldWhat it is
meta.countRecords in this response's data.
meta.limit / meta.offsetThe page size and cursor in effect.
meta.next_offsetOffset for the next page — null on the last page.
meta.sortOrder applied: closing or published.
meta.documentedURL of this documentation.
idStable identifier for the tender. Dedupe on this.
titleTender title.
buyerBuying organization.
province_codeTwo-letter province or territory code.
regionSub-provincial region, or null.
levelLevel of government, e.g. municipal.
trade_categoryCategory slug, or null.
closing_atBid deadline, ISO 8601 UTC.
posted_atPublication timestamp, ISO 8601 UTC.
value_cadContract value in Canadian dollars, or null.
value_est_cadEstimated value in Canadian dollars, or null.
urlThe tender's page at its source.
source_idIdentifier 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:

A matched-tenders digest

Poll your trade in your province once a day, dedupe by id, and email yourself what's new.

Watch the source catalog

Track last_success per source and get an alert when one goes stale.

Citation format

pubsec/data 2026.10, https://pubsecdata.org/api