Technical writing

FDA Food Enforcement Reports: The Federal Database Behind Food and Cosmetic Recalls

· 11 min read· AI Analytics
FDAFood SafetyRecallsCosmeticsFederal Data

The openFDA Food Enforcement dataset surfaces every food and cosmetic recall the FDA has formally classified through its Recall Enterprise System — roughly 25,000 records, each carrying the reason for the recall, the recalling firm, the hazard classification, the distribution footprint, and the dates that trace a recall from initiation to termination. It is the machine-readable backbone of the recall notices that scroll across grocery store bulletin boards and news tickers.

When a peanut butter maker discovers Salmonella on a production line, when a bakery realizes it printed labels that omit the milk in a glaze, or when a cosmetics importer learns a face cream contains an undeclared drug ingredient, the FDA opens an enforcement action. Once the agency reviews the hazard and assigns a classification, a structured record appears in the Food Enforcement reports and flows out through the openFDA API. The dataset is not a feed of every product that quietly leaves the market — it captures classified recalls, the subset the FDA has evaluated and graded by health risk. But within that boundary it is the most complete, queryable federal record of what was recalled, why, by whom, and how widely the product had spread before it was pulled.

What the dataset is

The Food Enforcement reports come from the FDA's Recall Enterprise System (RES), the internal case-management system the agency uses to track recall events from the moment a firm or the FDA identifies a problem through final termination. The FDA publishes these records in a weekly Enforcement Report, historically a static document and now also an interactive portal. The openFDA program harvests the same underlying data, normalizes it into JSON, and exposes it at the food/enforcement.json endpoint — one of several enforcement endpoints alongside drug and device recalls.

Despite the name, the food endpoint covers both food and cosmetics, the two product categories the FDA regulates that are not drugs or devices. The product_typefield distinguishes them, with values of Food (the large majority) and Cosmetics. Dietary supplements and animal feed also flow through this endpoint, classified under the food product type, because the FDA regulates them under the food provisions of the Federal Food, Drug, and Cosmetic Act. The dataset reaches back to recalls from the early 2000s, and at any given time holds on the order of 25,000 classified events.

Each record is a single recall event for a single firm and a defined set of products. The core fields describe the product and the problem: product_description is a free-text description of the recalled goods, including brand names, package sizes, and lot coverage; reason_for_recall is a free-text statement of the hazard; recalling_firm is the company conducting the recall; and product_quantity records how much product was in distribution. The code_info field lists the specific lot numbers, UPC codes, expiration dates, and establishment numbers that identify which units are affected — the practical key a retailer or consumer uses to tell a recalled package from a safe one. A set of address fields (address_1, address_2, city, state, postal_code, and country) locates the recalling firm.

Recall classification

The single most important field in the dataset is classification. When the FDA learns of a recall, the agency's health hazard evaluation board assesses the probability and severity of harm and assigns one of three classes. The class is the FDA's formal judgment of how dangerous the product is, and it drives everything from how aggressively the recall is publicized to how quickly the agency expects the firm to act.

Class I is the most serious. It denotes a situation in which there is a reasonable probability that use of or exposure to the product will cause serious adverse health consequences or death. Undeclared major allergens in a product eaten by allergic consumers, Listeria monocytogenes in a ready-to-eat food, botulism risk in an improperly processed canned good, and E. coli O157:H7 contamination all routinely draw Class I designations. Class I recalls are the ones that generate press releases, consumer alerts, and, in outbreak situations, coordinated public health messaging.

Class II covers situations in which use of or exposure to the product may cause temporary or medically reversible adverse health consequences, or where the probability of serious adverse consequences is remote. Many labeling defects, the presence of a minor allergen at low levels, or contamination unlikely to cause lasting harm fall into Class II. It is the most common classification in the dataset by volume.

Class III is the least serious. It applies when use of or exposure to the product is not likely to cause adverse health consequences — for example, a product that violates FDA regulations on labeling or manufacturing but poses little or no health risk, such as a minor net-weight discrepancy or a non-hazardous standard-of-identity violation. Class III recalls rarely receive public attention but are still tracked and terminated through the same lifecycle.

A subtle but important distinction sits beneath the classification: a market withdrawal is not a recall. When a firm removes a product for a minor issue that would not be subject to FDA legal action — or corrects a problem without removing product — the FDA treats it as a market withdrawal or a stock recovery, and it does not receive a class. Those events do not appear in the enforcement dataset. The dataset is, by construction, a record of classified recalls only.

Recall mechanics and the recall lifecycle

