# CPSC Product Recalls API — US Consumer Product Recall Watch (`mouadapi/cpsc-product-recalls`) Actor

US product recalls from the CPSC database by product words and date, or only new and updated ones: products, hazards, injuries, remedy, firms and the recall page; failed or unchanged rows are never charged. Not affiliated with or endorsed by the U.S. Consumer Product Safety Commission (CPSC).

- **URL**: https://apify.com/mouadapi/cpsc-product-recalls.md
- **Developed by:** [COMPASS DEV](https://apify.com/mouadapi) (community)
- **Categories:** Business, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 recall returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Returns US consumer product recalls from the CPSC recall database, filtered by product words and recall date, or only new and updated recalls since your last run: recall number and date, products, hazards, injuries, remedy, firms and the recall page; failed or unchanged rows are never charged.

Give product words (helmet, power bank, crib, a model number) or none, and a window; get one flat row per recall of the
U.S. Consumer Product Safety Commission (CPSC): the title, what the recall covers, the products and their types, the units
recalled, the hazard, the injuries reported, the remedy (refund, replace, repair), the manufacturers, importers,
distributors and retailers, where it was sold, the country of manufacture, photo links and the recall page on cpsc.gov.
Built for retailers, marketplaces, importers, compliance and product-safety teams, newsrooms and AI agents that need US
product recalls without polling cpsc.gov. *Not affiliated with or endorsed by the U.S. Consumer Product Safety Commission
(CPSC).*

### What it does

- **Export mode:** every recall dated in the window (default: the last 90 days) whose title, product names, models,
  product types or description hold every word of an entry, newest first, one flat row per recall. No words: every recall
  of the window.
- **Watch mode** (give a watch list name in `stateName`): remembers every recall it returned and gives only:
  - `baseline`: the first run of the list;
  - `new`: a recall the list did not have;
  - `changed`: CPSC updated the notice (its last publish date, title, products, hazard, remedy, units, injuries or firms;
    `changedFields` and `previousValues` say what and from what);
  - unchanged recalls are free and left out (`includeUnchanged: true` returns them as free rows).
  - A watch run with nothing new or updated returns **exactly one free `no_data` row** that says so.
- **No contacts, no personal data:** the firms' phone numbers and email addresses are never returned (the notice's
  `ConsumerContact` is not read into any row, and a phone number or email address inside a text becomes
  "\[phone on the recall page]" or "\[email on the recall page]"; the recall page in `url` has them). A firm whose name is,
  or may be, a person's (for example a sole trader trading under their own name) is left out of the recall's fields:
  `firmNamesOmitted` is `true`, the name is removed from every text, a text that still holds a word of it is `null`, and a
  title that named it becomes a neutral title such as "Recall: Table Lamps". When in doubt, the name is removed. The recall
  itself is returned and charged like any other.
- **One read per run:** the window is read once (one request; a window over a year, one request per year) and every entry
  is matched in it. A recall that matches two entries is returned and charged once, under the first.
- **You are never charged for failed results:** failed, no_data and unchanged rows are free.

### Quick start

Bike helmet recalls of the last 90 days:

```json
{ "queries": ["helmet"] }
```

Watch every new or updated recall (the first run is the baseline; later runs return only new and updated recalls; run it
daily with an Apify schedule):

```json
{ "stateName": "cpsc-recalls", "sinceDays": 30 }
```

Recalls of power banks, cribs and space heaters in the first half of 2026:

```json
{ "queries": ["power bank", "crib", "space heater"], "recalledFrom": "2026-01-01", "recalledTo": "2026-06-30" }
```

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `queries` | list of strings | — (prefilled `helmet`) | Product words, one entry per line: the recalls whose title, product names, models, product types or description hold every word of the entry (word starts, any case: `helmet` finds "Helmets"). Empty: every recall of the window. Never a URL |
| `sinceDays` | integer 1–3,650 | `90` | Recalls dated in the last N days, today included |
| `recalledFrom` | date `YYYY-MM-DD` | — | A fixed window instead: the first recall date (then `sinceDays` is not used) |
| `recalledTo` | date `YYYY-MM-DD` | today | The last recall date of the window |
| `mode` | `watch` or `export` | — | Empty: watch when `stateName` is given, otherwise export. An explicit mode wins |
| `stateName` | string | — (prefilled `cpsc-recalls`) | Watch list name. Watch mode without a name uses the list `default` |
| `includeUnchanged` | boolean | `false` | Watch: also return unchanged recalls (free) |
| `maxItems` | integer 1–100,000 | `1000` | Most charged recalls per run; in watch mode the rest come in the next run |

