MMedFortis Free Gap Scan™
Technical guide

openFDA Device Event API: How to Query FDA MAUDE Programmatically

By Kieran Redington, Founder of MedFortis Updated 24 July 2026 ~9 min read

The openFDA device/event endpoint is the programmatic door to FDA MAUDE — the adverse-event database — and it's how any automated monitoring is built. But its query language, counting rules and pagination limits trip up almost everyone the first time. This guide covers the endpoint, the search syntax, how to count and paginate, rate limits and API keys, and the specific gotchas that produce empty results or errors. Every example here is a real, working query.

What openFDA is (and the device endpoints)

openFDA is the FDA's public API programme. It exposes several device datasets as JSON over HTTPS, no authentication required to start. The one that maps to MAUDE is device/event; the others are useful for cross-referencing:

EndpointDataset
/device/event.jsonMAUDE adverse-event reports (MDRs)
/device/recall.jsonDevice recalls
/device/enforcement.jsonRecall enforcement reports
/device/510k.json510(k) clearances
/device/classification.jsonProduct-code classifications
/device/udi.jsonGUDID / UDI device records
/device/registrationlisting.jsonEstablishment registrations & listings

For the rest of this guide we focus on device/event, but the syntax below is identical across all of them.

Anatomy of a request

Every request is a single GET URL. The base is:

https://api.fda.gov/device/event.json

You shape it with four query parameters:

A minimal real query — one manufacturer's reports, first record only:

https://api.fda.gov/device/event.json?search=
device.manufacturer_d_name:"ACME+MEDICAL"&limit=1

The response has two parts: a meta object (including meta.results.total — the full match count, even when you only pulled one record) and a results array. Reading meta.results.total first is how you size a query before paginating it.

Search syntax

The search value is a Lucene-style expression. The building blocks:

GoalSyntax
Field equals termevent_type:Malfunction
Exact phrase (spaces)device.generic_name:"INFUSION+PUMP"
AND / ORa:x+AND+b:y · a:x+OR+a:z
Groupingevent_type:(Death+OR+Injury)
Date / numeric rangedate_received:[20250101+TO+20251231]
Field exists_exists_:device.device_report_product_code
Wildcarddevice.brand_name:acme*

Two rules save a lot of confusion: spaces in a phrase become + (URL-encoding), and dates use YYYYMMDD with no separators. A query combining a manufacturer, an event type and a date window:

https://api.fda.gov/device/event.json?search=
device.manufacturer_d_name:"ACME+MEDICAL"
+AND+event_type:(Death+OR+Injury)
+AND+date_received:[20250101+TO+20251231]&limit=100

The fields that matter

MAUDE records are deeply nested. The fields you'll actually query for device monitoring:

FieldWhat it is
device.brand_nameTrade name on the report
device.generic_nameGeneric/common device name
device.manufacturer_d_nameManufacturer as reported
device.device_report_product_codeThree-letter FDA product code — the most reliable axis
event_typeDeath · Injury · Malfunction · (No answer/Other)
date_receivedDate FDA received the report
date_of_eventDate of the incident (often blank)
report_numberReport identifier (used to de-duplicate)
product_problemsCoded problem descriptions
device.openfda.*Harmonised fields: device_class, regulation_number, medical_specialty_description

The device.openfda object is worth knowing — it enriches each record with the harmonised classification data, so you can filter by device class or medical specialty without a second lookup.

Counting & aggregation (the .exact rule)

The count parameter turns the API into an analytics engine — instead of records, you get the frequency of each value of a field. This is how you get a breakdown by event type, or the top product codes for a manufacturer, in a single call. But there's a rule that catches nearly everyone:

Use .exact when counting text fields. count=event_type returns a SERVER_ERROR ("Text fields are not optimised for … aggregations"). You must write count=event_type.exact. The .exact suffix counts the whole field value verbatim rather than tokenised words — which is also what you want, or "infusion pump" would be counted as "infusion" + "pump" separately.

Working count queries:

