FDA Recalls Watch & API: Food, Drug & Device (openFDA) avatar

FDA Recalls Watch & API: Food, Drug & Device (openFDA)

Pricing

from $1.00 / 1,000 recalls

Go to Apify Store
FDA Recalls Watch & API: Food, Drug & Device (openFDA)

FDA Recalls Watch & API: Food, Drug & Device (openFDA)

FDA recalls API and food recall monitor: new and changed food, drug and medical device recalls from the official openFDA API since your last run, or every match; failed or unchanged rows are never charged. Not affiliated with or endorsed by the U.S. FDA.

Pricing

from $1.00 / 1,000 recalls

Rating

0.0

(0)

Developer

COMPASS DEV

COMPASS DEV

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

a day ago

Last modified

Categories

Share

openFDA Recalls Watch returns U.S. FDA food, drug and device recalls from the official openFDA enforcement API as flat rows — only the recalls that are new or changed since your last run (watch mode), or every recall that matches your filters (export mode) — and never charges for failed results or unchanged recalls.

Not affiliated with or endorsed by the U.S. FDA.

FDA recall monitor and export API: watch FDA food recalls, drug recalls and medical device recalls (enforcement reports) and get back only what is new or changed — a new recall, or a recall whose status, classification, termination date or quantity changed, with the old and new values side by side. Or export every recall that matches your filters: product type, Class I / II / III, status, date range, the firm's state or country, a keyword in the product or reason, or the recalling firm.

Built for compliance, food safety, retail and AI-agent workflows that must react to recalls: run it on a schedule and act only on the rows that come back. You are never charged for unchanged recalls, empty searches or failed results — never charged for failed results. Every row has its source URL (url) and fetch time (scrapedAt).

Why this one:

  • Watch mode with a memory: each named watch list remembers the recalls it has seen, so a daily run returns only new recalls and real changes (changeType: new or changed, with changedFields and the previous… values).
  • Catches late changes: recalls reported before your date window are re-checked while they are open, so a termination months later still comes back as a change.
  • Official source only: the documented openFDA API (CC0 public data), one request at a time, no scraping.
  • Unchanged recalls are free: a watch run where nothing changed costs only Apify's small Actor-start charge.

Quick start

  • One call, current recalls: {"productTypes": ["food"]} returns the food recalls reported in the last 30 days (export mode), every time you call it.

  • Watch for changes: give a watch list name. The form is filled in like this (it also ticks "include unchanged recalls", which come back free):

    {
    "mode": "watch",
    "stateName": "example-watchlist",
    "productTypes": ["food"],
    "daysBack": 30,
    "includeUnchanged": true
    }

    The first run is the baseline: one row per recall (changeType: "baseline"). Run it again later (or add an Apify schedule, e.g. daily): only new recalls (new) and changed ones (changed) come back; unchanged recalls are left out (free), or returned free as unchanged with includeUnchanged.

Which mode runs: an explicit mode always wins. Without one, a watch list name (stateName) means watch and no name means export, so the same call returns data every time. mode: "watch" without a name uses the watch list default.

Use cases

  • Food safety and retail: watch Class I food recalls (classifications: ["Class I"]) and pull products from shelves the day FDA publishes them.
  • Supplier and brand monitoring: watch a firm (recallingFirm) or a keyword (keyword: "peanut", "listeria", "pacemaker") across food, drugs and devices.
  • Compliance records: export all device recalls for a period (mode: "export", dateFrom, dateTo) as a flat table.
  • Track specific recalls: list recall numbers (recallNumbers) and get a row when their status or termination date changes.

