openFDA Scraper: Drug Events, Recalls & Device Reports avatar

openFDA Scraper: Drug Events, Recalls & Device Reports

Pricing

from $0.56 / 1,000 record scrapeds

Go to Apify Store
openFDA Scraper: Drug Events, Recalls & Device Reports

openFDA Scraper: Drug Events, Recalls & Device Reports

Query openFDA for adverse event reports, enforcement actions and recalls across drugs, devices, food and tobacco. Filter by product, date and free-text search.

Pricing

from $0.56 / 1,000 record scrapeds

Rating

0.0

(0)

Developer

Arman Hossain

Arman Hossain

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Share

openFDA Scraper: Adverse event reports, recalls, device reports and drug labels, normalised into one flat row across drugs, devices, food and tobacco

Pulls FDA safety surveillance data into one flat, consistent dataset: adverse event reports, enforcement actions, product recalls and drug labels across drugs, medical devices, food and tobacco.

openFDA is the FDA's own public API, so there is no login and nothing to solve with a proxy. Nine dataset combinations are supported and every one normalises into the same record shape, so a drug adverse event and a food recall land in the same columns.

Agent skill: SKILL.md

https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/openfda-adverse-events-scraper.md

What you get

FieldWhat it holds
domain, endpointWhich openFDA dataset the record came from
recordId, safetyReportIdThe dataset's own identifier: FAERS report number, recall number, MDR report number or label set ID
productName, genericName, productDescriptionBrand name, generic or active ingredient, and the full product description where the dataset has one
manufacturer, recallingFirmManufacturer as openFDA resolves it, and the firm that issued a recall
reactionsMedDRA reaction terms for drug and food events; report-text types for device events
seriousnessWhich seriousness criteria the reporter ticked (Death, Hospitalization, Life-threatening and so on), or the device event type
patientAge, patientSexDecoded from the FAERS numeric codes into "27 years" and "Male"
outcomeReaction outcomes, decoded from codes to text
receiveDate, reportDateISO dates, converted from openFDA's YYYYMMDD and MM/DD/YYYY forms
recallReason, classification, recallNumber, statusRecall detail: Class I, II or III, ongoing or terminated
country, state, cityWhere the firm or reporter is located
narrativeThe longest free-text field the dataset offers: MDR narrative, indication, distribution pattern or root cause
productQuery, scrapedAtWhich product name produced the record, and the run timestamp

Every record carries the same key set whichever dataset you query. Fields that do not apply are null.

RUN_SUMMARY in the key-value store holds per-run counts, the filters you used, and any query that failed.

Use cases

  • Pharmacovigilance. Sweep FAERS nightly for new reports naming your molecules and page on a seriousness change.
  • Competitive recall monitoring. Watch drug/enforcement and device/recall for a list of competitor firms.
  • Clinical risk research. Pull every report pairing a drug with a reaction term and count by year.
  • Supply chain and food safety. Track Class I food recalls by state and distribution pattern.
  • Regulatory intelligence. Diff drug/label set IDs to spot label changes on a watched product.

Quick start

Recent adverse event reports for one drug:

{
"domain": "drug",
"endpoint": "event",
"productNames": ["ibuprofen"],
"maxResults": 100
}

Two drugs, a specific reaction and a date window:

{
"domain": "drug",
"endpoint": "event",
"productNames": ["metformin", "atorvastatin"],
"searchQuery": "patient.reaction.reactionmeddrapt:\"nausea\"",
"fromDate": "2024-01-01",
"toDate": "2025-12-31",
"maxResults": 2000
}

Every Class I food recall this year, no product filter:

{
"domain": "food",
"endpoint": "enforcement",
"searchQuery": "classification:\"Class I\"",
"fromDate": "2025-01-01",
"maxResults": 5000,
"apiKey": "YOUR_OPENFDA_KEY"
}

Input

FieldTypeDefaultNotes
domainstringdrugdrug, device, food or tobacco.
endpointstringeventevent, enforcement, label, recall or problem.
productNamesarray[]One query per name, run against the endpoint's own product field. Empty means the whole dataset.
searchQuerystring""Raw openFDA search expression, ANDed with the other filters.
fromDatestring""YYYY-MM-DD or YYYYMMDD, on the endpoint's own date field.
toDatestring""Same format, upper bound.
maxResultsinteger100Cap per product name. Hard ceiling 25,100.
apiKeystring (secret)""Optional openFDA key.

Only these nine domain and endpoint pairs exist. Anything else stops the run immediately with an error listing the valid ones:

DatasetDate field used for fromDate/toDateProduct field used for productNames
drug/eventreceivedatepatient.drug.medicinalproduct
drug/enforcementreport_dateproduct_description
drug/labeleffective_timeopenfda.brand_name
device/eventdate_receiveddevice.brand_name
device/enforcementreport_dateproduct_description
device/recallevent_date_initiatedproduct_description
food/eventdate_createdproducts.name_brand
food/enforcementreport_dateproduct_description
tobacco/problemdate_submittedtobacco_products

Results are always sorted newest first on the dataset's date field.

Output example

