# CanadaBuys Tender Notices Scraper (Canada) (`knotty_mistveil/canadabuys-tender-notices`) Actor

Canadian federal tenders and contract awards from the official CanadaBuys open data feeds: buyer contact, UNSPSC, GSIN, closing date, supplier and value.

- **URL**: https://apify.com/knotty\_mistveil/canadabuys-tender-notices.md
- **Developed by:** [Jason](https://apify.com/knotty_mistveil) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 notice returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## CanadaBuys Tender Notices Scraper (Canada)

Turns the **CanadaBuys** open-data CSV feeds published by **Public Services and Procurement Canada** into one flat JSON record per notice. CanadaBuys is the Government of Canada's authoritative source for federal tender opportunities and contract awards, and it publishes three bulk files a day: every tender still open for bidding, everything published in the last 24 hours, and the fiscal-year award files. This Actor downloads the file you ask for, streams it (they run to tens of megabytes), and gives you the notice with the buying department, the named contact and their email, UNSPSC and GSIN codes, the closing date, the trade agreements, the delivery regions, the attachments, and — on award notices — the winning supplier, the contract value and the contract end date. Filter by publication date, keyword, UNSPSC or GSIN prefix, procurement category, delivery region, buyer and closing date. No API key, no browser automation, no login.

### What you get

One record per notice. Every field below exists on every record and is `null` (or `[]`) where the feed does not publish it, so a CSV or Excel export always has the same columns. Percentages are the share of records carrying a value, measured on 8 September 2026 across the whole open-tender file (945 notices) and the whole 2026-2027 award file (3,331 notices).

| Field | Description |
| --- | --- |
| `id` | `referenceNumber` + `-` + `amendmentNumber`, e.g. `cb-772-82428859-000`. Unique within a run. |
| `source` | Always `canadabuys`. |
| `feed` | Which file the record came from: `open`, `new` or `awards`. |
| `url` | The notice's page on canadabuys.canada.ca, derived from the reference number (see [Limits and freshness](#limits-and-freshness) for the 2.5% of award references this does not work for). |
| `externalNoticeUrl` | The bidding-system link the buyer published (SAP Ariba, MERX, a provincial portal). Present on 46% of open tenders, never on awards. |
| `referenceNumber`, `amendmentNumber`, `solicitationNumber`, `contractNumber` | The source's identifiers. `solicitationNumber` is the one that stays stable between the tender feeds and the award feed. `contractNumber` is award-only (98%). |
| `title` | English title (100%); falls back to the French title if the English cell is ever empty. |
| `description` | Notice text with HTML entities decoded and tags stripped, first 5,000 characters. 98% of open tenders, 85% of awards. |
| `noticeType` | `Request for Proposal`, `Request for Standing Offer`, `Advance Contract Award Notice`, ... (86% / 96%). |
| `procurementCategory` | Array of the source's category codes: `GD`, `SRV`, `CNST`, `SRVTGD`. A notice can carry more than one. |
| `procurementCategoryNames` | The same categories in words: `Goods`, `Services`, `Construction`, `Services related to goods`. |
| `procurementMethod` | `Competitive - Open bidding`, `Competitive - Selective tendering`, `Advance contract award notice`, `Non-competitive`, ... |
| `selectionCriteria`, `limitedTenderingReason` | How the winner is chosen, and the reason given for limited tendering. |
| `instrumentType`, `amendmentType` | Award-only: `Contract`, `Standing Offer`, `Supply Arrangement`, `Contract against SA`; `Original`, `Amendment`, `Termination` (66% and 64% of awards). |
| `status` | `Open` on the tender feeds; `Active`, `Expired` or `Cancelled` on awards. |
| `publicationDate` | `YYYY-MM-DD`. This is the field every date filter works on. |
| `amendmentDate` | Date of the latest amendment, when the notice has been amended. |
| `closingDate` | Bid closing date and time as CanadaBuys publishes it, `YYYY-MM-DDTHH:MM:SS` in **Eastern Time** — see the note in [Limits and freshness](#limits-and-freshness). Tender feeds only. |
| `expectedContractStart`, `expectedContractEnd` | The buyer's expected contract dates on a tender notice (56% / 50%). |
| `contractAwardDate`, `contractStartDate`, `contractEndDate` | Award-only, and near-universal there (100% / 99% / 99%). `contractEndDate` is what you watch for re-tender timing. |
| `contractAmount`, `totalContractValue` | Award-only numbers in `contractCurrency`. 72% of awards publish a non-zero figure in at least one of them; many buyers publish `0` rather than disclose. |
| `contractCurrency` | `CAD` (98% of the awards that state one), `USD`, `EUR`. Null means CAD, per the source's own documentation. |
| `unspsc`, `unspscDescription` | Arrays of 8-digit UNSPSC codes and their English labels (84% of open tenders, 98% of awards). |
| `gsin`, `gsinDescription` | Arrays of Goods and Services Identification Numbers and their labels. **Sparse**: 4.8% of open tenders and 0.7% of awards. |
| `tradeAgreements` | Array of the trade agreements the procurement falls under (CFTA, CETA, WTO GPA, CPTPP, ...). |
| `regionsOfOpportunity` | Array; where bidders may be located. Tender feeds only (42%). |
| `regionsOfDelivery` | Array; where the work is delivered (`Canada`, `Ontario (except NCR)`, `National Capital Region (NCR)`, ...). 86% / 95%. |
| `buyerName` | Contracting entity, e.g. `Department of Public Works and Government Services (PSPC)` (100%). |
| `buyerAddress` | The buyer address on one line, plus `buyerCity`, `buyerProvince`, `buyerPostalCode`, `buyerCountry` split out. |
| `endUserEntities` | Array of the departments that will actually use the contract (45% of open tenders, 66% of awards) — often different from the buyer. |
| `supplierName` | Award-only: the winning supplier (100% of awards), plus `supplierAddress`, `supplierCity`, `supplierProvince`, `supplierCountry`. |
| `contactName`, `contactEmail`, `contactPhone` | The procurement officer named on the notice. `contactName` 100%, `contactEmail` 97.5% (open) / 99.3% (awards), `contactPhone` 42% / 32%. |
| `attachments` | Array of attachment URLs (tender documents, annexes, drawings). 42% of open tenders carry at least one; the record with the most on 8 September 2026 had 31. |
| `fetchedAt` | ISO timestamp of the run that produced the record. |

The dataset ships three views: **Overview** (date, title, buyer, category, UNSPSC, delivery regions, closing date, link), **Buyer contacts** (buyer, contact name, email, phone, city, end user) and **Awards and suppliers** (supplier, city, contract amount, total value, currency, award and end dates).

Records are delivered **newest publication date first**, so a small `maxItems` gives you the most recent notices rather than an arbitrary slice of the file.

### Input

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `feed` | string | `open` | `open` — every notice still open for bidding (about 950 at any time). `new` — only what was published in the last 24 hours. `awards` — contract awards, read from the fiscal-year award file(s) your date window touches. |
| `daysBack` | integer | `7` (`open`, `awards`); none for `new` | Look-back window in days on `publicationDate`, ending now. Ignored when `dateFrom`/`dateTo` are set. Left empty on the `new` feed it means no date filter at all, which is what you want for a 24-hour file. Max 1830. |
| `dateFrom` | string | – | Earliest publication date to keep, `YYYY-MM-DD`, inclusive. Overrides `daysBack`. |
| `dateTo` | string | today | Latest publication date to keep, `YYYY-MM-DD`, inclusive. |
| `keywords` | string\[] | none | Case-insensitive terms matched against the title and description; any match keeps the notice. |
| `unspscPrefixes` | string\[] | all | UNSPSC codes or prefixes, 2 to 8 digits. `43` = IT and telecom equipment, `81` = engineering and research services, `72` = building and maintenance services, `42` = medical equipment, `25` = vehicles. Any code starting with any prefix keeps the notice. |
| `gsinPrefixes` | string\[] | all | GSIN codes or prefixes, 1 to 10 letters/digits (`N7030`, `D3`). Only 5% of open tenders carry a GSIN, so this is a narrow filter by design. |
| `categories` | string\[] | all | `goods`, `services`, `construction`, `services-related-to-goods` (the source's `GD`, `SRV`, `CNST`, `SRVTGD` codes are also accepted). |
| `regions` | string\[] | all | Case-insensitive substrings matched against `regionsOfDelivery`: `"Ontario"`, `"Quebec"`, `"National Capital Region"`, `"British Columbia"`. See the warning below the table. |
| `buyers` | string\[] | all | Case-insensitive substrings matched against `buyerName`: `"National Defence"`, `"Shared Services Canada"`, `"Parks Canada"`. |
| `closingAfter` | string | – | Keep only tenders closing on or after this `YYYY-MM-DD`, so you skip notices that are about to close. Tender feeds only — setting it with `feed: "awards"` fails the run with a clear message rather than returning nothing. |
| `maxItems` | integer | `500` | Stop after this many notices. You are charged per notice delivered, so this is also the cost cap. Records are newest-first, so a value below the number of matches drops the **oldest** notices in the window. |

Example — open IT and engineering tenders from the last two weeks that are still open in three weeks' time:

```json
{
  "feed": "open",
  "daysBack": 14,
  "unspscPrefixes": ["43", "81"],
  "closingAfter": "2026-09-29",
  "maxItems": 500
}
```

Example — every contract National Defence awarded last month, with the winning suppliers:

```json
{
  "feed": "awards",
  "dateFrom": "2026-08-01",
  "dateTo": "2026-08-31",
  "buyers": ["National Defence"],
  "maxItems": 1000
}
```

Example — a daily job that returns whatever went up in the last 24 hours, unfiltered:

```json
{ "feed": "new", "maxItems": 200 }
```

**How `regions` matches.** It is a plain substring test on the notice's delivery regions, which are the source's own labels. A nationwide contract is published as `Canada` and will **not** match `"Ontario"`, so ask for `["Ontario", "Canada"]` if you want both. About one open tender in seven names no delivery region at all and is dropped whenever this filter is set.

Bad input fails fast: an unknown feed, a malformed date, a UNSPSC prefix that is not 2 to 8 digits, an unknown category or `closingAfter` on the award feed all stop the run with the validation message as its status, before anything is downloaded or charged.

### Output example

A real open tender notice from a live run on 8 September 2026 (`{"feed":"open","daysBack":7}`):

```json
{
  "id": "cb-772-82428859-000",
  "source": "canadabuys",
  "feed": "open",
  "url": "https://canadabuys.canada.ca/en/tender-opportunities/tender-notice/cb-772-82428859",
  "externalNoticeUrl": null,
  "referenceNumber": "cb-772-82428859",
  "amendmentNumber": "000",
  "solicitationNumber": "RFSO SEN-019",
  "contractNumber": null,
  "title": "RFSO Multipurpose Paper & Envelopes",
  "description": "The Senate of Canada (Senate) is seeking an offer to establish a Standing Offer (SO) for Multipurpose Paper and Envelopes as described in Annex “A” - Statement of Requirement (SOR), for a period of three (3) years with the Senate having the option to review for two (2) additional one-year (1) terms.",
  "noticeType": "Request for Standing Offer",
  "procurementCategory": ["GD"],
  "procurementCategoryNames": ["Goods"],
  "procurementMethod": "Competitive - Open bidding",
  "selectionCriteria": "Highest Combined Rating of Technical Merit and Price",
  "limitedTenderingReason": null,
  "instrumentType": null,
  "amendmentType": null,
  "status": "Open",
  "publicationDate": "2026-09-04",
  "amendmentDate": null,
  "closingDate": "2026-09-25T11:00:00",
  "expectedContractStart": "2026-11-01",
  "expectedContractEnd": "2029-10-31",
  "contractAwardDate": null,
  "contractStartDate": null,
  "contractEndDate": null,
  "contractAmount": null,
  "totalContractValue": null,
  "contractCurrency": null,
  "unspsc": ["14111500"],
  "unspscDescription": ["Printing and writing paper"],
  "gsin": [],
  "gsinDescription": [],
  "tradeAgreements": ["None"],
  "regionsOfOpportunity": ["National Capital Region (NCR)"],
  "regionsOfDelivery": ["Ontario (except NCR)"],
  "buyerName": "Senate",
  "buyerAddress": "1500-60 Queen Street, 15 Floor, Ottawa, Ontario, K1A 0A4, Canada",
  "buyerCity": "Ottawa",
  "buyerProvince": "Ontario",
  "buyerPostalCode": "K1A 0A4",
  "buyerCountry": "Canada",
  "endUserEntities": [],
  "supplierName": null,
  "supplierAddress": null,
  "supplierCity": null,
  "supplierProvince": null,
  "supplierCountry": null,
  "contactName": "Christine Tremblay",
  "contactEmail": "christine.tremblay@SEN.parl.gc.ca",
  "contactPhone": "(613) 995-8888",
  "attachments": [
    "https://canadabuys.canada.ca/sites/default/files/webform/tender_notice/105889/rfso-sen-019-2026-2027-multipurpose-printing-and-specialty-paper-final-1eng.pdf"
  ],
  "fetchedAt": "2026-09-08T02:35:30.780Z"
}
```

An award notice from the same day (`{"feed":"awards","daysBack":14}`), fields common to both omitted:

```json
{
  "id": "CW2456624-000",
  "feed": "awards",
  "url": "https://canadabuys.canada.ca/en/tender-opportunities/award-notice/cw2456624",
  "referenceNumber": "CW2456624",
  "solicitationNumber": "WS5742179565",
  "contractNumber": "CW2456624",
  "title": "EQ754-261618 - Long Lake Dam and Little Brother Lake Dam Replacement, Haliburton Ontario",
  "noticeType": "Request for Proposal",
  "procurementCategoryNames": ["Construction"],
  "procurementMethod": "Competitive - Open bidding",
  "instrumentType": "Contract",
  "amendmentType": "Original",
  "status": "Active",
  "publicationDate": "2026-09-04",
  "closingDate": null,
  "contractAwardDate": "2026-09-04",
  "contractStartDate": "2026-09-04",
  "contractEndDate": "2030-11-01",
  "contractAmount": 9857035.2,
  "totalContractValue": 9857035.2,
  "contractCurrency": "CAD",
  "unspsc": ["72141106", "81101507"],
  "unspscDescription": ["Gravel or dirt road construction service", "Dam engineering"],
  "regionsOfDelivery": ["Ontario (except NCR)"],
  "buyerName": "Department of Public Works and Government Services (PSPC)",
  "endUserEntities": ["Department of Public Works and Government Services (PSPC)"],
  "supplierName": "1014968-GDB CONSTRUCTEURS",
  "supplierAddress": "206 Fielding Road, Lively, Ontario, P3Y 1L6, CA",
  "supplierCity": "Lively",
  "supplierProvince": "Ontario",
  "contactName": "Premkumar, Mirunalini (SPAC/PSPC) (elle-la-lui / she-her-hers)",
  "contactEmail": "mirunalini.premkumar@tpsgc-pwgsc.gc.ca",
  "contactPhone": null
}
```

The run also writes a `SUMMARY` record to the key-value store with the counts, the resolved window, which CanadaBuys files were read, how many megabytes each was and when the source last refreshed it, and how many rows each filter rejected.

### Pricing

Pay per result: **US$0.003 per notice delivered** (US$3 per 1,000 notices). You are charged only for records pushed to the dataset, so a run that matches nothing costs nothing. The default run (`open`, 7 days) returned 123 notices on 8 September 2026 — about US$0.37. Runs on the Apify free plan are additionally limited by the platform's free-tier compute allowance.

### Data source and licence

- Open tender notices: `https://canadabuys.canada.ca/opendata/pub/openTenderNotice-ouvertAvisAppelOffres.csv`
- New tender notices (last 24 hours): `https://canadabuys.canada.ca/opendata/pub/newTenderNotice-nouvelAvisAppelOffres.csv`
- Award notices, per fiscal year: `https://canadabuys.canada.ca/opendata/pub/<fiscal year>-awardNotice-avisAttribution.csv`, e.g. `2026-2027-awardNotice-avisAttribution.csv`
- Dataset pages: [CanadaBuys tender notices](https://open.canada.ca/data/en/dataset/6abd20d4-7a1c-4b38-baa2-9525d0bb2fd2) and [CanadaBuys award notices](https://open.canada.ca/data/en/dataset/a1acb126-9ce8-40a9-b889-5da2b1dd20cb) on the Government of Canada Open Government Portal. Column-by-column reference: the [CanadaBuys supporting documentation](https://donnees-data.tpsgc-pwgsc.gc.ca/ba2/ac-cb/soutien-support-eng.html) and [data dictionary](https://donnees-data.tpsgc-pwgsc.gc.ca/ba2/ac-cb/achatscanada-canadabuys-dd.xml).
- Publisher: Public Services and Procurement Canada (PSPC), which runs CanadaBuys. Coverage is federal only: Schedule I, II and III departments, agencies and Crown corporations subject to the trade-agreement obligations. Provincial, municipal and MASH notices that appear on the CanadaBuys website are **not** in these files.
- **Licence: Open Government Licence – Canada** (<https://open.canada.ca/en/open-government-licence-canada>), as stated on both dataset pages and read on 8 September 2026. It permits commercial reuse, including redistribution and derivative works, provided you attribute the source. Use the wording the licence asks for: "Contains information licensed under the Open Government Licence – Canada." Do not suggest that PSPC endorses you or your product.

**Personal data.** `contactName`, `contactEmail` and `contactPhone` name a real procurement officer, and `supplierName` / `supplierAddress` can identify a sole trader. CanadaBuys publishes these deliberately so that suppliers can ask questions about the solicitation, and they are covered by the Open Government Licence, but that is not blanket consent for marketing. If you use these contacts for outreach you are the controller of that processing: Canada's Anti-Spam Legislation (CASL) governs commercial electronic messages to Canadian recipients and PIPEDA governs the personal information, and both are your responsibility, not the source's or this Actor's.

### Limits and freshness

All figures measured on 8 September 2026 unless stated.

- **Refresh schedule** (from the source's supporting documentation, and matching the `Last-Modified` headers observed): the **new tender notices** file is rebuilt every two hours from 06:15 to 22:15 Eastern; **all other files**, including the open-tender file and the award files, are refreshed **once a day between 07:00 and 08:30 Eastern**. Observed `Last-Modified`: open tenders `Mon, 07 Sep 2026 10:20 GMT`, awards 2026-2027 `Mon, 07 Sep 2026 09:30 GMT`, new tenders `Tue, 08 Sep 2026 02:15 GMT`. A daily schedule at 14:00 UTC or later always sees that morning's refresh.
- **Publication lag.** Because the daily files are built in the morning, the newest publication date in them is normally *yesterday*. Notices published through PSPC's SAP Ariba only enter the files the following day, and award notices coming from the Automated Buyer Environment (ABE) are transferred to CanadaBuys **monthly, in the third week of the month** — so a very recent ABE award can be up to a month late. Plan for a one-day lag on tenders and up to a month on some awards.
- **A short window over a weekend can be empty.** With 8 September 2026 as "today", `feed: "open"` with `daysBack: 3` returned **zero** notices: the newest publication date in that morning's file was Friday 4 September, and Monday 7 September was Labour Day. The default of 7 days exists for exactly this reason; do not go below 5 unless you are happy with empty weekend runs.
- **Volumes.** Combining the open file and the 2026-2027 tender file, CanadaBuys published a **median of 30 federal tender notices per weekday** (mean 32, range 19 to 46 over the 25 weekdays from 1 August to 4 September 2026; civic holidays drop to a handful, weekends to 1 or 2). **Award notices ran at a median of 23 per weekday** (mean 25, range 6 to 50 over those same 25 weekdays) and, unlike tenders, also arrive in weekend batches — 53 on Saturday 29 August, 60 on Saturday 5 September. The 2026-2027 award file held **3,331 notices** for 1 April to 5 September. In the default 7-day window the Actor returned 123 open tenders and 178 awards.
- **File sizes and run time.** One HTTP download per feed file - one for either tender feed, one per fiscal year for awards - streamed and parsed row by row so the file never lands in memory whole. Open tenders 6.5 MB / 945 rows; new tenders a few kilobytes; awards 2026-2027 11.4 MB / 3,331 rows; awards 2025-2026 27.8 MB / 7,848 rows. Measured wall-clock on a home connection: default open run 7.8 s, default award run 11.4 s, an award window straddling 1 April (two files, 39 MB) 31.5 s, a 365-day open window with `maxItems: 2000` 8.1 s. No rate limit is documented or was observed; a broken download is retried from the start up to three times.
- **The `open` feed is a snapshot, not an archive.** It contains only notices *still open for bidding today*, so asking for `dateFrom: "2026-05-01"` returns the May notices that have not closed yet, not everything published in May. The 2026-2027 fiscal-year tender file (which this Actor does not read) held 2,574 notices, all of them already `Expired` or `Cancelled`. For a complete tender history, use `awards` for what was bought and treat `open` as the live pipeline.
- **Award windows and fiscal years.** Awards are published one file per Government of Canada fiscal year (1 April to 31 March). A window that straddles 1 April downloads both files; a window that starts before 1 April 2022 is clamped, with a warning, because earlier awards are only in the CanadaBuys legacy file. A window entirely before 2022-2023 fails fast.
- **Closing times are Eastern local time, not a fixed offset.** The source documentation says `tenderClosingDate` is UTC-05:00, but the CanadaBuys website renders the same value as `EDT` in summer and `EST` in winter — checked against four live notice pages on 8 September 2026. The Actor therefore passes the value through unchanged: read it as America/Toronto wall-clock time.
- **`url` is derived, and 2.5% of award references have no page.** CanadaBuys builds a notice page alias from the reference number by lower-casing it and dropping every character that is not a letter, digit or hyphen (`PW-_NCS-030-11940` → `pw-ncs-030-11940`, `SSC-26-00034774:T` → `ssc-26-00034774t`). Spot-checked on 8 September 2026: 15 of 15 sampled open-tender URLs and 14 of 14 sampled award URLs with plain reference numbers returned HTTP 200, but only 1 of 17 sampled award references containing "/" (the older PSPC contract-number style, 2.5% of the 2026-2027 award file) resolved — the other 16 returned 404. `referenceNumber` and `solicitationNumber` are in every record so you can search the site for those.
- **Linking a tender to its award.** Use `solicitationNumber`. The source's own documentation warns that `referenceNumber` is not consistent between the tender and award files.
- **Contract values.** 72% of awards publish a non-zero `contractAmount` or `totalContractValue`; the rest publish `0.00`, which means "not disclosed" rather than "free". A null `contractCurrency` means CAD, per the source. Values are never converted between currencies.
- **`maxItems` truncation.** `maxItems` counts records *kept after filtering* and the stream is sorted newest-first, so a value below the number of matches drops the oldest notices in the window and logs `limitReached: true` in the log and the `SUMMARY` record. Measured on 8 September 2026, an unfiltered award run needs about 200 for 7 days, 450 for 14 and 700 for 30; an unfiltered open-tender run about 130, 280 and 500.
- **Deduplication and amendments.** Each file carries one row per notice at its current amendment level, so a notice amended three times appears once, with `amendmentNumber: "003"`. Records are de-duplicated on `id` within a run. Re-running a week later returns the same notice again with a higher `amendmentNumber` if it changed.
- **Bilingual source, English output.** Every CanadaBuys column is published in English and French; this Actor emits the English column and falls back to the French one only for `title` and `buyerName` when English is missing. Quebec and other French-language notices therefore come through with their French titles intact.
- **Safety valve.** A run stops reading after 200,000 matching notices and warns; no realistic window reaches that.

### Use cases

- **Daily bid pipeline.** Schedule `{"feed": "new"}` or `{"feed": "open", "daysBack": 2}` with your UNSPSC prefixes and delivery regions, and push straight to a CRM or Slack: title, closing date, buying department and the procurement officer's email are all in the record.
- **Competitor and incumbent tracking.** The award feed names the winning supplier, the contract value and the `contractEndDate`. Filter `buyers` or `unspscPrefixes` to your market, watch who wins, and diarise the re-tender before the incumbent's contract runs out.
- **Market sizing and compliance archives.** Pull a whole fiscal year of awards by category or department to size a market, or keep a dated archive of every notice a department published for bid-history and audit purposes.

### Support

Open an issue on the Actor's Issues tab on Apify Store. Replies within 14 days. Bug reports that name the run id and the input used are fixed fastest.

# Actor input Schema

## `feed` (type: `string`):

Which CanadaBuys file to read. 'Open tenders' holds every notice still open for bidding (about 950 at any time). 'New tenders' is the last 24 hours only. 'Award notices' holds contract awards for the fiscal years your date window touches.

## `daysBack` (type: `integer`):

Look-back window in days on the notice's publication date, ending now. Ignored when Date from / Date to are set. Left empty it means 7 days for the open and award feeds, and no date filter at all for the 24-hour 'New tenders' feed.

## `dateFrom` (type: `string`):

Earliest publication date to keep, YYYY-MM-DD (inclusive). Overrides Days back.

## `dateTo` (type: `string`):

Latest publication date to keep, YYYY-MM-DD (inclusive). Defaults to today.

## `keywords` (type: `array`):

Case-insensitive terms matched against the notice title and description; a notice is kept when any term matches. Empty = no keyword filter.

## `unspscPrefixes` (type: `array`):

United Nations Standard Products and Services Codes or prefixes, 2 to 8 digits. 43 = IT and telecom equipment, 81 = engineering and research services, 72 = building and maintenance services, 42 = medical equipment, 25 = vehicles. A notice is kept when any of its UNSPSC codes starts with any prefix. Empty = all.

## `gsinPrefixes` (type: `array`):

Goods and Services Identification Numbers or prefixes, 1 to 10 letters/digits, for example N7030 (ADP software) or D3 (telecommunication services). Only a minority of notices carry a GSIN - see the README. Empty = all.

## `categories` (type: `array`):

Keep only these CanadaBuys procurement categories: goods (GD), services (SRV), construction (CNST) or services related to goods (SRVTGD). A notice can carry more than one. Empty = all categories.

## `regions` (type: `array`):

Case-insensitive substrings matched against the notice's regions of delivery, for example "Ontario", "Quebec", "National Capital Region", "British Columbia". A national contract is published as "Canada" and will NOT match a province name, and about one notice in seven names no region at all and is dropped when this filter is set. Empty = all.

## `buyers` (type: `array`):

Case-insensitive substrings matched against the contracting entity name, for example "National Defence", "Shared Services Canada", "Parks Canada". Empty = all buyers.

## `closingAfter` (type: `string`):

Keep only tenders whose closing date is on or after this day, YYYY-MM-DD - use it to skip notices that are about to close. Tender feeds only; award notices have no closing date and the run fails fast if you set it with feed 'awards'.

## `maxItems` (type: `integer`):

Stop after this many notices. You are charged per notice delivered. Results are ordered newest publication date first, so a value below the number of matches drops the OLDEST notices in the window.

## Actor input object example

```json
{
  "feed": "open",
  "daysBack": 7,
  "keywords": [],
  "unspscPrefixes": [],
  "gsinPrefixes": [],
  "categories": [],
  "regions": [],
  "buyers": [],
  "maxItems": 500
}
```

# Actor output Schema

## `results` (type: `string`):

All notices returned by the run, one JSON object each, newest publication date first. Append ?format=csv or ?format=xlsx to download a spreadsheet.

## `summary` (type: `string`):

Counts, the date window used, which CanadaBuys files were read and when each was last refreshed, why rows were filtered out, and any warnings, written at the end of the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "feed": "open",
    "daysBack": 7,
    "keywords": [],
    "unspscPrefixes": [],
    "gsinPrefixes": [],
    "categories": [],
    "regions": [],
    "buyers": [],
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("knotty_mistveil/canadabuys-tender-notices").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "feed": "open",
    "daysBack": 7,
    "keywords": [],
    "unspscPrefixes": [],
    "gsinPrefixes": [],
    "categories": [],
    "regions": [],
    "buyers": [],
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("knotty_mistveil/canadabuys-tender-notices").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "feed": "open",
  "daysBack": 7,
  "keywords": [],
  "unspscPrefixes": [],
  "gsinPrefixes": [],
  "categories": [],
  "regions": [],
  "buyers": [],
  "maxItems": 500
}' |
apify call knotty_mistveil/canadabuys-tender-notices --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,knotty_mistveil/canadabuys-tender-notices"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/pHLTfpBXGJPocQP9p/builds/mFxTBUNiC1mFETagy/openapi.json
