Product Recall to Catalog Matcher (CPSC, unofficial) avatar

Product Recall to Catalog Matcher (CPSC, unofficial)

Pricing

from $200.00 / 1,000 candidate matches

Go to Apify Store
Product Recall to Catalog Matcher (CPSC, unofficial)

Product Recall to Catalog Matcher (CPSC, unofficial)

Match a merchant catalog to official U.S. CPSC recall records. Exact UPCs and published model numbers are confirmed first. Brand and model similarities are only proposed for human review. No candidate found is not a safety clearance.

Pricing

from $200.00 / 1,000 candidate matches

Rating

0.0

(0)

Developer

Olalekan Johnson

Olalekan Johnson

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 days ago

Last modified

Security

Limited permissions

Share

Unofficial. Not affiliated with the U.S. Consumer Product Safety Commission or Health Canada.

A specialty retailer, resale shop, or ecommerce team pastes a catalog. The Actor reads official CPSC recall records for a date window, then builds a review queue. An exact UPC or a published model number can be a confirmed identifier match. A brand-and-model similarity is only a plausible candidate. A product that cannot be judged stays unassessable. A row that says no candidate found is not a safety clearance.

Who it is for

Use it when a newly published recall might overlap products you already sell, list, or accept on trade-in. The output is a human review queue, not a compliance certificate. Every candidate explains which identifiers matched, which facts are missing, and which official recall page to open.

What the default input returns

The default input is an empty object. It uses the built-in sample catalog and the last 120 days of CPSC recalls (or recalls published since the saved watermark). The sample copies brand, model, and UPC values from public CPSC records so the demo queue is not empty. It is not a claim that any merchant stocked those products.

On a first run of that sample, expect one row per item:

Catalog itemWhat it demonstratesStatus
sample-hampton-halwinUPC 840059614922 and model AK396H-MBK on recall 26702Confirmed identifier match
sample-scotch-tl1302zModel TL1302Z on recall 27024Confirmed identifier match
sample-agrish-ag12011Model AG-12011 on recall 27014Confirmed identifier match
sample-love-to-dream-datedModel LTD-SM23 plus date code 7W15TN on recall 26798Confirmed identifier match
sample-love-to-dream-undatedSame model, date code not recordedPlausible candidate
sample-brookstone-prefixBS-FIREPIT is only a prefix of BSFIREPIT01 on recall 26687Plausible candidate
sample-victgoal-outside-dateModel HT-006 matches recall 26749, but the catalog date and lot are outside the published populationNo candidate found, with an exclusion note
sample-lithonia-brand-onlyBrand hits recall 27041, but the catalog has no arm code or manufacture dateUnassessable
sample-northwind-paperInvented UPC and model, no recall hitNo candidate found
sample-title-onlyTitle only, no brand, model, or UPCUnassessable, and not billed

Nine of the ten items are checked. The title-only row is not billed, because there is nothing to check. The first run proposes six candidate packages (four confirmed, two plausible). Sample mode also asks the CPSC API for recall numbers 26702, 27024, 27014, 26798, 26687, 26749, and 27041 if that window did not already include them, so the demo still returns matches after the lookback moves.

A local default run on 2026-10-23 retrieved 199 CPSC recalls for 2026-06-25 through 2026-10-23, coverage complete, and wrote those ten rows. It charged 9 catalog-item-checked events and 6 candidate-match events. At the prices below that is $1.209 before the platform start event.

safetyClearance is always false. notASafetyClearance is always true.

Input

