# FDA Change Feed (`1mp/fda-change-feed`) Actor

openFDA records new or changed since a date, for regulatory, compliance and market-watch teams: FDA recalls, enforcement, 510(k), PMA, Drugs@FDA, Orange Book. Rows are new or updated, with a firm_key joining a firm across endpoints.

- **URL**: https://apify.com/1mp/fda-change-feed.md
- **Developed by:** [Jesse Hawkins](https://apify.com/1mp) (community)
- **Categories:** Business, Lead generation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 results

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 Change Feed

**FDA records that are new or changed since a date, for regulatory,
compliance and market-watch teams.** One run returns the change set
from eight openFDA endpoints: 510(k) decisions, PMA decisions, device
recalls, drug, device and food enforcement reports, Drugs@FDA
applications and Orange Book products. Each row says if the record is
`new` or `updated`, and carries a `firm_key` that joins one firm
across all eight endpoints.

Use it as an **FDA recalls** monitor (device recalls and drug, device
and food enforcement reports), a 510(k) and PMA clearance watch, or a
Drugs@FDA and Orange Book approvals feed. It reads the official
**openFDA API** (`api.fda.gov`) directly, with no key.

Run it on a schedule, with `since` a few weeks before your last run,
and keep the rows you do not hold yet (see Limits).

### What you get

One row per openFDA record with a source date between `since` and
today:

| Endpoint | In the change set when | `new` when |
|---|---|---|
| `drug/drugsfda` | a submission status date is in the window | its first original (ORIG) submission is in the window |
| `drug/orangebook` | the approval date or a patent submission date is in the window | the approval date is in the window |
| `drug/enforcement` | the report date or the termination date is in the window | the report date is in the window |
| `device/510k` | the decision date is in the window | always |
| `device/pma` | the decision date is in the window | always (a supplement is its own record) |
| `device/recall` | the posting date or the termination date is in the window | the posting date is in the window |
| `device/enforcement` | as `drug/enforcement` | as `drug/enforcement` |
| `food/enforcement` | as `drug/enforcement` | as `drug/enforcement` |

Every other row is `updated`: the record existed before `since`, and
a later date on it falls in the window (a termination, a new
submission, a new patent).

### Input

```json
{
  "since": "2026-10-01",
  "endpoints": ["device/510k", "device/recall"],
  "firms": ["Medtronic"],
  "maxItems": 500
}
```

- `since` (required): a date, `YYYY-MM-DD`, or a number of days back,
  such as `"30"`.
- `endpoints`: any of the eight names above. Empty reads all eight.
- `firms`: firm names. A row stays when its `firm_key` equals the
  key of a name you give, or starts with it as whole words. So
  `Medtronic` keeps `MEDTRONIC` and `MEDTRONIC MINIMED`, and not
  `MEDTRONICS`. Empty keeps every firm.
- `maxItems`: stop after this many rows in all. Each endpoint gives
  its newest rows first. Default 1,000; the most is 100,000.

### Output

```json
{
  "endpoint": "device/recall",
  "change": "new",
  "change_date": "2026-10-14",
  "change_field": "event_date_posted",
  "firm_name": "Ethicon, LLC",
  "firm_key": "ETHICON",
  "record_key": "Z-0153-2027",
  "key_fields": "product_res_number",
  "export_date": "2026-10-15",
  "application_number": "",
  "recall_number": "",
  "k_number": "",
  "pma_number": "",
  "supplement_number": "",
  "product_code": "GAQ",
  "product_number": "",
  "event_id": "99754",
  "record": "{\"action\":\"On 9/8/2026, recall...",
  "record_fields": 22,
  "record_len": 4977,
  "record_sha256": "a5073324...",
  "dropped_fields": "additional_info_contact",
  "source_url": "https://api.fda.gov/device/recall.json?search=...",
  "retrieved_at": "2026-10-16T04:30:00Z",
  "sha256": "2aed2c2c..."
}
```

- `record`: the whole openFDA record as compact JSON with sorted keys,
  minus the fields that name a person (see below).
- `record_sha256`: the SHA-256 of `record`. Store it, and you can see
  if a record changed between two runs.
- `export_date`: the date openFDA last updated that endpoint.
- `source_url`, `retrieved_at`, `sha256`: the exact API page, when it
  was read, and the SHA-256 of its body.
- `record_key`, `key_fields` and the id columns match the 1mp FDA
  regulatory decisions library row for row, so you can join the two.

### firm_key

openFDA writes one firm in many ways: `BAXTER HLTHCARE CORP` in
Drugs@FDA, `Baxter Healthcare Corporation` in a recall. `firm_key`
makes them one key, `BAXTER HEALTHCARE`. The rule, in order:

1. Fold to ASCII and upper case.
2. Delete apostrophes. Change `&` to `AND` and `+` to `PLUS`.
3. Change every other character that is not a letter or a digit into
   a space.
4. Join a spaced legal initialism into one word: `L L C` is `LLC`.
5. Remove a leading `THE`.
6. Remove legal-form words from the end until none is left: `INC`,
   `CORP`, `CORPORATION`, `CO`, `COMPANY`, `LLC`, `LP`, `LTD`,
   `LIMITED`, `PLC`, `GMBH`, `AG`, `KG`, `SE`, `SA`, `SPA`, `SRL`,
   `BV`, `NV`, `AB`, `PTE`, `PTY`, `PVT`, `PRIVATE` and others. A
   dangling `AND` goes too.
7. Remove a last `USA` or `US`, then step 6 again.
8. Spell out the short forms Drugs@FDA uses: `PHARMS` and `PHARM`
   are `PHARMACEUTICALS`, `LABS` is `LABORATORIES`, `HLTHCARE` is
   `HEALTHCARE`, `INDS` is `INDUSTRIES`.

**Measured on 2026-10-16**, on 200 rows (25 from each endpoint) of a
30-day window: 16 rows (8.0%) had the same firm under another key in
that window. **Known misses:**

- A descriptor word that one endpoint drops: `QUAGEN` and `QUAGEN
  PHARMACEUTICALS`, `NEPHRON` and `NEPHRON PHARMACEUTICALS`, `ELEVAR`
  and `ELEVAR THERAPEUTICS`. Drugs@FDA often cuts names short
  (`PIRAMAL CRITICAL`). Give the short name in `firms`, and the
  starts-with rule finds both.
- Divisions and subsidiaries keep their own key: `MEDTRONIC MINIMED`
  is not `MEDTRONIC`, and `GE HEALTHCARE` is not `GE MEDICAL
  SYSTEMS`. The `firms` filter finds the divisions that start with
  the parent's name.
- Entities of one name in two countries share a key: `BIOTRONIK AG`
  and `Biotronik, Inc.` are both `BIOTRONIK`. The key names a firm by
  its name, not a legal entity.
- `firms` asks openFDA for every firm whose name starts with the
  first word you give (`Baxter Healthcare` is asked as `BAXTER*`),
  then keeps the rows whose `firm_key` matches. A short first word,
  such as `GE`, reads more pages, but you pay only for the rows kept.

### Limits

- **openFDA updates each endpoint on its own schedule**, about once a
  week for enforcement reports and 510(k). A record can appear days
  after its source date. So set `since` a few weeks before your last
  run, and drop rows whose `record_key` and `record_sha256` you
  already hold.
- `device/classification` is not in the feed: it has no date field,
  so it has no change set.
- openFDA allows 240 requests a minute and 1,000 a day per IP
  without a key. One request returns up to 1,000 rows. The Actor
  waits 0.3 s between requests and retries a 429 or a 5xx.
- The Actor reads `api.fda.gov` only. It uses no browser and no key.

### What it will not do

- No personal data. The adverse-event and MAUDE endpoints are not
  read. The 510(k) `contact` field and the recall
  `additional_info_contact` field name a person, so they are removed
  from every record. `dropped_fields` says which ones a record had.
- No GMDN text: `device/udi` is not read.
- No interpretation. A row is FDA's administrative record. It does
  not say what an approval, a recall or a classification means.

### Source and licence

openFDA, U.S. Food and Drug Administration. openFDA content is in the
public domain under CC0 1.0 (open.fda.gov/license). openFDA says: "Do
not rely on openFDA to make decisions regarding medical care."

### Price

$5.00 per 1,000 rows ($0.005 a row), billed per row returned, with no
start fee.

# Actor input Schema

## `since` (type: `string`):

Start of the window: a date such as 2026-10-01, or a number of days back such as 30. Rows have a source date on or after it.

## `endpoints` (type: `array`):

openFDA endpoints to read, in this order. Empty reads all eight. Fewer endpoints give fewer rows.

## `firms` (type: `array`):

Firm names. Keeps rows whose firm_key equals a name's key or starts with it: Medtronic keeps Medtronic Minimed. Empty keeps all.

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

Stop after this many rows across all endpoints, newest first per endpoint. Each row is one charge.

## Actor input object example

```json
{
  "since": "30",
  "endpoints": [],
  "firms": [],
  "maxItems": 20
}
```

# Actor output Schema

## `changes` (type: `string`):

Every row, as JSON from the run's default dataset.

## `overview` (type: `string`):

Rows in the overview view: endpoint, change, date, firm, firm key and ids.

# 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 = {
    "since": "30",
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("1mp/fda-change-feed").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 = {
    "since": "30",
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("1mp/fda-change-feed").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 '{
  "since": "30",
  "maxItems": 20
}' |
apify call 1mp/fda-change-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,1mp/fda-change-feed"
        }
    }
}
```

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/S6gsy8VqJuMIxAt9h/builds/N76IH27esxeaAx0GC/openapi.json
