FDA Recalls Scraper (Food Enforcement Data)
Pricing
from $5.00 / 1,000 recall delivereds
FDA Recalls Scraper (Food Enforcement Data)
Track official FDA food enforcement recalls from openFDA: recall number, event, report date, classification, status, product, reason, recalling firm, distribution. For recall monitoring, food-safety alerts and building a recall database. Public CC0 data, clean JSON, pay per event.
Pricing
from $5.00 / 1,000 recall delivereds
Rating
0.0
(0)
Developer
Steadfetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 hours ago
Last modified
Share
steadfetch/fda-recall-monitor
Monitor official FDA food enforcement recalls through the openFDA API: recall number, event, report date, classification, status, product, reason, recalling firm, distribution and location as structured JSON. Public CC0 data, no API key, no browser, pay per event.
What it does
Extract food and supplement recall records from the official openFDA food enforcement endpoint (https://api.fda.gov/food/enforcement.json). Filter by report-date window, FDA classification (Class I, Class II or Class III), US state and keyword, and receive one dataset item per recall. The source is a public JSON API, not a scraped web page: no login, no CAPTCHA and no headless browser.
The endpoint is key-free and publishes 29,485 food enforcement records at the time of writing (openFDA metadata, 2026-10-20). The data is released under CC0 and, per openFDA, does not contain personally identifiable information. The Actor only emits company and product fields and deliberately drops the firm street address and postal code present in the raw record, so no person-level or private-address data is returned.
Monitor mode is on by default. The Actor remembers the recall numbers already delivered for the same classification/state/keyword stream in its key-value store, and only pushes and charges recalls it has not delivered before. Set monitorMode: false for a one-off historical backfill.
Input: since (YYYY-MM-DD, default 30 days before until/today), until (YYYY-MM-DD, default today), classification (Class I, Class II, Class III), state (two-letter code), keyword, maxResults (default 1,000, maximum 5,000), monitorMode (default true) and strictSchema (default true).
Output: one dataset item per recall with recall_number, event_id, report_date, classification, status, product_description, reason_for_recall, recalling_firm, distribution_pattern, product_quantity, voluntary_mandated, city, state, country and source_url, plus the common url and scrapedAt envelope. Format: JSON dataset.
Example output
Real record from the committed fixture, which was saved from the official API on 2026-10-20:
{"recall_number": "H-1407-2026","event_id": "99887","report_date": "2026-10-07","classification": "Class I","status": "Ongoing","product_description": "Todd's Seeds Salad Sprouting Mixture - Broccoli, Mustard, Alfalfa and Cabbage, packaged in heat-sealed resealable bags in the following sizes: 1/4 lb., 1/2 lb., and 1 lb.","reason_for_recall": "Products contain alfalfa seed recalled by the supplier and may be contaminated with E. coli and/or Salmonella (downstream recall)","recalling_firm": "TODD'S SEEDS LLC","distribution_pattern": "Nationwide; through Amazon or direct to consumer","product_quantity": "37,481 retail units (TOTAL)","voluntary_mandated": "Voluntary: Firm initiated","city": "Cookeville","state": "TN","country": "United States","source_url": "https://api.fda.gov/food/enforcement.json?search=recall_number:%22H-1407-2026%22","url": "https://api.fda.gov/food/enforcement.json?search=recall_number:%22H-1407-2026%22","scrapedAt": "2026-10-20T08:00:00.000Z"}
Missing optional values are null; incomplete records that lack a required field (recall number, valid report date, classification, product description or recalling firm) are dropped instead of filled with made-up values.
openFDA occasionally publishes a recall as Not Yet Classified. That value is passed through unchanged instead of being guessed or mapped to a class; the classification input only filters the three final classes (Class I, Class II, Class III).
Pricing
Pay-per-event pricing (.actor/pay_per_event.json, referenced from actor.json as payPerEvent):
| Event | Charged when | Price |
|---|---|---|
apify-actor-start | a run starts (synthetic, automatic) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute) |
apify-default-dataset-item | a validated recall is saved to the dataset (synthetic, automatic) | 0.005 USD per recall = 5.00 USD per 1,000 recalls |
There is no custom per-item event and no Actor.charge() call: the platform already charges apify-default-dataset-item per pushed item, so adding another event would bill users twice. Failed requests, retries, empty results and recalls already delivered in monitor mode are not charged per recall.
The price sits inside the direct-competitor band for this source (about 8-50 USD per 1,000 events) and inside Steadfetch's 1-10 USD band, entering below the competition.
Cost per 1,000 results
The Actor uses plain HTTP and JSON: no browser and no proxy. Local measurement on 2026-10-20 over a 1,000-result request: one API request, about 1.5 seconds of wall time and 7 milliseconds of parsing, yielding 999 validated recalls (one source record had an empty recall number and was dropped). At 256 MB and Apify's USD 0.20 per GB-hour compute rate, that is under 0.01 USD per 1,000 results, leaving a gross margin above 99 percent. The first paid platform run will replace this local measurement with the billed figure.
Monitor mode and dedupe
- Recalls are keyed by
recall_number, the FDA identifier for the enforcement record. Duplicates inside one response are removed before validation. - With
monitorMode: true(default), the Actor reads its key-value store, skips recall numbers already delivered for the same classification/state/keyword stream and updates the store only with recalls it actually pushed. - Date bounds are not part of the stream identity, so moving the default 30-day window forward keeps recognising earlier deliveries.
- A different classification, state or keyword is a different stream and starts with its own history.
monitorMode: falseignores and does not update the store, which is what you want for a complete historical backfill.- No dataset item means no per-result charge; the automatic
apify-actor-startevent still applies once per run.
Input reference
Minimal run:
{"since": "2026-09-01","maxResults": 10}
Filtered monitor:
{"since": "2026-09-01","until": "2026-10-20","classification": "Class I","state": "FL","keyword": "allergen","maxResults": 1000}
| Field | Type | Description |
|---|---|---|
since | string | Inclusive YYYY-MM-DD lower bound for report_date. Defaults to 30 days before until/today. |
until | string | Inclusive YYYY-MM-DD upper bound for report_date. Defaults to today (UTC). |
classification | string | One of Class I, Class II, Class III. |
state | string | Two-letter US state code of the recalling firm, e.g. FL. |
keyword | string | Case-insensitive match against product description, reason for recall and recalling firm. |
maxResults | integer | Default 1,000, maximum 5,000. |
monitorMode | boolean | Default true; only emit and charge new recalls. Disable for a backfill. |
strictSchema | boolean | Default true; fail the run on a schema violation. |
FAQ
Do I need an openFDA API key? No. The food enforcement endpoint is key-free. The Actor sends only plain HTTP requests to the official host.
Does it need a browser or a proxy? No. openFDA returns structured JSON, so plain HTTP is enough and roughly 10 times cheaper than a browser-based Actor.
When am I charged?
Once per run start (apify-actor-start) and once per validated recall saved to the dataset (apify-default-dataset-item). Failed requests, retries, empty results and already-delivered recalls are not charged per result.
What happens when there are no matching recalls?
openFDA returns NOT_FOUND; the Actor treats it as an empty result, exits successfully and pushes nothing. Only the automatic start event applies.
Does monitor mode keep charging the same recalls? No. Recalls already delivered for the same classification/state/keyword stream are skipped and not pushed, so they are not charged again.
Does it return personal data? No. Only product and company-level fields are emitted. The raw record's street address and postal code are dropped, and no person-level fields are requested.
Why does a run return fewer items than maxResults?
The report-date window or filters may match fewer records, or an incomplete source record may have been dropped. The Actor never invents values to fill a gap.
Is the output schema stable?
Yes. New fields are additive and the required fields in .actor/dataset_schema.json do not change.
Maintenance
Maintenance status: the Actor is registered for daily catalog QA and is tested daily once published. The label Tested daily. Last verified: {YYYY-MM-DD}. will be published only after the first successful scheduled run is logged. Until then no verified date is claimed.
The source is the versioned official openFDA API. Committed fixtures and schema tests catch response-shape changes in CI, and any break in the daily run is reported as an issue in the Steadfetch Actors repository.
Keywords
fda recall, food recall, openFDA, food enforcement, recall monitor, product recall, allergen recall, compliance data, supply chain risk, JSON dataset
Development
npm installnpm test # lint + typecheck + offline Vitest suites (no Apify credit)npm run publish:check # Steadfetch publish gatenpm run build && npm run verify:build # production entrypoint guardnpm run schemas:validatenpm run smoke # local Apify run (no platform credit)
The default test suite runs against a committed real API response and never touches the network or the Apify platform. src/extract.ts is pure parsing (no network, no Apify imports), src/fda-client.ts is the key-free HTTP client with retries, and src/monitor-state.ts holds the cross-run dedupe logic.
Project structure:
.actor/actor.json Actor definition (payPerEvent, dataset schema, output schema).actor/input_schema.json INPUT options (date window, classification, state, keyword, maxResults, monitorMode).actor/dataset_schema.json Item JSON Schema (fields) + table view for the Store.actor/pay_per_event.json PPE pricing (two synthetic platform events)src/fda-client.ts openFDA HTTP client: query building, pagination, retriessrc/extract.ts Pure normalization and filtering (fixture-tested, no network)src/monitor-state.ts Cross-run dedupe by recall_number per filter streamsrc/main.ts Entry point: fetch, validate, dedupe, push, save statesrc/validate-output.ts Ajv validator shared by the run and the teststest/ Offline fixtures and Vitest suites