FieldDefaultMeaning
catalogSourcesamplesample, inline (pasted CSV or JSON), url, or shopify
catalogTextemptyCSV or JSON when the source is inline
catalogUrlemptyPublic http or https file. Private IPs, localhost, and file:// are rejected
shopifyStoreUrlemptyStorefront origin. Only /products.json is read
dateStart, dateEndemptyInclusive YYYY-MM-DD. Explicit dates use the CPSC recall date
lookbackDays120First-run window when there is no watermark and no explicit dates
sinceLastRuntrueAfter a successful run, request CPSC records by last-publish date from the watermark, with a three-day overlap
includeHealthCanadafalseAlso read Health Canada consumer-product open data
stateStoreNamecpsc-recall-catalog-matcher-stateNamed key-value store for the watermark and match receipts
stateKeydefaultIsolate one catalog or merchant inside that store
alertOnlyOnChangefalseStill checks every item. The dataset keeps new, updated, and resolved rows
fuzzyThreshold80Prefix similarity required before a non-exact model is proposed
maxCatalogItems5000Extra items are not checked. The run is marked partial and the watermark stays put

Leave the input empty to run the sample.

Catalog format

CSV needs a header row. JSON is an array of objects, or {"items": [...]}. Recognized columns include id, brand (also manufacturer or vendor), model (also mpn), upc (also gtin, ean, barcode), sku, title, batch (also dateCode), lot, and manufactureDate (YYYY-MM-DD).

id,brand,model,upc,sku,title,batch,lot,manufactureDate
fan-1,Hampton Bay,AK396H-MBK,840059614922,HB-1,Halwin ceiling fan,,,

A public Shopify URL such as https://your-store.myshopify.com is read as /products.json only (vendor, barcode, sku, title). The public product feed does not include a manufacturer model number. Barcode matches can still confirm. Brand-only products from that feed usually come back unassessable.

Catalog files are limited to 8 MB. Redirects are checked hop by hop. Hosts that resolve to loopback, private, or link-local addresses are rejected.

Output rows

Each dataset item is one flat assessment. The run summary is a separate key-value record named OUTPUT.

Confirmed identifier match (recall 26702, Hampton Bay Halwin fan):

{
"catalogItemId": "sample-hampton-halwin",
"status": "confirmed_identifier_match",
"statusLabel": "Confirmed identifier match",
"confidence": 0.99,
"change": "new",
"matchedFeatures": ["brand:Hampton Bay", "model:AK396H-MBK", "upc:0840059614922"],
"missingInformation": [],
"exclusionNotes": null,
"recallNumber": "26702",
"recallUrl": "https://www.cpsc.gov/Recalls/2026/Hampton-Bay-Halwin-52-Inch-Ceiling-Fans-Recalled-Due-to-Impact-and-Injury-Hazards-Manufactured-by-Youngo-Limited",
"recallDate": "2026-08-13",
"hazard": "The fan blades can separate from the fan motor flywheel, posing impact and injury hazards to consumers.",
"remedy": "Consumers should stop using the recalled ceiling fans immediately and contact Youngo for either a full refund...",
"affectedModelLanguage": "This recall involves Hampton Bay Halwin 52-inch Indoor/Outdoor Ceiling fans with model numbers AK396H-MBK and AK396H-BN.",
"safetyClearance": false,
"notASafetyClearance": true,
"coverageStatus": "complete",
"recallsChecked": 10,
"source": "cpsc",
"jurisdiction": "US"
}

Plausible candidate, because the recall names a date code the catalog did not supply (recall 26798):

{
"catalogItemId": "sample-love-to-dream-undated",
"status": "plausible_candidate",
"confidence": 0.64,
"matchedFeatures": ["brand:Love to Dream", "model:LTD-SM23"],
"missingInformation": [
"The recall limits the population with date code or lot 7W15TN, and the catalog item did not supply one."
],
"recallNumber": "26798",
"recallUrl": "https://cpsc.gov/Recalls/2026/Love-To-Dream-Recalls-Portable-Sleep-Machines-Due-to-Risk-of-Injury-from-Fire-and-Burn-Hazards",
"safetyClearance": false
}

No candidate found. The model matches recall 26749, and the supplied date and lot do not. This is still not a clearance:

{
"catalogItemId": "sample-victgoal-outside-date",
"status": "no_candidate_found",
"confidence": null,
"matchedFeatures": [],
"missingInformation": [
"Checked 10 retrieved recalls. No exact identifier or reviewable brand-and-model candidate was found. This is not a safety clearance."
],
"exclusionNotes": "Excluded CPSC 26749: model HT-006 matches published HT-006, but manufacture date 2024-03-15 is outside November 2025, Production: 2025/11 and supplied batch/lot 2024A0301 is not the published code 2025A1101.",
"recallNumber": null,
"safetyClearance": false,
"notASafetyClearance": true
}

Unassessable, brand only (recall 27041). The published arm codes are not in the catalog:

{
"catalogItemId": "sample-lithonia-brand-only",
"status": "unassessable",
"matchedFeatures": ["brand:Lithonia Lighting"],
"missingInformation": [
"Catalog item has this brand but no model or UPC. The recall identifies affected units by model, batch, or date, so this row cannot be confirmed or cleared."
],
"recallNumber": "27041",
"recallUrl": "https://cpsc.gov/Recalls/2027/Lithonia-Lighting-Recalls-RSX-Luminaires-with-Adjustable-Tilt-Arm-Mountings-Due-to-Risk-of-Serious-Injury-from-Impact-Hazard",
"safetyClearance": false
}

recallsChecked on a live run is the number of records actually retrieved, which is larger than the ten-record fixture used for the snippets above. Every row also carries observedAt, officialApiUrl, evidenceSnippet, recallContentHash, disclaimer, the window start and end, and change (new, updated, unchanged, resolved, or not_applicable).

OUTPUT in the default key-value store records coverage, counts, charged units, and whether the watermark moved. A blocked retrieval writes coverageStatus: "blocked", charges nothing, and does not replace a saved baseline.

How a match is decided

  1. Exact UPC or GTIN, including a 12-digit UPC-A and the same code as a 13-digit EAN with a leading zero. A UPC hit stays confirmed even if the brand text disagrees. The disagreement is listed in missingInformation.
  2. Exact model (hyphens and spaces ignored) plus a compatible brand. A required date code, lot, or manufacture window that the catalog does not supply keeps the row plausible. A supplied date outside every published manufacture window, or a conflicting lot, is an exclusion, not a candidate.
  3. A model recovered only from the product title is never confirmed. It can be a plausible candidate.
  4. A prefix of a published model (for example BS-FIREPIT against BSFIREPIT01) is a plausible candidate only when the brand agrees and the shorter code covers at least fuzzyThreshold percent of the published code. This is not typo matching. 120 does not match 12014.
  5. Brand only, when the recall names models, windows, or codes, is unassessable (up to five recent recalls per item). It is checked, and it is not a candidate charge.
  6. No brand and no model or UPC is unassessable and is not charged.
  7. No hit is no_candidate_found. That sentence is not a safety clearance. A different model from the same brand is intentionally left alone.

A merchant SKU confirms a row only when it equals a model or item number the recall actually published.

Pricing

This Actor uses pay-per-event pricing:

EventWhen it is chargedPrice
Platform start eventOnce per run, charged by Apify$0.005
catalog-item-checkedOnce per catalog item actually checked against that run's recalls, including a scheduled run that pushes no new rows$0.001
candidate-matchOnce per new or materially changed confirmed or plausible evidence package$0.20

Unassessable rows, no-candidate rows, exclusions, and resolved rows are not candidate-match charges. The same evidence package is not charged again on a later run. A failed fetch is not charged. If the run hits your charge limit, the unit that was accepted is delivered, later items are not billed, the watermark does not advance, and OUTPUT says the limit was reached.

There is no per-dataset-item event.

Worked examples, plus the $0.005 start event:

RunChecksNew candidate packagesEventsWith start
Default sample, first run96$1.209$1.214
5,000 SKUs each week, 8 new candidates, earlier candidates unchanged5,0008$6.60$6.605
50,000 item checks and 150 new candidates50,000150$80.00$80.005