Changing the product words of a watch list starts a new baseline; changing `sinceDays` does not (the window moves with each
run).

### Output

```json
{
    "status": "ok",
    "attempts": 1,
    "error": null,
    "input": "helmet",
    "mode": "export",
    "watchList": null,
    "dropReason": null,
    "recallNumber": "26789",
    "recallId": 10992,
    "recallDate": "2026-09-24",
    "lastPublishDate": "2026-09-24",
    "title": "5Color Recalls Children’s Bicycle Helmet and Pads Sets Due to Risk of Serious Injury or Death from Head Injury; Violate Mandatory Standard for Bicycle Helmets",
    "description": "This recall involves 5Color-branded bicycle helmet and pads sets. …",
    "productNames": "5Color Children’s Bike Helmet and Pads Sets",
    "productTypes": "Helmets & Helmet Accessories",
    "models": null,
    "numberOfUnits": "About 324",
    "hazards": "The helmets in the recalled sets violate the mandatory safety standard for bicycle helmets … posing a serious risk of injury or death due to head injury.",
    "injuries": "None reported",
    "remedies": "Consumers should stop using the helmets immediately and contact 5Color for a full refund of the set. … email a photo of the destroyed helmet to [email on the recall page]. …",
    "remedyOptions": "Refund",
    "manufacturers": null,
    "importers": null,
    "distributors": null,
    "retailers": "Hengqin Guangwei Consulting Co., Ltd., dba. 5Color, of China",
    "whereSold": "Sold Online At: Amazon.com in May 2026 for between $25 and $26.",
    "manufacturerCountries": "China",
    "upcs": null,
    "imageUrls": "https://cpsc.gov/s3fs-public/5Color1.jpg?VersionId=… | https://cpsc.gov/s3fs-public/5Color2.jpg?VersionId=…",
    "jointRecallUrls": null,
    "firmNamesOmitted": false,
    "changeType": null,
    "changedFields": null,
    "previousValues": null,
    "url": "https://cpsc.gov/Recalls/2026/5Color-Recalls-Childrens-Bicycle-Helmet-and-Pads-Sets-Due-to-Risk-of-Serious-Injury-or-Death-from-Head-Injury-Violate-Mandatory-Standard-for-Bicycle-Helmets",
    "source": "CPSC Recalls API (U.S. Consumer Product Safety Commission). Source: CPSC (cpsc.gov), public information.",
    "license": "Public information of a U.S. federal agency (CPSC): free to copy and distribute, credit CPSC",
    "licenseUrl": "https://www.cpsc.gov/About-CPSC/Policies-Statements-and-Directives/Privacy-Policy",
    "scrapedAt": "2026-10-04T18:40:00.000Z"
}
```

| Status | Meaning | Charged? |
|---|---|---|
| `ok` | A recall returned | Export: yes. Watch: `baseline`, `new` and `changed` yes; `unchanged` no |
| `no_data` | No recall in the window matches the entry, or nothing new since the last run | No |
| `failed` | An invalid entry (a URL, no letters or digits), or no answer from CPSC after retries (`error` says why) | No |

The key-value store holds `RUN_REPORT` (counts, charged and free rows, stop reason) and, when something fails, the raw
response without contacts (`SNAPSHOT_*`).

### Output fields

### Output fields

