Reading RxNorm with the AutoICD reference API
RxNorm uses term types like IN, BN, SCD, and SBD to organise medications by ingredient, brand, and dose form. A guided tour with code that traverses from a brand name to its underlying ingredients via /v1/reference/rxnorm.
RxNorm is the National Library of Medicine's normalised vocabulary for clinical drugs. It exists because the obvious alternatives (free-text drug names, NDC codes) all break in interesting ways: spelling drift, vendor-specific package codes, and the lack of a stable identifier for the abstract concept of an ingredient. RxNorm fixes that by minting an RxCUI (RxNorm Concept Unique Identifier) for every ingredient, brand, dose form, and clinical drug it knows about, and tagging each one with a term type that says what kind of concept it represents.
If you are integrating with an EHR, an e-prescribing pipeline, or a clinical decision-support engine, RxNorm is almost certainly the layer you talk to. This post explains the term types you actually need to know and shows how to use the AutoICD reference API to traverse from a brand name to its underlying ingredients.
Term types, the short version
RxNorm has dozens of term types but four account for most of the traffic on a typical integration:
- IN: Ingredient. The active substance, no dose, no form. Example: ibuprofen. RxCUI 5640.
- BN: Brand Name. The marketed name. Example: Advil. RxCUI 153010.
- SCD: Semantic Clinical Drug. Ingredient plus strength plus dose form, generic. Example: ibuprofen 200 MG Oral Tablet. RxCUI 198440.
- SBD: Semantic Branded Drug. SCD with a brand attached. Example: ibuprofen 200 MG Oral Tablet [Advil]. RxCUI 1247385.
There are intermediate types: SCDC (semantic clinical drug component, ingredient plus strength), SBDC (branded version of the same), DF (dose form), PIN (precise ingredient, where stereochemistry matters), and a few more. They are the linkage backbone, and you mostly traverse them rather than display them.
What the reference endpoint returns
The AutoICD reference API exposes RxNorm at /v1/reference/rxnorm/{rxcui}. The response includes the canonical name, the term type, language, synonyms, and cross-references into UMLS, SNOMED CT, and ICD-10 where they exist:
curl -H "Authorization: Bearer sk_..." \
https://autoicdapi.com/api/v1/reference/rxnorm/153010{
"system": "rxnorm",
"code": "153010",
"record": {
"rxcui": "153010",
"name": "Advil",
"tty": "BN",
"language": "ENG",
"synonyms": ["ADVIL"],
"cross_references": {
"umls": ["C0593935"],
"snomed": ["317107002"]
}
}
}Brand to ingredient, in TypeScript
Going from a brand to its ingredient is a two-hop walk: the brand sits as a BN concept, the ingredient sits as an IN concept, and the relationship goes through UMLS and SNOMED cross-references plus the RxNorm graph. The simplest path is to use the AutoICD reference search and filter by term type:
import { AutoICD } from "autoicd-js";
import type { RxnormCodeDetail } from "autoicd-js";
const client = new AutoICD({ apiKey: "sk_..." });
async function brandToIngredient(brand: string) {
const search = await client.reference.search("rxnorm", brand, { limit: 10 });
const brandHit = search.results.find((hit) => hit.meta?.tty === "BN");
if (!brandHit) {
throw new Error(`No RxNorm BN concept matched "${brand}"`);
}
const detail = await client.reference.lookup("rxnorm", brandHit.code);
if (detail.system !== "rxnorm") throw new Error("unexpected system");
const brandRecord: RxnormCodeDetail = detail.record;
// The "umls" cross-references on a BN entry usually include a CUI that
// groups the brand with its ingredient. Look up the UMLS concept and
// pull the RxNorm IN atom from its source-vocabulary list.
const cui = brandRecord.cross_references.umls?.[0];
if (!cui) throw new Error("no UMLS bridge for this brand");
const umls = await client.reference.lookup("umls", cui);
if (umls.system !== "umls") throw new Error("unexpected system");
const inAtom = umls.record.atoms.find(
(a) => a.source_vocabulary === "RXNORM" && a.term_type === "IN",
);
if (!inAtom) throw new Error("no RxNorm IN atom on the UMLS concept");
const ingredient = await client.reference.lookup("rxnorm", inAtom.source_code);
return ingredient.system === "rxnorm" ? ingredient.record : null;
}
const result = await brandToIngredient("Advil");
console.log(result?.rxcui, result?.name, result?.tty);
// "5640" "ibuprofen" "IN"If you are doing this at scale, cache the brand-to-ingredient map. The relationship is stable across an RxNorm release; a key-value store keyed on (brand_rxcui) returns the ingredient_rxcui in microseconds and saves you three API calls per lookup.
Going the other direction
Ingredient to brands fans out: an ingredient often has many brand-name products. Search by ingredient name and filter to BN; alternatively, look up the ingredient and walk its UMLS cross-reference to find sibling RxNorm atoms tagged BN. Both produce the same set, with the search path being usually faster because it avoids the UMLS round-trip.
When to call RxNorm at all
There are three integration shapes that consistently make sense:
- Drug name normalisation. Free-text drug names land on a canonical RxCUI before they hit your data warehouse, your DUR engine, or your reporting layer.
- Cross-system mapping. Inbound feeds use NDC, internal lists use ingredient names, claims use ICD-10. RxNorm is the canonical join key that binds them.
- Decision-support inputs. Allergy alerts, drug-drug interactions, and dose-range checks are usually authored against RxCUIs at the ingredient level. Resolving the prescribed SCD or SBD up to its ingredients is the integration point.
If your application does none of those things, you can probably skip RxNorm and store free-text. Most healthcare applications eventually grow into one of the three, though, and adding RxNorm later is more work than starting with it.