A quiet week still costs the per-item check, because each run compares the catalog with the recalls retrieved that day. It does not cost another $0.20 for a candidate whose hazard, remedy, and identifiers did not change. OUTPUT.estimatedChargeUsdAtSuggestedPrices estimates the charge using the prices above.

Repeat runs

State lives in the named key-value store, record baseline__{stateKey}. A successful run (coverageStatus complete or empty, catalog not truncated, charge limit not hit) stores a per-source watermark. The next run with sinceLastRun asks CPSC for records whose last publish date is on or after that watermark minus three days. An errored, blocked, partial, truncated, or charge-limited run does not move the watermark. Receipts for candidate packages that were delivered can still be merged, so a retry does not bill those packages again.

alertOnlyOnChange omits unchanged rows from the dataset. The check event is still charged. A previously delivered candidate that disappears from a complete window is pushed as change: "resolved". Disappearance is not a clearance either.

Use a different stateKey per merchant if they share one store.

Health Canada

Off by default, so the daily default run stays on the CPSC API. Turn on includeHealthCanada to also read the Recalls and Safety Alerts open-data file and keep rows whose organization is a consumer-product program. Food, health, vehicle, and other programs in that file are skipped. The open data often has no UPC. Its date filter uses Last updated, which is not the original publication date. A large file is downloaded on every enabled run.

Contains information licensed under the Open Government Licence – Canada.

Integrations

The dataset is flat, so a webhook, Make, or n8n scenario can map status, catalogItemId, sku, recallUrl, hazard, and missingInformation without unnesting. Google Sheets can take the same CSV columns. Point a monitor at the OUTPUT record when you only need coverage and charged-unit counts. Filter change to new or updated if the destination should stay quiet on repeat runs, or set alertOnlyOnChange in the input.

Limits

  • This is not a certified safety or compliance service. A person reviews every candidate. False negatives matter: a model number the parser did not see will not be flagged.
  • Model numbers are taken from CPSC structured fields when those fields are filled, and otherwise from text near the word "model", hyphenated codes, and quoted codes. All-letter component codes such as AASP are not extracted. A brand-only row is the fallback when the recall still has a manufacture window or similar limit.
  • Fuzzy matching is prefix overlap, not a typo distance. TL1302Z does not propose TL1302Y.
  • A different model from the same brand is not a candidate.
  • At most 4,000 recall records are kept. If the window is larger, the newest 4,000 are kept, coverage is partial, and the watermark does not advance.
  • The default catalog cap is 5,000 items (maximum input 50,000). Items past the cap are not checked.
  • An empty recall window still charges catalog-item-checked and says the assessed scope was empty. A blocked source charges nothing and fails the run.
  • Shopify public JSON usually has no manufacturer part number.
  • CPSC RecallNumber search is a wildcard on the API side. This Actor keeps a record only when the number is an exact match.
  • Sample mode pins a few recall numbers so the demo stays populated. Your own catalog does not pin them.

Run locally

Python 3.12.

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
pytest
python3 -m src

pytest includes a live CPSC smoke of the default sample. Skip it with pytest -m "not live" when you have no network. ACTOR_TEST_PAY_PER_EVENT=1 python3 -m src writes a local charging log. Results land in storage/datasets/default and storage/key_value_stores/default/OUTPUT.json.

On Apify, the default memory is 512 MB and the run timeout is 10 minutes. The image is apify/actor-python:3.12. Do not grant the Actor full permissions. It only calls the official CPSC API, the optional Health Canada open-data URL, and the catalog URL you supply.

Sources

CPSC recall text comes from the public SaferProducts REST API at https://www.saferproducts.gov/RestWebServices/Recall. Attribute those records to the U.S. Consumer Product Safety Commission and follow the official recall link on each row. This Actor is an unofficial client.

Health Canada rows, when enabled, come from the public open-data JSON and are under the Open Government Licence – Canada.

The User-Agent identifies cpsc-recall-catalog-matcher and plusonets. Requests are retried with backoff on rate limits and server errors.

Contact

Questions about this Actor: plusonecares@gmail.com.