How it will work: 3 steps
- Follow us on ApifyThe tool is built and waits in the queue for the Apify Store. Apify is the cloud platform where our tools run.
- Paste your input and press StartNo code needed: you fill in a form (the input). It already has a working example. For example:
foodin Product categories. Each start is one run. - Download the resultsWhen the run ends, the results are in a table (the dataset). Open Output, press Export and pick Excel or CSV. JSON and the API are there too.
This tool is an Apify Actor: a small program that runs on Apify. You can also run it on a schedule, for example every Monday. New to it? Read how it works, step by step and the 8 words you need first.
What it does
FDA Recalls & Enforcement Monitor searches FDA recalls of food, drugs and medical devices in the official FDA Enforcement Reports, through the openFDA API. Filter by recall class, status, dates, firm, product, reason and firm state. You get one clean row per recalled product, with the four FDA dates kept apart, the recall number, the firm, the reason and a link to the record in openFDA.
- One request per category. Each category you pick is one search, newest records first.
maxResultsapplies to each category. - Four dates, never mixed.
recallInitiationDate(the firm started the recall),centerClassificationDate(FDA set the class),reportDate(FDA published it in the Enforcement Report) andterminationDate(FDA closed it). You choose which date your range uses. - Not yet classified recalls. The newest recalls appear before FDA gives them a class and a recall number. You can include or skip them. They get a stable
recallKeyanyway. - Monitoring. With a
monitorKey, the Actor saves the recalls it saw in your account. The next run marks each recallnew,updated(for example the status changed toTerminated) orunchanged, and by default writes only new and updated ones.
Good for
- Get a weekly list of new Class I food recalls for your quality or safety team.
- Check if a supplier, brand or competitor had FDA recalls, by firm name.
- Find recalls by reason:
Listeria,undeclared peanut,sterility,mislabeling. - Watch medical device or drug recalls of one firm state, such as
CAorNJ. - Feed FDA recall rows into a spreadsheet, a dashboard or an AI agent.
Not a good fit
- Official consumer alerts or a guarantee that you see every recall in time. FDA publishes Enforcement Reports weekly, and a recall can appear later than the firm's press release.
- Full recall lifecycle tracking. The Actor sees only what the Enforcement Report says on the day of the run.
- Cosmetics or tobacco as separate categories, animal food recall press releases, and device recall details from the separate device recall database.
- A full copy of all FDA recalls since 2004. Use the openFDA downloads for that.
Who uses it
Food safety and QA teams
Get a weekly list of new Class I food recalls.
Buyers and supplier managers
Check if a supplier had recalls.
Health researchers
Study recall reasons, like allergens or germs.
Price
Prices will be shown after release.
Questions
Where does the recall data come from?
From openFDA, the official open data API of the US FDA. The records are FDA Enforcement Reports.
What do Class I, II and III mean?
Class I is the most serious risk. Class II is a temporary or reversible risk. Class III is unlikely to cause health problems.
Do I need an openFDA API key?
No. A free key is optional. It only gives you your own request limit.
Is this medical advice?
No. It is FDA data as published. Ask FDA, the firm or your doctor about a product you use.
How fresh is the data?
Each run reads openFDA live. openFDA updates the enforcement data about once a week, after FDA publishes the Enforcement Report. Firm press releases can come earlier.
Does monitoring cost more?
No. Unchanged recalls are not written and not charged when onlyChanges is on.
Why are there no device codes like GMDN?
GMDN terms have their own licence, so the Actor does not output openFDA device classification data. If a firm wrote a code into its own codeInfo text, it stays as FDA published it.
Details
Step by step on Apify
- Open the Input tab. The form already has an example: food, Class I, last 30 days, 20 recalls.
- Click Start.
- Open the Recalls view in the Output tab. Check
recallNumber,recallingFirm,productDescription,reasonForRecallandreportDate. - Change the categories, classes or filters to your own. To get only changes next time, add a
monitorKeysuch asfood-class-1.
Use cases
| Task | Input | You get | Key fields |
|---|---|---|---|
| Weekly food safety watch | categories: ["food"], classifications: ["I"], monitorKey, on a schedule | only new and updated Class I recalls | changeType, reasonForRecall, distributionPattern |
| Supplier check | firmNames, all categories, a 90-day range | that firm's recalls | recallingFirm, productDescription, status |
| Allergen or pathogen research | reasonQuery: "Listeria" or "undeclared peanut" | matching recalls | reasonForRecall, codeInfo, productQuantity |
| Medical device recall search | categories: ["device"], productQuery, firmStates | device recall rows | recallNumber, classification, centerClassificationDate |
| Drug recall list with NDCs | categories: ["drug"] | drug rows | drugBrandNames, drugGenericNames, drugProductNdcs |
| Closed recalls | dateField: "termination_date" | recalls FDA terminated in the range | terminationDate, status |
Tips for better results
- Pick which date your range uses. FDA keeps four different dates per recall.
- Use a monitor key to get only new and updated recalls each week.
- openFDA updates about once a week, so a weekly run is often enough.
Limits and good to know
- Up to 1,000 recalls per category and 3 categories, so up to 3,000 rows per run. In our test, 2,541 rows took about 4 seconds
- The date range is at most 90 days. Enforcement Reports start in 2004
- Without a key, openFDA limits requests per IP address per day, and the IP can be shared with other users. Each category is one request. If openFDA refuses with a rate limit, the run stops with
ERRORand a hint. Add your freeopenFdaApiKeyto get your own quota - If openFDA does not answer (up to 30 seconds per try, at most 3 tries and 2 timeouts), the run stops early and does not ask for the other categories. You get the rows found so far. The internal time budget is 240 seconds
- Monitor snapshots keep up to 20,000 recalls per
monitorKey.previousDatasetIdreads up to 20,000 rows - Not yet classified recalls have no recall number. When FDA classifies them, they come back with a recall number as a
newrow with a newrecallKey firmStatesis the firm's address, not where the product was sold- openFDA updates the enforcement data about once a week.
sourceUpdatedAtshows the last update
Troubleshooting
| Symptom | Check | Action |
|---|---|---|
NO_RESULTS | RUN_SUMMARY.filters | Widen the dates, remove a class or a filter, or try another dateField. |
ERROR with a rate limit message | RUN_SUMMARY.message | Add your free openFdaApiKey, or run again later. |
ERROR, openFDA unavailable | RUN_SUMMARY.message | Run again later. A failed run does not remove recalls from your monitor snapshot; it only adds the rows it reported. |
INVALID_INPUT | RUN_SUMMARY.message | Fix the field named in the message. The range is at most 90 days. |
truncated: true | categories[].matchedAtSource | Raise maxResults (up to 1,000) or narrow the filters or dates. |
| A recent recall is missing | sourceUpdatedAt, reportDate | FDA adds recalls to the weekly report after classification. Try dateField: "recall_initiation_date" and include unclassified. |
| Warning about different filters | RUN_SUMMARY.warnings | You changed filters but kept the same monitorKey. Use one key per saved search. |
For developers
Sample result
One result from a real test run, in JSON. Real values change from run to run. In Excel or CSV each field is a column.
{
"recallKey": "food:H-1395-2026",
"recallNumber": "H-1395-2026",
"eventId": "99874",
"category": "food",
"classification": "Class I",
"recallClass": "I",
"status": "Ongoing",
"recallingFirm": "WFM Purchasing, LP",
"productDescription": "Fromagerie Coop de Mean Cabricharme and Belgium Cabricharme ...",
"productQuantity": "21 Wheels",
"reasonForRecall": "Undeclared Egg Allergen",
"distributionPattern": "CA, AZ, NJ, MA, RI, CT, NY",
"recallInitiationDate": "2026-09-08",
"centerClassificationDate": "2026-09-22",
"reportDate": "2026-09-30",
"terminationDate": null,
"city": "Austin",
"state": "TX",
"changeType": null,
"sourceUrl": "https://api.fda.gov/food/enforcement.json?search=recall_number%3A%22H-1395-2026%22",
"provider": "openFDA (U.S. Food and Drug Administration)",
"sourceUpdatedAt": "2026-09-30",
"fetchedAt": "2026-10-12T10:54:50.382Z"
}Data fields (37)
| Field | Type | Meaning |
|---|---|---|
changeType | string | Change since the baseline: initial (first monitored run), new, updated or unchanged. null when no monitorKey/previousDatasetId is used. |
changedFields | array | For updated recalls: which tracked fields changed (status, classification, terminationDate, the other dates, firm, product, quantity, code info, reason, distribution, voluntaryMandated). |
previousValues | object | For updated recalls: previous value of each changed field. |
recallKey | string | Stable key: category:recallNumber, e.g. food:F-0123-2026; for not-yet-classified recalls category:unclassified:eventId:hash. Use it to deduplicate and join. |
recallNumber | string | FDA recall number, e.g. F-0123-2026 (food), D-0860-2026 (drug), Z-3247-2026 (device). null while FDA has not classified the recall yet. |
eventId | string | FDA recall event ID. One event (one firm action) can cover several products, each with its own recall number. |
category | string | openFDA enforcement category searched: food, drug or device. |
productType | string | Product type as reported by FDA, e.g. Food/Cosmetics, Drugs, Devices. |
classification | string | FDA classification text: Class I, Class II, Class III or Not Yet Classified. |
recallClass | string | Short class: I, II or III; null when not yet classified. |
status | string | Recall status: Ongoing, Completed or Terminated. |
recallingFirm | string | Firm that initiated the recall. |
productDescription | string | Product as described in the enforcement report. |
productQuantity | string | Quantity of product recalled, as free text. |
codeInfo | string | Lot numbers, codes, expiry dates identifying the recalled units. |
moreCodeInfo | string | Continuation of codeInfo when FDA splits it. |
reasonForRecall | string | Reason for the recall, as written by FDA. |
distributionPattern | string | Where the product was distributed, as free text (states, countries, nationwide). |
voluntaryMandated | string | Voluntary: Firm initiated, or FDA Mandated. null when FDA reports N/A or nothing. |
initialFirmNotification | string | How the firm first notified consignees, e.g. Letter, E-Mail, Press Release. |
recallInitiationDate | string | Date the firm started the recall (YYYY-MM-DD, calendar date without time zone). |
centerClassificationDate | string | Date the FDA center classified the recall (YYYY-MM-DD). |
reportDate | string | Date FDA published the recall in its weekly Enforcement Report (YYYY-MM-DD). |
terminationDate | string | Date FDA terminated the recall (YYYY-MM-DD); null while it is not terminated. |
address1 | string | Recalling firm address line 1. |
address2 | string | Recalling firm address line 2. |
city | string | Recalling firm city. |
state | string | Recalling firm state (2-letter code for US firms). |
postalCode | string | Recalling firm postal code. |
country | string | Recalling firm country. |
drugBrandNames | array | Drug recalls only: brand names linked by openFDA. null for food and device rows. |
drugGenericNames | array | Drug recalls only: generic names linked by openFDA. |
drugProductNdcs | array | Drug recalls only: product NDC codes linked by openFDA. |
sourceUrl | string | openFDA API query returning this recall (by recall number; by event ID while not yet classified). |
provider | string | Data provider: openFDA (U.S. Food and Drug Administration). |
sourceUpdatedAt | string | openFDA dataset last_updated date at fetch time (YYYY-MM-DD). |
fetchedAt | string | When this run fetched the record (ISO 8601 UTC). |
Input settings (16)
The main fields of the input form. With the API you send them as JSON.
| Setting | Name in the API | Type | Example value |
|---|---|---|---|
| Product categories | categories | array | ["food"] |
| Recall class | classifications | array | ["I"] |
| Recall status | statuses | array | ["Ongoing", "Completed", "Terminated"] |
| Look back (days) | lookbackDays | integer | 30 |
| Date the range applies to | dateField | string | "report_date" |
| Date from | dateFrom | string | |
| Date to | dateTo | string | |
| Recalling firm names | firmNames | array | |
| Product contains | productQuery | string | |
| Reason contains | reasonQuery | string | |
| Firm states | firmStates | array | |
| Max recalls per category | maxResults | integer | 20 |
| Monitor key (for scheduled runs) | monitorKey | string | |
| Compare with dataset | previousDatasetId | string | |
| Only write new and updated recalls | onlyChanges | boolean | true |
| openFDA API key (optional) | openFdaApiKey | string |