| Field | Type | Description |
|---|---|---|
| `status` | string (or null) | ok = recall returned (charged in export mode and for baseline, new and changed rows; free when unchanged); no_data = nothing to return (no recall in the window matches the entry, or nothing new since the last run) (free); failed = invalid input or no answer (free) |
| `attempts` | integer (or null) | Requests made for this row (retries included) |
| `error` | string (or null) | Why a row is no_data or failed; null on ok rows |
| `input` | string (or null) | The entry of product words this row came from ("all recalls" when no words were given), or the input that failed |
| `mode` | string (or null) | watch or export |
| `watchList` | string (or null) | Watch list name (watch mode); null in export mode |
| `dropReason` | string (or null) | Always null for recalls: a firm name that may be a person's is left out of the recall's fields and the recall is kept (firmNamesOmitted) |
| `recallNumber` | string (or null) | CPSC recall number |
| `recallId` | integer (or null) | CPSC recall database ID |
| `recallDate` | string (or null) | Date of the recall notice (YYYY-MM-DD) |
| `lastPublishDate` | string (or null) | Date the notice was last published or updated (YYYY-MM-DD) |
| `title` | string (or null) | Title of the recall notice (a neutral title when the original named a firm that may be a person) |
| `description` | string (or null) | What the recall covers: products, models, labels and dates |
| `productNames` | string (or null) | Recalled product names, joined with " / " |
| `productTypes` | string (or null) | CPSC product types, joined with " / " |
| `models` | string (or null) | Model numbers given in the product list, joined with " / " (often only in the description) |
| `numberOfUnits` | string (or null) | Number of units recalled, as CPSC writes it (e.g. "About 324") |
| `hazards` | string (or null) | The hazard, joined with " / " when there are several |
| `injuries` | string (or null) | Incidents and injuries reported (counts and kinds; no names) |
| `remedies` | string (or null) | What consumers should do (contacts replaced by "\[email on the recall page]" or "\[phone on the recall page]") |
| `remedyOptions` | string (or null) | Refund, Replace, Repair …, joined with " / " |
| `manufacturers` | string (or null) | Manufacturers as "<firm>, of <place>", joined with " / " |
| `importers` | string (or null) | Importers as "<firm>, of <place>", joined with " / " |
| `distributors` | string (or null) | Distributors as "<firm>, of <place>", joined with " / " |
| `retailers` | string (or null) | Retailer firms as "<firm>, of <place>", joined with " / " (where it was sold is in whereSold) |
| `whereSold` | string (or null) | Where, when and for how much the product was sold |
| `manufacturerCountries` | string (or null) | Countries of manufacture, joined with " / " |
| `upcs` | string (or null) | Product UPC codes, joined with " / " |
| `imageUrls` | string (or null) | Links to the recall's product photos on cpsc.gov, joined with " / " |
| `jointRecallUrls` | string (or null) | Pages of the same recall elsewhere (e.g. Health Canada), joined with " / " |
| `firmNamesOmitted` | boolean (or null) | True when a firm name that may be a person's was left out of every field of this recall (people filter) |
| `changeType` | string (or null) | Watch mode: baseline (first run of the list), new, changed or unchanged; null in export mode |
| `changedFields` | string (or null) | Watch mode: the tracked fields that changed, comma-separated |
| `previousValues` | string (or null) | Watch mode: the changed fields' previous values, as JSON |
| `url` | string (or null) | The recall notice as CPSC links it, on cpsc.gov (it carries the firm's contacts); null when the record has no usable link (the recall is still returned) |
| `source` | string (or null) | The data source and its credit line |
| `license` | string (or null) | The terms of the data |
| `licenseUrl` | string (or null) | Where the terms are published |
| `scrapedAt` | string (or null) | When the row was made (ISO 8601) |

### Pricing

Pay per event: one `recall` event per returned recall (export: every recall; watch: `baseline`, `new` and `changed` rows).

| Plan | Price per recall | Per 1,000 recalls |
|---|---|---|
| Free (no discount) | $0.0015 | $1.50 |
| Bronze | $0.0013 | $1.30 |
| Silver | $0.00115 | $1.15 |
| Gold (and Platinum, Diamond) | $0.0010 | $1.00 |

- Never charged: failed rows (an invalid entry, no answer from CPSC), no_data rows (no match, nothing new) and unchanged
  rows. You are never charged for failed results.
- Apify also charges its small per-run start event. There are no usage fees on top, so the actor is eligible for x402
  agent payments.

### Use it from AI agents

One clear main input, `queries` (product words; empty for every recall); every row has `status`, `error`, `recallNumber`
and `scrapedAt`, and `url` (the recall page) whenever CPSC gives one. Call it through the Apify API, the Apify MCP server
(`mouadapi/cpsc-product-recalls`) or x402 agentic payments. Copy-paste call (your Apify token in place of
`YOUR_APIFY_TOKEN`):

```bash
curl -X POST "https://api.apify.com/v2/acts/mouadapi~cpsc-product-recalls/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" -H "Content-Type: application/json" -d '{"queries": ["power bank"], "sinceDays": 365}'
```

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('mouadapi/cpsc-product-recalls').call({ queries: ['crib', 'stroller'], stateName: 'baby-products' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### Limits

- One request at a time, at most one a second: CPSC publishes no rate limit, so this actor keeps well under any reasonable
  use. If the API answers HTTP 429, the run pauses 60, 120 and 240 seconds on the same connection, then stops with free
  `failed` rows. No other IP or proxy is ever tried.
- The window is read by recall date: an update to a recall dated before the window is not seen. At most 5,000 recalls are
  read per run (CPSC publishes a few hundred a year).
- Product words are matched in the recall's own text, by word starts (`crib` also finds "cribs"): a recall that names the
  product differently is not found. Leave `queries` empty to get every recall of the window.
- US recalls by CPSC only (consumer products). Food, drugs, medical devices, cars and boats are recalled by other agencies
  (FDA, NHTSA, USCG) and are not here.

### Known issues

- CPSC's API sometimes answers with its own error ("Error retrieving Recalls: The underlying provider failed on Open.",
  HTTP 200; seen on 2 October 2026). The actor tries again after 1 and 2 seconds, then each entry is a free `failed` row
  that quotes it.
- How names are handled: a firm trading under a person's name, and a firm name shaped like a person's name, are left out of
  the recall's fields (`firmNamesOmitted: true`; the test leans towards removing, so some company trading names are left
  out too). Such a recall can have a neutral title and `null` in a text field that repeated the name. `RUN_REPORT` counts
  these recalls (`firmNamesOmitted`) and the fields set to null (`textFieldsNulled`), never the names.
- A few recalls have no usable page link in the API: such a recall is still returned (and charged), with `url` null. A
  relative cpsc.gov path gets the site's address, and a link on another host is kept as CPSC gives it. `RUN_REPORT`
  counts them (`pageLinks`).

### FAQ

**Does it include every CPSC recall?** Every recall notice the CPSC Recalls API returns for the window. Use the recall page
(`url`) for the firm's contact details and the full notice.

**Why did a watch run return one `no_data` row?** Nothing was new or updated since the last run of the watch list; the row
says how many recalls were unchanged. It is free.

### Data and licence

- Source: the CPSC Recalls API (www.saferproducts.gov/RestWebServices), documented at
  https://www.cpsc.gov/Recalls/CPSC-Recalls-Application-Program-Interface-API-Information.
- Terms: CPSC's Privacy and Security Notice, "Copyright": web page text "is public information. You may freely distribute,
  copy, or link to any of this information. However, the information may not be used in a way that states or implies CPSC
  endorsement. If you distribute, copy or link to any of this information, please credit CPSC." Every row credits CPSC in
  `source`.
- This actor is not affiliated with, endorsed by or provided by the U.S. Consumer Product Safety Commission. It returns the
  recalls as published; always check the linked recall page before acting on it.

# Actor input Schema

## `queries` (type: `array`):

One entry per line, e.g. helmet, power bank, crib or a model number: the recalls whose title, product names, models, product types or description hold every word of the entry (word starts, any case). Leave empty for every recall in the window. Never a URL.

## `sinceDays` (type: `integer`):

Recalls dated in the last N days, today included. Watch mode reads the same moving window and returns only new and updated recalls.

## `recalledFrom` (type: `string`):

Optional: a fixed window instead of the last N days. The first recall date, as YYYY-MM-DD.

## `recalledTo` (type: `string`):

Optional: the last recall date of the window, as YYYY-MM-DD. Empty: today.

## `mode` (type: `string`):

Empty: watch when a watch list name is given, otherwise export. "watch" returns only recalls that are new or updated since the last run of the watch list; "export" returns every recall. An explicit mode wins.

## `stateName` (type: `string`):

Name of your watch list (kept in your own storage between runs). Giving a name turns on watch mode. Watch mode without a name uses the list "default".

## `includeUnchanged` (type: `boolean`):

Watch mode: also return recalls that did not change, as free rows.

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

Most recalls returned and charged per run. In watch mode the rest come in the next run.

## Actor input object example

```json
{
  "queries": [
    "helmet",
    "power bank"
  ],
  "sinceDays": 90,
  "stateName": "cpsc-recalls",
  "includeUnchanged": false,
  "maxItems": 1000
}
```

# Actor output Schema

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

Dataset with one row per recall, or per entry when nothing is returned

## `runReport` (type: `string`):

Summary of the run (counts, charged and free rows, stop reason)

# 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 = {
    "queries": [
        "helmet"
    ],
    "stateName": "cpsc-recalls"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mouadapi/cpsc-product-recalls").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 = {
    "queries": ["helmet"],
    "stateName": "cpsc-recalls",
}

# Run the Actor and wait for it to finish
run = client.actor("mouadapi/cpsc-product-recalls").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 '{
  "queries": [
    "helmet"
  ],
  "stateName": "cpsc-recalls"
}' |
apify call mouadapi/cpsc-product-recalls --silent --output-dataset

```

## MCP server setup

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

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/lbSZKursEeOfc8g2T/builds/asKJmy1f1TEv2c2RA/openapi.json
