# Canadian Federal Tender Alerts & Change Monitor (`tender_signall/canadian-federal-tenders-monitor`) Actor

Find relevant Canadian federal tenders and detect amendments, deadline changes, and removals using the official CanadaBuys open-data feed.

- **URL**: https://apify.com/tender\_signall/canadian-federal-tenders-monitor.md
- **Developed by:** [Tender Signall](https://apify.com/tender_signall) (community)
- **Categories:** Business, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 delivered opportunity or changes

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/platform/actors/running/actors-in-store#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

## Canadian Federal Tender Alerts & Change Monitor

Turn the official CanadaBuys open-tender feed into a focused, recurring opportunity watchlist. Get only the federal tenders that fit your business, then receive new notices, amendments, deadline changes, and meaningful field updates as clean rows for Apify, CSV, API, schedules, and webhooks.

**Best for:** Canadian suppliers, bid teams, consultants, contractors, software vendors, and procurement-intelligence workflows that cannot afford to re-read the same tender list every day.

No browser, proxy, CAPTCHA solver, AI model, external API key, or CanadaBuys login is required.

> **Unofficial tool.** This Actor is not affiliated with, sponsored by, or endorsed by the Government of Canada, CanadaBuys, or Public Services and Procurement Canada. CanadaBuys remains the source of truth.

### Start in 60 seconds

1. Choose a **Quick-start preset** or keep **Custom filters**.
2. Give the watchlist a stable `monitorId`.
3. Click **Start**. The first run returns clearly labelled `baseline` rows.
4. Inspect or export the clean Dataset table.
5. Save the input as an Apify Task and schedule it daily with the same `monitorId`.

The default custom example is ready to run for Canadian federal IT and software opportunities.

#### Example of what you receive

Illustrative Dataset view:

| Event | Score | Tender | Organization | Deadline | What changed |
|---|---:|---|---|---|---|
| `amended` | 83 | Cloud security services | Shared Services Canada | 2026-09-04 | amendment number, description |
| `deadline_changed` | 76 | Data platform modernization | Public Services and Procurement Canada | 2026-09-12 | closing date |
| `new` | 71 | Software licensing and support | Department of National Defence | 2026-09-18 | new since previous snapshot |

Every row links back to the official notice and explains why it matched. Actual results depend on the live source and your filters.

### Why use this instead of checking CanadaBuys manually?

| Need | CanadaBuys website | This Actor |
|---|---|---|
| Official notice and bid documents | Source of truth | Direct official link on every result |
| Search current opportunities | Yes | Yes, through reusable filters and presets |
| Detect exactly which fields changed | Manual comparison | `changedFields`, previous values, and event types |
| Receive delta-only structured data | Limited manual/email workflow | Dataset, CSV, Excel, JSON, XML, RSS, JSONL, and API |
| Reuse the same watchlist every day | Saved search | Stateful `monitorId`, Task, Schedule, and webhook workflow |
| Audit source quality | Manual | Source metadata, schema guards, counts, warnings, and run summary |

CanadaBuys is free and authoritative. This Actor charges for the normalization, stateful comparison, precision filtering, and automation layer—not for ownership of the public data.

### Quick-start presets

Choose a preset to replace the custom keyword and category fields:

| Preset | Designed to find |
|---|---|
| **IT & software** | Software, SaaS, cloud, digital services, IT, and data analytics |
| **Cybersecurity** | Cybersecurity, zero trust, assessments, penetration testing, SIEM, and SOC work |
| **Construction** | Federal construction, renovation, building, infrastructure, roofing, mechanical, and electrical work |
| **Consulting** | Advisory, professional services, project management, business analysis, and management consulting |
| **Custom** | Your own keywords, official categories, organizations, regions, and classification codes |

Presets are deliberately deterministic and visible—there is no hidden AI classification.

### Custom input example

```json
{
  "preset": "custom",
  "monitorId": "canada-cyber-opportunities",
  "keywords": ["cybersecurity", "zero trust", "penetration testing"],
  "excludeKeywords": ["physical security guard"],
  "matchMode": "any",
  "categories": ["SRV", "GD", "SRVTGD"],
  "organizations": ["National Defence", "Shared Services Canada"],
  "regions": [],
  "gsinCodes": [],
  "unspscCodes": ["8111"],
  "publishedWithinDays": 0,
  "minDaysRemaining": 2,
  "closingWithinDays": 90,
  "minScore": 40,
  "outputMode": "changes_only",
  "emitBaseline": true,
  "maxResults": 500,
  "resetState": false
}
```

#### Precision controls

- `keywords` and `excludeKeywords` use whole-word or exact-phrase matching across bilingual titles, descriptions, classifications, organizations, and regions.
- `matchMode` can require any configured keyword or all of them.
- `categories` supports the official `CNST`, `GD`, `SRV`, and `SRVTGD` values.
- `organizations` and `regions` narrow the feed to target buyers and delivery/opportunity regions.
- `gsinCodes` and `unspscCodes` accept a full code or a broader prefix. A tender may match any configured code.
- `publishedWithinDays` optionally limits the original publication age. Keep it at `0` when older tenders may still receive important amendments.
- `minDaysRemaining` and `closingWithinDays` define an actionable deadline window.
- `minScore` suppresses lower-priority rows: `0` maximizes recall, `40` is a useful clean-feed starting point, and `70` is strict.

### What it detects

| Event | Meaning |
|---|---|
| `baseline` | First reliable observation. It is deliberately not labelled “new.” |
| `new` | The reference number was absent from the previous snapshot. |
| `amended` | The official amendment number or amendment date changed. |
| `deadline_changed` | The official tender closing value changed. |
| `updated` | Another monitored field changed, such as title, description, organization, category, status, or official URL. |
| `removed_from_open` | The record disappeared from the current open-tenders file. This does not by itself prove cancellation. |
| `unchanged` | Returned only in `all_matching` mode. |

Multiple changes can occur together. `eventType` is the primary event; `changeTypes` and `changedFields` preserve the complete explanation.

### Output

Each Dataset row includes:

- event type, all change types, changed fields, and previous amendment/deadline/status values;
- deterministic `relevanceScore`, a plain-language reason, and matched keywords;
- English and French titles and organizations when supplied upstream;
- categories, GSIN/UNSPSC codes, regions, dates, procurement method, and notice type;
- a short privacy-redacted description excerpt;
- an official notice or CanadaBuys search URL;
- source record ID, source URL, timezone, and collection timestamp.

Example JSON shape:

```json
{
  "eventType": "deadline_changed",
  "changeTypes": ["deadline_changed", "updated"],
  "changedFields": ["closingDate", "description"],
  "relevanceScore": 76,
  "relevanceReason": "Matched \"cloud\" in title and classification; closes in 18 days; closing deadline changed.",
  "titleEn": "Cloud infrastructure services",
  "organizationEn": "Shared Services Canada",
  "closingDate": "2026-09-12T14:00:00",
  "previousClosingDate": "2026-09-05T14:00:00",
  "officialNoticeUrl": "https://canadabuys.canada.ca/en/tender-opportunities/tender-notice/example"
}
```

The `OUTPUT` key-value record contains the auditable run summary: source metadata, validation counts, match counts, event counts, delivered rows, queued backlog, truncation reasons, baseline reason, and state status.

### No-lost-change delivery

`maxResults` and Apify's maximum run-cost control can limit what is written in one run. Version 0.2 keeps every undelivered selected row in a private backlog and delivers that backlog before fresh results on the next run.

This means:

- a large change day does not silently consume unreturned alerts;
- a spending limit does not discard unpaid rows;
- already delivered rows are not deliberately repeated just to advance the snapshot;
- the run summary shows `pendingItemsAfterRun` and why any rows were queued.

If another run updates the same monitor concurrently, the stale run cannot overwrite the newer state. Avoid overlapping runs with the same `monitorId`.

### Pricing

Transparent pay-per-event pricing:

- **$0.01 per Actor start**;
- **$0.005 per delivered Dataset row**;
- Apify platform usage is included in those event prices.

Examples:

| Run result | Event charge |
|---|---:|
| No changes | $0.01 |
| 1 delivered opportunity/change | $0.015 |
| 10 delivered opportunities/changes | $0.06 |
| 100 delivered opportunities/changes | $0.51 |

A first baseline can contain multiple chargeable rows. Use `maxResults` and the maximum run-cost control to bound the first run. The minimum allowed budget is $0.015, enough for the start and one result.

### Daily alerts: Task, Schedule, and webhook

The official **Open Tender Notices** file is refreshed each morning between 7:00 and 8:30 at the source's documented UTC-05:00 clock. Hourly runs do not create fresher upstream data.

Recommended production setup:

1. Run the Actor once and review its `baseline` rows.
2. Click **Save as new task** and keep the tested input unchanged.
3. Schedule the Task **daily at 10:00 AM in `America/Toronto`**. Apify schedules support timezone/DST handling.
4. Reuse the exact same `monitorId`; changing filters automatically starts a replacement baseline rather than generating false “new” alerts.
5. Add an `ACTOR.RUN.SUCCEEDED` webhook from the Task to Make, Zapier, n8n, Slack, email, Google Sheets, or your own endpoint.
6. Read the Dataset for alerts and `OUTPUT` for the run receipt. A successful no-change run normally has an empty Dataset and a populated `OUTPUT` summary.

Apify references: [Actor Tasks](https://docs.apify.com/actors/running/tasks), [Schedules](https://docs.apify.com/actors/running/schedules), and [webhook events](https://docs.apify.com/integrations/webhooks/events).

### How it works

1. Downloads the authoritative CanadaBuys **Open Tender Notices** CSV over HTTPS.
2. Rejects unexpected hosts, HTML responses, oversized files, missing headers, malformed CSV, or excessive invalid records.
3. Normalizes bilingual source fields into a stable schema.
4. Applies preset/custom business-fit and deadline filters.
5. Compares matches with the previous successful snapshot for the same `monitorId`.
6. Scores and sorts explainable opportunity events.
7. Writes affordable, bounded output and queues anything not delivered.
8. Saves the source snapshot and backlog only after successful parsing, output, and concurrency checks.

#### Relevance score

The auditable 0–100 score contains:

- keyword location: up to 65 points (title > classification > description > organization > region);
- closing urgency: up to 20 points;
- publication/amendment freshness: up to 10 points;
- new/amended/deadline event: 5 points.

Use it for triage, not as a legal or bid/no-bid decision.

### Reliability, privacy, and compliance

- Hard-coded, allowlisted CanadaBuys HTTPS source; no user-supplied URL or SSRF surface.
- Download timeout, bounded retries, and a 25 MB source ceiling.
- Strict CSV column/header validation and an invalid-record quality threshold.
- Reference-number deduplication and conservative source-removal handling.
- Versioned state, watchlist fingerprints, stale-run concurrency protection, and persistent delivery backlog.
- Structured contact names, emails, phone/fax numbers, and postal addresses are not extracted.
- Common email addresses and North American phone numbers are redacted from description excerpts.
- No login, cookies, proxy, CAPTCHA bypass, or access-control circumvention.

### Important limitations

- Version 0.2 monitors the federal **Open Tender Notices** file. It does not claim complete provincial or municipal coverage.
- An absent record is labelled `removed_from_open`, not “cancelled.” Verify the final status on CanadaBuys.
- Some notices originate in external procurement systems, so bilingual fields or direct URLs can be blank upstream.
- The Actor does not download or interpret attachments, submit bids, or provide procurement/legal advice.
- Always verify deadlines, amendments, requirements, and bid instructions on the official notice before acting.

### Data source and licence

- [CanadaBuys tender notices dataset](https://open.canada.ca/data/en/dataset/6abd20d4-7a1c-4b38-baa2-9525d0bb2fd2)
- [CanadaBuys data dictionary](https://donnees-data.tpsgc-pwgsc.gc.ca/ba2/ac-cb/achatscanada-canadabuys-dd.xml)
- [Open Government Licence — Canada](https://open.canada.ca/en/open-government-licence-canada)

Contains information licensed under the Open Government Licence — Canada. The data is transformed and normalized by this unofficial Actor; the Government of Canada has not endorsed the transformation.

### Local development

Requires Node.js 20+.

```bash
npm ci
npm test
npm run check
npm run build
```

The production image uses the browser-free `apify/actor-node:24` base and a multi-stage TypeScript build.

# Actor input Schema

## `preset` (type: `string`):

Choose a ready-made market watchlist. A preset replaces the custom keywords and categories below; choose Custom to use your own.

## `monitorId` (type: `string`):

Stable name for this watchlist. Reuse it on scheduled runs to compare with the previous snapshot. Use a different ID for each independent watchlist.

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

Custom mode only. Whole words or phrases matched across bilingual titles, descriptions, classifications, organizations, and regions. Leave empty to keep every tender that passes the other filters.

## `excludeKeywords` (type: `array`):

Discard a tender when any excluded whole word or phrase appears in its searchable fields.

## `matchMode` (type: `string`):

ANY keeps a tender matching at least one keyword. ALL requires every configured keyword.

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

Custom mode only. Leave empty for all official CanadaBuys categories.

## `gsinCodes` (type: `array`):

Optional classification targeting, for example D302A. A tender may match any configured exact code or code prefix.

## `unspscCodes` (type: `array`):

Optional classification targeting. Use a full code such as 81111500 or a broader prefix such as 8111.

## `organizations` (type: `array`):

Optional partial organization names, for example National Defence. A tender may match any listed organization.

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

Optional region names, matched against official opportunity and delivery regions in either language.

## `publishedWithinDays` (type: `integer`):

Optional publication-age filter. Use 0 for any publication date; amendments to older notices are excluded when this filter is active.

## `minDaysRemaining` (type: `integer`):

Exclude opportunities closing sooner than this. Partial days are rounded up.

## `closingWithinDays` (type: `integer`):

Only keep opportunities whose official closing time is within this many days.

## `minScore` (type: `integer`):

Suppress lower-priority results using the Actor's transparent 0–100 score. Use 0 for maximum recall, 40 for a cleaner feed, or 70 for strict triage.

## `outputMode` (type: `string`):

Changes only is best for schedules. All matching also returns unchanged current opportunities. New only suppresses amendments and removals.

## `emitBaseline` (type: `boolean`):

Recommended. The first run has no earlier snapshot, so matches are labelled baseline—not falsely labelled new.

## `maxResults` (type: `integer`):

Limits rows written in one run. Undelivered rows are queued and delivered first on the next run, so the monitor does not lose changes.

## `resetState` (type: `boolean`):

Ignore the stored snapshot and create a new baseline for this monitor ID. Leave off for normal and scheduled runs.

## Actor input object example

```json
{
  "preset": "custom",
  "monitorId": "canada-it-opportunities",
  "keywords": [
    "software",
    "cloud",
    "cybersecurity",
    "data analytics"
  ],
  "excludeKeywords": [],
  "matchMode": "any",
  "categories": [
    "SRV",
    "GD"
  ],
  "gsinCodes": [],
  "unspscCodes": [],
  "organizations": [],
  "regions": [],
  "publishedWithinDays": 0,
  "minDaysRemaining": 0,
  "closingWithinDays": 90,
  "minScore": 0,
  "outputMode": "changes_only",
  "emitBaseline": true,
  "maxResults": 500,
  "resetState": false
}
```

# Actor output Schema

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

Validated, normalized result rows from the official CanadaBuys open-tender dataset.

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

Source metadata, record counts, validation warnings, queued backlog, and snapshot status.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("tender_signall/canadian-federal-tenders-monitor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("tender_signall/canadian-federal-tenders-monitor").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 '{}' |
apify call tender_signall/canadian-federal-tenders-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tender_signall/canadian-federal-tenders-monitor"
        }
    }
}

```

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/eUD8nH7pUoyOHWQTP/builds/Jvj3nwim3nx6t3Is7/openapi.json