{
"domain": "drug",
"endpoint": "event",
"recordId": "26940509",
"safetyReportId": "26940509",
"productName": "METFORMIN",
"genericName": "METFORMIN",
"manufacturer": "Mylan Pharmaceuticals Inc.",
"reactions": [
"Adrenal insufficiency",
"Hepatocellular injury",
"Tachycardia",
"Abdominal pain",
"Nausea",
"Vomiting",
"Diarrhoea",
"Muscular weakness"
],
"seriousness": "Hospitalization; Other serious",
"patientAge": "27 years",
"patientSex": "Male",
"receiveDate": "2026-06-30",
"reportDate": "2026-07-09",
"outcome": ["Recovered/resolved", "Not recovered/not resolved"],
"recallReason": null,
"classification": null,
"recallNumber": null,
"status": null,
"recallingFirm": null,
"productDescription": null,
"country": "US",
"state": null,
"city": null,
"narrative": "Diabetes mellitus inadequate control",
"productQuery": "metformin",
"scrapedAt": "2026-08-06T11:49:45.177Z"
}

A drug/enforcement record fills the recall side of the same shape instead:

{
"domain": "drug",
"endpoint": "enforcement",
"recordId": "D-0390-2026",
"productName": "IBUPROFEN",
"manufacturer": "Sun Pharmaceutical Industries, Inc.",
"receiveDate": "2026-03-02",
"reportDate": "2026-03-25",
"recallReason": "Presence of foreign substance: the firm received complaints for a gel-like mass and black particles in the product.",
"classification": "Class II",
"recallNumber": "D-0390-2026",
"status": "Ongoing",
"recallingFirm": "STRIDES PHARMA INC",
"productDescription": "Children's Ibuprofen Oral Suspension, USP, 100 mg per 5mL, 4 FL OZ (120 mL) bottle .",
"country": "United States",
"state": "NJ",
"city": "Bridgewater",
"narrative": "U.S.A. Nationwide"
}

Writing a search expression

searchQuery is passed through to openFDA's Lucene-style syntax and ANDed with the product and date filters. Useful patterns:

GoalExpression
One reaction termpatient.reaction.reactionmeddrapt:"nausea"
Serious reports onlyserious:1
Reports where the patient diedpatient.reaction.reactionoutcome:5
One manufacturerpatient.drug.openfda.manufacturer_name:"Pfizer"
Class I recallsclassification:"Class I"
Recalls in one statestate:"CA"
Two terms as an ORpatient.reaction.reactionmeddrapt:("nausea" OR "vomiting")

Field names differ between datasets. The openFDA field reference lists every searchable field per endpoint.

API example

curl -X POST "https://api.apify.com/v2/acts/arman-bd~openfda-adverse-events-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"domain": "drug",
"endpoint": "event",
"productNames": ["ibuprofen"],
"fromDate": "2025-01-01",
"maxResults": 500
}'

JavaScript example

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/openfda-adverse-events-scraper').call({
domain: 'drug',
endpoint: 'enforcement',
searchQuery: 'classification:"Class I"',
fromDate: '2025-01-01',
maxResults: 1000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const byFirm = {};
for (const r of items) byFirm[r.recallingFirm] = (byFirm[r.recallingFirm] ?? 0) + 1;
console.log(Object.entries(byFirm).sort((a, b) => b[1] - a[1]).slice(0, 10));

Notes

  • 25,000 records per query is the ceiling. openFDA refuses any skip above 25,000, so split a bigger pull into date windows, or into several product names, to get past it.
  • A query that matches nothing comes back as a 404. openFDA returns NOT_FOUND rather than an empty list, and the Actor treats that as zero results rather than an error and moves on.
  • Codes are decoded, not passed through. FAERS ships sex, age unit, reaction outcome and seriousness as numeric codes, including a seriousness flag where "2" means no, which is easy to misread as a set flag. All four are decoded to text.
  • The searched product is the one reported. A FAERS report lists every drug the patient was taking, so the record picks the drug matching your productNames entry and falls back to the reporter's designated suspect drug.
  • Rate limits are per IP. Unauthenticated you get 240 requests per minute and 1,000 per day. With a free key it is 240 per minute and 120,000 per day. One page is 100 records, so a 5,000-record pull is 50 requests.
  • openFDA is slow. Some device endpoints take four seconds per request, so the request timeout is 60 seconds and the default run timeout is 30 minutes.
  • Transient errors get retried. 429 and 5xx responses get three attempts with linear backoff, and one failing product name does not stop the others.

FAQ

Do I need an API key? No, but a free key from open.fda.gov raises the daily cap from 1,000 to 120,000 requests. Paste it into apiKey, which is stored as a secret.

Do I need a proxy? No. Proxy configuration is not required to run this Actor.

Is this data validated? No, and the FDA says so explicitly. Adverse event reports are voluntary submissions, and a report does not establish that the product caused the effect. Duplicate reports of the same case are common, so dedupe on safetyReportId before counting.

Why did a search return nothing? Field names are dataset-specific. patient.drug.medicinalproduct exists in drug/event and nowhere else. Check the endpoint table above and the openFDA field reference.

How current is it? FAERS is refreshed quarterly with rolling additions, and enforcement reports update weekly. The meta.last_updated date openFDA reports is typically within a week or two of today.

Can I schedule it? Yes. Run daily with a fromDate a few days back and dedupe on recordId to build an incremental store.