US CPSC Product Recalls Scraper (Recall Data JSON)
Pricing
from $3.50 / 1,000 recall delivereds
US CPSC Product Recalls Scraper (Recall Data JSON)
Pull official CPSC product recall notices from the public SaferProducts API: recall dates, products, hazards, remedies and involved organisations. For product-safety monitoring, compliance alerts and building a recall database. Schema-validated JSON, pay per result.
Pricing
from $3.50 / 1,000 recall delivereds
Rating
0.0
(0)
Developer
Steadfetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
an hour ago
Last modified
Share
steadfetch/cpsc-recall-monitor
Monitor U.S. CPSC product recalls from the official SaferProducts.gov public API. Every recall is normalized to a stable, English-field schema, validated before it reaches the dataset, and delivered only once per user so a scheduled monitor never bills the same recall twice.
No website scraping, no login, no API key. The source is the keyless REST endpoint https://www.saferproducts.gov/RestWebServices/Recall returning JSON.
What it does
- Queries the official CPSC SaferProducts.gov recall API for an inclusive date range (
since/until). - Optionally filters recalls by a case-insensitive keyword on the title and description.
- Normalizes each recall into the fields below and validates it against the published dataset schema.
- Deduplicates by
recall_number: the first run delivers all matching recalls, and later runs deliver only recalls that have never been delivered to that Apify user. - Excludes
ConsumerContactand every other personal-contact field. Only product and company data is published. - Skips malformed records instead of inventing values, and fails the run if any item would violate the output schema.
Input
| Field | Type | Default | Description |
|---|---|---|---|
since | string (YYYY-MM-DD) | 30 days before today | Start of the inclusive recall-date range. |
until | string (YYYY-MM-DD) | no upper bound | End of the inclusive recall-date range. |
keyword | string | none | Case-insensitive keyword matched against the title and description. |
maxResults | integer (1-5000) | 1000 | Maximum number of new recalls delivered in one run. |
monitorMode | boolean | true | Deliver only recalls not delivered before. Set to false for a full backfill that ignores monitor state. |
Example input:
{"since": "2026-10-01","maxResults": 10}
Monitor state
The Actor is a monitor by design. Delivered recall_number values are stored in the named key-value store cpsc-recall-monitor-state, under the key seen-<apify-user-id> (locally seen-local). The user ID in the key isolates tenants: one customer's runs can never hide recalls from another customer's runs. Set monitorMode: false to skip this state entirely (the Store default input does, so its daily probe always gets recalls).
- First run: delivers every matching recall in the requested range.
- Later runs: delivers only recalls whose
recall_numberhas not been delivered before. - Filtered-out, malformed or failed records are not marked as seen, so they remain eligible for a later run.
- Clearing that key resets the monitor and makes the next run a full backfill.
Output fields
recall_number, recall_id, recall_date, title, description, product_names, hazards, remedies, units, injuries, manufacturer_countries, last_publish_date, source_url, plus the common Steadfetch envelope fields url, title and scrapedAt.
remedies contains the short CPSC remedy options (Refund, Repair, Replace, Dispose), not the long consumer instructions.
Example output
{"url": "https://cpsc.gov/Recalls/2027/HatsanUSA-Recalls-Blitz-Air-Rifles-Due-to-Risk-of-Serious-Injury-or-Death-from-Projectile-Hazard","title": "HatsanUSA Recalls Blitz Air Rifles Due to Risk of Serious Injury or Death from Projectile Hazard","scrapedAt": "2026-10-20T08:00:00.000Z","recall_number": "27025","recall_id": 11026,"recall_date": "2026-10-15","description": "This recall involves all units of the Hatsan Blitz and Blitz 777 pellet shooting air rifles...","product_names": ["Blitz and Blitz 777 air rifles"],"hazards": ["The recalled air rifles can fire unexpectedly when releasing the bolt before the air rifle is fully cocked, posing a risk of serious injury or death from projectile hazard."],"remedies": ["Replace"],"units": "About 16,170","injuries": "The company has received one report of an accidental misfire. No injuries have been reported.","manufacturer_countries": ["Turkey"],"last_publish_date": "2026-10-15","source_url": "https://cpsc.gov/Recalls/2027/HatsanUSA-Recalls-Blitz-Air-Rifles-Due-to-Risk-of-Serious-Injury-or-Death-from-Projectile-Hazard"}
Project structure
.actor/actor.json Actor definition (payPerEvent -> pay_per_event.json, storages.dataset -> dataset_schema.json).actor/input_schema.json INPUT schema (since, until, keyword, maxResults).actor/dataset_schema.json Dataset schema: item JSON Schema (fields) + Store card table view (views).actor/output_schema.json Actor output schema: links the default dataset as the results output.actor/pay_per_event.json PPE pricing: the two synthetic platform events and their pricessrc/main.ts Entry point: fetch, normalize, dedupe against monitor state, validate, pushsrc/fetch.ts HTTP client for the official CPSC JSON API (retries, timeouts, date validation)src/extract.ts Pure normalization and filtering (fixture-tested, no network)src/monitor-state.ts Cross-run dedupe state (per-user key in a named key-value store)src/validate-output.ts Dataset item schema validator (Ajv) shared by the run and the teststest/ Offline tests: extraction, HTTP client, state, schemas, pricing, publish gate
This folder lives inside the Steadfetch Actors monorepo: the publish gate (tools/publish-check.mjs), the shared schema validator (tools/schema-validator/) and the GitHub Actions CI are shared across all Actors.
Pay-per-event pricing
The Actor uses the Apify pay-per-event (PPE) model with the two synthetic platform events (.actor/pay_per_event.json, referenced from actor.json as payPerEvent):
| Event | Charged when | Price |
|---|---|---|
apify-actor-start | a run starts (synthetic, charged automatically) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute per run) |
apify-default-dataset-item | a new recall is saved to the dataset (synthetic, charged automatically) | 0.0035 USD per recall = 3.50 USD per 1,000 new recalls |
There is deliberately no custom per-item event and no Actor.charge() call in the code: the platform charges apify-default-dataset-item for every pushed item, and a custom event on top would bill users twice. Because already-delivered recall numbers are removed before the push, they are never charged again.
Price rationale: direct competitors on the Apify Store charge between 2.7 and 50 USD per 1,000 recalls. 3.50 USD per 1,000 sits inside the Steadfetch 1-10 USD band and below the competitor median.
Cost per 1,000 recalls (estimated): under 0.10 USD. The source needs one keyless JSON request per run (no browser, no proxy) plus a few seconds of 256 MB compute to normalize and validate the records. That leaves a gross margin above 97% at the 3.50 USD price.
After apify push, apply the prices to the platform with node ../../../tools/apply-pricing.mjs --apply (pushing alone does not set them).
Output schema validation
Nothing at Steadfetch is published without automated output-schema tests:
npm testruns lint, typecheck and Vitest offline: the schema is compiled with Ajv, valid and invalid fixture items behave as expected, and extraction plus the HTTP client run against committed API fixtures (no network, no credit).npm run publish:checkruns the Steadfetch publish gate (schema compilation, PPE price band, English listing, CI presence). It must exit 0 before publishing.- CI runs both on every push, so a failing schema blocks merge and release.
FAQ
Does the Actor need an API key or a paid subscription? No. The CPSC SaferProducts.gov recall API is public, free and keyless. No Actor secret, login or account is needed.
Does it scrape the SaferProducts.gov website?
No. It calls the official REST endpoint and asks for JSON (format=json). It never parses the website HTML.
Does it collect personal data?
No. Only product and company recall fields are mapped. ConsumerContact and any other personal-contact data are deliberately excluded.
Why do I get fewer results on the second run?
That is the monitor behavior: recalls already delivered to your Apify user are deduplicated by recall_number and are not charged again. Clear the seen-<your-user-id> key in the cpsc-recall-monitor-state store to force a full backfill.
When am I charged?
Once per run start (apify-actor-start) and once per new recall saved to the dataset (apify-default-dataset-item), as shown in the pricing table. A run that delivers no new recalls only incurs the start event.
Are the dates inclusive and in which timezone?
since and until are inclusive calendar dates interpreted by the CPSC API. Normalized recall dates are published as YYYY-MM-DD and scrapedAt is UTC.
Is the output schema stable?
Yes. New fields are added additively and required fields never change; your downstream code can rely on recall_number, recall_date, title and source_url.
Can I request a new source? Yes, open an issue in this repository describing the public page or API you want tracked.
Maintenance
This Actor is tested daily: automated runs keep the source reachable and the output schema valid. Schema changes are announced through repository releases.