# FDA Recalls Scraper - Food, Drug and Device (`s-r/fda-recalls-scraper`) Actor

Search FDA enforcement reports across food, drug and medical device recalls. Returns recall class, recalling firm, product, reason, distribution and normalised dates from the official openFDA API.

- **URL**: https://apify.com/s-r/fda-recalls-scraper.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 run start fees

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## FDA Recalls Scraper

Search **FDA enforcement reports** across food, drug and medical device recalls
in one run. Recall class, recalling firm, product, reason, distribution area,
lot codes and dates.

Reads the official **openFDA API**. No key, no login.

### Class I is the field that matters

The FDA grades every recall, and the grade is the whole point:

| Class | What it means |
|---|---|
| **Class I** | A reasonable probability of **serious harm or death** |
| Class II | Temporary or medically reversible harm |
| Class III | Unlikely to cause harm |

There is no numeric severity field anywhere in the data. Sort by date or firm
and the dangerous recalls sit among the labelling corrections with nothing to
distinguish them. Every row here carries a derived **`is_class_i`** so that
filter is one step.

To put the scale in perspective: of 29,317 food enforcement reports, **12,867
are Class I**.

### Three datasets, one schema

| Category | Records |
|---|---|
| `food` | 29,317 |
| `drug` | 17,899 |
| `device` | 39,794 |

They share one shape, so you can pull all three in one run and every row carries
its `category`. Drug recalls additionally carry `brand_names`, `generic_names`
and `manufacturer_names`, lifted out of openFDA's nested block.

### Three things about this data that trip people up

**Dates arrive as `20160808`, not as dates.** Six date fields come back as bare
YYYYMMDD strings. Feed one into anything date-aware and you get a wrong answer
or an exception. All six are normalised to ISO here.

**`Terminated` does not mean cancelled.** It means the FDA considers the
enforcement action complete. A recall that was withdrawn and a recall that ran
its course look identical if you read that field as plain English, and counting
"active recalls" wrongly is the usual result.

**An empty result comes back as HTTP 404.** openFDA answers 404 when a search
matches nothing. Treating that as a transport failure turns "no recalls for this
firm" into "the scraper broke" — which is worse, because the first is a useful
answer. This Actor returns zero rows and says so.

### Why this Actor connects directly

across every `.gov` host tested:

| Route | Result |
|---|---|
| Direct | **HTTP 200**, working JSON |
| Through a residential proxy | `CONNECT tunnel failed, response 491` |

A 491 on the CONNECT is **our proxy refusing to tunnel to the host**, not the
FDA refusing us. That pattern held for every government host, so on a `.gov`
target a proxy error is a fact about the proxy rather than the site. openFDA
publishes this data for public use, and this Actor takes it directly at a polite
pace.

### Input reference

| Field | Type | Default |
|---|---|---|
| `categories` | food, drug, device, any combination | `["food"]` |
| `classification` | Class I, II or III | all |
| `firm` | recalling company name | — |
| `product` | words from the product description | — |
| `reason` | words from the recall reason, e.g. `Listeria` | — |
| `state` | two-letter state of the firm | — |
| `status` | Ongoing, Completed, Terminated | all |
| `date_from`, `date_to` | YYYY-MM-DD | — |
| `limit` | 1-5000 | 100 |
| `retries` | 1-6 | 3 |

Filters combine with AND. `limit` is split across the categories you choose, so
asking for 60 across all three gives roughly 20 of each.

### Typical uses

- **Food safety monitoring.** `categories: food`, `classification: Class I`, run
  daily. `reason` searches like `Listeria` or `undeclared` narrow it further.
- **Supplier risk.** Search `firm` for your suppliers and see their recall
  history, with class and reason attached.
- **Pharmacovigilance.** Drug recalls with brand and generic names, filterable
  by manufacturer.
- **Medical device compliance.** Device recalls by firm or product, which is the
  first check in a hospital procurement review.
- **Geographic exposure.** `distribution_pattern` names the states or regions a
  recalled product reached, and `state` filters by where the firm is.
- **Trend analysis.** Date ranges plus the classification breakdown show whether
  recalls in a category are rising, and whether the severity mix is shifting.

### Notes on behaviour

`code_info` carries the lot numbers, UPCs and expiry dates in scope, which is
what you need to check whether a specific batch is affected rather than a
product line.

`event_id` groups related recalls, so one contamination incident spanning many
products can be collapsed back into a single event.

openFDA allows 240 requests a minute per IP without a key. This Actor paces
itself well under that, so long runs are slower than they could be and stay
within what a free public service is offered on.

The data covers US enforcement only. A product recalled elsewhere and not in the
US will not appear.

# Actor input Schema

## `categories` (type: `array`):

Which enforcement datasets to search: food, drug, device. Any combination.

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

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

## `firm` (type: `string`):

Company name to search for, for example Pfizer or Nestle.

## `product` (type: `string`):

Words from the product description, for example spinach or insulin.

## `reason` (type: `string`):

Words from the recall reason, for example Listeria or mislabeling.

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

Two-letter state of the recalling firm, for example CA.

## `status` (type: `string`):

Ongoing, Completed or Terminated. Terminated means the FDA closed the action, not that it was cancelled.

## `date_from` (type: `string`):

Only recalls reported on or after this date, as YYYY-MM-DD.

## `date_to` (type: `string`):

Only recalls reported on or before this date, as YYYY-MM-DD.

## `limit` (type: `integer`):

How many recalls to return in total, split across the categories you chose.

## `retries` (type: `integer`):

Retries with backoff before a request is reported as an error.

## Actor input object example

```json
{
  "categories": [
    "food"
  ],
  "classification": "",
  "limit": 100,
  "retries": 3
}
```

# Actor output Schema

## `recalls` (type: `string`):

One row per recall.

## `summary` (type: `string`):

Counts, totals per category and the classification breakdown.

## `errors` (type: `string`):

Failures with a code and a redacted message.

# 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 = {
    "categories": [
        "food"
    ],
    "limit": 100,
    "retries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/fda-recalls-scraper").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 = {
    "categories": ["food"],
    "limit": 100,
    "retries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/fda-recalls-scraper").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 '{
  "categories": [
    "food"
  ],
  "limit": 100,
  "retries": 3
}' |
apify call s-r/fda-recalls-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,s-r/fda-recalls-scraper"
        }
    }
}
```

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/AvoBsYC2SaEvVzk1u/builds/uVA0zGhhcGYvBftar/openapi.json
