--- name: fda-device-intel description: Query FDA medical-device databases directly with curl — no server or API key needed. Use for 510(k) clearances, predicate device research, product code classification, MAUDE adverse events, recalls (incl. Class I/II/III), warning letters, PMA approvals, establishment registrations, and UDI/GUDID brand-to-company lookup. Triggers - "find a predicate", "510k", "product code", "MAUDE", "FDA recall", "warning letter", "adverse events for ", "who makes ", "postmarket health check", "device classification". --- # FDA device intelligence via public APIs All sources are public and keyless. Anonymous openFDA limits: ~240 req/min, ~1,000 req/day per IP — fine for interactive use; add small sleeps in loops. Always cite record URLs in answers. Data is unvalidated and informational only, not regulatory advice (https://open.fda.gov/terms/). ## openFDA query syntax (critical quirks) Base: `https://api.fda.gov/device/.json` - Join terms with **literal `+AND+`**; spaces inside a value become `+`. NEVER let a URL encoder turn `+` into `%2B` — openFDA needs it literal. - Phrases: `field:%22two+words%22`. Date ranges: `field:%5B+TO+%5D`. - `search=` filters; add `count=` to aggregate instead of listing. Count-field names are inconsistent: text fields need `.exact` (`applicant.exact`, `registration.name.exact`), keyword fields must NOT have it (`registration.iso_country_code`). **Empty counts result = wrong field name, not zero data** — retry with/without `.exact`. - `sort=:desc`, `limit=` (max 100, aggregate max 1000), `skip=`. - A 404 with `NOT_FOUND` means zero matches, not a bad URL. ## Endpoints and key fields | Endpoint | Purpose | Key fields | Date field (format) | |---|---|---|---| | `510k` | Clearances | `k_number`, `product_code`, `applicant`, `device_name`, `decision_description`, `clearance_type` | `decision_date` (YYYY-MM-DD) | | `classification` | Description/code → class | `product_code`, `device_name`, `device_class`, `regulation_number`, `medical_specialty_description` | — | | `event` | MAUDE adverse events | `device.device_report_product_code`, `device.brand_name`, `device.manufacturer_d_name`, `product_problems`, `event_type`, `mdr_text[].text` | `date_received` (YYYYMMDD) | | `recall` | Recalls w/ root cause | `product_code`, `recalling_firm`, `product_description`, `reason_for_recall`, `root_cause_description` | `event_date_initiated` (YYYY-MM-DD) | | `enforcement` | Recalls w/ **severity** | `classification` ("Class I/II/III"), `recalling_firm`, `product_description`, `status` | `report_date` (YYYYMMDD) | | `pma` | Class III approvals | `pma_number`, `supplement_number`, `trade_name`, `applicant`, `decision_code`, `product_code` | `decision_date` (YYYY-MM-DD) | | `registrationlisting` | Who makes what | `products.product_code`, `registration.name`, `registration.iso_country_code`, `establishment_type`, `proprietary_name` | — | | `udi` | GUDID; brand→company | `brand_name`, `company_name`, `version_or_model_number`, `device_description`, `product_codes.code` | — | Examples: ```sh # description -> product code (then class/regulation) curl -s 'https://api.fda.gov/device/classification.json?search=device_name:bipolar+AND+device_name:laparoscopic&limit=5' # newest clearances under a code curl -s 'https://api.fda.gov/device/510k.json?search=product_code:GEI&sort=decision_date:desc&limit=10' # who files most under a code since 2020 (clearance velocity) curl -s 'https://api.fda.gov/device/510k.json?search=product_code:GEI+AND+decision_date:%5B2020-01-01+TO+2030-01-01%5D&count=applicant.exact&limit=10' # MAUDE top problem codes for a product code since 2025 (note compact dates) curl -s 'https://api.fda.gov/device/event.json?search=device.device_report_product_code:GEI+AND+date_received:%5B20250101+TO+20301231%5D&count=product_problems.exact&limit=10' # serious recalls only curl -s 'https://api.fda.gov/device/enforcement.json?search=recalling_firm:olympus+AND+classification:%22Class+I%22&sort=report_date:desc&limit=5' # resolve a brand to its legal company (entity resolution) curl -s 'https://api.fda.gov/device/udi.json?search=brand_name:ligasure&count=company_name.exact&limit=5' ``` ## Warning letters (NOT in openFDA) Undocumented fda.gov Drupal/Solr endpoint behind their search page; needs a browser User-Agent. Returns JSON `{recordsFiltered, data: [[html cells]]}` — cells are: posted date, issued date, company (with `href` to letter page), issuing office, subject. Strip HTML tags from cells. ```sh curl -s -A 'Mozilla/5.0 (X11; Linux x86_64)' \ 'https://www.fda.gov/datatables/views/ajax?view_name=warning_letter_solr_index&view_display_id=warning_letter_solr_block&view_path=/node/360089&search_api_fulltext=COMPANY&search_api_fulltext_issuing_office=Center+for+Devices+and+Radiological+Health&length=20&start=0&draw=1' ``` Omit `search_api_fulltext_issuing_office` for all centers. Matching is full-text → false positives are common; verify the company cell of each hit. Low volume only; endpoint may change without notice. ## 510(k) summary PDFs (indications for use) The indications statement lives only in the summary PDF, not the API. URL pattern from the K-number's first two digits (the year): ``` K213042 -> https://www.accessdata.fda.gov/cdrh_docs/pdf21/K213042.pdf K025432 -> .../cdrh_docs/pdf2/K025432.pdf (2002-2009: pdf2..pdf9) K99xxxx -> .../cdrh_docs/pdf/K99xxxx.pdf (pre-2002: often scanned, no text) ``` Download with `-A 'Mozilla/5.0'`, extract with `pdftotext -layout file.pdf -` (or read the PDF directly if your Read tool supports PDFs). Then find the "Indications for Use" / "Intended Use" section — **skip windows containing "substantially equivalent (for the indications" or "premarket approval application (PMA)"**: that's FDA decision-letter boilerplate preceding the real summary. The true IFU is often the 2nd or 3rd occurrence. The PDF also contains the predicate device table (predicate K-numbers) absent from the API. ## Recipe: postmarket health check (predicate/competitor due diligence) For "is this device/company safe to cite as a predicate": 1. Recalls: `recall` by `product_description:`; get total + latest. 2. Severity: `enforcement` same query — any Class I? 3. MAUDE volume last ~2y: `event` by `device.brand_name:` (count only). 4. Warning letters: WL endpoint with company name, CDRH-filtered. 5. First resolve brand→company via `udi` to avoid name collisions. Interpretation caveats to state in answers: keyword matches collide (a "Voyant" query matches an unrelated contact-lens brand — verify every hit); high MAUDE volume may just mean a big install base (interpret per-volume); MAUDE data lags months behind real time; `recall` total ≠ `enforcement` total (different views of the same actions). ## No shell? URL-fetch-only agents (browsing / web-fetch tools) If you have no shell but can fetch URLs (ChatGPT browsing, Claude web fetch, Copilot HTTP actions), most of this skill still works — every openFDA query is a plain HTTPS GET with no headers or auth required. - **Encoding (verified):** `%20` is equivalent to the literal `+` separator — `search=product_code:GEI%20AND%20decision_date:%5B2020-01-01%20TO%202030-01-01%5D` works identically. So it is safe to compose queries with spaces and let your fetch tool percent-encode them. The ONLY breaker is `%2B` (encoded plus) — if you get `SERVER_ERROR ... parse_exception`, your tool encoded `+` as `%2B`; rewrite the query using spaces instead of plus signs. - **510(k) summary PDFs:** fetch the `accessdata.fda.gov/cdrh_docs/...` URL directly — many clients parse PDFs natively. If yours returns garbage or refuses binary content, this leg needs a shell or the fda-mcp server's `get_510k_summary` tool. - **Warning letters:** the fda.gov endpoint is User-Agent-sensitive (curl's default UA passes; `python-requests` gets a 302 redirect). Try it; if you get a redirect, HTML challenge page, or non-JSON, fall back to fetching the human search page `https://www.fda.gov/inspections-compliance-enforcement-and-criminal-investigations/compliance-actions-and-activities/warning-letters` and reading its rendered table, or report that this leg needs the fda-mcp server. - Everything else (all eight openFDA endpoints, count aggregations, sorting, pagination) is fully usable as plain URLs. Same rate limits and caveats apply.