Most food recalls in the United States are voluntary in the sense that the firm conducts them, not that they are entirely the firm's idea. The voluntary_mandated field captures this, most commonly with the value Voluntary: Firm initiated. A firm may discover a problem through its own testing, a supplier may notify it of a contaminated ingredient, or the FDA may approach the firm after its own inspection or after laboratory results from an outbreak investigation. In the great majority of cases the firm then agrees to recall, and the action is recorded as firm-initiated even when the FDA prompted it.

For most of the FDA's history, the agency had no authority to force a food recall; it could request one and apply pressure, but a recall ultimately depended on the firm's cooperation. That changed with the FDA Food Safety Modernization Act of 2011, which for the first time granted the FDA mandatory recall authority over most foods. Under that authority, if a firm refuses to recall a product that the FDA determines is adulterated or misbranded and presents a reasonable probability of serious adverse health consequences, the agency can order the recall. Mandatory recalls remain rare — the threat is usually enough to secure a voluntary action — but the authority shifted the dynamics of every recall negotiation. The voluntary_mandated field is where a mandated action would be flagged.

The initial_firm_notification field records how the recalling firm first alerted its customers — values such as letter, telephone, email, press release, or a combination. This is a proxy for urgency and reach: a press release signals a broad consumer recall, while a letter to a handful of distributors signals a contained business-to-business action.

The lifecycle of a recall is captured by a sequence of date fields. The recall_initiation_date is when the firm first began removing or correcting product. The center_classification_date is when the relevant FDA center assigned the hazard class — the gap between these two dates is the classification lag, and it can run from days to many weeks. The report_date is when the recall was posted to the FDA's public Enforcement Report. The termination_date is when the FDA concluded that all reasonable efforts to remove or correct the product were complete and the recall could be closed.

Running alongside the dates is the status field. A recall is Ongoing while the firm is still actively recovering or correcting product; Completed when the firm has carried out the recall strategy but the FDA has not yet formally closed the case; and Terminated once the FDA has determined the recall is finished. Because the dataset is updated continuously, a record's status and its termination_date can both change over time, which matters for any analysis that snapshots the data.

Common reasons for recall

If there is one fact that surprises people about food recalls, it is that the leading cause is not contamination but labeling. Undeclared allergens are consistently the single most common reason for food recalls in the dataset. The Food Allergen Labeling and Consumer Protection Act requires that the major food allergens be clearly declared on packaging, and the FASTER Act of 2021 added sesame as the ninth major allergen effective in 2023. The nine major allergens are milk, egg, peanut, tree nuts, soy, wheat, fish, crustacean shellfish, and sesame. A product that contains one of these but fails to declare it — through a label printing error, an undisclosed ingredient change, a wrong label applied to the wrong product, or cross-contact on a shared line — is a recall waiting to happen, and for an allergic consumer the consequences can be life-threatening, which is why these recalls frequently land as Class I.

After undeclared allergens, the most prominent reasons are microbiological. Listeria monocytogenes is a perennial driver of food recalls and of the most serious ones, because it grows at refrigerator temperatures, carries a high case fatality rate, and disproportionately harms pregnant women, newborns, the elderly, and the immunocompromised. It turns up in ready-to-eat foods, soft cheeses, deli items, smoked seafood, frozen vegetables, and fresh produce. Salmonella is the other dominant pathogen, implicated in recalls of eggs, poultry, nut butters, raw flour, spices, fresh produce, and pet food. Shiga toxin-producing E. coli, particularly O157:H7, drives recalls of ground beef and leafy greens and is treated with particular seriousness because of its capacity to cause hemolytic uremic syndrome in children.

Beyond allergens and pathogens, foreign material is a recurring reason — fragments of metal, hard or sharp plastic, glass, rubber, or wood that entered the product during processing. Mislabeling unrelated to allergens accounts for another slice: incorrect ingredient statements, missing or wrong nutrition information, products sold under the wrong identity, or, on the cosmetics side, products containing undeclared drug ingredients or microbial contamination. Chemical contamination, including elevated levels of heavy metals or unapproved additives, rounds out the common categories. Because reason_for_recallis free text, these categories are not predefined fields; any analysis of recall causes requires keyword classification of the text, which the Python example below demonstrates.

Distribution patterns and the Reportable Food Registry

