
Openfda Database
- 1.3k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
openfda-database is a Google DeepMind science skill that queries FDA public drug, device, and adverse-event datasets through the openFDA API from Claude, Cursor, or custom coding agents.
About
openfda-database is a science-skills reference for the openFDA REST API at https://api.fda.gov/{category}/{endpoint}.json. Without an API key, requests cap at 240 per minute and 1,000 per day per IP—limits an automated agent can exceed in one session—while keyed access allows 240 per minute and 120,000 per day. Authentication uses --api_key or FDA_API_KEY. The skill documents search syntax with field:term queries, AND combinations, and category endpoints for drugs, devices, and adverse events. Developers reach for openfda-database when building health-research agents, pharmacovigilance tooling, or compliance dashboards that need reliable FDA data access patterns.
- Full reference for all openFDA API categories and endpoints
- Rate-limit guidance: 1,000 requests/day without key vs 120,000 with key
- Exact matching rules using .exact suffix for brand names, reactions, manufacturers
- Search syntax examples including AND/OR, date ranges, and phrase matching
- Sort and count aggregation patterns for data exploration
Openfda Database by the numbers
- 1,300 all-time installs (skills.sh)
- +168 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #347 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill openfda-databaseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.3k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you query FDA drug and device data via API?
Query the FDA's public drug, device, and adverse-event datasets reliably from Claude, Cursor or custom agents.
Who is it for?
Developers building health-research or regulatory agents who need reliable openFDA API query patterns and rate-limit guidance.
Skip if: Clinical decision support requiring validated medical advice rather than raw FDA public dataset retrieval.
When should I use this skill?
A developer queries FDA drugs, devices, adverse events, or needs openFDA search syntax and API key setup.
What you get
Structured openFDA JSON query results for drug, device, and adverse-event endpoints with documented search filters.
- FDA JSON query results
- search query strings
- endpoint configuration
By the numbers
- Unauthenticated limit: 240 requests/min and 1,000 requests/day per IP
- With FDA_API_KEY: 240 requests/min and 120,000 requests/day per key
Files
openFDA Search and Query
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the terms at https://open.fda.gov/apis/ and https://open.fda.gov/license, then (2) create the file recording the notification text and timestamp. 3. `.env` file: Make sure the .env file exists in your home directory. Create one if it does not exist. 4. `FDA_API_KEY` (optional but recommended): Raises the daily request limit from 1,000 to 120,000. The skill works without it, but an agent can easily exhaust the keyless limit in a single session. The user can register for a free key at https://open.fda.gov/apis/authentication/. If the variable is missing from .env, do NOT ask the user to paste it into the chat (this would leak the key into the agent's context). Instead, give the user this command — substituting `ENV_FILE` with the resolved literal path to the `.env` file:
printf "Enter openFDA API key (typing hidden): " && read -s key && echo && echo "FDA_API_KEY=$key" >> "ENV_FILE" && echo "Saved."The scripts load credentials automatically via dotenv. NEVER read, print, or inspect the .env file or its variables (e.g. no cat, grep, echo, printenv, or os.environ.get on keys). Credentials must stay out of the agent's context.
Core Rules
- Use the Wrapper: ALWAYS execute the provided helper scripts to query the
database rather than accessing the database directly. The scripts automatically enforce the required rate limit gracefully.
- Rate Limiting: Respect openFDA rate limits. Without API key: 240
requests/min, 1,000 requests/day per IP. With API key: 240 requests/min, 120,000 requests/day per key. Always set an API key before running multi-query workflows.
Warning: An automated agent can easily exhaust the 1,000-request daily
limit in a single research session. Always set an API key before running
multi-query workflows.
Instruct the user to register for a free key at
https://open.fda.gov/apis/authentication/ and follow the prerequisite
instructions above to addFDA_API_KEYto the.envfile. The script will
emit a warning to stderr if no API key is detected.
- Always Use `--output`: All subcommands require
--output <file>to
write results to a file. This prevents large output becoming overwhelming. Use jq or code to read the output file.
- Notification: If this skill is used, ensure this is mentioned in the
output.
Utility Script
Single script for all operations:
uv run scripts/openfda_query.py {search,count,download} --output <file> [options]1. Search
Search any of the 28 endpoints and save JSON results to a file.
uv run scripts/openfda_query.py search \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:aspirin" \
--limit 5 --output /tmp/fda_results.jsonStdout prints a compact summary:
{"status": "success", "output": "/tmp/fda_results.json", "results_in_file": 5, "total_matching": 601477}Options:
-
--output: Output file for full JSON results (required). -
--category: API category —drug,device,food,tobacco,other,
animalandveterinary, cosmetic, transparency.
-
--endpoint: Endpoint within the category (e.g.,event,label,510k).
See references/api_endpoints.md for full list.
-
--search: Query string (e.g.,
patient.drug.medicinalproduct:aspirin+AND+serious:1).
-
--sort: Sort field and order (e.g.,receivedate:desc). -
--limit: Max results (default 10, max 1000). -
--skip: Pagination offset (default 0). -
--api_key: API key (also readsFDA_API_KEYenv var).
2. Count
Count unique values of a field within matching results.
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:aspirin" \
--count_field "patient.reaction.reactionmeddrapt.exact" \
--summary 10 --output /tmp/aspirin_reactions.jsonStdout prints a summary with the top 5 terms. Full data is in the output file.
Additional options:
-
--count_field: Field to count (append.exactfor whole-phrase counting). -
--summary N: Return only the top N most frequent terms. Use this to avoid
flooding the context with hundreds of infrequent terms.
3. Download
Download multiple pages of results to a file.
uv run scripts/openfda_query.py download \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:aspirin" \
--limit 100 --max_pages 5 \
--output /tmp/aspirin_events.jsonAdditional options:
-
--max_pages: Maximum pages to fetch (default 10). -
--all_results: Automatically paginate to fetch all matching results.
Safety cap of 25,000 records maximum per download to prevent runaway downloads and prevent excessive API usage.
Tip: Common drugs can have excessive reports. Use a date range (e.g.,
receivedate:[20250101+TO+20250131]) to limit the volume of download.Entity Resolution: Using .exact for Precision
When searching for specific product names, drug names, or categorical terms, always use the .exact suffix on the field to get exact-match results. Without it, the API tokenizes multi-word values and returns noisy partial matches.
# Precise: matches only "ADVIL"
uv run scripts/openfda_query.py search --category drug --endpoint label \
--search 'openfda.brand_name.exact:"ADVIL"' \
--limit 5 --output /tmp/advil_label.jsonNote: Many brand names in the FDA database include variant suffixes (e.g.,
"TYLENOL Extra Strength" rather than just "TYLENOL"). If an .exact searchreturns 0 results, try without .exact to see the available brand namevariants, then re-query with the full exact name.
The .exact suffix is also required when using --count_field to aggregate whole phrases instead of individual words.
MedDRA Term Resolution
openFDA adverse event data uses MedDRA (Medical Dictionary for Regulatory Activities) terms for reactions. The API reports Preferred Terms (PTs) but does not provide the MedDRA hierarchy (System Organ Class, High Level Terms, etc.).
Note: MedDRA is a proprietary ontology and is not indexed in the
EMBL-EBI OLS. To approximate MedDRA hierarchy lookups, use the **Human
Phenotype Ontology (HP) or NCI Thesaurus (NCIT)** as proxy ontologies —
they cross-reference MedDRA IDs and provide parent/ancestor relationships.
# Step 1: Get top reactions from openFDA
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:metformin" \
--count_field "patient.reaction.reactionmeddrapt.exact" \
--summary 5 --output /tmp/metformin_reactions.json
# Step 2: Look up the top reaction term using a biomedical ontology service
# skill (e.g. embl-ebi-ols skill).
# MedDRA is not available in OLS; use the Human Phenotype Ontology (HP) or
# NCI Thesaurus (NCIT) as a proxy to find the hierarchical classification of
# the reaction term.Available Endpoints (28 total)
Category to endpoint mapping:
-
drug: event, label, ndc, enforcement, drugsfda, shortages -
device: 510k, classification, enforcement, event, pma, recall,
registrationlisting, udi, covid19serology
-
food: enforcement, event -
tobacco: problem, researchpreventionads, researchdigitalads,
researchsmokefree
-
other: historicaldocument, nsde, substance, unii -
animalandveterinary: event -
cosmetic: event -
transparency: crl
Reference
- Query syntax and all endpoints: See
references/api_endpoints.md for field names, search syntax, date ranges, and boolean operators.
Recipes
Common query patterns for drugs, devices, foods, tobacco, cosmetics, animal and veterinary products, substances, transparency data, adverse events, recalls, labeling, approvals, shortages, 510(k) clearances, NDC lookups, any FDA safety or regulatory data query, and more. See references/recipes.md for the full recipes.
Workflow
1. Search for records using search with --output. Read the output file. 2. Use count with --summary 10 --output to summarize field distributions. 3. Use download (with --all_results for exhaustive pulls) to fetch larger datasets. 4. Read and analyze the output file using standard tools. 5. For MedDRA term hierarchy questions, use a biomedical ontology service skill (e.g. EMBL-EBI OLS skill with the HP or NCIT ontology) to look up the term.
openFDA API Endpoints Reference
Base URL
All requests go to https://api.fda.gov/{category}/{endpoint}.json.
Authentication
- Without API key: 240 requests/min, 1,000/day per IP
- With API key: 240 requests/min, 120,000/day per key
Pass via --api_key flag or FDA_API_KEY environment variable.
Important: Without an API key you are capped at 1,000 requests/day.
An automated agent can easily exceed this in a single session. Always set a
key before running multi-query workflows.
Query Syntax
Search
search=field:termsearch=field:term— match a single termsearch=field:term+AND+field:term— match ALL termssearch=field:term+field:term— match ANY term (OR)search=field:[20200101+TO+20201231]— date rangesearch=field:"exact+phrase"— exact phrase matching
Exact Matching (.exact)
For categorical fields (drug names, reaction terms, manufacturer names), use the .exact suffix to match whole phrases rather than individual tokens:
search=openfda.brand_name.exact:"TYLENOL"
count=patient.reaction.reactionmeddrapt.exactWithout .exact, the API tokenizes multi-word values and returns noisy partial matches. Always use .exact when:
- Searching for a specific product by brand name
- Counting reaction terms or manufacturer names
- Querying any field that contains multi-word values
Sort
sort=field:asc
sort=field:descCount
Use count=field to aggregate unique values. Add .exact suffix for whole-phrase counting:
count=patient.reaction.reactionmeddrapt.exactPagination
limit=N— number of results (max 1000)skip=N— offset for pagination (max 25000)
Date Formats
openFDA requires dates in YYYYMMDD format (not ISO 8601). Date ranges use bracket syntax with +TO+:
search=receivedate:[20230101+TO+20231231]Common pitfalls:
| Format | Valid? |
|---|---|
20230101 | ✓ Correct |
[20230101+TO+20231231] | ✓ Correct range |
2023-01-01 | ✗ Will cause an API error |
2023/01/01 | ✗ Will cause an API error |
January 1, 2023 | ✗ Will cause an API error |
All Endpoints
Drug (6 endpoints)
| Endpoint | Path | Description |
|---|---|---|
| event | /drug/event.json | Adverse event reports (FAERS) |
| label | /drug/label.json | Structured product labeling (SPL) |
| ndc | /drug/ndc.json | National Drug Code directory |
| enforcement | /drug/enforcement.json | Recall enforcement reports |
| drugsfda | /drug/drugsfda.json | Drug approvals since 1939 |
| shortages | /drug/shortages.json | Drug shortage reports |
Device (9 endpoints)
| Endpoint | Path | Description |
|---|---|---|
| 510k | /device/510k.json | 510(k) premarket clearances |
| classification | /device/classification.json | Device classification data |
| enforcement | /device/enforcement.json | Recall enforcement reports |
| event | /device/event.json | Adverse event reports (MDR) |
| pma | /device/pma.json | Premarket approval (Class III) |
| recall | /device/recall.json | Device recall details |
| registrationlisting | /device/registrationlisting.json | Facility registrations |
| udi | /device/udi.json | Unique Device Identifiers (GUDID) |
| covid19serology | /device/covid19serology.json | COVID-19 antibody test data |
Food (2 endpoints)
| Endpoint | Path | Description |
|---|---|---|
| enforcement | /food/enforcement.json | Food recall enforcement reports |
| event | /food/event.json | CAERS adverse event reports |
Tobacco (4 endpoints)
| Endpoint | Path | Description |
|---|---|---|
| problem | /tobacco/problem.json | Tobacco product problem reports |
| researchpreventionads | /tobacco/researchpreventionads.json | Prevention ads research |
| researchdigitalads | /tobacco/researchdigitalads.json | Digital ads research |
| researchsmokefree | /tobacco/researchsmokefree.json | Smokefree campaign research |
Other (4 endpoints)
| Endpoint | Path | Description |
|---|---|---|
| historicaldocument | /other/historicaldocument.json | FDA press releases 1913-2014 |
| nsde | /other/nsde.json | NDC SPL data elements |
| substance | /other/substance.json | Substance molecular data |
| unii | /other/unii.json | Unique Ingredient Identifiers |
Animal & Veterinary (1 endpoint)
| Endpoint | Path | Description |
|---|---|---|
| event | /animalandveterinary/event.json | Animal drug adverse events |
Cosmetic (1 endpoint)
| Endpoint | Path | Description |
|---|---|---|
| event | /cosmetic/event.json | Cosmetic adverse event reports |
Transparency (1 endpoint)
| Endpoint | Path | Description |
|---|---|---|
| crl | /transparency/crl.json | Complete Response Letters |
Common Field Names
Drug Adverse Events (drug/event)
patient.drug.medicinalproduct— drug namepatient.reaction.reactionmeddrapt— reaction term (MedDRA PT)serious— 1 for serious, 2 for not seriousoccurcountry— country codereceivedate— date received (YYYYMMDD)patient.drug.drugindication— drug indication
Drug Labeling (drug/label)
openfda.brand_name— brand name (use.exactfor precise matching)openfda.generic_name— generic nameopenfda.manufacturer_name— manufacturerindications_and_usage— indicationswarnings— warnings sectiondosage_and_administration— dosage info
Drug Shortages (drug/shortages)
generic_name— generic drug namestatus— shortage statusproprietary_name— brand/proprietary name
Device Events (device/event)
device.generic_name— device generic namedevice.brand_name— device brand namedevice.manufacturer_d_name— manufacturermdr_text.text— event narrative
Animal & Veterinary Events (animalandveterinary/event)
animal.species— species name (e.g. Dog, Cat, Horse)animal.breed.breed_component— breedanimal.gender— genderreaction.veddra_term_name— reaction termdrug.brand_name— drug brand nameserious_ae— serious adverse event flag
Food Enforcement (food/enforcement)
reason_for_recall— recall reasonclassification— Class I, II, or IIIrecalling_firm— company nameproduct_description— product detailsrecall_initiation_date— date of recall (YYYYMMDD)
openFDA Query Recipes
Common query patterns for drugs, devices, foods, tobacco, cosmetics, animal and veterinary products, substances, transparency data, adverse events, recalls, labeling, approvals, shortages, 510(k) clearances, NDC lookups, any FDA safety or regulatory data query, and more.
Drug Adverse Event Reactions (Top N)
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:metformin" \
--count_field "patient.reaction.reactionmeddrapt.exact" \
--summary 10 --output /tmp/metformin_reactions.jsonDrug Indication / Reasons for Use
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:metformin" \
--count_field "patient.drug.drugindication.exact" \
--summary 5 --output /tmp/metformin_indications.jsonDrug Route of Administration
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:aspirin" \
--count_field "patient.drug.drugadministrationroute.exact" \
--summary 5 --output /tmp/aspirin_routes.jsonPatient Sex Demographics Breakdown
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:aspirin" \
--count_field "patient.patientsex" \
--output /tmp/aspirin_sex.jsonEvents by Reporter Country
uv run scripts/openfda_query.py count \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:ozempic" \
--count_field "primarysource.reportercountry.exact" \
--summary 5 --output /tmp/ozempic_countries.jsonDrug-Drug Co-Occurrence
uv run scripts/openfda_query.py search \
--category drug --endpoint event \
--search "patient.drug.openfda.generic_name:METFORMIN+AND+patient.drug.openfda.generic_name:SITAGLIPTIN" \
--limit 3 --output /tmp/cooccurrence.jsonNDC by Manufacturer
uv run scripts/openfda_query.py search \
--category drug --endpoint ndc \
--search "openfda.manufacturer_name:pfizer" \
--limit 10 --output /tmp/pfizer_ndc.jsonVoluntary vs Mandated Recalls
uv run scripts/openfda_query.py count \
--category drug --endpoint enforcement \
--count_field "voluntary_mandated.exact" \
--output /tmp/voluntary_mandated.jsonDrug Shortages
uv run scripts/openfda_query.py search \
--category drug --endpoint shortages \
--limit 10 --output /tmp/shortages.jsonCOVID-19 Serology Test Performance Data
uv run scripts/openfda_query.py search \
--category device --endpoint covid19serology \
--limit 3 --output /tmp/covid_serology.jsonAnimal & Veterinary Adverse Events (Dogs, Cats, etc.)
uv run scripts/openfda_query.py search \
--category animalandveterinary --endpoint event \
--search "animal.species:Dog" \
--limit 3 --output /tmp/dog_events.jsonDevice Adverse Events
uv run scripts/openfda_query.py search \
--category device --endpoint event \
--limit 3 --output /tmp/device_events.jsonFood Adverse Events (CAERS)
uv run scripts/openfda_query.py search \
--category food --endpoint event \
--limit 3 --output /tmp/food_events.jsonSubstance / UNII Lookup
uv run scripts/openfda_query.py search \
--category other --endpoint unii \
--limit 3 --output /tmp/unii_data.jsonHistorical FDA Documents
uv run scripts/openfda_query.py search \
--category other --endpoint historicaldocument \
--limit 3 --output /tmp/historical_docs.jsonDownload All Results (Auto-Paginate)
uv run scripts/openfda_query.py download \
--category drug --endpoint event \
--search "patient.drug.medicinalproduct:ozempic+AND+serious:1" \
--limit 100 --all_results \
--output /tmp/ozempic_serious.json# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Queries the openFDA API and returns results in clean JSON format.
This script provides a CLI for searching, counting, and downloading data from
all 28 openFDA API endpoints across 8 categories: drug, device, food, tobacco,
other, animalandveterinary, cosmetic, and transparency.
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# "python-dotenv",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
from __future__ import annotations
import argparse
import json
import os
import sys
from typing import Any
import dotenv
from science_skills.skills.scienceskillscommon import http_client
BASE_URL = "https://api.fda.gov"
CLIENT = http_client.HttpClient(BASE_URL, qps=4.0)
VALID_ENDPOINTS = {
"drug": [
"event",
"label",
"ndc",
"enforcement",
"drugsfda",
"shortages",
],
"device": [
"510k",
"classification",
"enforcement",
"event",
"pma",
"recall",
"registrationlisting",
"udi",
"covid19serology",
],
"food": ["enforcement", "event"],
"tobacco": [
"problem",
"researchpreventionads",
"researchdigitalads",
"researchsmokefree",
],
"other": ["historicaldocument", "nsde", "substance", "unii"],
"animalandveterinary": ["event"],
"cosmetic": ["event"],
"transparency": ["crl"],
}
ALL_RESULTS_SAFETY_CAP = 25000
def _warn_no_api_key(api_key: str | None) -> None:
key = api_key or os.environ.get("FDA_API_KEY")
if not key:
print(
"WARNING: No API key provided. This API has much lower rate limits"
" without a key. Get one at https://open.fda.gov/apis/authentication/"
" and pass via --api_key or the FDA_API_KEY environment variable.",
file=sys.stderr,
)
def _write_output(data: dict[str, Any], output_path: str) -> None:
out_dir = os.path.dirname(output_path)
if out_dir:
os.makedirs(out_dir, exist_ok=True)
if isinstance(data, dict):
data["_license_notice"] = (
"Data provided by openFDA. Please review the licensing terms at"
" https://open.fda.gov/license/"
)
with open(output_path, "w") as f:
json.dump(data, f, indent=2)
def _print_json(data: dict[str, Any]) -> None:
"""Prints a dictionary as indented JSON to stdout."""
print(json.dumps(data, indent=2))
def _validate_endpoint(category: str, endpoint: str) -> bool:
if category not in VALID_ENDPOINTS:
return False
return endpoint in VALID_ENDPOINTS[category]
def _print_endpoint_error(category: str, endpoint: str) -> None:
"""Prints an error message for an invalid endpoint and exits.
The error message includes the valid endpoints for the given category.
Args:
category: The API category provided.
endpoint: The invalid API endpoint provided.
"""
valid = ", ".join(VALID_ENDPOINTS.get(category, []))
_print_json({
"status": "error",
"message": (
f"Invalid endpoint '{endpoint}' for category "
f"'{category}'. Valid endpoints: {valid}"
),
})
sys.exit(1)
def _build_url(
*,
category: str,
endpoint: str,
search: str | None = None,
sort: str | None = None,
limit: int = 10,
skip: int = 0,
count_field: str | None = None,
api_key: str | None = None,
) -> str:
"""Builds the openFDA API URL with the given parameters.
Args:
category: The API category (e.g., "drug").
endpoint: The API endpoint within the category (e.g., "event").
search: Optional search query string.
sort: Optional sort field and order string.
limit: The maximum number of results per request.
skip: The number of records to skip for pagination.
count_field: Optional field to count unique values for.
api_key: Optional API key.
Returns:
A string representing the constructed URL.
"""
url = f"{BASE_URL}/{category}/{endpoint}.json?"
params = []
key = api_key or os.environ.get("FDA_API_KEY")
if key:
params.append(f"api_key={key}")
if search:
params.append(f"search={search}")
if sort:
params.append(f"sort={sort}")
if count_field:
params.append(f"count={count_field}")
else:
params.append(f"limit={limit}")
params.append(f"skip={skip}")
return url + "&".join(params)
def _fetch(url: str) -> dict[str, Any]:
"""Fetches JSON data from the given URL and handles errors."""
try:
return CLIENT.fetch_json(url)
except http_client.HttpError as e:
if e.status_code == 429:
return {
"status": "error",
"http_code": 429,
"message": (
"Rate limit exceeded (HTTP 429). You have hit the openFDA request"
" limit. Without an API key the limit is 240 requests/min and"
" 1,000/day. With a free API key the daily limit increases to"
" 120,000. Register at: https://open.fda.gov/apis/authentication/"
),
}
if e.status_code is not None:
body = ""
if e.body:
body = e.body.decode("utf-8", errors="replace")
try:
error_json = json.loads(body)
return {"status": "error", "http_code": e.status_code, **error_json}
except (json.JSONDecodeError, ValueError):
return {
"status": "error",
"http_code": e.status_code,
"message": f"HTTP {e.status_code}: {str(e)}",
"body": body[:500],
}
return {"status": "error", "message": f"Network error: {str(e)}"}
def cmd_search(args: argparse.Namespace) -> None:
"""Searches the openFDA API and writes the results to a file.
Constructs a search URL based on the provided arguments, fetches the data,
and writes the JSON response to the file specified by --output. Also prints
a summary of the operation to stdout.
Args:
args: An argparse.Namespace object containing the command-line arguments,
including category, endpoint, search, sort, limit, skip, api_key, and
output.
"""
if not _validate_endpoint(args.category, args.endpoint):
_print_endpoint_error(args.category, args.endpoint)
_warn_no_api_key(args.api_key)
url = _build_url(
category=args.category,
endpoint=args.endpoint,
search=args.search,
sort=args.sort,
limit=args.limit,
skip=args.skip,
api_key=args.api_key,
)
result = _fetch(url)
_write_output(result, args.output)
total = result.get("meta", {}).get("results", {}).get("total", "?")
count = len(result.get("results", []))
_print_json({
"status": "success",
"output": args.output,
"results_in_file": count,
"total_matching": total,
})
def cmd_count(args: argparse.Namespace) -> None:
"""Counts unique values for a specified field using the openFDA API.
Fetches counts of unique terms for a given field based on the provided
search criteria. Optionally, it can return only a summary of the top N
most frequent terms. The results are written to the file specified by
--output.
Args:
args: An argparse.Namespace object containing the command-line arguments,
including category, endpoint, search, count_field, summary, and output.
"""
if not _validate_endpoint(args.category, args.endpoint):
_print_endpoint_error(args.category, args.endpoint)
_warn_no_api_key(args.api_key)
url = _build_url(
category=args.category,
endpoint=args.endpoint,
search=args.search,
sort=args.sort,
count_field=args.count_field,
api_key=args.api_key,
)
result = _fetch(url)
if args.summary and "results" in result:
result["results"] = result["results"][: args.summary]
_write_output(result, args.output)
entries = len(result.get("results", []))
top_terms = []
for r in result.get("results", [])[:5]:
top_terms.append({"term": r.get("term"), "count": r.get("count")})
_print_json({
"status": "success",
"output": args.output,
"entries": entries,
"top_terms": top_terms,
})
def cmd_download(args: argparse.Namespace) -> None:
"""Downloads multiple pages of openFDA results and writes them to a file.
Fetches results from the specified openFDA endpoint, handling pagination
based on --max_pages or --all_results. The fetched records are
accumulated and written as a single JSON object to the file specified
by --output.
Args:
args: An argparse.Namespace object containing the command-line arguments.
"""
if not _validate_endpoint(args.category, args.endpoint):
_print_endpoint_error(args.category, args.endpoint)
_warn_no_api_key(args.api_key)
all_results = []
skip = args.skip
limit = args.limit
if args.all_results:
max_records = ALL_RESULTS_SAFETY_CAP
limit = min(limit, 1000) if limit else 1000
max_pages = max_records // limit + 1
print(
f"Fetching all results (safety cap: {max_records} records)...",
file=sys.stderr,
)
else:
max_pages = args.max_pages
max_records = max_pages * limit
for page in range(max_pages):
url = _build_url(
category=args.category,
endpoint=args.endpoint,
search=args.search,
sort=args.sort,
limit=limit,
skip=skip,
api_key=args.api_key,
)
result = _fetch(url)
if result.get("status") == "error" or "error" in result:
print(json.dumps(result, indent=2), file=sys.stderr)
break
results = result.get("results", [])
if not results:
break
all_results.extend(results)
meta = result.get("meta", {}).get("results", {})
total = meta.get("total", 0)
print(
f"Page {page + 1}: fetched {len(results)} records "
f"({len(all_results)}/{total} total)",
file=sys.stderr,
)
skip += limit
if skip >= total or len(all_results) >= max_records:
break
output_data = {
"status": "success",
"results_count": len(all_results),
"results": all_results,
}
_write_output(output_data, args.output)
_print_json({
"status": "success",
"message": f"Downloaded {len(all_results)} records to {args.output}",
})
def parse_args() -> argparse.Namespace:
"""Parses command-line arguments for the openFDA query script.
Defines subparsers for 'search', 'count', and 'download' commands,
along with common arguments like category, endpoint, search, sort,
limit, skip, api_key, and output.
Returns:
argparse.Namespace: The parsed command-line arguments.
"""
parser = argparse.ArgumentParser(
description="Query the openFDA API (all 28 endpoints)"
)
subparsers = parser.add_subparsers(dest="command", required=True)
common = argparse.ArgumentParser(add_help=False)
common.add_argument(
"--category",
type=str,
required=True,
choices=sorted(VALID_ENDPOINTS.keys()),
help="API category (e.g. drug, device, food)",
)
common.add_argument(
"--endpoint",
type=str,
required=True,
help="API endpoint within the category (e.g. event, label, 510k)",
)
common.add_argument(
"--search",
type=str,
default=None,
help="Search query (e.g. 'patient.drug.medicinalproduct:aspirin')",
)
common.add_argument(
"--sort",
type=str,
default=None,
help="Sort field:order (e.g. 'receivedate:desc')",
)
common.add_argument(
"--limit",
type=int,
default=10,
help="Max results per request (default 10, max 1000)",
)
common.add_argument(
"--skip",
type=int,
default=0,
help="Pagination offset (default 0)",
)
common.add_argument(
"--api_key",
type=str,
default=None,
help="API key (also reads FDA_API_KEY env var)",
)
common.add_argument(
"--output",
type=str,
required=True,
help="Output file path for results JSON (required)",
)
subparsers.add_parser(
"search",
parents=[common],
help="Search an endpoint and save JSON results to --output",
)
count_parser = subparsers.add_parser(
"count",
parents=[common],
help="Count unique field values and save to --output",
)
count_parser.add_argument(
"--count_field",
type=str,
required=True,
help="Field to count on (e.g. 'patient.reaction.reactionmeddrapt.exact')",
)
count_parser.add_argument(
"--summary",
type=int,
default=None,
metavar="N",
help="Return only the top N most frequent terms (e.g. --summary 10)",
)
download_parser = subparsers.add_parser(
"download",
parents=[common],
help="Download multiple pages of results to --output",
)
download_parser.add_argument(
"--max_pages",
type=int,
default=10,
help="Max pages to fetch (default 10)",
)
download_parser.add_argument(
"--all_results",
action="store_true",
default=False,
help=(
"Fetch all matching results (auto-paginate). "
f"Safety cap: {ALL_RESULTS_SAFETY_CAP} records."
),
)
return parser.parse_args()
def main():
dotenv.load_dotenv(os.path.expanduser("~/.env"))
main_args = parse_args()
if main_args.command == "search":
cmd_search(main_args)
elif main_args.command == "count":
cmd_count(main_args)
elif main_args.command == "download":
cmd_download(main_args)
if __name__ == "__main__":
main()
Related skills
How it compares
Use openfda-database for FDA public regulatory datasets; use general REST skills when querying non-FDA scientific APIs.
FAQ
What are openFDA rate limits?
openfda-database documents 240 requests per minute for all callers. Without an API key, openFDA caps at 1,000 requests per day per IP. With FDA_API_KEY, the daily limit rises to 120,000 requests per key.
How do you authenticate openFDA requests?
openfda-database passes authentication via the --api_key flag or FDA_API_KEY environment variable. Automated agents should set a key before multi-query workflows to avoid exhausting the 1,000 daily unauthenticated cap.
Is Openfda Database safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.