What it does

  • Searches the openFDA enforcement reports (FDA's weekly recall reports) for food, drug and device, with your filters, newest reports first, 1,000 records per request (the API maximum).

  • Watch mode (with a watch list name, or mode: "watch"): compares every recall with what the watch list (stateName) saw at the last run:

    • baseline — the first run of a watch list (charged);
    • new — a recall that appeared since the last run (charged);
    • changed — its status, classification, termination date or product quantity changed (charged), with changedFields and previousRecallStatus, previousClassification, previousTerminationDate, previousProductQuantity;
    • unchanged — not returned (free), or returned free with includeUnchanged.

    Recalls the list already knows that are still open are re-checked by number even when they fall outside your date window, so later changes are not missed.

  • Export mode (no watch list name, or mode: "export"): every recall that matches the filters (charged), up to maxItems.

  • Recall numbers you list are looked up directly: an unknown number gives a free no_data row, an invalid one a free failed row.

  • Leaves out the firm's street address and postal code. A firm name that is, or may be, a person's name is left out of every field, and the recall is kept (recallingFirm is null, recallingFirmOmitted: true; see Known issues).

Input

FieldWhat it doesDefault
modewatch (new and changed recalls since the last run) or export (every match)watch if stateName is given, else export
stateNameWatch list name; runs with the same name compare with each other (gives watch mode)— (mode: "watch" without a name uses default)
productTypesfood, drug, device (empty = only the recall numbers you list)food
classificationsClass I, Class II, Class III (empty = all)all
statusesOngoing, Completed, Terminated, Pending (empty = all)all
daysBackRecalls whose date is within the last N days (ignored with dateFrom / dateTo)30
dateFrom, dateToFixed date range (YYYY-MM-DD)—
dateFieldreportDate (FDA published the report) or recallInitiationDate (firm started the recall)reportDate
states, countriesThe recalling firm's US state (e.g. CA) or country (e.g. United States)—
keywordA word or phrase in the product description or the reason for recall—
recallingFirmA word or phrase of the firm's name—
recallNumbersSpecific recalls, e.g. H-0123-2026 (food), D-0456-2026 (drug), Z-0789-2026 (device)—
includeUnchangedWatch mode: also return unchanged recalls, freefalse
maxItemsMost charged rows per run; in watch mode the rest come in the next run100
apiKeyOptional: your own free openFDA API key (secret; never logged or output)—

Example: watch Class I recalls of food and drugs that mention listeria, in a watch list of their own:

{
"stateName": "listeria-class-1",
"productTypes": ["food", "drug"],
"classifications": ["Class I"],
"keyword": "listeria",
"daysBack": 90
}

Output

One row per recall (flat, ready for CSV, Excel or an agent). A changed row from watch mode:

{
"status": "ok",
"error": null,
"attempts": 1,
"input": "watch list \"default\" (re-check)",
"mode": "watch",
"watchList": "default",
"recallNumber": "H-0123-2026",
"productType": "food",
"classification": "Class I",
"recallStatus": "Terminated",
"recallInitiationDate": "2026-08-12",
"reportDate": "2026-08-20",
"terminationDate": "2026-09-25",
"recallingFirm": "Example Foods Inc.",
"recallingFirmOmitted": false,
"city": "Springfield",
"state": "IL",
"country": "United States",
"productDescription": "Example brand peanut butter cookies, 12 oz",
"reasonForRecall": "Undeclared peanut",
"distributionPattern": "Nationwide",
"productQuantity": "1,200 cases",
"voluntaryMandated": "Voluntary: Firm initiated",
"eventId": "98765",
"changeType": "changed",
"changedFields": "recallStatus, terminationDate",
"previousRecallStatus": "Ongoing",
"previousClassification": "Class I",
"previousTerminationDate": null,
"previousProductQuantity": "1,200 cases",
"url": "https://api.fda.gov/food/enforcement.json?search=recall_number%3A%22H-0123-2026%22",
"source": "openFDA food enforcement reports (U.S. Food and Drug Administration)",
"license": "CC0 1.0 public domain dedication (openFDA terms); not endorsed by the U.S. FDA",
"licenseUrl": "https://open.fda.gov/license/",
"dataUpdatedAt": "2026-09-23",
"scrapedAt": "2026-09-30T06:30:00.000Z"
}
  • status: ok (a recall), no_data (nothing to return: no match, no changes since the last run, or an unknown recall number — free) or failed (invalid input or no answer — free), with error and attempts.
  • url is the source URL (the openFDA query that returns this recall); scrapedAt is the fetch time; dataUpdatedAt is the day openFDA last refreshed the data.
  • Dates are ISO 8601 (YYYY-MM-DD). Values are exactly as FDA publishes them.

Pricing

Pay per recall returned (event recall): baseline, new, changed and exported recalls.

Apify planPer recallPer 1,000 recalls
Free$0.0015$1.50
Bronze$0.0013$1.30
Silver$0.00115$1.15
Gold (and Platinum, Diamond)$0.0010$1.00
  • Never charged for failed results, empty searches, unknown recall numbers or unchanged recalls. A watch run where nothing changed costs only Apify's small Actor-start charge.
  • Your maximum charge per run is respected: the run stops before it and outputs only what it could charge.
  • No usage fees on top: the price per recall covers the platform's compute.

Use it from AI agents

  • MCP: add the Actor through the Apify MCP server (https://mcp.apify.com?actors=mouadapi/openfda-recalls), then ask e.g. "Which FDA Class I food recalls are new since yesterday?" Each row says ok, no_data or failed, and changeType says what happened.
  • API: one call returns the rows:
curl -X POST "https://api.apify.com/v2/acts/mouadapi~openfda-recalls/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" -d '{"stateName": "agent-food", "productTypes": ["food"], "classifications": ["Class I"]}'
  • x402 payments: the Actor is pay-per-event only, with no usage fees, limited permissions and no Standby mode, so agents can pay per recall with x402.
  • Flat rows with the source URL and fetch time on each, ready to cite.

The same call from JavaScript (the Apify client):

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('mouadapi/openfda-recalls').call({ productTypes: ['food'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

Limits

  • openFDA's own limits: without an API key, 1,000 requests a day per IP address (and 240 a minute). Apify runs share IP addresses, so other users' openFDA calls count too. For large or frequent runs, add your free openFDA key (apiKey): 120,000 requests a day.
  • One request at a time (at most 2 a second), 1,000 recalls per request. A typical watch run needs 1–5 requests.
  • Rows are saved and charged one at a time (so a stopped run never charges a row you didn't get): about 8 rows a second, so 100 rows take seconds and a 2,000-row export about 4 minutes.
  • On "too many requests" (HTTP 429) the run pauses (1, 2, then 4 minutes, or longer if openFDA asks) at most 3 times, then stops with a clear message; if openFDA asks for a wait of more than 5 minutes (a daily limit), it stops at once. It never switches IP address or proxy.
  • One filter search reads at most 26,000 recalls per product type (the API's paging limit); maxItems is at most 10,000.
  • FDA publishes enforcement reports weekly, so a daily watch run mostly finds nothing new between weekly updates (dataUpdatedAt shows the last update).
  • No street addresses, postal codes or personal names are returned.

Known issues

  • How names are handled: a firm name that is, or may be, a person's name (judged by its shape, never by a list of first names: "Jane Doe", "Smith, John", "Dr. Jane Doe", "Jane Doe dba Doe Farms", and, when in doubt, two- or three-word names without a business word such as "Fresh Express") is left out: recallingFirm is null and recallingFirmOmitted is true. The same name is also removed from every other text field of that recall (product description, reason, distribution pattern, quantity…); a field that still holds a word of the name is null. The recall itself is kept and charged as usual. Names with a business word (Inc, LLC, Foods, Pharmacy…) are kept.
  • Recall numbers starting with H or F are looked up in food, D in drug and Z in device; other letters are tried in all three (up to 3 requests).
  • openFDA lists a few recalls without a recall number (blank, or "N/A" while not yet classified; about 1 in 1,000 records): they are skipped (counted in the run report) until they get a number; then they come back as new recalls.
  • Changing the filters of a watch list restarts its baseline: recalls it doesn't know yet come back as baseline rows.

FAQ

How much does it cost? $1.50 per 1,000 recalls returned on the Free plan, down to $1.00 on Gold, plus Apify's small Actor-start charge per run. Watching Class I food recalls daily (a few new ones a week) costs a few cents a month.

Am I charged for recalls that didn't change? No. Unchanged recalls, empty searches, unknown recall numbers and failed checks are free.

Do I need an openFDA API key? No. It runs without one, within the limits openFDA publishes for calls without a key. For large or frequent runs, add your free openFDA key (open.fda.gov/apis/authentication): it raises the daily limit from 1,000 requests per IP address to 120,000 per key, and is stored as a secret, never logged or output.

Is this allowed? openFDA data is published under CC0 (public domain) and may be reused, including commercially. This tool is not affiliated with or endorsed by the U.S. FDA. The Actor uses only the documented openFDA API, one request at a time.

Also by the same author: Wikipedia Article Watch & Scraper API, DNS Lookup & SSL Certificate Checker and Woolworths Price Scraper & Monitor.