Skip to content

NerdlystTech · Markets · Code

Track FDA Recalls with Python: A Free openFDA Weekly Alert

Track FDA Recalls with Python: A Free openFDA Weekly Alert
Photo by Haley Lawrence on Unsplash

The FDA lists every food, drug and medical device recall it monitors in a weekly Enforcement Report, and you can track FDA recalls with Python without ever opening that website. The free openFDA API serves the same records as JSON, and about 40 lines of standard-library code can turn it into a Monday-morning alert.

Key takeaways

  • openFDA has three recall endpoints, for food, drugs and devices, and all three share the same fields.
  • You don’t need to sign up. Without a key you get 240 requests a minute and 1,000 a day per IP address, and a free key raises the daily cap to 120,000.
  • Filter on report_date, the date a recall first appeared in the weekly report, to catch new recalls.
  • A 404 response means “no matches”. Your request isn’t broken.
  • The FDA says this data must not be used to send alerts to the public or to make medical decisions.

Where does the FDA recall data come from?

The software firm Veridra published a step-by-step guide on dev.to on 19 October 2026, and it is worth reading in full. This piece checks its claims against the official documentation and adds the context you need before you rely on the output.

The source is the FDA’s Recall Enterprise System. According to openFDA’s food enforcement page, the API covers publicly releasable records from 2004 onward and is updated weekly. The FDA’s Enforcement Report definitions say the report includes every recall the agency monitors, including ones marked “Not Yet Classified”.

Think of the weekly report as a newspaper and openFDA as its searchable archive. You can’t get the news before it’s printed, but you never have to read the whole paper again.

Which openFDA endpoints and recall classes matter?

There are three endpoints: api.fda.gov/food/enforcement.json (which also covers dietary supplements and cosmetics), api.fda.gov/drug/enforcement.json and api.fda.gov/device/enforcement.json. Each record has a classification field, and that field tells you how worried to be.

ClassWhat the FDA meansTypical example
Class IA reasonable probability of serious health problems or deathFood contaminated with Listeria or Salmonella
Class IITemporary or medically reversible harm, or a remote chance of serious harmA drug that is below its labelled strength
Class IIIUnlikely to cause harm, but breaks the rulesA labelling error
Not Yet ClassifiedListed, but the hazard review isn’t finishedA recall in its first weeks

For scale, Veridra reported that on 19 October 2026 a 30-day query returned 51 Class I food recalls. That’s more than one serious food recall a day, and it doesn’t count drugs or devices.

How to track FDA recalls with Python, step by step

The script below uses only Python’s standard library, so you don’t need to install anything. It checks all three endpoints for recalls reported in the last 14 days, remembers the recall numbers it has already shown you, and prints only the new ones.

import json, urllib.request, urllib.parse, urllib.error
from datetime import date, timedelta

BASE = 'https://api.fda.gov/{}/enforcement.json'
SEEN_FILE = 'seen_recalls.json'

def fetch(category, days=14):
    start = (date.today() - timedelta(days=days)).strftime('%Y%m%d')
    end = date.today().strftime('%Y%m%d')
    query = urllib.parse.urlencode({
        'search': f'report_date:[{start} TO {end}]',
        'sort': 'report_date:desc',
        'limit': 1000,
    })
    try:
        with urllib.request.urlopen(BASE.format(category) + '?' + query, timeout=60) as r:
            return json.load(r)['results']
    except urllib.error.HTTPError as e:
        if e.code == 404:  # openFDA's way of saying 'no matches'
            return []
        raise

try:
    with open(SEEN_FILE) as f:
        seen = set(json.load(f))
except FileNotFoundError:
    seen = set()

for category in ('food', 'drug', 'device'):
    for rec in fetch(category):
        if rec['recall_number'] in seen:
            continue
        seen.add(rec['recall_number'])
        print(category, rec['classification'], rec['report_date'],
              rec['recalling_firm'], '-', rec['reason_for_recall'][:100])

with open(SEEN_FILE, 'w') as f:
    json.dump(sorted(seen), f)

Two design choices do most of the work. The 14-day window overlaps the weekly report, so if a run fails or your laptop is asleep, the next run still catches what you missed. The seen-file stops that overlap from showing you the same recall twice.

Run the script once to fill the file, then schedule it. On macOS or Linux, the cron line 0 8 * * 1 python3 /path/to/fda_recalls.py runs it at 08:00 every Monday. A weekly job uses three requests, well under the 1,000-a-day limit for users without a key, which is confirmed on openFDA’s authentication page.

Five traps that will break your recall tracker

  1. The wrong date field. recall_initiation_date is when the company started the recall, which can be weeks before the FDA lists it. Filter on it and new entries can fall outside your window. Use report_date.
  2. Treating 404 as a crash. openFDA returns a 404 with “No matches found!” when a search is empty. Catch it and treat it as zero results.
  3. Trusting the state field. It is the company’s home state, not where the product was sold. To find out whether a recall reached Texas, search the free-text distribution_pattern field instead.
  4. Expecting brand names for food. The openfda block of brand and generic names is mostly filled in for drugs and mostly empty for food. For food, match against product_description.
  5. Hitting the paging ceiling. The query parameters page caps limit at 1,000 and skip at 25,000. For bigger pulls, follow the rel="next" URL in the response’s Link header, which uses search_after.

Veridra also warns about some text arriving with stray “¿” characters where accents or symbols were lost, such as “jalape¿no”. Clean those up before the text goes into an email.

Who should build one, and who shouldn’t?

A recall tracker is useful for food safety teams, independent pharmacies, clinic buyers, importers and health reporters. Anyone with a supplier list can filter by firm name or ingredient and skip the rest.

Our opinion: this is one of the best returns on 40 lines of code you’ll find. But read the fine print. openFDA states that its data “should not be used as a method to collect data to issue alerts to the public”, and it tells you not to rely on it for medical care. Use it for internal monitoring, and check every hit against the official FDA notice before you act.

What happens next

Once the basic loop works, there are three natural upgrades. You can narrow the search, for example with classification:"Class I" or a firm name. You can send the output to email or to a Slack or Teams webhook so nobody has to read a terminal. And you can store the full record, which includes quantities, lot codes and the distribution pattern.

One limitation won’t go away. The FDA does not update a recall’s status after it is classified, so openFDA tells you when a recall first appeared but not when it ended. Treat your tracker as an early-notice system, not a live status board.

FAQ

Do I need an API key to use openFDA?

No. Without a key you can make 240 requests a minute and 1,000 a day per IP address. A free key from open.fda.gov raises the daily limit to 120,000.

How often is FDA recall data updated?

Weekly, in line with the FDA’s Enforcement Report, so running a tracker once a week with an overlapping lookback window is enough.

Why does my openFDA query return a 404 error?

A 404 with the message “No matches found!” means your search returned no records. Check your date range and field names, and handle the 404 as an empty result.

Your next step: copy the script, run it once, and add one filter for the supplier or ingredient you care about most.

1 comment

Leave a Reply