# Joined answers

URL: https://docs.healthcare.dev/joined-answers

Two routes answer "what do we know about this?" in one request:
`GET /v1/drugs/ndc/{ndc}/full` for a drug and [`GET /v1/providers/{npi}/full`](#providers) for a
provider. Both are part of the Pro and Team plans (`403` `plan-upgrade-required` on Free and
Builder) and count as one lookup, however many datasets they join. The MCP tools are
`get_drug_full` and `get_provider_full`; the SDK methods are `hdev.drugs.full()` and
`hdev.providers.full()`.

## Drugs

Pass a product NDC (`0006-3026`), a package NDC in any 10-digit layout (`0006-3026-02`) or an
11-digit NDC (`00006302602`). The datasets are looked up together:

| Component  | What it holds                                                                                                                                     | Dataset                                       |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `product`  | The FDA product with its packages                                                                                                                 | [NDC Directory](/datasets/ndc)                |
| `rxnorm`   | RxNorm concepts linked to the product's (or the package's) NDC-11s                                                                                | [RxNorm](/datasets/rxnorm)                    |
| `asp`      | J-codes the current NDC-HCPCS crosswalk assigns, each with the payment limit in effect today and the newest quarter                               | [Medicare Part B ASP](/datasets/medicare-asp) |
| `nadac`    | The current NADAC rate of the package, or of each package of a product                                                                            | NADAC (CMS)                                   |
| `label`    | FDA label status: set id, version, effective date and which sections exist (not their text)                                                       | [FDA drug labels](/datasets/drug-labels)      |
| `shortage` | Whether FDA lists a current shortage, with the shortage list entries                                                                              | FDA drug shortages                            |
| `recalls`  | Whether FDA has a recall of the NDC it hasn't terminated, with the open recalls and closed ones from the last `recall_months` months (default 12) | FDA drug recalls                              |

### Package or product

With a package NDC, every source that names packages is matched at the package level: RxNorm
links, crosswalk entries, NADAC rates, shortage entries (each is one presentation) and recalls
(the NDCs a recall names). A shortage entry or recall that names no package matches by product,
says `matched_on: "product"`, and the component's `reason` notes it. `product` is always the
whole NDC Directory product, as `/v1/drugs/ndc/{ndc}` returns it.

`hcpcs_codes` lists the J-codes the newest or current quarter's crosswalk assigns. Codes only an
earlier quarter assigned are in `asp.data.historical`, with the last quarter that listed them.

`open_recall` is true for any recall of the NDC FDA hasn't terminated, however old;
`recall_months` only limits which closed recalls are listed next to the open ones.

### Nothing is left out silently

Every component is always in the response, with a `status`:

- `available`: the dataset was consulted and `data` holds its answer. For the shortage and recall
  flags, `false` is an answer: the dataset was checked and lists nothing.
- `not_listed`: the dataset was consulted and has nothing for this NDC. `data` is `null` and
  `reason` says what that means (for example, most retail pharmacy drugs have no J-code).
- `unavailable`: the dataset isn't published yet, or its license keeps it private. `data` and
  `source` are `null` and `reason` says which.

`missing` lists every component that isn't `available`, with its reason, so even the compact view
says why something has no answer. The `flags` (`active_shortage`, `open_recall`,
`boxed_warning`) are `null`, never `false`, when the data to answer isn't available.

### Provenance per component

Each component carries its own `source`: the dataset, `data_version`, `as_of` (the date that
version takes effect), when it was published, the source URL, hashes and license terms. The
top-level `meta` combines every dataset consulted, the NDC Directory first, with each one's
provenance in `meta.sources` (see [Responses](/responses)).

### Views

`view=compact` returns the flags and key ids only: `ndc`, `product_ndc`, `ndc11`, `name`,
`rxcuis`, `hcpcs_codes`, `label_set_ids`, `flags` and `missing`. `standard` and `full` (the
default) return every component. The MCP tool defaults to compact.

```sh
curl "https://api.healthcare.dev/v1/drugs/ndc/0006-3026-02/full?view=compact" \
  -H "Authorization: Bearer $HDEV_API_KEY"
```

```json
{
  "data": {
    "ndc": "00006302602",
    "product_ndc": "0006-3026",
    "ndc11": "00006302602",
    "name": "Keytruda (pembrolizumab)",
    "rxcuis": [],
    "hcpcs_codes": ["J9271"],
    "label_set_ids": [],
    "flags": { "active_shortage": false, "open_recall": false, "boxed_warning": null },
    "missing": [
      {
        "component": "rxnorm",
        "status": "not_listed",
        "reason": "RxNorm links no concept to this package."
      },
      {
        "component": "nadac",
        "status": "not_listed",
        "reason": "CMS publishes no current NADAC rate for this package (NADAC covers drugs retail pharmacies dispense; physician-administered and new products often have none)."
      },
      {
        "component": "label",
        "status": "not_listed",
        "reason": "No current human prescription label on DailyMed lists this product (OTC, animal and withdrawn labels are not served)."
      }
    ]
  },
  "meta": {
    "source": "fda-ndc+nlm-rxnorm+cms-asp+cms-nadac+nlm-dailymed+openfda-drug-shortages+openfda-drug-enforcement",
    "data_version": "ndc@2026-10-23,rxnorm@2026-10-05,medicare-asp@2026-10-r20261008,...",
    "sources": ["..."]
  }
}
```