The distribution_pattern field describes how far the recalled product had spread, again as free text. At one extreme are nationwide recalls, often phrased as distributed nationwide or to all fifty states, which signal the broadest consumer exposure and the most complex recovery. At the other are tightly scoped recalls limited to a single state, a handful of named states, or a short list of named retail accounts. Some entries describe distribution through specific channels — foodservice only, a particular retail chain, or export to named countries. Distribution scope is a primary lever for risk: a Class I nationwide recall of a staple food is a fundamentally different event from a Class I recall confined to a regional bakery's wholesale customers, even though both share the same classification.

Sitting upstream of many recalls is the Reportable Food Registry, an electronic portal established by Congress in 2007 and launched in 2009. It requires the responsible party for an FDA-regulated food — other than infant formula and dietary supplements, which have their own reporting regimes — to notify the FDA within twenty-four hours when there is a reasonable probability that the food will cause serious adverse health consequences or death. The registry functions as an early-warning system: a single report from a manufacturer about a contaminated ingredient can ripple outward into recalls by every downstream firm that used that ingredient. Many of the larger multi-firm recall clusters in the enforcement dataset trace back to a single reportable food event at an ingredient supplier.

FSMA and preventive controls

The FDA Food Safety Modernization Act, signed into law in January 2011, is the legal backdrop for the modern recall landscape and the most significant overhaul of US food safety law since the 1930s. Its central idea is a shift from reacting to contamination after the fact toward preventing it, by requiring food facilities to systematically anticipate and control hazards. The Preventive Controls for Human Food rule requires registered facilities to maintain a written food safety plan built around a hazard analysis and risk-based preventive controls, including process controls, sanitation controls, supply-chain controls, and — critically for the recall picture — a dedicated food allergen control. Allergen controls cover both preventing cross-contact during production and ensuring that finished labels accurately declare allergens, directly targeting the leading cause of recalls.

FSMA also created the mandatory recall authority described above, strengthened the FDA's access to facility records, expanded inspection mandates, and built a framework for import oversight through the Foreign Supplier Verification Program. A later FSMA rule established enhanced traceability recordkeeping for designated high-risk foods, so that when a recall does occur, the contaminated product can be traced through the supply chain in hours rather than weeks. The cumulative effect is visible in the enforcement data: recalls increasingly originate from a firm's own preventive-controls testing and supplier verification, and the framework within which every recall is negotiated is shaped by the authorities FSMA created.

Major cases for context

A handful of large events illustrate the kinds of recalls the dataset records and why classification and distribution matter so much. The 2008 to 2009 Peanut Corporation of America outbreak linked Salmonella Typhimurium in peanut butter and peanut paste to a Georgia plant. Because peanut paste is an ingredient sold to hundreds of other manufacturers, the contamination propagated into one of the largest recall cascades in US history — thousands of products across many firms — and ultimately led to criminal convictions of company executives. It is the canonical example of how a single contaminated ingredient generates a sprawling, multi-firm footprint in the enforcement records.

The 2011 Jensen Farms cantaloupe outbreak, caused by Listeria monocytogenes traced to a Colorado packinghouse, sickened scores of people across dozens of states and caused dozens of deaths, making it among the deadliest foodborne outbreaks in modern US history and underscoring the lethality that earns whole-produce Listeria recalls their Class I status. The 2018 and 2019 romaine lettuce outbreaks, driven by E. coli O157:H7 contamination tied to specific growing regions, produced sweeping advisories to discard romaine and reshaped agricultural water standards. And in 2022, an infant formula recall connected to Cronobacter sakazakii and Salmonella concerns at a major Michigan facility shut down a critical production plant, fed into a national formula shortage, and demonstrated how a single facility's recall can cascade into a supply crisis far beyond the recalled lots themselves.

Real-world uses

The structured, queryable nature of the dataset makes it valuable well beyond reading individual recall notices. Supply-chain and procurement teams use it for risk monitoring, screening their suppliers and ingredient sources against the recall history and watching for new Class I actions involving firms they buy from. Because the data is updated continuously, a scheduled query against the recalling_firm and report_date fields can function as an automated early-warning system for a company's vendor list.

Food safety researchers and journalists use it for allergen-trend analysis, quantifying how the share of recalls attributable to undeclared allergens has grown over time and which allergens dominate, work that informed the policy case for adding sesame as a labeled allergen. Analysts track recall velocity — the count of recalls per month or quarter, or the lag between recall_initiation_date and center_classification_date — as an indicator of both contamination pressure in the food supply and the pace of FDA processing. Importers and trade-compliance teams screen the country and address fields to flag recalls tied to foreign suppliers, complementing the Foreign Supplier Verification Program. And public health and insurance analysts join the recall records against outbreak surveillance to study how recalls correlate with downstream illness.

