Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query FDA drug labels (DailyMed) via REST API. Search structured product labels (SPLs) by name, NDC, set ID, or RxCUI; get indications, dosage, warnings, adverse reactions, packaging. No auth. For adverse events use fda-database; for DDIs use ddinter-database.
.claude/skills/jaechang-hits-dailymed-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 1299% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 286% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 202% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 261% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 202% | 0% |
DailyMed is the National Library of Medicine's official repository of FDA-approved drug labeling information, containing 140,000+ structured product labels (SPLs) for prescription drugs, OTC medications, biologics, and vaccines. The REST API (v2) provides structured JSON/XML access to the full label content including indications, dosage, warnings, contraindications, adverse reactions, and packaging data — with no authentication required.
fda-database instead; DailyMed contains label text, not post-market safety signalsddinter-database; DailyMed label text is unstructured for interactionsrequests, pandas, matplotlibtime.sleep(0.3) in batch loopsbashpip install requests pandas matplotlib
pythonimport requests BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" # Search drug labels by name r = requests.get(f"{BASE}/spls.json", params={"drug_name": "metformin", "pagesize": 5}) r.raise_for_status() data = r.json() print(f"Total labels found: {data['metadata']['total_elements']}") for spl in data["data"][:3]: print(f" {spl['title']!r:60s} setid={spl['setid']}")
Search for structured product labels (SPLs) using drug name. Returns paginated list of matching labels with set IDs.
pythonimport requests import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def search_spls(drug_name, pagesize=20, page=1): """Search DailyMed SPLs by drug name. Returns list of label summaries.""" r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": pagesize, "page": page}, timeout=15) r.raise_for_status() return r.json() result = search_spls("atorvastatin", pagesize=10) meta = result["metadata"] print(f"Search: 'atorvastatin' → {meta['total_elements']} labels across {meta['total_pages']} pages") df = pd.DataFrame(result["data"]) print(df[["setid", "title", "published_date"]].to_string(index=False)) # setid title published_date # 8f6c7c7c-... ATORVASTATIN CALCIUM tablet 2024-03-15 # a4b7d3e1-... ATORVASTATIN CALCIUM tablet, film coated 2023-11-20
Fetch the complete structured product label for a specific drug using its set ID. Returns all label sections including indications, warnings, dosage, and adverse reactions.
pythonimport requests BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def get_spl(setid): """Retrieve full SPL document by set ID. Returns label metadata and XML/JSON.""" r = requests.get(f"{BASE}/spls/{setid}.json", timeout=20) r.raise_for_status() return r.json() # Use a known set ID from search results setid = "8f6c7c7c-1f7f-4f1a-af86-8b2eef2a8b2c" # example atorvastatin label label = get_spl(setid) data = label["data"] print(f"Title: {data.get('title')}") print(f"Set ID: {data.get('setid')}") print(f"Published: {data.get('published_date')}") print(f"Version: {data.get('version')}") # Access structured sections if "sections" in data: sections = data["sections"] print(f"\nLabel sections ({len(sections)} total):") for sec in sections[:5]: print(f" [{sec.get('loinc_code', 'N/A')}] {sec.get('title', 'Untitled')}")
Look up drug labels by National Drug Code (NDC) — useful when you have a product barcode or dispensing record.
pythonimport requests BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def search_by_ndc(ndc_code): """Find SPL by NDC code (formatted as XXXXX-XXXX-XX or without dashes).""" r = requests.get(f"{BASE}/spls.json", params={"ndc": ndc_code}, timeout=15) r.raise_for_status() return r.json() # NDC for Lipitor 10mg (atorvastatin) result = search_by_ndc("0071-0155-23") if result["data"]: spl = result["data"][0] print(f"Drug: {spl['title']}") print(f"Set ID: {spl['setid']}") print(f"Published: {spl['published_date']}") else: print("No label found for this NDC")
Get detailed packaging data (NDC codes, package types, quantities) for a specific drug label by set ID.
pythonimport requests import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def get_packaging(setid): """Retrieve packaging information for a label (NDC codes, dosage forms, quantities).""" r = requests.get(f"{BASE}/spls/{setid}/packaging.json", timeout=15) r.raise_for_status() return r.json() setid = "8f6c7c7c-1f7f-4f1a-af86-8b2eef2a8b2c" # example setid pkg = get_packaging(setid) if pkg["data"]: packages = pkg["data"] print(f"Packaging variants: {len(packages)}") df = pd.DataFrame(packages) # Common fields: ndc, dosage_form, route, marketing_status for col in ["ndc", "dosage_form", "route", "marketing_status"]: if col in df.columns: print(f"\n{col.upper()}:") print(df[col].value_counts().head(5))
Retrieve drug labels using RxNorm Concept Unique Identifier (RxCUI) for integration with RxNorm-based clinical systems.
pythonimport requests import time BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def get_spls_by_rxcui(rxcui): """Get all SPLs associated with an RxCUI. Returns list of set IDs and titles.""" r = requests.get(f"{BASE}/rxcuis/{rxcui}/spls.json", timeout=15) r.raise_for_status() return r.json() # RxCUI for atorvastatin: 83367 rxcui = "83367" result = get_spls_by_rxcui(rxcui) print(f"SPLs for RxCUI {rxcui} (atorvastatin):") for spl in result["data"][:5]: print(f" {spl['setid']} | {spl['title'][:70]}") print(f" Total: {result['metadata']['total_elements']} labels")
List all standardized drug names in DailyMed — useful for name normalization and autocomplete in drug lookup pipelines.
pythonimport requests BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def search_drug_names(query, pagesize=20): """Search the DailyMed drug name index for name normalization.""" r = requests.get(f"{BASE}/drugnames.json", params={"drug_name": query, "pagesize": pagesize}, timeout=15) r.raise_for_status() return r.json() result = search_drug_names("metformin") print(f"Drug name matches for 'metformin': {result['metadata']['total_elements']}") for entry in result["data"][:8]: print(f" {entry['drug_name']}") # METFORMIN HYDROCHLORIDE # METFORMIN HYDROCHLORIDE AND SITAGLIPTIN PHOSPHATE # METFORMIN HYDROCHLORIDE AND SAXAGLIPTIN
Extract specific label sections (e.g., indications, warnings) from multiple drug labels for comparative analysis.
pythonimport requests import time import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" # LOINC codes for common SPL sections SECTION_LOINC = { "34067-9": "Indications and Usage", "34068-7": "Dosage and Administration", "34071-1": "Warnings", "34084-4": "Adverse Reactions", "34070-3": "Contraindications", "43685-7": "Warnings and Precautions", } def get_label_sections(setid): """Extract structured sections from an SPL by set ID.""" r = requests.get(f"{BASE}/spls/{setid}.json", timeout=20) r.raise_for_status() data = r.json()["data"] sections = {} for sec in data.get("sections", []): loinc = sec.get("loinc_code") if loinc in SECTION_LOINC: sections[SECTION_LOINC[loinc]] = sec.get("text", "") return sections # Get indications for two statin labels statins = [ ("Atorvastatin", "8f6c7c7c-1f7f-4f1a-af86-8b2eef2a8b2c"), ("Rosuvastatin", "a3b2c4d5-1234-5678-abcd-ef0123456789"), # example ] records = [] for drug_name, setid in statins: try: sections = get_label_sections(setid) records.append({"drug": drug_name, "indications_length": len(sections.get("Indications and Usage", ""))}) except Exception as e: print(f"Warning: {drug_name} failed — {e}") time.sleep(0.3) df = pd.DataFrame(records) print(df.to_string(index=False))
An SPL is the official FDA-approved drug label in XML format, structured using Health Level 7 (HL7) Clinical Document Architecture (CDA). Each unique drug product label has a globally unique set ID (UUID format). Multiple versions of the same label share the same set ID but have different version numbers. Always use the most recent published version for current prescribing information.
DailyMed SPLs use standardized LOINC codes to identify label sections, enabling consistent programmatic extraction across all labels:
| LOINC Code | Section Name | |------------|-------------| | 34067-9 | Indications and Usage | | 34068-7 | Dosage and Administration | | 34070-3 | Contraindications | | 34071-1 | Warnings | | 43685-7 | Warnings and Precautions | | 34084-4 | Adverse Reactions | | 34073-7 | Drug Interactions | | 34076-0 | Patient Counseling Information | | 42229-5 | Mechanism of Action | | 34069-5 | How Supplied/Storage |
National Drug Codes (NDCs) identify drug products with a three-segment numeric format: labeler-product-package (e.g., 0071-0155-23). NDCs can also appear without dashes in some systems. DailyMed accepts both formats in API queries.
Goal: Retrieve and compare label content for all formulations of an active ingredient — useful for generic vs. brand name comparison.
pythonimport requests import time import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def search_spls(drug_name, pagesize=50): r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": pagesize}, timeout=15) r.raise_for_status() return r.json() def get_packaging(setid): r = requests.get(f"{BASE}/spls/{setid}/packaging.json", timeout=15) r.raise_for_status() return r.json() # Search for all metformin labels result = search_spls("metformin", pagesize=20) labels = result["data"] print(f"Found {len(labels)} metformin labels") rows = [] for label in labels[:10]: # limit for demo setid = label["setid"] try: pkg = get_packaging(setid) for item in pkg["data"][:2]: rows.append({ "title": label["title"][:60], "setid": setid, "ndc": item.get("ndc"), "dosage_form": item.get("dosage_form"), "route": item.get("route"), "marketing_status": item.get("marketing_status"), "published_date": label.get("published_date"), }) except Exception as e: print(f"Skipping {setid}: {e}") time.sleep(0.3) df = pd.DataFrame(rows) print(f"\nFormulations with packaging data: {len(df)}") print(df[["title", "dosage_form", "route", "marketing_status"]].drop_duplicates().to_string(index=False)) df.to_csv("metformin_formulations.csv", index=False) print("\nSaved: metformin_formulations.csv")
Goal: Systematically extract structured warning and adverse reaction text from a set of drug labels for pharmacovigilance research.
pythonimport requests import time import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" TARGET_SECTIONS = { "34071-1": "Warnings", "43685-7": "Warnings_and_Precautions", "34084-4": "Adverse_Reactions", "34070-3": "Contraindications", } def search_and_get_sections(drug_name, max_labels=5): """Search for labels and extract safety-relevant sections.""" search_r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": max_labels}, timeout=15) search_r.raise_for_status() labels = search_r.json()["data"][:max_labels] records = [] for label in labels: setid = label["setid"] try: label_r = requests.get(f"{BASE}/spls/{setid}.json", timeout=20) label_r.raise_for_status() spl_data = label_r.json()["data"] row = {"drug_name": drug_name, "title": label["title"][:80], "setid": setid} for loinc, col in TARGET_SECTIONS.items(): text = "" for sec in spl_data.get("sections", []): if sec.get("loinc_code") == loinc: text = sec.get("text", "")[:500] # first 500 chars break row[col] = text records.append(row) except Exception as e: print(f" Skipping {setid}: {e}") time.sleep(0.3) return pd.DataFrame(records) # Compare safety sections across ACE inhibitor labels drugs = ["lisinopril", "enalapril"] all_records = [] for drug in drugs: print(f"Processing: {drug}") df = search_and_get_sections(drug, max_labels=3) all_records.append(df) combined = pd.concat(all_records, ignore_index=True) print(f"\nExtracted safety sections: {len(combined)} labels") print(combined[["drug_name", "title"]].to_string(index=False)) combined.to_csv("ace_inhibitor_safety_sections.csv", index=False) print("Saved: ace_inhibitor_safety_sections.csv")
Goal: Query multiple drugs in a class, count their labeled formulations, and visualize the distribution.
pythonimport requests import time import pandas as pd import matplotlib.pyplot as plt BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def count_labels(drug_name): """Return total SPL count for a drug name.""" r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": 1}, timeout=15) r.raise_for_status() return r.json()["metadata"]["total_elements"] # Count labels for common statins statins = { "atorvastatin": "Atorvastatin", "rosuvastatin": "Rosuvastatin", "simvastatin": "Simvastatin", "pravastatin": "Pravastatin", "lovastatin": "Lovastatin", "fluvastatin": "Fluvastatin", "pitavastatin": "Pitavastatin", } counts = {} for query, label in statins.items(): try: counts[label] = count_labels(query) print(f" {label}: {counts[label]} labels") except Exception as e: print(f" {label}: error — {e}") time.sleep(0.3) # Visualization df = pd.DataFrame(list(counts.items()), columns=["Drug", "Label_Count"]) df = df.sort_values("Label_Count", ascending=True) fig, ax = plt.subplots(figsize=(9, 5)) bars = ax.barh(df["Drug"], df["Label_Count"], color="#2196F3", edgecolor="white") ax.bar_label(bars, fmt="%d", padding=4, fontsize=9) ax.set_xlabel("Number of DailyMed SPL Entries") ax.set_title("DailyMed: FDA Drug Label Count by Statin\n(includes brand + generic formulations)") ax.set_xlim(0, df["Label_Count"].max() * 1.15) plt.tight_layout() plt.savefig("statin_label_counts.png", dpi=150, bbox_inches="tight") print(f"Saved: statin_label_counts.png (total labels: {df['Label_Count'].sum()})")
| Parameter | Endpoint | Default | Range / Options | Effect | |-----------|----------|---------|-----------------|--------| | drug_name | /spls, /drugnames | — | any drug name string | Filter labels by drug name (partial match supported) | | ndc | /spls | — | NDC code string | Filter labels by National Drug Code | | pagesize | /spls, /drugnames | 20 | 1–100 | Results per page; max 100 | | page | /spls, /drugnames | 1 | positive integer | Page number for pagination | | setid | /spls/{setid}, /spls/{setid}/packaging | — | UUID string | Unique label identifier; required for direct access | | rxcui | /rxcuis/{rxcui}/spls | — | RxNorm concept ID | Look up labels by RxCUI for clinical system integration | | format | any endpoint | json | json, xml | Response format; JSON is default and preferred |
/spls search to get set IDs, then fetch full labels by set ID. Do not construct set IDs from drug names — they are UUID identifiers assigned by FDA.time.sleep(0.3) in batch loops: DailyMed has no published rate limits but is public NIH infrastructure. Polite delays prevent throttling.python import time for drug in drug_list: result = search_spls(drug) time.sleep(0.3) # 200 requests/minute safe upper bound
pagesize=20 may miss formulations. Use metadata["total_elements"] to detect if pagination is needed:python meta = result["metadata"] total_pages = meta["total_pages"] if total_pages > 1: for p in range(2, total_pages + 1): result = search_spls(drug_name, page=p)
/rxcuis/{rxcui}/spls) for standardized, unambiguous drug identification.When to use: Quickly determine whether a drug carries the FDA's strongest warning level.
pythonimport requests BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" BLACK_BOX_LOINC = "34066-1" def has_black_box_warning(setid): """Return True and warning text if drug label has a black box (boxed) warning.""" r = requests.get(f"{BASE}/spls/{setid}.json", timeout=20) r.raise_for_status() sections = r.json()["data"].get("sections", []) for sec in sections: if sec.get("loinc_code") == BLACK_BOX_LOINC: return True, sec.get("text", "")[:300] return False, "" # Example: check warfarin label setid = "8b7c3d4e-5678-90ab-cdef-1234567890ab" # example warfarin setid has_warning, text = has_black_box_warning(setid) print(f"Black box warning: {has_warning}") if has_warning: print(f"Warning text (first 300 chars): {text}")
When to use: Retrieve all matching labels for a drug when total count exceeds one page.
pythonimport requests import time BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def search_all_spls(drug_name, pagesize=100, delay=0.3): """Retrieve all SPLs for a drug name across multiple pages.""" all_labels = [] page = 1 while True: r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": pagesize, "page": page}, timeout=15) r.raise_for_status() data = r.json() all_labels.extend(data["data"]) if page >= data["metadata"]["total_pages"]: break page += 1 time.sleep(delay) return all_labels labels = search_all_spls("ibuprofen") print(f"Total ibuprofen labels: {len(labels)}")
When to use: Build a flat reference table of drug labels for offline analysis.
pythonimport requests import pandas as pd BASE = "https://dailymed.nlm.nih.gov/dailymed/services/v2" def export_drug_labels(drug_name, pagesize=50): """Export label summaries for a drug name to DataFrame.""" r = requests.get(f"{BASE}/spls.json", params={"drug_name": drug_name, "pagesize": pagesize}, timeout=15) r.raise_for_status() data = r.json() df = pd.DataFrame(data["data"]) df["drug_query"] = drug_name return df, data["metadata"]["total_elements"] df, total = export_drug_labels("amoxicillin") print(f"Retrieved {len(df)} of {total} amoxicillin labels") df[["setid", "title", "published_date"]].to_csv("amoxicillin_labels.csv", index=False) print(f"Saved: amoxicillin_labels.csv (columns: {list(df.columns[:5])})")
| Problem | Cause | Solution | |---------|-------|----------| | 404 Not Found on /spls/{setid} | Invalid or retired set ID | Re-search by drug name to get current set IDs | | Empty data list from search | Drug name not matching any labels | Try shorter name (e.g., "metformin" not "metformin HCl tablets"); check spelling | | Missing sections in SPL JSON | Older labels may not have all LOINC-coded sections | Check len(sections) before iterating; fall back to XML format for legacy labels | | ConnectionError / timeout | DailyMed server overload | Retry with exponential backoff; increase timeout=30 | | Pagination returning duplicates | Page boundary race condition | De-duplicate results by setid after all pages collected | | Packaging endpoint returns empty | Label exists but has no packaging records | Some biologics and vaccines lack packaging data; use label data directly | | RxCUI lookup returns no results | RxCUI not mapped in DailyMed | Verify RxCUI via RxNorm API before querying; some experimental drugs are not mapped |
fda-database — openFDA for adverse event reports (FAERS), drug recalls, and enforcement actionsddinter-database — drug-drug interaction severity and mechanisms from DDInterdrugbank-database-access — comprehensive drug information including targets, pathways, and chemical propertiesclinicaltrials-database-search — ClinicalTrials.gov for clinical trial data on drugs| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 14,699 | 6,900 | -53% | 1 | 1 | 0% | 2,711 | 8,437 | +211% | 0 | 0 | — |
case-05 | pass→pass | 16,703 | 6,744 | -60% | 1 | 1 | 0% | 2,878 | 8,403 | +192% | 0 | 0 | — |
case-01 | fail→fail | 11,490 | 21,489 | +87% | 1 | 1 | 0% | 648 | 7,760 | +1098% | 0 | 0 | — |
case-02 | fail→fail | 9,331 | 10,622 | +14% | 1 | 1 | 0% | 499 | 7,661 | +1435% | 0 | 0 | — |
case-03 | fail→pass | 11,801 | 12,234 | +4% | 1 | 1 | 0% | 641 | 8,970 | +1299% | 0 | 0 | — |
case-06 | fail→pass | 13,327 | 10,401 | -22% | 1 | 1 | 0% | 2,347 | 9,067 | +286% | 0 | 0 | — |
case-07 | fail→pass | 15,613 | 8,176 | -48% | 1 | 1 | 0% | 2,830 | 8,560 | +202% | 0 | 0 | — |
case-08 | fail→pass | 11,273 | 5,143 | -54% | 1 | 1 | 0% | 2,230 | 8,043 | +261% | 0 | 0 | — |
case-09 | pass→pass | 12,195 | 6,561 | -46% | 1 | 1 | 0% | 2,150 | 8,283 | +285% | 0 | 0 | — |
case-10 | pass→pass | 13,171 | 9,270 | -30% | 1 | 1 | 0% | 2,512 | 8,731 | +248% | 0 | 0 | — |
case-11 | pass→pass | 16,692 | 14,618 | -12% | 1 | 1 | 0% | 3,154 | 9,898 | +214% | 0 | 0 | — |
case-12 | fail→pass | 17,266 | 10,308 | -40% | 1 | 1 | 0% | 2,951 | 8,912 | +202% | 0 | 0 | — |
case-13 | pass→pass | 13,636 | 11,630 | -15% | 1 | 1 | 0% | 2,572 | 9,350 | +264% | 0 | 0 | — |
case-14 | pass→pass | 14,873 | 8,019 | -46% | 1 | 1 | 0% | 2,854 | 8,725 | +206% | 0 | 0 | — |
case-15 | pass→pass | 14,841 | 4,757 | -68% | 1 | 1 | 0% | 2,621 | 8,022 | +206% | 0 | 0 | — |
case-20 | pass→pass | 3,405 | 3,410 | +0% | 1 | 1 | 0% | 593 | 7,624 | +1186% | 0 | 0 | — |
case-16 | pass→pass | 9,337 | 4,055 | -57% | 1 | 1 | 0% | 1,542 | 7,841 | +408% | 0 | 0 | — |
case-17 | pass→pass | 6,921 | 3,636 | -47% | 1 | 1 | 0% | 1,249 | 7,720 | +518% | 0 | 0 | — |
case-18 | fail→pass | 5,427 | 4,980 | -8% | 1 | 1 | 0% | 934 | 7,974 | +754% | 0 | 0 | — |
case-19 | pass→pass | 13,148 | 8,899 | -32% | 1 | 1 | 0% | 2,179 | 8,544 | +292% | 0 | 0 | — |
case-21 | pass→pass | 11,353 | 7,336 | -35% | 1 | 1 | 0% | 2,310 | 8,478 | +267% | 0 | 0 | — |
case-22 | pass→pass | 5,080 | 3,433 | -32% | 1 | 1 | 0% | 785 | 7,757 | +888% | 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. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 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 +27 percentage points is the difference between those two pass rates over the 19 comparable cases.
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.