# US Product Recalls Search API (`thesjostrandproject/us-recalls-search`) Actor

Search US food, drug and device recalls in the official FDA enforcement reports. One typed row per recall: recall_number, classification, status, firm, report_date. Filters: category, date, class, status, firm, state. Not included: recall reasons, product text. Pay per record.

- **URL**: https://apify.com/thesjostrandproject/us-recalls-search.md
- **Developed by:** [The Sjöstrand Project](https://apify.com/thesjostrandproject) (community)
- **Categories:** Agents, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 recall records

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

## US Product Recalls Search API

### What this Actor returns

Search US food, drug and device recalls in the official enforcement reports of the US food and drug regulator, and get one clean, typed record per recall. The records are made for a spreadsheet or a monitoring job: one row each, the same 12 fields every time, and a value the source does not have is `null`, never a missing key.

- `recall_number`, `event_id`, `product_type`: which recall it is (the number starts with F for food, D for drug, Z for device)
- `classification`, `status`, `voluntary_mandated`: how serious it is, where it stands and who started it
- `recalling_firm_title`, `firm_city`, `firm_state`, `firm_country`: the firm, as the regulator publishes the name
- `recall_initiation_date`, `report_date`: when the firm started the recall and when it appeared in the weekly report

You can filter by category, report date, class, status, a word in the firm's name and the firm's state. The Actor reads the regulator's public data API and nothing else. No login, no scraping.

It does not return the reason for the recall, the product description, the distribution text, addresses or device terminology codes (GMDN), and it does not say what to do about a recall. Rows without a recall number are left out.

### Who it helps

- **Quality, compliance and supply chain teams** who watch recalls that touch their suppliers, products or categories, and want them in a table or a feed.
- **Analysts, journalists and developers** who want tidy recall data for a dashboard or a model, through the Apify API, without writing their own client for the source.

### Quick start

1. Open the Input tab and leave the defaults as they are. They search all three categories for recalls reported in January 2024, so a first run returns 10 records (the default Maximum records).
2. Press Start. The default run costs about $0.03: 10 records at $0.003 each, plus Apify's tiny start event (see What it costs). Try it on one record first by setting Maximum records to 1; it costs a fraction of a cent.
3. Open the Output tab, check the records, then set your own categories, dates and filters.

### Input

| Field | What it does | Default |
|---|---|---|
| Categories | `food`, `drug`, `device`; one or more. They are read in that order. | all three |
| Reported from / to | Only recalls whose report date is in this range, as `YYYY-MM-DD`. Empty for no bound. | `2024-01-01` to `2024-01-31` |
| Class | `Class I`, `Class II` or `Class III`. Empty for any. | empty |
| Status | `Ongoing`, `Completed`, `Terminated` or `Pending`. Empty for any. | empty |
| Firm name contains | Words in the recalling firm's name, like `Acme Foods`. Letters, digits, spaces and `. , & ' -` only. Empty for any. | empty |
| Firm state | A two-letter code like `CA`. Empty for any. | empty |
| Your own openFDA key | Optional and secret. See Limits and honest notes. | empty |
| Maximum records | How many records to return at most, from 1 to 50000. | 10 |

Records come category by category, in recall number order within each category.

### Output

One record per enforcement report row. The record below is what the default input returned on 13 October 2026, unchanged (the first of the 10 records).

```json
{
  "recall_number": "F-0655-2024",
  "event_id": "93478",
  "classification": "Class II",
  "status": "Terminated",
  "product_type": "Food",
  "recalling_firm_title": "Publix Super Markets, Inc.",
  "recall_initiation_date": "2023-11-16",
  "report_date": "2024-01-03",
  "voluntary_mandated": "Voluntary: Firm initiated",
  "firm_city": "Lakeland",
  "firm_state": "FL",
  "firm_country": "United States"
}
```

### What it costs

You pay for one event, **Recall record**, at $0.003 per record returned. Apify also adds its standard run-start event, $0.00005 per start for an Actor run with up to 1 GB of memory; it is the platform's own event, not ours. The default run of 10 records at $0.003 costs about $0.03005. Records the Actor leaves out (see below) are never charged.

For example, every Class I food recall in a year is a few dozen records, which costs a few cents. If your Apify plan includes monthly credit, a small run draws on that credit.

If the source cannot be reached or keeps answering with errors, the run stops with a clear status message, and only the records already returned are charged.

### Limits and honest notes

- **Requests per day.** The source allows 1,000 requests a day per address without a key, and 120,000 a day per key. This Actor runs without a key unless you give it one, and Apify runs share addresses, so a busy day may use up the keyless allowance. If a run ends with a message that the source is busy, wait and run again, or give your own free key in the optional key field (the run's message points there too); it is secret, it is sent only to the source, and it raises the limit to 120,000 requests a day.
- **Pace.** The source allows 240 requests a minute. The Actor sends at most 120 a minute, always says who it is in the request, and reads up to 100 rows per request; when the source is busy it waits as asked, up to about a minute per request, and then stops with a clear message.
- **The reach of one search.** The source lets a search skip at most 25,000 rows. A search with more matches than that returns the first part of each category and the status message says how many matches it could not reach; narrow the dates or add a filter to get the rest.
- **Freshness** follows the source, which publishes the enforcement reports weekly. `report_date` is the date of the weekly report, not the date the Actor ran.
- **The firm name is published data.** It is output as the regulator publishes it. A small firm's name can be a person's name; the Actor does not change names and returns no other personal details.
- **Not a decision tool.** The source itself says its data are unvalidated and not to be relied on for medical care decisions. Check a recall with the regulator before you act on it.
- **What the filters match.** Firm name matches whole words in the name. Class and status match the source's own wording.

### Questions

**Why did my run return fewer records than I asked for?**
Either fewer recalls matched, or some rows were left out because they lacked a recall number in the usual shape or a core field. You were not charged for those. The final status message counts them and says why.

**What does "beyond_the_skip_limit" mean in the status message?**
The search matched more rows in that category than the source lets any search reach. The number says how many were out of reach. Narrow the dates and run again.

**What does the run say when something is wrong?**
It says what it found and what to try. A message about a field in the input names the field to fix and shows the right shape. A message about the key means the source refused it: check the key, or leave it empty. "Could not be reached" or "kept answering" means the source is busy; wait a few minutes and run again.

**Why does a search for a firm name return firms with other words too?**
The name filter finds your words anywhere in the firm's name, so `Acme` also finds `Acme Foods Inc`.

**Why are there several records for one event?**
One recall event can cover several products, and each gets its own recall number. `event_id` ties them together.

**Can I run it on a schedule?**
Yes, with the Apify scheduler or the API. Each run is independent and charged per record returned.

### Data source and AI note

Data source: the openFDA enforcement reports (food, drug and device) of the U.S. Food and Drug Administration, released under the Creative Commons CC0 1.0 Universal licence, a public domain dedication that allows commercial use without asking permission. Data provided by the U.S. Food and Drug Administration. `report_date` is the date of the weekly enforcement report; the records have no source_updated_at field.

This tool is not affiliated with, endorsed by or sponsored by the U.S. Food and Drug Administration.

This tool was built with the help of AI coding assistants.

### Help

Questions, a record that looks wrong, or an idea for a field: please open an issue on the Issues tab. Thank you for the report; every one is read.

# Actor input Schema

## `category` (type: `array`):

Which recall reports to search: food, drug, device. They are read in the order given. Leave all three for everything.

## `reportDateFrom` (type: `string`):

Only recalls whose enforcement report date is on or after this date (YYYY-MM-DD). Leave empty for no lower bound.

## `reportDateTo` (type: `string`):

Only recalls whose enforcement report date is on or before this date (YYYY-MM-DD). Leave empty for no upper bound.

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

Only this recall class: Class I (most serious), Class II or Class III. Leave empty for any.

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

Only this recall status: Ongoing, Completed, Terminated or Pending. Leave empty for any.

## `firmName` (type: `string`):

Only recalls whose recalling firm's name contains these words, like Acme Foods. Letters, digits, spaces and . , & ' - only. Leave empty for any.

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

Only firms in this US state, as a two-letter code like CA or TX. Leave empty for any.

## `apiKey` (type: `string`):

Optional. Without a key the source allows about 1,000 requests a day per address; your own free key raises that to 120,000 a day. It is kept secret and sent only to the source.

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

How many records to return at most, from 1 to 50000. You pay only for records returned.

## Actor input object example

```json
{
  "category": [
    "food",
    "drug",
    "device"
  ],
  "reportDateFrom": "2024-01-01",
  "reportDateTo": "2024-01-31",
  "classification": "",
  "status": "",
  "firmName": "",
  "state": "",
  "maxItems": 10
}
```

# Actor output Schema

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

The recalls of this run, one record per enforcement report row, with the 12 fields described in the dataset schema (recall number, class, status, product type, firm, dates, and the firm's city, state and country).

# 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 = {
    "category": [
        "food",
        "drug",
        "device"
    ],
    "reportDateFrom": "2024-01-01",
    "reportDateTo": "2024-01-31",
    "classification": "",
    "status": "",
    "firmName": "",
    "state": "",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("thesjostrandproject/us-recalls-search").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 = {
    "category": [
        "food",
        "drug",
        "device",
    ],
    "reportDateFrom": "2024-01-01",
    "reportDateTo": "2024-01-31",
    "classification": "",
    "status": "",
    "firmName": "",
    "state": "",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("thesjostrandproject/us-recalls-search").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 '{
  "category": [
    "food",
    "drug",
    "device"
  ],
  "reportDateFrom": "2024-01-01",
  "reportDateTo": "2024-01-31",
  "classification": "",
  "status": "",
  "firmName": "",
  "state": "",
  "maxItems": 10
}' |
apify call thesjostrandproject/us-recalls-search --silent --output-dataset

```

## MCP server setup

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

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/5l9bgs2V44CNOe5Iv/builds/7KN6lqBYKZQKzzDbN/openapi.json