Querying the food/enforcement API with Python

The openFDA Food Enforcement endpoint speaks a compact query language layered over a simple REST interface. The search parameter accepts field-scoped expressions joined with+AND+ and +OR+, exact-match phrases via the .exact suffix, and range queries with bracket syntax. The count parameter is the most powerful feature for aggregate work: it returns server-side term frequencies for a field without transferring individual records, which is the efficient way to tally recalls by class, by reason term, or by firm. For record-level retrieval, limit and skip page through results, with skip capped at 25,000, so broad pulls must be narrowed by date, state, or classification. Note that all date fields are stored as eight-digit YYYYMMDD strings with no separators, which range queries must match exactly. No API key is required for light use; registering for a free key raises the rate limits substantially.

The script below works through a representative analysis. It first counts every classified recall by class to size the dataset, then pulls a bounded slice — California food recalls reported between 2020 and 2024 — and buckets each into a reason category by keyword-matching the free-text reason_for_recall field. It cross-tabulates reason against classification, and finally uses a count query to rank the firms with the most Class I food recalls nationwide.

import requests
import pandas as pd
from collections import Counter

# ---------------------------------------------------------------
# openFDA Food Enforcement API
# Endpoint: https://api.fda.gov/food/enforcement.json
# No API key required for <= 240 requests/min and 1,000/day.
# Register at https://open.fda.gov/apis/authentication/ for
# 120,000 requests/day and a higher per-minute ceiling.
#
# This script:
#   1. Counts classified recalls by classification (Class I/II/III)
#   2. Pulls a date- and state-filtered slice of food recalls
#   3. Buckets each recall into a reason category from free text
#   4. Aggregates recalls by reason x classification
#   5. Ranks the recalling firms with the most Class I actions
# ---------------------------------------------------------------

BASE = "https://api.fda.gov/food/enforcement.json"


def count_field(field: str, search: str | None = None) -> dict:
    """Use the openFDA count parameter for a fast server-side tally.

    The count endpoint returns aggregate term frequencies without
    transferring individual records -- the efficient way to size a
    dataset before downloading rows.
    """
    params = {"count": field}
    if search:
        params["search"] = search
    resp = requests.get(BASE, params=params, timeout=30)
    resp.raise_for_status()
    results = resp.json().get("results", [])
    return {row["term"]: row["count"] for row in results}


def fetch_records(search: str, page_size: int = 1000) -> list[dict]:
    """Page through enforcement records for a search expression.

    openFDA caps skip at 25,000, so very broad queries must be
    narrowed (by date, state, or classification) to retrieve in full.
    """
    records: list[dict] = []
    skip = 0
    while True:
        params = {"search": search, "limit": page_size, "skip": skip}
        resp = requests.get(BASE, params=params, timeout=60)
        if resp.status_code == 404:        # openFDA returns 404 on empty result sets
            break
        resp.raise_for_status()
        batch = resp.json().get("results", [])
        if not batch:
            break
        records.extend(batch)
        print(f"  Fetched {len(records):,} records so far...")
        if len(batch) < page_size or skip + page_size >= 25000:
            break
        skip += page_size
    return records


# ---------------------------------------------------------------
# Step 1: How many classified recalls in each class?
# ---------------------------------------------------------------
print("Counting food recalls by classification...")
by_class = count_field("classification")
total = sum(by_class.values())
print("\nFood Recalls by Classification (all years)")
print("-" * 45)
for cls in ["Class I", "Class II", "Class III"]:
    n = by_class.get(cls, 0)
    pct = (n / total * 100) if total else 0
    print(f"{cls:<12} {n:>8,}  ({pct:5.1f}%)")
print(f"{'TOTAL':<12} {total:>8,}")


# ---------------------------------------------------------------
# Step 2: A bounded slice -- California food recalls, 2020-2024
#         report_date is stored as YYYYMMDD with no separators.
# ---------------------------------------------------------------
search = (
    "product_type:Food"
    "+AND+state:CA"
    "+AND+report_date:[20200101+TO+20241231]"
)
print("\nFetching California food recalls reported 2020-2024...")
rows = fetch_records(search)
df = pd.DataFrame(rows)
print(f"Retrieved {len(df):,} California food recall records.")


# ---------------------------------------------------------------
# Step 3: Bucket each recall into a reason category.
#         reason_for_recall is free text, so we keyword-match.
# ---------------------------------------------------------------
ALLERGENS = ["milk", "egg", "peanut", "tree nut", "almond", "cashew",
             "walnut", "pecan", "soy", "wheat", "gluten", "fish",
             "shellfish", "crustacean", "sesame", "undeclared"]


