# Canada Recalls & Safety Alerts Search (`coachable_bufflehead/canada-recalls-search`) Actor

Search Government of Canada recalls and safety alerts for food, vehicles, health and consumer products.

- **URL**: https://apify.com/coachable\_bufflehead/canada-recalls-search.md
- **Developed by:** [Qi Wang](https://apify.com/coachable_bufflehead) (community)
- **Categories:** Business, News, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Canada Recalls Search

Search the Government of Canada's **Recalls and Safety Alerts** (food, vehicles, health products, medical devices, consumer products and cannabis) and export structured results: title, category, dates, issue, product description, brand, models, hazard and what to do.

> **Unofficial tool.** This Actor is not affiliated with, endorsed by or operated by the Government of Canada, Health Canada, the Canadian Food Inspection Agency or Transport Canada. All data comes from information the Government of Canada publishes openly at [recalls-rappels.canada.ca](https://recalls-rappels.canada.ca/en). Always check the linked official notice before acting on a recall.

### Use cases

#### 1. E-commerce sellers: compliance checks before you list or restock

Amazon, Shopify and marketplace sellers in Canada can check a brand or product against recent recalls before listing it, and re-check their catalogue every week. A hit means: pause the listing, contact the supplier, and read the official notice.

```json
{ "query": "baby crib", "category": "consumer-products", "dateFrom": "2026-01-01", "maxResults": 20 }
```

Feed every brand from your catalogue as a separate run (or Apify Task) and alert on any non-empty dataset.

#### 2. AI agents: a tool call that answers "is this product recalled in Canada?"

The input is small and the output is flat JSON, so an LLM agent can call it through the [Apify MCP server](https://mcp.apify.com) or the Apify API as a tool. A typical agent call is fast and feed-only:

```json
{ "query": "peanut butter", "category": "food", "maxResults": 5, "includeDetails": false }
```

The agent then reads `title`, `dateUpdated`, `hazard` and `url` and links the user to the official notice. Turn `includeDetails` on when the agent needs `brand`, `models` or `whatToDo`.

#### 3. Procurement and supply-chain teams: risk screening of suppliers and parts

Screen suppliers, brands or component types (lithium batteries, airbags, medical devices) before signing a purchase order, and monitor them on a schedule.

```json
{ "query": "lithium battery", "category": "all", "dateFrom": "2025-10-01", "maxResults": 100 }
```

Run it weekly with an [Apify Schedule](https://docs.apify.com/platform/schedules) and send new rows to Slack, email or a spreadsheet with an Apify integration.

### How it works

1. Downloads the official open-data JSON feed of the Recalls and Safety Alerts site (updated daily):

   - English: `https://recalls-rappels.canada.ca/sites/default/files/opendata-donneesouvertes/HCRSAMOpenData.json`
   - French: `https://recalls-rappels.canada.ca/sites/default/files/opendata-donneesouvertes/SCRSAMDonneesOuvertes.json`

   Dataset page: [Recalls and Safety Alerts – Open Government Portal](https://open.canada.ca/data/en/dataset/d38de914-c94c-429b-8ab1-8776c31643e3).
2. Filters notices **locally** (see below), sorted with the most recently updated first.
3. Optionally (`includeDetails`, on by default) opens each matching notice page over plain HTTP and reads the brand, models, hazard, product description, publication date and "What you should do" sections. No browser is used.

The feed has no brand, model or hazard columns. These fields come from the detail pages, and the page layout varies by publisher (CFIA, Health Canada, Transport Canada). When a section is missing, the Actor falls back to feed data. `detailsFetched` tells you whether the detail page was read.

### How filtering works (local, not server-side)

All filtering (`query`, `category`, `dateFrom`, `includeArchived`) happens **inside the Actor, after download**. The official source is a static JSON file. It accepts no search, category, date or paging parameters, and any query string sent to it is ignored. The website's own search (`/en/search/site?search_api_fulltext=…`) is an HTML page, not an API, so the Actor does not use it.

- **Keywords (`query`)**: every word must appear, ignoring case and accents, in at least one of `title`, `productDescription`, `brand` or `models`. Simple plurals match too (`raspberry` finds "raspberries"). Text that appears only in `issue`, the category or the organization does not count.
- **Category**: matched against the normalized `category`, which is derived from the publishing organization and the feed's category text.
- **`dateFrom`**: compared with the feed's "Last updated" date (`dateUpdated`).
- **Paging and re-checking**: matching notices are processed in pages, newest first. With `includeDetails`, each page's detail pages are fetched and every item is checked again against its final `title`, `productDescription`, `brand` and `models`. For example, a detail-page description can replace the feed's product name. Items that no longer match are dropped, and the Actor moves on to the next page. It stops at `maxResults` matches or when no notices are left.
- **Limitation**: brand and model names that appear *only* on a detail page, and not in the feed's title or product name, cannot be searched. Finding them would mean downloading every one of the ~34,000 detail pages.

To check the filters against the live feed, run `npm run verify`. It fails if any result does not contain the keywords or violates the category or date filter.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `query` | string | `""` | Keywords matched locally against `title`, `productDescription`, `brand` and `models`. All words must match. Matching ignores case and accents. Leave empty to list the latest notices. |
| `category` | string | `all` | One of `all`, `food`, `vehicles`, `health-products`, `medical-devices`, `consumer-products`, `cannabis`. |
| `dateFrom` | string | – | `YYYY-MM-DD` (a full ISO timestamp is also accepted). Only notices last updated on or after this date. |
| `maxResults` | integer | `50` | 1–1000. |
| `language` | string | `en` | `en` or `fr`. Selects the feed and the detail-page language. |
| `includeDetails` | boolean | `true` | Visit notice pages for brand, models, hazard and similar fields. Turn it off for a fast, feed-only run. |
| `includeArchived` | boolean | `true` | Include notices the publisher has archived. |

Example:

```json
{
    "query": "peanut",
    "category": "food",
    "dateFrom": "2026-01-01",
    "maxResults": 20,
    "language": "en"
}
```

### Output

Each dataset item:

| Field | Description |
|---|---|
| `recallId` | Notice ID (the feed's `NID`). |
| `title` | Notice title. |
| `category` | Normalized category (`food`, `vehicles`, `health-products`, `medical-devices`, `consumer-products`, `cannabis`, `other`). |
| `subcategory` | The feed's original category text, e.g. `Allergen - Peanut`. |
| `organization` | Publishing organization from the feed (e.g. `CFIA`, `TC`). |
| `datePublished` | Publication/start date from the notice page. Falls back to the feed's "Last updated" date. |
| `dateUpdated` | The feed's "Last updated" date. |
| `issue` | Short issue label from the feed (e.g. `Allergen - Peanut`, `Fire hazard`). |
| `productDescription` | Product description section of the page. Falls back to the feed's product name. |
| `brand` | Brand names from the affected-products table or labels. Falls back to the brand named in the title. |
| `models` | Model names/numbers (consumer products, vehicles, devices), when listed. |
| `hazard` | "Hazard identified" section. Falls back to the feed's issue label. |
| `whatToDo` | "What you should do" section. |
| `recallClass` | Recall class/type when provided (e.g. `Class 2`, `Type II`). |
| `archived` | Whether the publisher archived the notice. |
| `language` | `en` or `fr`. |
| `url` | Official notice URL. |
| `detailsFetched` | `true` if the detail page was loaded and parsed. |
| `fetchedAt` | ISO timestamp of this run. |

Example item (illustrative; values depend on the live notice):

```json
{
    "recallId": "82692",
    "title": "Certain Summerhill Market brand raspberries and raspberry-containing products recalled due to norovirus",
    "category": "food",
    "subcategory": "Frozen - Other",
    "organization": "CFIA",
    "datePublished": "2026-09-29",
    "dateUpdated": "2026-09-29",
    "issue": "Norovirus",
    "productDescription": "Frozen raspberries, frozen berry mix, frozen smoothie mix, protein power pod raspberry",
    "brand": ["Summerhill Market"],
    "models": [],
    "hazard": "Norovirus",
    "whatToDo": "Do not consume, serve, use, sell, or distribute recalled products.\nCheck to see if you have recalled products.",
    "recallClass": "Class 2",
    "archived": false,
    "language": "en",
    "url": "https://recalls-rappels.canada.ca/en/alert-recall/certain-summerhill-market-brand-raspberries-and-raspberry-containing-products-recalled",
    "detailsFetched": true,
    "fetchedAt": "2026-09-30T12:00:00.000Z"
}
```

### FAQ

**Is this an official Government of Canada service?**
No. It is an unofficial tool that reads the Government of Canada's open data. Always confirm with the linked official notice.

**How fresh is the data?**
The official feed is refreshed daily. Every run downloads the current feed, so results are as fresh as the feed. `fetchedAt` shows when the run read it.

**Why did my brand search miss a recall?**
Keywords are matched against the title, product description, brand and models. A brand that appears only deep inside a detail page and nowhere in the feed cannot be found (see "How filtering works").

**How do I make it fast and cheap?**
Set `includeDetails` to `false`. The run then reads only the feed and finishes in seconds. Keep `maxResults` small.

**Does it cover the United States or other countries?**
No. Only notices published on recalls-rappels.canada.ca (Health Canada, CFIA, Transport Canada).

**Can I get French results?**
Yes. Set `language` to `fr`.

**Can I monitor new recalls automatically?**
Yes. Save your input as an Apify Task, run it on a Schedule with `dateFrom` set to a recent date, and connect an integration (Slack, email, webhook, Google Sheets) to the run's dataset.

### Local development

```bash
npm install
npm test            # unit tests (feed normalization, filtering, paging, detail-page parsing)
npm run verify      # check query/category/dateFrom filtering against the live feed
npm run build
apify run --input '{"query":"peanut","maxResults":5}'   # results in storage/datasets/default
```

### Data licence

Contains information licensed under the [Open Government Licence – Canada](https://open.canada.ca/en/open-government-licence-canada). Source: Government of Canada, Recalls and Safety Alerts.

# Actor input Schema

## `query` (type: `string`):

Keywords to match against the notice title, product, issue, category and organization. All words must match (case- and accent-insensitive). Leave empty to list the latest notices.

## `category` (type: `string`):

Limit results to one kind of notice.

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

Only return notices whose last update is on or after this date (YYYY-MM-DD).

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

Maximum number of notices to return, most recently updated first.

## `language` (type: `string`):

Language of the feed and detail pages.

## `includeDetails` (type: `boolean`):

Visit each notice page to extract brand, models, hazard, product description and publication date. Turn off for a faster, feed-only run.

## `includeArchived` (type: `boolean`):

Include notices the publisher has archived.

## Actor input object example

```json
{
  "query": "peanut",
  "category": "all",
  "maxResults": 50,
  "language": "en",
  "includeDetails": true,
  "includeArchived": true
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing the matching recalls and safety alerts

# 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 = {
    "query": "peanut"
};

// Run the Actor and wait for it to finish
const run = await client.actor("coachable_bufflehead/canada-recalls-search").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 = { "query": "peanut" }

# Run the Actor and wait for it to finish
run = client.actor("coachable_bufflehead/canada-recalls-search").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 '{
  "query": "peanut"
}' |
apify call coachable_bufflehead/canada-recalls-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,coachable_bufflehead/canada-recalls-search"
        }
    }
}
```

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/d1LeO7dGgxf7px8Iv/builds/Urav9wNkZFnTfdY50/openapi.json
