# FDA Recalls Scraper - openFDA Enforcement (`dataio/openfda-recalls-enforcement`) Actor

Scrape FDA recalls for food, drugs and medical devices in one feed and one schema, from openFDA enforcement reports: severity class decoded, days open computed, product, reason and the recalling firm's address. Filter by product type, class, firm and date.

- **URL**: https://apify.com/dataio/openfda-recalls-enforcement.md
- **Developed by:** [Tom Awake](https://apify.com/dataio) (community)
- **Categories:** News, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 recalls

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does FDA Recalls Scraper do?

Food, drug and medical device recalls in **one feed and one schema** — with
severity explained, days open computed, and the recalling firm's address.

**No login. No API key. No proxies.**

### Three endpoints, one answer

FDA splits recalls across three separate APIs. A quality or compliance team
does not think in FDA endpoints — it thinks in **suppliers**. Query all
three here and get one table.

### Two things the source leaves out

**What the class actually means.** FDA publishes `Class I` and stops there.

| Class | What it means |
|---|---|
| **I** | Reasonable probability of serious adverse health consequences or death |
| **II** | Temporary or medically reversible consequences |
| **III** | Unlikely to cause adverse consequences |

Both the code and the sentence ship on every row, so nobody has to remember
the nomenclature to triage a list.

**How long it has been open.** The source gives dates, never elapsed time.
A real result, monitoring one supplier:

| Firm | Class | Days open | Reason |
|---|---|---|---|
| Abbott Diabetes Care | **I** | **781** | FreeStyle Libre 3 sensors |
| Abbott Point Of Care | II | 388 | Lack of 510(k) premarket clearance |

A Class I recall open for 781 days reads very differently from one opened
last week — and no column in the source tells you which you are looking at.

Rows come back **most severe and longest running first**.

### Output

| Field | Example |
|---|---|
| `recallingFirm` | Abbott Diabetes Care, Inc. |
| `address`, `city`, `state`, `postalCode`, `country` | full firm address |
| `productType` | Food / Drug / Device |
| `classification`, `severityLevel`, `severityMeaning` | Class I, I, and the sentence |
| `status`, `isOngoing` | Ongoing / Terminated |
| `daysSinceInitiation` | 781 |
| `daysToTermination` | how long a closed recall took |
| `reasonForRecall` | the failure, in FDA's words |
| `productDescription`, `productQuantity`, `codeInfo` | lots and volumes |
| `distributionPattern` | US Nationwide, Worldwide, single state |
| `voluntaryOrMandated`, `firmNotification` | how it started |
| `recallInitiationDate`, `reportDate`, `terminationDate` | |
| `recallNumber`, `eventId` | FDA identifiers |

### Input

```json
{
  "productTypes": ["food", "drug", "device"],
  "reportedAfter": "2026-01-01",
  "maxItems": 500
}
```

| Field | Default | Notes |
|---|---|---|
| `productTypes` | all three | Queried separately, returned together |
| `reportedAfter` | `2026-01-01` | Weekly enforcement reports |
| `classification` | — | Class I, II or III |
| `ongoingOnly` | `false` | Excludes terminated recalls |
| `maxItems` | `500` | Split evenly across product types |
| `recallingFirm` | — | Monitor one supplier |
| `state` | — | Firm's state |
| `reportedBefore` | today | |

`maxItems` is divided between the product types you select, so a
high-volume domain cannot crowd out the others. Device recalls outnumber
drug recalls roughly four to one in 2026 — without the split you would
barely see the drugs.

### Use cases

- **Supplier monitoring** — name a firm, see every recall it has issued,
  how severe and how long open.
- **Quality and compliance** — Class I recalls in your category, ongoing
  only, with distribution pattern.
- **Insurance and risk** — recall history by company and state.
- **Retail and distribution** — what is being pulled, in what quantity,
  across which territory.
- **Journalism and research** — the full recall record, searchable.

### Running this as a scheduled watch

A recall is time-critical. Knowing a week late that a component in your
supply chain was pulled is not the same as knowing the day it happened.

```json
{
  "productTypes": ["device"],
  "reportedWithinDays": 7,
  "classification": "Class I",
  "maxItems": 500
}
```

**Why not a fixed `reportedAfter` date?** A schedule replays the same
input every time, so a fixed date returns the same recalls at every run
— and charges for them again. `reportedWithinDays` moves the window.

***

### Limits, honestly

- **FDA posts recalls in weekly enforcement reports**, so the most recent
  days are always incomplete. A recall can also be classified weeks after
  it began.
- `daysSinceInitiation` is computed against the run date, so a saved export
  ages. The dates themselves do not.
- **`device/recall.json` is deliberately excluded.** It is a fourth FDA
  dataset with different field names, and mapping it into this schema would
  mean guessing at equivalences. The three enforcement endpoints share 23
  of 24 fields; that one does not.
- Firm names are not normalised: Abbott appears as several distinct
  entities. Search broadly, then group.
- openFDA stops paginating past 25,000 results per query.
- A recall is a safety action, not a verdict. Most are voluntary and many
  are precautionary.
- Requests are paced out of courtesy to a free public service.
- Not affiliated with the FDA.

### How much does it cost?

You pay per recall returned: **$0.003 each**, that is $3.00 per 1,000. There is no start fee, and subscription plans pay less per recall.

The example input below asks for up to 500 recalls, so it costs **$1.50 at most**.

If a run reaches the spending limit you set, the output stops at that limit and never goes past it. You are never charged for rows that were not delivered.

### Use FDA Recalls Scraper as an API

Call it from your own code with the Apify client, here in Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("DataIO/openfda-recalls-enforcement").call(run_input={
    'productTypes': ['food', 'drug', 'device'],
    'reportedAfter': '2026-01-01',
    'maxItems': 500,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

It also works from JavaScript, Make, Zapier, n8n, and from AI agents through the Apify MCP server.

### Other actors you might like

- [FDA 510(k) Scraper](https://apify.com/DataIO/openfda-510k-device-clearances): Device Clearances Data
- [NPI Registry Scraper](https://apify.com/DataIO/nppes-npi-provider-registry): US Healthcare Providers
- [CMS Open Payments Scraper](https://apify.com/DataIO/cms-open-payments-physicians): Physician Payments

### FAQ

#### Is it legal to use this data?

The actor reads public data from its official source, without logging in and without bypassing any access control. What you do with the data, for example contacting people listed in it, is your responsibility under the laws that apply to you, such as GDPR in Europe.

#### Can I run it on a schedule?

Yes. See *Running this as a scheduled watch* above: use the rolling-window setting so each run returns only what is new.

#### Can AI agents use it?

Yes. It is available through the Apify MCP server, and every input field is described in its input schema, so an agent can call it directly.

# Actor input Schema

## `productTypes` (type: `array`):

Food, drug and device recalls come from three separate FDA endpoints. This Actor queries all the ones you pick and returns them in one schema.

## `reportedWithinDays` (type: `integer`):

Rolling window, for a scheduled watch: 7 returns recalls reported in the last seven days. A recall is time-critical, so this is the setting that makes a weekly watch useful. Overrides Reported after.

## `reportedAfter` (type: `string`):

YYYY-MM-DD. FDA posts recalls in weekly enforcement reports, so recent days fill in gradually.

## `classification` (type: `string`):

Class I means a reasonable probability of serious harm or death. Class II is temporary or reversible. Class III is unlikely to cause harm. Leave empty for all.

## `ongoingOnly` (type: `boolean`):

Excludes recalls FDA has marked terminated. Ongoing is what still needs acting on.

## `maxItems` (type: `integer`):

Split evenly across the product types you selected, so one domain cannot crowd out the others.

## `recallingFirm` (type: `string`):

Company name, for example Abbott. Use this to monitor a supplier.

## `state` (type: `string`):

Two-letter US state code of the recalling firm.

## `reportedBefore` (type: `string`):

YYYY-MM-DD. Defaults to today.

## Actor input object example

```json
{
  "productTypes": [
    "food",
    "drug",
    "device"
  ],
  "reportedWithinDays": 30,
  "reportedAfter": "2026-01-01",
  "ongoingOnly": false,
  "maxItems": 500
}
```

# Actor output Schema

## `results` (type: `string`):

Every row produced by the run, in JSON.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "productTypes": [
        "food",
        "drug",
        "device"
    ],
    "reportedWithinDays": 30,
    "reportedAfter": "2026-01-01",
    "maxItems": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataio/openfda-recalls-enforcement").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "productTypes": [
        "food",
        "drug",
        "device",
    ],
    "reportedWithinDays": 30,
    "reportedAfter": "2026-01-01",
    "maxItems": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("dataio/openfda-recalls-enforcement").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "productTypes": [
    "food",
    "drug",
    "device"
  ],
  "reportedWithinDays": 30,
  "reportedAfter": "2026-01-01",
  "maxItems": 500
}' |
apify call dataio/openfda-recalls-enforcement --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataio/openfda-recalls-enforcement"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Z09SPT0FS2UkQfqKp/builds/cnQk5tPkRl7Evnl00/openapi.json