# Breakdown of a manufacturer's reports by event type
...?search=device.manufacturer_d_name:Medtronic&count=event_type.exact

# Top product codes a manufacturer is reported under
...?search=device.manufacturer_d_name:Medtronic
   &count=device.device_report_product_code.exact

# Device-class split (openfda numeric field — no .exact needed)
...?search=device.manufacturer_d_name:Medtronic&count=device.openfda.device_class

Note the last one: device.openfda.device_class is not a free-text field, so it counts without .exact. Rule of thumb: text → .exact; keyword/numeric → plain.

Pagination & limits

Two hard ceilings shape how you pull large result sets:

For result sets larger than that — and MAUDE queries routinely are; a broad event_type:Malfunction search returns millions — you have two options:

  1. search_after — openFDA's cursor pagination. Each response's meta gives you a token to pass as search_after on the next call, walking past the 25k wall.
  2. Date-window chunking — split the query into monthly or quarterly date_received ranges, each small enough to page normally. This is often simpler for scheduled monitoring, since you're pulling "what's new since last run" anyway.
Practical pattern: read meta.results.total first. Under ~26,000 → page with skip. Over it → chunk by date_received, or use search_after.

Rate limits & API keys

Without a key, openFDA allows 1,000 requests/day and 240/minute per IP. A free API key (register at open.fda.gov) raises the daily limit to 120,000 requests/day, same 240/minute. For anything scheduled, get the key — then append it:

...?search=event_type:Malfunction&limit=1000&api_key=YOUR_KEY

Keep the key server-side (it's tied to your rate budget, and a key in front-end code is a key anyone can spend). Handle 429 Too Many Requests with backoff, and cache aggressively — the data only refreshes periodically, so re-pulling the same window minutes later is wasted budget.

Gotchas that waste an afternoon

These are API mechanics. The deeper interpretive limits of MAUDE — that it can't prove causation or support rate calculations — are covered in our guide to searching and monitoring the MAUDE database, which this article is the technical companion to.

A worked monitoring query

Putting it together — a weekly job watching a product code for new death/injury reports would, each run: read the total for the new window, then page it.

# 1. How many new serious reports this week for product code DZE?
https://api.fda.gov/device/event.json?search=
device.device_report_product_code:DZE
+AND+event_type:(Death+OR+Injury)
+AND+date_received:[20260717+TO+20260724]
&limit=1&api_key=YOUR_KEY
# -> read meta.results.total

# 2. Pull them (page with skip if total > 1000)
...&limit=1000&skip=0
...&limit=1000&skip=1000   # next page, etc.

De-duplicate the results by report_number, triage by event_type, read the narrative for context, and log what you found. Run it on a schedule keyed to date_received and you have a monitoring loop.

That's one product code, one endpoint, one regulator. The engineering reality of doing it for a whole portfolio — dozens of codes and brand-name variants, de-duplicated, cross-referenced against your devices, and reconciled with the other five regulators who don't offer anything as clean as openFDA — is where a hand-rolled script stops scaling.

The openFDA API is one source of six

MedFortis runs this pipeline continuously — openFDA plus MHRA, Health Canada, EU Safety Gate, Swissmedic and EUDAMED — cross-referenced to your device lines, de-duplicated and logged. See it on your own devices with a free Gap Scan™.

Request your free Gap Scan →

Key takeaways

  • MAUDE lives at api.fda.gov/device/event.json; the same syntax covers recalls, 510(k)s, classification and UDI.
  • Read meta.results.total before paginating; limit caps at 1000, skip at 25000 — beyond that, use search_after or chunk by date_received.
  • Count text fields with .exact (keyword/numeric fields don't need it) — the top source of errors.
  • Filter on date_received not date_of_event, de-duplicate by report_number, and get a free API key for scheduled use (120k/day).
This article is provided for general information only and is not legal or regulatory advice. openFDA API details (endpoints, parameters, limits) reflect the openFDA documentation at the time of writing and may change; always confirm against the current documentation at open.fda.gov. openFDA data is provided by the FDA as unvalidated and must not be used to make medical-care decisions.