Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query, search, and download data from the openFDA API for drugs, devices, foods, tobacco, cosmetics, animal and veterinary products, substances, and transparency data. Use for FDA adverse events, recalls, labeling, approvals, shortages, 510(k) clearances, NDC lookups, and any FDA safety or regulatory data query across all 28 API endpoints.
.claude/skills/mkurman-openfda-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 236% | 0% |
| case-19 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-03 | ✓→✗ | ▼ Worse | 98% | 0% |
| case-04 | ✓→✗ | ▼ Worse | 17% | 0% |
| case-05 | ✓→✗ | ▼ Worse | 169% | 0% |
uv: Read the uv skill and follow its Setup instructions to ensureuv is installed and on PATH.
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.
.env file: Make sure the .env file exists in your home directory.Create one if it does not exist.
FDA_API_KEY (optional but recommended): Raises the daily request limitfrom 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:
bash 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.
database rather than accessing the database directly. The scripts automatically enforce the required rate limit gracefully.
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 add FDA_API_KEY to the .env file. The script will > emit a warning to stderr if no API key is detected.
--output: All subcommands require --output <file> towrite results to a file. This prevents large output becoming overwhelming. Use jq or code to read the output file.
output.
Single script for all operations:
bashuv run scripts/openfda_query.py {search,count,download} --output <file> [options]
Search any of the 28 endpoints and save JSON results to a file.
bashuv run scripts/openfda_query.py search \ --category drug --endpoint event \ --search "patient.drug.medicinalproduct:aspirin" \ --limit 5 --output /tmp/fda_results.json
Stdout prints a compact summary:
json{"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 reads FDA_API_KEY env var).Count unique values of a field within matching results.
bashuv 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.json
Stdout prints a summary with the top 5 terms. Full data is in the output file.
Additional options:
--count_field: Field to count (append .exact for whole-phrase counting).--summary N: Return only the top N most frequent terms. Use this to avoidflooding the context with hundreds of infrequent terms.
Download multiple pages of results to a file.
bashuv run scripts/openfda_query.py download \ --category drug --endpoint event \ --search "patient.drug.medicinalproduct:aspirin" \ --limit 100 --max_pages 5 \ --output /tmp/aspirin_events.json
Additional 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.
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.
bash# 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.json
> Note: Many brand names in the FDA database include variant suffixes (e.g., > "TYLENOL Extra Strength" rather than just "TYLENOL"). If an .exact search > returns 0 results, try without .exact to see the available brand name > variants, 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.
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.
bash# 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.
Category to endpoint mapping:
drug: event, label, ndc, enforcement, drugsfda, shortagesdevice: 510k, classification, enforcement, event, pma, recall,registrationlisting, udi, covid19serology
food: enforcement, eventtobacco: problem, researchpreventionads, researchdigitalads,researchsmokefree
other: historicaldocument, nsde, substance, uniianimalandveterinary: eventcosmetic: eventtransparency: crlreferences/api_endpoints.md for field names, search syntax, date ranges, and boolean operators.
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.
search with --output. Read the output file.count with --summary 10 --output to summarize field distributions.download (with --all_results for exhaustive pulls) to fetch largerdatasets.
(e.g. EMBL-EBI OLS skill with the HP or NCIT ontology) to look up the term.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | fail→fail | 11,624 | 7,146 | -39% | 1 | 1 | 0% | 753 | 2,710 | +260% | 0 | 0 | — |
case-25 | fail→fail | 31,519 | 8,089 | -74% | 1 | 1 | 0% | 6,279 | 2,822 | -55% | 0 | 0 | — |
case-02 | fail→fail | 15,782 | 8,985 | -43% | 1 | 1 | 0% | 529 | 2,780 | +426% | 0 | 0 | — |
case-01 | fail→fail | 24,992 | 7,587 | -70% | 1 | 1 | 0% | 3,434 | 3,021 | -12% | 0 | 0 | — |
case-03 | pass→fail | 8,985 | 8,330 | -7% | 1 | 1 | 0% | 1,458 | 2,888 | +98% | 0 | 0 | — |
case-04 | pass→fail | 14,248 | 10,103 | -29% | 1 | 1 | 0% | 2,634 | 3,084 | +17% | 0 | 0 | — |
case-05 | pass→fail | 6,256 | 8,482 | +36% | 1 | 1 | 0% | 1,076 | 2,891 | +169% | 0 | 0 | — |
case-06 | pass→pass | 4,365 | 7,030 | +61% | 1 | 1 | 0% | 795 | 3,911 | +392% | 0 | 0 | — |
case-07 | fail→fail | 8,613 | 10,027 | +16% | 1 | 1 | 0% | 1,577 | 3,207 | +103% | 0 | 0 | — |
case-08 | fail→fail | 9,863 | 6,517 | -34% | 1 | 1 | 0% | 1,679 | 2,744 | +63% | 0 | 0 | — |
case-09 | fail→fail | 19,672 | 7,925 | -60% | 1 | 1 | 0% | 2,470 | 2,833 | +15% | 0 | 0 | — |
case-10 | fail→fail | 6,489 | 7,242 | +12% | 1 | 1 | 0% | 227 | 3,060 | +1248% | 0 | 0 | — |
case-11 | fail→fail | 10,414 | 6,134 | -41% | 1 | 1 | 0% | 647 | 2,795 | +332% | 0 | 0 | — |
case-12 | fail→fail | 10,238 | 6,309 | -38% | 1 | 1 | 0% | 528 | 2,720 | +415% | 0 | 0 | — |
case-13 | fail→fail | 11,580 | 7,012 | -39% | 1 | 1 | 0% | 590 | 2,824 | +379% | 0 | 0 | — |
case-15 | fail→pass | 5,652 | 2,198 | -61% | 1 | 1 | 0% | 821 | 2,760 | +236% | 0 | 0 | — |
case-16 | fail→fail | 8,300 | 7,122 | -14% | 1 | 1 | 0% | 275 | 2,700 | +882% | 0 | 0 | — |
case-17 | fail→fail | 69,142 | 8,527 | -88% | 1 | 1 | 0% | 6,166 | 2,825 | -54% | 0 | 0 | — |
case-18 | fail→fail | 8,772 | 7,681 | -12% | 1 | 1 | 0% | 494 | 2,833 | +473% | 0 | 0 | — |
case-19 | fail→pass | 13,866 | 6,360 | -54% | 1 | 1 | 0% | 2,388 | 3,610 | +51% | 0 | 0 | — |
case-26 | fail→fail | 8,717 | 7,528 | -14% | 1 | 1 | 0% | 1,118 | 2,907 | +160% | 0 | 0 | — |
case-20 | fail→fail | 7,039 | 6,629 | -6% | 1 | 1 | 0% | 944 | 2,705 | +187% | 0 | 0 | — |
case-21 | fail→fail | 9,155 | 5,877 | -36% | 1 | 1 | 0% | 517 | 2,720 | +426% | 0 | 0 | — |
case-22 | fail→fail | 15,245 | 8,853 | -42% | 1 | 1 | 0% | 648 | 2,875 | +344% | 0 | 0 | — |
case-23 | pass→pass | 14,681 | 18,864 | +28% | 1 | 1 | 0% | 2,695 | 4,513 | +67% | 0 | 0 | — |
case-24 | fail→fail | 11,852 | 6,346 | -46% | 1 | 1 | 0% | 393 | 2,716 | +591% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted, and 4 counted toward the lift figure. The other 22 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of -4 percentage points is the difference between those two pass rates over the 4 comparable cases. 4 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.