The example is illustrative and shortened. A payment limit is not a coverage decision or a patient price, NADAC is
an average pharmacy acquisition cost, and no recall or shortage listed means FDA lists none, not
that a product is safe. Reference data, not clinical or billing advice.

## Providers

`GET /v1/providers/{npi}/full` takes a 10-digit NPI and looks up:

| Component           | What it holds                                                                                                  | Dataset                                              |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `provider`          | Who the NPI is: name, individual or organization, active or deactivated, enumeration and deactivation dates    | [NPI](/datasets/npi)                                 |
| `taxonomy`          | Every taxonomy listed for the NPI, the primary one, and NUCC's names when that code set is served              | [NPI](/datasets/npi)                                 |
| `location`          | The practice location address                                                                                  | [NPI](/datasets/npi)                                 |
| `enrollment`        | `enrolled` and the Medicare fee-for-service enrollments that list the NPI                                      | [Medicare enrollment](/datasets/medicare-enrollment) |
| `opt_out`           | `opted_out` today and the affidavit in effect, else the latest listed (`in_effect` says which)                 | [Medicare opt-out](/datasets/medicare-opt-out)       |
| `exclusion`         | `exclusion_recorded`, `matched_by: "npi_as_listed"` and the exclusion records, in effect or reinstated         | [OIG exclusions](/datasets/oig-exclusions)           |
| `exclusion_matches` | Possible matches, derived: OIG records that list no NPI and may be this provider, with confidence and evidence | [OIG exclusions](/datasets/oig-exclusions)           |

The checks answer exactly as `/v1/providers/{npi}/enrollment`, `/opt-out` and `/exclusions` do,
with the same `note`. Provider data comes from the CMS NPI Registry until the NPPES file is
published (the `provider` source says which). An NPI the provider source has no record of is a
`404` with a hint.

### Not listed is not "no"

A check whose current file doesn't list the NPI (no records, or only ones a later release dropped) is `not_listed`, and its `reason` is the check's own
note. Above all for exclusions: exclusions are matched by NPI as listed in the OIG file, and
most OIG records carry no NPI, so `exclusion_recorded: false` means no exclusion recorded with
this NPI, never that the provider is not excluded. Search by name too (`/v1/exclusions?q=`) and
confirm identity with OIG's online search. `exclusion_note` carries that rule in words, in every
view; show it with the answer.

A dataset that isn't published, or whose license keeps it private, is `unavailable`: its flag is
`null`, never `false`. `flags` are `medicare_enrolled`, `opted_out`, `exclusion_recorded` and
`possible_exclusion_match`; `missing` lists every component that isn't `available`, with its
reason.

### Possible exclusion matches

