# Product Recall Scraper: CPSC and FDA (`red_rook_ai/product-recall-scraper`) Actor

US product recalls as normalized JSON from the CPSC recalls API and FDA openFDA enforcement reports (food, drugs, devices). Filter by date range, source and keyword. One record per recall: stable id, dates, product, firm, hazard, remedy, official URL. 25 records free per run, then pay per record.

- **URL**: https://apify.com/red\_rook\_ai/product-recall-scraper.md
- **Developed by:** [RedRook AI](https://apify.com/red_rook_ai) (community)
- **Categories:** Automation, MCP servers, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 one recall record beyond the free 25s

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

## Product Recall Scraper: CPSC and FDA

One normalized JSON record per US product recall, pulled live from two official, keyless government APIs:

- **CPSC** (Consumer Product Safety Commission) recalls API at saferproducts.gov: toys, furniture, appliances, electronics, sporting goods and other consumer products.
- **FDA openFDA enforcement reports** for food, drugs and medical devices at api.fda.gov.

Every record has the same shape regardless of source, so an agent or a script can filter recalls by date, product, firm, hazard or FDA class without knowing which agency published them. Each record carries a stable id (`<source>:<recall_number>`), the official recall page or API URL, and a `retrieved_at` timestamp.

A run with no input returns the newest 25 recalls from all four sources for the last 30 days. **The first 25 records of every run are free.** Records 26 and up cost $0.003 each (see Pricing).

Not affiliated with, endorsed by or maintained by the CPSC or the FDA. This is an unofficial client for public data.

#### Three sample records (trimmed for length)

Fetched 2026-09-10 23:22 UTC by the runs recorded in TEST-LOG.md. Long strings are cut with ` ...`; the shipped sample file is untrimmed.

```json
[
  {
    "id": "cpsc:26764",
    "source": "cpsc",
    "agency": "CPSC",
    "recall_number": "26764",
    "recall_id": "10976",
    "recall_date": "2026-09-10",
    "last_updated": "2026-09-10",
    "title": "FJTJBSI Recalls Six Drawer Dressers Due to Risk of Serious Injury or Death from Tip Over and Entrapment Hazards Violate Mandatory Standard for Clothin ...",
    "summary": "This recall involves the FJTJBSI-branded wood dressers. The black or white dressers measure about 15.1 inches long, 27.6 inches wide and 42.2 inches h ...",
    "hazard": "The recalled dressers are unstable if they are not anchored to the wall, posing tip-over and entrapment hazards that can result in risks of serious in ...",
    "remedy": "Consumers should stop using the recalled dressers immediately if they are not anchored to the wall, place them in an area that children cannot access ...",
    "remedy_options": [
      "Refund"
    ],
    "units": "About 9,000",
    "products": [
      {
        "name": "FJTJBSI Six-drawer Dressers",
        "type": "Desks, Dressers, Chests, Bureaus, or Buffets",
        "units": "About 9,000"
      }
    ],
    "firms": [
      {
        "name": "Fujian Laobosi Cross-Border E-commerce Co., Ltd., dba FJTJBSI, of China",
        "role": "retailer"
      }
    ],
    "sold_at": "Amazon.com from November 2024 through July 2026 for about $170.",
    "manufacturer_countries": [
      "China"
    ],
    "injuries": "None reported.",
    "image_urls": [
      "https://www.cpsc.gov/s3fs-public/FJT1.png?VersionId=GGkzZKI9x_uU.i8CTbkN09b_5YpENVvh"
    ],
    "source_url": "https://www.cpsc.gov/Recalls/2026/FJTJBSI-Recalls-Six-Drawer-Dressers-Due-to-Risk-of-Serious-Injury-or-Death-from-Tip-Over-and-Entrapment-Hazards-Viol ...",
    "retrieved_at": "2026-09-10T23:22:25+00:00"
  },
  {
    "id": "fda-food:H-1278-2026",
    "source": "fda-food",
    "agency": "FDA",
    "recall_number": "H-1278-2026",
    "recall_id": "99629",
    "recall_date": "2026-09-02",
    "recall_initiation_date": "2026-08-11",
    "last_updated": "2026-08-26",
    "title": "Byron White Formulas, Inc. recalls Byron White FORMULAS A-FNG Herbal Supplement 1.0 fl. oz./30 mL May Contain Nuts (Juglans nigra hull) Byron White Fo ...",
    "summary": "Byron White FORMULAS A-FNG Herbal Supplement 1.0 fl. oz./30 mL May Contain Nuts (Juglans nigra hull) Byron White Formulas Inc. 3315 Chanate Rd. Suite ...",
    "hazard": "Label does not declare walnuts in English",
    "classification": "Class II",
    "status": "Ongoing",
    "voluntary_mandated": "Voluntary: Firm initiated",
    "units": "10,403 units",
    "products": [
      {
        "name": "Byron White FORMULAS A-FNG Herbal Supplement 1.0 fl. oz./30 mL May Contain Nuts (Juglans n ...",
        "type": "Food",
        "units": "10,403 units",
        "code_info": "Lot Code Format = MMD,DYY: 022,224. 061,424. 092,324. 022,125. 071,825. 112,125. 040,826."
      }
    ],
    "firms": [
      {
        "name": "Byron White Formulas, Inc.",
        "role": "recalling_firm"
      }
    ],
    "sold_at": "Domestic: AK, AL, AR, AZ, CA, CO, CT, DC, DE, FL, GA, HI, IA, ID, IL, IN, KS, KY, LA, MA, MD, ME, MI, MN, MO, MT, NC, NE, NH, NJ, NM, NV, NY, OH, OK, ...",
    "city": "Santa Rosa",
    "state": "CA",
    "country": "United States",
    "code_info": "Lot Code Format = MMD,DYY: 022,224. 061,424. 092,324. 022,125. 071,825. 112,125. 040,826.",
    "source_url": "https://api.fda.gov/food/enforcement.json?search=recall_number:\"H-1278-2026\"",
    "retrieved_at": "2026-09-10T23:22:27+00:00"
  },
  {
    "id": "fda-drug:D-0831-2026",
    "source": "fda-drug",
    "agency": "FDA",
    "recall_number": "D-0831-2026",
    "recall_id": "99711",
    "recall_date": "2026-09-02",
    "recall_initiation_date": "2026-08-24",
    "last_updated": "2026-09-03",
    "title": "Hikma Pharmaceuticals USA INC. recalls Ramipril Capsules USP, 10 mg, packaged in a) 100 Capsules (NDC 76282-673-01) and b) 500 Capsules (NDC 76282-673 ...",
    "summary": "Ramipril Capsules USP, 10 mg, packaged in a) 100 Capsules (NDC 76282-673-01) and b) 500 Capsules (NDC 76282-673-05) bottles, Rx only, Manufactured for ...",
    "hazard": "Failed Impurities/Degradation Specifications: OOS result obtained for a process impurity, dicyclohexylurea (DCU).",
    "classification": "Class II",
    "status": "Ongoing",
    "voluntary_mandated": "Voluntary: Firm initiated",
    "units": "a) 25,970 bottles; b) 2,384 bottles",
    "products": [
      {
        "name": "Ramipril Capsules USP, 10 mg, packaged in a) 100 Capsules (NDC 76282-673-01) and b) 500 Ca ...",
        "type": "Drugs",
        "units": "a) 25,970 bottles; b) 2,384 bottles",
        "code_info": "a) Lot #: AC5470A, Exp. Date July 2027 b) Lot #: AC5470B, Exp. Date July 2027",
        "brand_names": [
          "RAMIPRIL"
        ],
        "generic_names": [
          "RAMIPRIL"
        ]
      }
    ],
    "firms": [
      {
        "name": "Hikma Pharmaceuticals USA INC.",
        "role": "recalling_firm"
      },
      {
        "name": "Exelan Pharmaceuticals, Inc.",
        "role": "manufacturer"
      }
    ],
    "sold_at": "Nationwide within the United States",
    "city": "Columbus",
    "state": "OH",
    "country": "United States",
    "code_info": "a) Lot #: AC5470A, Exp. Date July 2027 b) Lot #: AC5470B, Exp. Date July 2027",
    "source_url": "https://api.fda.gov/drug/enforcement.json?search=recall_number:\"D-0831-2026\"",
    "retrieved_at": "2026-09-10T23:22:30+00:00"
  }
]
```

A full 20-record sample (5 per source, fetched 2026-09-10 23:22 UTC, untrimmed) ships with the Actor source as `samples/sample-dataset-2026-09-10.json` and is the sample output shown on this listing.

### Free tier

Every run returns up to **25 records at no per-record charge**, whatever the input. That is enough to cover a daily "what was recalled this week" check, a keyword watch on a brand, or a single-source pull. Records 26 and beyond are metered. If your run's maximum total charge is reached, the Actor stops cleanly after the last paid record and says so in the run status message; nothing is charged for records that were not delivered.

### Pricing

Pay per event. Two events exist:

| Event | Price | When |
|---|---|---|
| `result` | $0.003 per record | Charged once for each record from record 26 onward in a run. Records 1 to 25 are never charged. |
| Actor start | Apify platform default | Apify's standard per-run start event, charged by the platform, not by this Actor. |

Worked examples: 25 records cost nothing beyond the start event; 100 records cost 75 x $0.003 = $0.225; 1,000 records cost 975 x $0.003 = $2.93. Platform usage (compute) is included in the event price. There are no hidden per-dataset-item charges: the platform's default dataset item event is disabled for this Actor.

### Input

All fields are optional. A `{}` input is valid.

| Field | Type | Default | Meaning |
|---|---|---|---|
| `sources` | array of `all`, `cpsc`, `fda-food`, `fda-drug`, `fda-device` | `["all"]` | Which agencies and FDA product types to query. `fda` is accepted as shorthand for the three FDA endpoints. |
| `dateFrom` | `YYYY-MM-DD` | today minus 30 days | Earliest recall date (CPSC recall date; FDA report date). |
| `dateTo` | `YYYY-MM-DD` | today (UTC) | Latest recall date. |
| `keyword` | string | none | Case-insensitive substring match on title, summary, hazard, product names and firm names. |
| `maxResults` | integer 1 to 5000 | 25 | Newest-first cap across all selected sources. |
| `outputFormat` | `full` or `flat` | `full` | `flat` joins every array into a single `' | '`-separated string for CSV and spreadsheet export. |
| `healthCheck` | boolean | false | Monitoring mode, see below. |

Example: all lithium battery recalls in the last 90 days, up to 200 records:

```json
{ "keyword": "lithium", "dateFrom": "2026-06-12", "maxResults": 200 }
```

Example: only FDA Class I food recalls this month, flattened for a spreadsheet (filter `classification` after export):

```json
{ "sources": ["fda-food"], "dateFrom": "2026-09-01", "outputFormat": "flat", "maxResults": 100 }
```

### Output fields

| Field | Type | Notes |
|---|---|---|
| `id` | string | Stable entity id `<source>:<recall_number>`, identical across runs for the same recall. |
| `source` | string | `cpsc`, `fda-food`, `fda-drug` or `fda-device`. |
| `agency` | string | `CPSC` or `FDA`. |
| `recall_number` | string | CPSC recall number or FDA recall number (`H-`, `F-`, `D-`, `Z-` prefixes). |
| `recall_id` | string | CPSC RecallID or FDA event\_id. |
| `recall_date` | date | CPSC recall date; FDA report date (the date FDA published the enforcement report). |
| `recall_initiation_date` | date or null | FDA only: the date the firm started the recall, usually weeks before `recall_date`. |
| `last_updated` | date or null | CPSC last publish date; FDA center classification date. |
| `title` | string | CPSC headline; for FDA, recalling firm plus the product description. |
| `summary` | string or null | Product description. |
| `hazard` | string or null | CPSC hazard text or FDA reason for recall. |
| `remedy` | string or null | CPSC remedy instructions; FDA enforcement reports carry no remedy text. |
| `remedy_options` | array | CPSC: Refund, Replace, Repair and similar. |
| `classification` | string or null | FDA Class I (most serious), II or III. |
| `status` | string or null | FDA: Ongoing, Completed or Terminated. |
| `voluntary_mandated` | string or null | FDA: firm-initiated or FDA-mandated. |
| `units` | string or null | Units affected, as published (free text). |
| `products` | array of objects | `name`, `description`, `model`, `type`, `units`; FDA adds `code_info` (lots, expiry, UDI), `brand_names`, `generic_names`. |
| `firms` | array of objects | `name` and `role` (`manufacturer`, `importer`, `distributor`, `retailer`, `recalling_firm`). Organizations only. |
| `sold_at` | string or null | CPSC where-sold text or FDA distribution pattern. |
| `manufacturer_countries` | array | CPSC country of manufacture. |
| `city`, `state`, `country` | string or null | FDA recalling firm location (city level only). |
| `injuries` | string or null | CPSC incidents and injuries text. |
| `upcs` | array | CPSC product UPCs. |
| `code_info` | string or null | FDA lot and batch information. |
| `image_urls` | array | CPSC official recall photographs (URLs only, no copies). |
| `source_url` | string | cpsc.gov recall page, or the openFDA API URL that returns exactly this record. |
| `retrieved_at` | datetime | UTC timestamp of the fetch. |

Personal data is stripped before output. CPSC's `ConsumerContact` field is dropped entirely, email addresses and phone numbers are redacted from every free-text field (they appear as `[email redacted]` and `[phone redacted]`), and FDA's street address fields (`address_1`, `address_2`, `postal_code`) are dropped. Firm names are organization names as published by the agencies; a sole proprietor's trade name can coincide with a person's name and the Actor does not attempt to detect that.

### Health check mode

Set `"healthCheck": true` to run a monitoring probe: the Actor queries all four sources for the last 60 days, pushes 5 records, charges nothing, writes a `HEALTH` record to the key-value store with per-source status, and fails the run (non-zero exit, status message starting with `HEALTH CHECK FAILED`) if any source does not answer or fewer than 5 records come back. Schedule it daily to be told when an upstream API changes.

### Sources, terms and legal notes

- **CPSC**: recalls API at `https://www.saferproducts.gov/RestWebServices/Recall` (no key, no published rate limit). CPSC's privacy policy states that recall notices may be freely copied and distributed without permission and asks that CPSC be credited. Data credit: U.S. Consumer Product Safety Commission. The Actor emits image URLs, never copies of the photographs.
- **FDA openFDA**: `https://api.fda.gov/{food,drug,device}/enforcement.json`. openFDA data is in the public domain under a Creative Commons CC0 1.0 Universal dedication; credit is requested but not required. Data credit: U.S. Food and Drug Administration, openFDA. The enforcement endpoints used here carry no GMDN device-nomenclature content, which openFDA licenses separately.
- Both agencies are part of the United States Government; their recall records are US Government works with no copyright protection under 17 U.S.C. 105.
- This Actor is unofficial. It is not affiliated with, sponsored by or endorsed by the CPSC, the FDA or any US agency. Recall records are published by the agencies and may be corrected or withdrawn; verify against `source_url` before acting on them.
- Rate discipline: one request in flight at a time, a 1 second pause between paginated calls, up to 4 retries with growing backoff (2, 4, 6, 8 seconds) on connection errors and HTTP 5xx, and a descriptive User-Agent.

### Limits and known gaps

- openFDA allows 1,000 requests per day per IP without an API key. Each FDA source costs 1 request per 1,000 records, so ordinary runs use 1 to 3 requests. The Actor runs from shared Apify IPs, so an exhausted daily quota shows up as a failed FDA source in the run summary, not as an error in your data.
- The CPSC API has no pagination: a wide date window loads the whole window in one call and is then truncated to `maxResults` newest-first. Windows of a few years still return in a few seconds.
- `recall_date` for FDA is the report date, not the initiation date; use `recall_initiation_date` when you need the latter.
- `keyword` is a plain substring match, not a full-text search. Search once per term.
- The FDA's human-readable IRES recall page is not linked because that site blocks automated verification; `source_url` points to the openFDA record instead.
- openFDA occasionally answers HTTP 500 for a few seconds at a time. The Actor retries up to 4 times over about 20 seconds; if one source still fails, the other sources are delivered, the failure is named in the run status message and in the `RUN_SUMMARY` key-value record, and the run only fails outright when every selected source failed.
- NHTSA vehicle recalls are not included in this version.
- Hard cap of 5,000 records per run.

### FAQ

**Can I run it for free?** Yes. Every run returns up to 25 records free, and Apify's free plan covers the platform cost of running it.

**Why did I get fewer records than `maxResults`?** Either the date window and keyword matched fewer recalls, or your run's maximum total charge was reached; the run status message tells you which.

**Is the id stable?** Yes. `id` is `<source>:<recall_number>`; the same recall keeps the same id on every run, so you can deduplicate across scheduled runs.

**Does it include CPSC "Sold At" retailers as firms?** No. The CPSC feed mixes where-sold text into its retailer list; that text is moved to `sold_at`, and `firms` only holds named organizations.

**Can I get CSV?** Choose `"outputFormat": "flat"` and export the dataset as CSV or Excel from the run's Output tab or the API.

# Actor input Schema

## `sources` (type: `array`):

Which official sources to query. 'cpsc' is the Consumer Product Safety Commission recalls API (consumer products). 'fda-food', 'fda-drug' and 'fda-device' are the FDA openFDA enforcement report endpoints. 'all' selects every source. Default: all.

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

Earliest recall date to include, YYYY-MM-DD (CPSC recall date; FDA report date). Default: 30 days before today.

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

Latest recall date to include, YYYY-MM-DD. Default: today (UTC).

## `keyword` (type: `string`):

Optional case-insensitive text filter applied to the title, summary, hazard, product names and firm names of each recall. Example: 'lithium', 'infant', 'salmonella', 'Pfizer'.

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

Maximum number of recall records to return, newest first across all selected sources. The first 25 records per run are free; each record beyond 25 is charged one 'result' event. Default 25 (the free allowance). Hard cap 5000.

## `outputFormat` (type: `string`):

'full' keeps nested arrays (products, firms, image\_urls). 'flat' joins every array into a single string with ' | ' separators so the dataset exports cleanly to CSV or Excel. Default: full.

## `healthCheck` (type: `boolean`):

Fetches 5 records across all sources from the last 60 days, charges nothing, and fails the run (non-zero exit) if any source does not answer or fewer than 5 records come back. Used for scheduled monitoring; ignores the other inputs.

## Actor input object example

```json
{
  "sources": [
    "all"
  ],
  "maxResults": 25,
  "outputFormat": "full",
  "healthCheck": false
}
```

# Actor output Schema

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

No description

# 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 = {
    "sources": [
        "all"
    ],
    "maxResults": 25,
    "outputFormat": "full"
};

// Run the Actor and wait for it to finish
const run = await client.actor("red_rook_ai/product-recall-scraper").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 = {
    "sources": ["all"],
    "maxResults": 25,
    "outputFormat": "full",
}

# Run the Actor and wait for it to finish
run = client.actor("red_rook_ai/product-recall-scraper").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 '{
  "sources": [
    "all"
  ],
  "maxResults": 25,
  "outputFormat": "full"
}' |
apify call red_rook_ai/product-recall-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/2Aewddl4wIeTMfDdt/builds/7zdP6upySi49njDXK/openapi.json