def classify_reason(text: str) -> str:
    t = (text or "").lower()
    if any(a in t for a in ALLERGENS) and ("undeclar" in t or "allerg" in t or "label" in t):
        return "Undeclared allergen"
    if "listeria" in t:
        return "Listeria monocytogenes"
    if "salmonella" in t:
        return "Salmonella"
    if "coli" in t or "stec" in t or "shiga" in t:
        return "E. coli / STEC"
    if "foreign" in t or "metal" in t or "plastic" in t or "glass" in t:
        return "Foreign material"
    if "mislabel" in t or "label" in t:
        return "Mislabeling"
    if "botul" in t or "clostridium" in t:
        return "Clostridium botulinum"
    return "Other"


if not df.empty:
    df["reason_bucket"] = df["reason_for_recall"].apply(classify_reason)

    print("\nCalifornia Food Recalls by Reason (2020-2024)")
    print("-" * 50)
    reason_counts = df["reason_bucket"].value_counts()
    for reason, n in reason_counts.items():
        print(f"{reason:<28} {n:>6,}")

    # -----------------------------------------------------------
    # Step 4: Reason x classification cross-tab
    # -----------------------------------------------------------
    print("\nReason x Classification (2020-2024)")
    print("-" * 60)
    cross = pd.crosstab(df["reason_bucket"], df["classification"])
    for col in ["Class I", "Class II", "Class III"]:
        if col not in cross.columns:
            cross[col] = 0
    cross = cross[["Class I", "Class II", "Class III"]]
    print(cross.to_string())


# ---------------------------------------------------------------
# Step 5: Firms with the most Class I food recalls, nationwide.
#         A count query keeps this lightweight even at full scale.
# ---------------------------------------------------------------
print("\nFetching top recalling firms for Class I food recalls...")
firm_counts = count_field(
    "recalling_firm.exact",
    search="product_type:Food+AND+classification:Class+I",
)
top_firms = Counter(firm_counts).most_common(15)
print("\nTop 15 Firms by Class I Food Recall Count (all years)")
print("-" * 60)
print(f"{'Firm':<45} {'Class I recalls':>14}")
print("-" * 60)
for firm, n in top_firms:
    print(f"{firm[:44]:<45} {n:>14,}")

The pattern generalizes. Swap product_type:Food for product_type:Cosmetics to study cosmetic recalls, which are dominated by microbial contamination and undeclared drug or color additives rather than allergens. Replace the state and date filters with a recalling_firm.exact term to build a per-supplier recall dossier. Or drop the search entirely and iterate the count parameter across classification, status, and state to profile the whole corpus before downloading a single record.

Caveats and limits

Three limits shape any honest use of this dataset. First, it is a record of classified recalls, not of every product removed from the market. Market withdrawals and stock recoveries — the lower-severity removals that never receive a class — are absent by design, so the dataset systematically undercounts the full universe of product-removal activity and skews toward events the FDA deemed worth grading. It also covers only FDA-regulated foods and cosmetics; meat, poultry, and most egg products fall under the USDA's Food Safety and Inspection Service and appear in a separate FSIS recall system, not here.

Second, there is reporting lag at every stage. A recall does not appear until it has been classified and posted, the termination_date and status fields update only as cases close, and the most recent weeks of data are necessarily incomplete. Because the records are revised in place rather than appended, two queries run weeks apart can return different values for the same recall, which means any reproducible analysis should record the retrieval date and ideally snapshot the data.

Third, the most analytically important fields — reason_for_recall, product_description, and distribution_pattern — are free text written by many different firms and FDA staff over two decades. The same hazard is phrased countless ways; allergens may be named individually or lumped together; brand and product names are inconsistent in spelling, casing, and punctuation. There are no standardized hazard codes or product taxonomies in the food enforcement records, so reliable categorization depends on careful keyword logic and manual review, and any automated bucketing — including the example above — will misclassify some edge cases. Treated with those caveats in mind, the Food Enforcement dataset remains the authoritative, openly accessible record of what the FDA has recalled from the American food and cosmetics supply, and why.

Related writing

CDC foodborne outbreak data covers the upstream illness surveillance that often triggers the recalls catalogued here.

NHTSA vehicle complaints is the parallel consumer-safety reporting system for motor vehicles, with its own recall pipeline.

EPA RCRA hazardous waste tracking is another federal enforcement dataset built on classification and chain-of-custody recordkeeping.