openFDA Scraper: Drug Events, Recalls & Device Reports
Pricing
from $0.56 / 1,000 record scrapeds
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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share

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
| Field | What it holds |
|---|---|
domain, endpoint | Which openFDA dataset the record came from |
recordId, safetyReportId | The dataset's own identifier: FAERS report number, recall number, MDR report number or label set ID |
productName, genericName, productDescription | Brand name, generic or active ingredient, and the full product description where the dataset has one |
manufacturer, recallingFirm | Manufacturer as openFDA resolves it, and the firm that issued a recall |
reactions | MedDRA reaction terms for drug and food events; report-text types for device events |
seriousness | Which seriousness criteria the reporter ticked (Death, Hospitalization, Life-threatening and so on), or the device event type |
patientAge, patientSex | Decoded from the FAERS numeric codes into "27 years" and "Male" |
outcome | Reaction outcomes, decoded from codes to text |
receiveDate, reportDate | ISO dates, converted from openFDA's YYYYMMDD and MM/DD/YYYY forms |
recallReason, classification, recallNumber, status | Recall detail: Class I, II or III, ongoing or terminated |
country, state, city | Where the firm or reporter is located |
narrative | The longest free-text field the dataset offers: MDR narrative, indication, distribution pattern or root cause |
productQuery, scrapedAt | Which 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/enforcementanddevice/recallfor 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/labelset 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
| Field | Type | Default | Notes |
|---|---|---|---|
domain | string | drug | drug, device, food or tobacco. |
endpoint | string | event | event, enforcement, label, recall or problem. |
productNames | array | [] | One query per name, run against the endpoint's own product field. Empty means the whole dataset. |
searchQuery | string | "" | Raw openFDA search expression, ANDed with the other filters. |
fromDate | string | "" | YYYY-MM-DD or YYYYMMDD, on the endpoint's own date field. |
toDate | string | "" | Same format, upper bound. |
maxResults | integer | 100 | Cap per product name. Hard ceiling 25,100. |
apiKey | string (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:
| Dataset | Date field used for fromDate/toDate | Product field used for productNames |
|---|---|---|
drug/event | receivedate | patient.drug.medicinalproduct |
drug/enforcement | report_date | product_description |
drug/label | effective_time | openfda.brand_name |
device/event | date_received | device.brand_name |
device/enforcement | report_date | product_description |
device/recall | event_date_initiated | product_description |
food/event | date_created | products.name_brand |
food/enforcement | report_date | product_description |
tobacco/problem | date_submitted | tobacco_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:
| Goal | Expression |
|---|---|
| One reaction term | patient.reaction.reactionmeddrapt:"nausea" |
| Serious reports only | serious:1 |
| Reports where the patient died | patient.reaction.reactionoutcome:5 |
| One manufacturer | patient.drug.openfda.manufacturer_name:"Pfizer" |
| Class I recalls | classification:"Class I" |
| Recalls in one state | state:"CA" |
| Two terms as an OR | patient.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
skipabove 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_FOUNDrather 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
productNamesentry 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.