`exclusion_matches` lists OIG exclusion records without an NPI that may belong to this provider:
the same name in the same state, scored on middle name, address and specialty (never date of
birth). Each match has `match` (`answer: "possible_match"`, `confidence`, `derivation`, `evidence`
and `as_of`), the `evidence` field by field and the OIG record. `derivation: "derived"` means the
rules published it on their own (one candidate, strong evidence); `"reviewed"` means a person
checked an ambiguous one first. Ambiguous matches that haven't been reviewed aren't shown. A
possible match is never a finding that the provider is excluded, and `exclusion_recorded` is
unchanged by it: show the component's `note` and confirm with OIG's online search. How matches
are made: [Ownership and entity matching](/datasets/medicare-ownership#possible-exclusion-matches).

### Conflicts

Some facts are stated by both NPPES and CMS's Medicare enrollment file (PECOS), and the two don't
always agree. Where they disagree, `conflicts` lists the fact with **every source's value**,
each with its dataset, `data_version`, `as_of`, our API request for that record (`link`) and the
source's public page (`page`). We never pick a winner: there is no "correct" or "preferred"
value, and a test keeps it that way. Look at each source's `as_of` and page, and decide for your
use.

| `field`                   | Compared                                                                                       |
| ------------------------- | ---------------------------------------------------------------------------------------------- |
| `status`                  | NPPES says deactivated, but a current PECOS enrollment still lists the NPI (`status_mismatch`) |
| `entity_type`             | Individual or organization: NPPES vs the NPI's own PECOS enrollments                           |
| `name.organization`       | Legal business name: NPPES vs the NPI's own PECOS enrollments                                  |
| `name.last`               | An individual's last name: NPPES vs the NPI's own PECOS enrollments                            |
| `practice_location.state` | NPPES practice location state vs the states of the NPI's own PECOS enrollments                 |

Values are normalized before they are compared, so formatting never counts as a conflict: case,
punctuation and spacing, legal forms in organization names (`Inc`, `LLC`, `P.C.`, `&` and
`AND`), and compound last names (`SMITH-JONES` agrees with `JONES`, `DELACRUZ` with `DE LA CRUZ`;
names that share only particles such as `DE` or `VAN` don't agree). Each value comes in `display`
(as the source states it) and `normalized` (what was compared). The rules are fixed and
deterministic.

They are deliberately conservative: a provider enrolled in several states agrees when one of them
is the practice location's; an organization's additional NPIs are compared only for status,
since they're listed under the parent enrollment's name and state; an enrollment the current
file no longer lists isn't compared. Street addresses aren't compared yet: the PECOS file we load
states only the enrollment's state, and the address on a Medicare opt-out affidavit dates from
its filing (a provider may also practice at both places).

`conflicts_checked` lists every comparison that ran, so an empty `conflicts` means "compared,
and the sources agree". A fact missing from `conflicts_checked` wasn't compared: PECOS is
unavailable or lists no current enrollment with the NPI, or one side doesn't state the fact. That
is not agreement.

```json
{
  "conflicts": [
    {
      "field": "practice_location.state",
      "kind": "value_mismatch",
      "description": "NPPES lists the practice location in MN; none of this NPI's own current Medicare enrollments (PECOS) is in MN: WI.",
      "sources": [
        {
          "source": "nppes",
          "data_version": "2026-10",
          "as_of": "2026-10-01",
          "value": { "display": "MN", "normalized": "MN" },
          "link": "/v1/providers/1234567893",
          "page": "https://npiregistry.cms.hhs.gov/provider-view/1234567893"
        },
        {
          "source": "medicare-enrollment",
          "data_version": "2026-10-01",
          "as_of": "2026-10-01",
          "value": { "display": "WI (enrollment O20031103000285)", "normalized": "WI" },
          "link": "/v1/providers/1234567893/enrollment",
          "page": "https://data.cms.gov/..."
        }
      ]
    }
  ],
  "conflicts_checked": [
    { "field": "status", "sources": ["nppes", "medicare-enrollment"] },
    { "field": "entity_type", "sources": ["nppes", "medicare-enrollment"] },
    { "field": "name.organization", "sources": ["nppes", "medicare-enrollment"] },
    { "field": "practice_location.state", "sources": ["nppes", "medicare-enrollment"] }
  ]
}
```

The example is illustrative.

### Views and provenance

`view=compact` returns `npi`, `name`, `entity_type`, `status`, `primary_taxonomy`,
`practice_location`, `flags`, `exclusion_note`, `missing`, `conflicts` and `conflicts_checked`. Each component has its own
`source` with `data_version` and `as_of`, and `meta` combines the provider source, NUCC (when it
named taxonomies) and each check dataset consulted.

```sh
curl "https://api.healthcare.dev/v1/providers/1003926940/full?view=compact" \
  -H "Authorization: Bearer $HDEV_API_KEY"
```

```json
{
  "data": {
    "npi": "1003926940",
    "name": "ROY S SHELBURNE, DDS",
    "entity_type": "individual",
    "status": "active",
    "primary_taxonomy": { "code": "1223G0001X", "name": null },
    "practice_location": { "city": "BRISTOL", "state": "VA", "postal_code": "242024317" },
    "flags": { "medicare_enrolled": false, "opted_out": true, "exclusion_recorded": true },
    "exclusion_note": "Matched by NPI as listed in the OIG file (LEIE). Most LEIE records carry no NPI, so an empty answer means no exclusion recorded with this NPI, not that the provider is not excluded: ...",
    "missing": [
      {
        "component": "enrollment",
        "status": "not_listed",
        "reason": "Matched by NPI (the enrollment's NPI or one of its additional NPIs) in CMS's Medicare Fee-For-Service Public Provider Enrollment file, release 2026-10-01. Not listed means that file has no enrollment with this NPI, not a determination of eligibility."
      }
    ]
  },
  "meta": {
    "data_version": "nppes@2026-10,medicare-enrollment@2026-10-01,medicare-opt-out@2026-08-01,oig-exclusions@2026-10-09",
    "sources": ["..."]
  }
}
```

The example is illustrative and shortened. Reference data, not a credentialing decision: confirm
an exclusion's identity with OIG before acting on it.
