How OpenBill works out its numbers
This page is generated from the rules the OpenBill page runs (src/core/rules.js) and from data/drugs.json, built from the NADAC file as of 2026-10-14. Every count on it is computed from that file.
What OpenBill shows
Search for a drug and OpenBill shows, for each product:
- What pharmacies pay. The national average price retail pharmacies pay to buy a drug from their suppliers (their acquisition cost), per unit, from a CMS survey of pharmacy invoices. The figure is CMS’s NADAC (National Average Drug Acquisition Cost).
- A Medicare price for the same drug. What Medicare Part D spent per unit in its latest data year (2024 in this build), or else the Medicare Part B payment limit for the current quarter, converted to the same unit. Which one is used is a rule below.
- The spread. The Medicare price divided by what pharmacies pay, stated as a multiple in one plain sentence.
- A cost-plus estimate. What a pharmacy would charge if it added a 15% markup and a $5.00 fee to what pharmacies pay. It is always estimated, and it is a model, not anyone’s price (the rule).
- What you paid, if you type it. Your own amount, as a multiple of what pharmacies pay and of the cost-plus estimate. It stays in your browser (the rule).
Every one of those numbers carries its state, the file it came from and its dates.
What it does not show:
- Your price. NADAC is what pharmacies pay their suppliers, not what a patient pays at the counter. Medicare’s figures are what Medicare paid or pays. What you pay depends on your pharmacy, your insurance, your deductible and the quantity you are given.
- A quote. A price here is a range to expect, read from government files that each have their own date.
- Cash prices, pharmacy list prices or insurers’ prices. No government file publishes them. The companies and pharmacies that do publish cash prices forbid storing or copying them, so OpenBill shows none. The cost-plus estimate is a calculation from NADAC; it is not a price any pharmacy charges.
- Medical advice. Nothing here says which drug to take or where to buy it.
There is no warranty. The figures come from other people’s data, which can be wrong, and from rules that can have mistakes. If you find a wrong number, tell us.
How to read a card
A card is one product: every NDC (the 11-digit National Drug Code printed on the package) that NADAC lists under the same description. This build has 5,936 products made of 30,031 NDCs.
Every number on a card has a state label and a provenance line, always visible: the source, the file it was read from, the date it applies from (the effective date) and the date OpenBill downloaded the file (retrieved). A figure that covers a period, such as a Part D data year or a Part B quarter, shows the period; its effective date is the first day of that period. A figure OpenBill calculates from others, such as a spread, shows each input’s own date or period beside it (for example “NADAC eff. 2026-09-23 · Part D 2024-01-01 to 2024-12-31”), never only the oldest. The provenance panel, opened from the card, adds the reason for the state, the NADAC explanation codes, the NDCs and the formula.
When a figure is missing the card says “We don’t know yet” and why. It never shows a zero instead. When a source is older than its limit (freshness), the card labels it stale and still shows it.
The four states
Strongest first:
- filed
- Read from a published government file (NADAC, ASP, a fee schedule). Strongest.
- published
- From a payer's or provider's own published rate.
- derived
- Computed from a published rule (e.g. Medicare pays 80% of the fee schedule).
- estimated
- Inferred, with the reason recorded.
In this build all 38,503 records in data/drugs.json are filed: each is a number printed in a government file. The numbers the page calculates from them, such as a Part B price per NADAC unit or a spread, are derived or estimated: of the 19,302 spreads on cards, 25 are derived and 19,277 estimated, for the reasons in the rules. A pack total is the exception: multiplying a price by your quantity is arithmetic, so the total keeps the state of the price it multiplies.
An example from this build
ARIPIPRAZOLE 10 MG TABLET, 31 NDCs:
- What pharmacies pay
- $0.11173 per EA filed CMS NADAC · nadac-
national- average- drug- acquisition- cost- 20261014. csv · effective 2026-09-23 · retrieved 2026-10-17 - For 30 EA
- $3.35 filed (the state of the price it multiplies: pack total)
- Medicare Part D average spending
- $1.54994 per EA filed Medicare Part D · DSD_
PTD_ RY26_ P04_ V10_ DY24_ BGM. csv · 2024-01-01 to 2024-12-31 · retrieved 2026-10-18 - The spread
- Medicare paid 13.9× what pharmacies pay. estimated (why it is estimated or derived)
- Cost-plus estimate for 30 EA
- $8.85 estimated (how it is worked out)
The rules
These are the rules in src/core/rules.js, in the words the page uses. Every number OpenBill calculates links to its rule here.
Pack total
The price for the quantity you enter is the price per unit times that quantity, worked out exactly and then rounded to the nearest cent, with a half cent rounding up. That is your own arithmetic on a published price, not a new estimate, so the total keeps the label (filed, derived or estimated), the source and the dates of the price per unit it multiplies, and its explanation shows the multiplication. A total above zero but under half a cent is shown as “<$0.01”, never as $0.00.
Formula: pack total = quantity × price per unit, rounded half-up to the cent
Starting quantity
Before you type a quantity, each card fills one in for you. NADAC (National Average Drug Acquisition Cost, a weekly survey by CMS, the Centers for Medicare & Medicaid Services, of what retail pharmacies pay for drugs) prices each drug per EA (each: one tablet, capsule, patch or other single unit), per ML (milliliter) or per GM (gram). For an oral tablet or capsule priced per EA it starts at 30, a common one-month supply of a once-a-day medicine, with buttons for 30, 60, 90. That means every NDC of the product the FDA NDC Directory (the U.S. Food and Drug Administration’s list of drug packages, each identified by its NDC, the National Drug Code printed on the package) lists has a tablet or capsule dosage form of any kind (film coated, extended or delayed release, orally disintegrating, chewable), or is a lozenge or troche, with the route ORAL, or is a tablet or film with the route SUBLINGUAL or BUCCAL (taken under the tongue or against the cheek, and counted like one that is swallowed). When more than half of those NDCs are such a form and the product’s NADAC description says tablet or capsule, the others are treated as errors in FDA’s listing: the card starts at 30 and its explanation names each of them. For a product FDA does not list, its NADAC description must name such a form (TABLET, CAPSULE, CAPLET, SOFTGEL, ODT …) and no other form or route, except SL, SUBLINGUAL or BUCCAL next to a tablet or film. Anything else priced per EA, such as a kit, pen, syringe, vial, patch, inhaler or suppository, starts at 1, with buttons for 1, 2, 3: 30 of them could be many months of supply, and for some of them NADAC’s EA is the whole package, for others one dose in it. When neither FDA nor the NADAC description says what the product is, the card starts at 1 too and says so. For ML and GM it starts at the amount in one package, read from the package description in the FDA NDC Directory: “5 SYRINGE in 1 CARTON / 3 mL in 1 SYRINGE” is 15 mL. When that package is a wholesale pack (a shipping case, or a carton, box, package or tray of at least 10 identical bottles, tubes, vials, jars or ampules), it counts the amount in one of them instead: “40 CARTON in 1 CASE / 1 BOTTLE in 1 CARTON / 60 g in 1 BOTTLE” is 60 g, not 2,400 g. That is used only when every NDC of the product listed there gives the same amount; otherwise the whole packages decide, and when those differ too the card starts at 1. It is a starting point, not your prescription: change it to match what you were given.
Formula: per EA: 30 for an oral tablet, capsule, lozenge or troche, or a tablet or film taken under the tongue or against the cheek, else 1; per ML or GM: the amount in one package (in one inner unit of a wholesale case or multi-pack), when every package description of the product agrees; otherwise 1
Which Medicare price is compared
NADAC (National Average Drug Acquisition Cost) measures what retail pharmacies pay for a drug, and Medicare Part D is the part of Medicare that pays for drugs filled at pharmacies, so Part D comes first: the average Part D spent per unit in its latest data year, for the brand and generic name this drug matched. It is used only when it is per the same unit as NADAC and CMS (the Centers for Medicare & Medicaid Services, which publishes both files) has not flagged it as an outlier, meaning a figure distorted by a few unusual records. When the outlier flag is blank in the CMS file, OpenBill counts it as not flagged and says so. Otherwise OpenBill uses Medicare Part B, the part of Medicare that pays for drugs given in a clinic or doctor’s office: the current quarter’s payment limit, matched by the exact NDC (National Drug Code, the number on the package) through the CMS crosswalk and converted to the NADAC unit (the next rule). Otherwise there is no Medicare price, and the card says why. When both exist, the card shows Part D and its details also show Part B, with a note that Part B pays for a pharmacy drug only in specific settings, such as a drug given in a clinic.
Formula: Medicare price = Part D average per unit if usable, else Part B payment limit per unit if usable, else none
What the provenance panel says with every Part B figure: “Medicare Part B pays for a drug like this only in specific settings, such as a drug given in a clinic or doctor’s office or used with certain medical equipment; most prescriptions filled at a pharmacy are paid by Part D.”
And with a Part D figure whose outlier flag is blank: “The Part D file leaves the outlier flag for this drug blank. OpenBill counts a blank flag as not flagged, so the figure is used, but CMS has not said whether unusual records distort it.”
Part B payment limit per NADAC unit
Medicare Part B sets a payment limit per HCPCS billing unit. HCPCS is the coding system Medicare uses for billing, and a billing unit is an amount such as “10 MG”. NADAC is per tablet (EA), milliliter (ML) or gram (GM) instead. To compare the two, OpenBill uses the CMS NDC-HCPCS crosswalk, a CMS table that gives, for each NDC (National Drug Code, the number on the package), the billing code it is billed under and how many billing units the whole package holds. The price per NADAC unit is the limit times the billing units in the whole package, divided by the amount in the package (the amount in one item times the number of items). This is done only when OpenBill could confirm, from the package description in the FDA NDC Directory (the U.S. Food and Drug Administration’s list of drug packages), that the package amount is in the same unit NADAC uses.
Formula: price per unit = payment limit × billing units in the package ÷ (amount in one item × number of items)
Part B coinsurance
Coinsurance is the share of the Part B payment limit a patient owes after meeting the deductible: the payment limit per billing unit times the coinsurance percentage printed for that billing code in the CMS payment limit file (usually 20%). Deductibles, supplemental insurance and other coverage change what a person actually owes.
Formula: coinsurance = payment limit × coinsurance percentage ÷ 100
The spread
The spread compares the two prices: the Medicare price per unit divided by what pharmacies pay per unit (NADAC). 4.9× means Medicare paid 4.9 times what pharmacies pay; 0.8× means Medicare paid 20% less. Every number on a card is labelled filed (read from a government file), derived (calculated by a fixed rule from published figures) or estimated (inferred, with the reason written down). A spread computed from Part D is always labelled estimated, because Part D puts every strength and form sold under one name into a single average, covers a past calendar year rather than this week, and counts spending the way CMS defines it (including pharmacy dispensing fees, and before the discounts drug makers pay back). A spread computed from Part B is labelled derived only when a single company sells under that billing code (the next rule); otherwise it is estimated too.
Formula: spread = Medicare price per unit ÷ NADAC price per unit
When a Part B spread counts as derived
Part B sets one payment limit per billing code, worked out from the average sales price of every product billed under that code. A labeler is the company named on a drug’s label. When the CMS crosswalk lists only one labeler for the code, the limit describes that company’s own product, so the spread is derived. CMS sometimes writes one company’s name more than one way on the same code (“UCB, INC.” and “UCB, Inc.”); names that differ only in capitals, punctuation, spacing or a closing Inc, LLC, Ltd, Corp or Co count as one labeler, and the explanation quotes each spelling. When several labelers share the code, the limit is an average across all their products and can sit well above or below the price of this one package, so the spread is estimated, and its explanation says how many labelers share the code.
Formula: derived when exactly one labeler (one company, however its name is spelled) is listed for the billing code on the crosswalk; otherwise estimated
Very large or very small spreads
An estimated spread of 20× or more, or 0.2× or less, also carries a warning that the two figures may not describe the same unit or product: for example, Part D may count a unit differently from NADAC, or average in strengths that cost far more or far less than this one. The number is still shown, with the warning; it is never hidden. But the card does not lead with a sentence such as “Medicare paid 99% less than pharmacies pay” for it: its headline becomes “These two figures may not measure the same thing”, the multiple is shown beside it, and the warning says why.
Formula: estimated spread ≥ 20× or ≤ 0.2× → add a warning; the card’s headline becomes “These two figures may not measure the same thing”
The thresholds are tested on the ratio at full precision, before it is rounded for display. A ratio of 0.2046 is above 0.2, so it carries no warning and keeps its sentence, and the card shows it as 0.205×: near a threshold the multiple gets the digits that keep it from reading as the threshold itself.
A product far from the others under its Part D name
Part D gives one average for every product sold under a brand and generic name, weighted by claims. When one product costs far more than the others under that name (a specialty formulation or an uncommon strength of a cheap generic, for example), the average can mostly reflect what the others cost, and a spread against it then says little about this product. So OpenBill compares the middle NADAC price of this product’s NDCs with the middle of the other products’ own middle prices under the same name, per the same unit (the lower of the two middle prices when there is an even number, so the comparison is between real filed prices). When this product is 20 or more times the others, the spread says so with both prices. The spread is still shown; the join is not refused, because many such names really do average strengths that sit close together.
Formula: middle NADAC of this product ÷ middle of the other products’ middle NADACs under the same Part D name ≥ 20 → add a warning
Cost-plus estimate
No free source of the prices pharmacies charge people paying cash may be reused: the companies and pharmacies that publish those prices forbid storing or copying them. So OpenBill shows no one’s cash price. Instead it works out what a pharmacy would charge if it priced the way Cost Plus Drugs, an online pharmacy, publicly describes (what it pays for a drug, plus a 15% markup, plus a flat pharmacy fee) and paid what NADAC says pharmacies pay: the pack total at NADAC, rounded to the cent, plus 15% of it, rounded to the cent, plus $5.00 per fill. For example, if pharmacies pay $10.00 for a pack, the estimate is $10.00 + $1.50 + $5.00 = $16.50. It is always labelled estimated. It is a model, not a price any pharmacy quoted, and not a typical price: Cost Plus Drugs’ own costs differ from NADAC, other pharmacies price differently, and shipping is not included. OpenBill copies no price from Cost Plus Drugs. There is no spread against this estimate, because it is built from NADAC itself: for a cheap generic the $5.00 fee alone can make it many times NADAC.
Formula: cost-plus estimate = quantity × NADAC price per unit, rounded half-up to the cent, + 15% of that, rounded half-up to the cent, + $5.00 per fill (shipping not included)
| Applied to | what pharmacies pay for the pack: quantity × NADAC per unit, rounded half-up to the cent |
|---|---|
| Markup | 15% of that, rounded half-up to the cent |
| Pharmacy fee | $5.00 per fill |
| Shipping | not included |
| State | estimated, always |
Where the parameters come from: Cost Plus Drugs publicly describes how it sets its prices: what it pays for a drug, plus a 15% markup, plus a pharmacy labor fee, plus shipping. KFF Health News coverage of the company (February 2023) describes the same method, with a lower labor fee than today’s. The $5.00 fee is the one the company’s own website showed on 2026-10-18. OpenBill takes only those two parameters, the 15% markup and the $5.00 fee per fill, and applies them to NADAC in place of Cost Plus Drugs’ own cost, which it does not publish per drug. It copies no price from Cost Plus Drugs.
- KFF Health News, 17 February 2023: coverage of Mark Cuban Cost Plus Drug Company, describing its pricing as its cost plus a 15% markup, a pharmacy labor fee (lower in 2023 than today) and shipping
- Cost Plus Drugs’ own website, which showed a $5.00 pharmacy labor fee on 2026-10-18 (the source of the fee)
The two sources do not give the same fee. The KFF Health News article of February 2023 reports a $3 labor fee per drug and $5 shipping per order; the $5.00 fee here is the one Cost Plus Drugs’ website showed on 2026-10-18. If the company changes its fee or markup, OpenBill changes COST_PLUS and this date together.
What the estimate is not:
- This is a model, not a price any pharmacy quoted.
- Cost Plus Drugs’ own costs differ from NADAC, and other pharmacies price differently.
- Shipping is not included.
- It is not a price from Cost Plus Drugs or from any other pharmacy. OpenBill copies, stores and shows none of their prices.
- It is not compared with NADAC as a spread. It is NADAC plus a markup and a fee, so the multiple would only measure the fee. The headline multiple on a card is always Medicare’s price ÷ what pharmacies pay.
In this build, for 30 EA of ARIPIPRAZOLE 10 MG TABLET: pharmacies pay $3.35; 15% of that, rounded to the cent, is $0.50; with the $5.00 fee the estimate is $8.85.
5,924 of 5,936 products have a cost-plus estimate at their starting quantity. The others show “We don’t know yet”, for the same reason as their pack total.
What you paid
You can type what you were charged for the pack shown on a card. The card then shows it as a multiple of what pharmacies pay for that pack and of the cost-plus estimate, labelled as your number: 3.6× means you paid 3.6 times as much. When a product has several NDCs at different prices, the multiple is a range. The amount must be more than $0 and at most $100,000.00, in dollars and cents; otherwise the card says why and shows no multiple. Your number is worked out in your browser. OpenBill never sends or saves it; it is kept in this page’s address after the #, so your browser’s history keeps it like any other page you visit. Browsers do not send the part after the # to the website; it is there so you can share your own card if you choose to, and a link you share carries your amount. Insurance, coupons and discounts change what different people pay, so a multiple is a question to ask your pharmacy, not proof of a mistake.
Formula: your multiple = what you paid ÷ what pharmacies pay for the pack; and what you paid ÷ the cost-plus estimate for the pack
- The amount must be more than $0 and at most $100,000.00, in dollars and cents (at most two digits after the point). A “$” and thousands commas are fine. For anything else the card says why and shows no multiple; it never repeats what you typed.
- The card labels the multiples “your number”: the amount is yours, not a figure from a file.
- The arithmetic runs in your browser. OpenBill never sends or saves it; it is kept in this page’s address after the #, so your browser’s history keeps it like any other page you visit. The amount is written after the # in the address as
&paid=12.00; browsers do not send that part to the website. That lets you share your own card. A link you share carries your amount, so share it only if you mean to. - Only the interactive card has this box. The drug pages have no script.
For example, if you type $12.00 on the card for 30 EA of ARIPIPRAZOLE 10 MG TABLET, it says: “You paid 3.6× what pharmacies pay for this pack, and 1.4× the cost-plus estimate.” The amount is an illustration, not anyone’s price.
Freshness
Each figure’s age is counted in whole days up to today: for NADAC from the date of its weekly file (the “as of” date), for Medicare Part B from the first day of the quarter its payment limits apply to, and for Medicare Part D from the last day of its data year. A figure older than its source’s limit (14 days for NADAC, 100 days for Part B, 913 days for Part D) is labelled stale on the card; it is never hidden or replaced by a zero. If a date comes out later than today, which a device’s clock or time zone can cause, the age is shown as 0 days and the figure is not stale.
Formula: age = today − the source’s date, in whole days; stale when age > the source’s limit
How prices are presented
OpenBill holds every screen to these five rules, word for word:
- A price is a range to expect, never a quote.
- Medicare's price is not your price. Say so on every screen that shows it.
- State which dataset and which effective date produced every figure.
- If a source is stale or a file is empty, show that rather than a zero.
- Disclaimer footer: not medical advice, no warranty, other people's data can be wrong.
The drug pages
On the website, each product also has a page of its own, and there is a page for each drug class and an A to Z index. These pages are built from data/drugs.json with the same rules as the card each time the site is published; they are not kept in the repository. They have no script. Their numbers are in the page itself, each with its state and source line, and each page links to the interactive card for its product, where you can change the quantity or enter what you paid. Start from the A to Z index of every drug.
- Drug classes are the U.S. Food and Drug Administration’s Established Pharmacologic Classes (the “[EPC]” entries of PHARM_CLASSES in the FDA NDC Directory), read for each NDC’s exact package. A product’s classes come from its NDCs that have an FDA identity. In this build 24,902 of the 27,083 NDCs with an FDA identity have classes listed, 24,086 of them an established class; there are 329 classes, and 4,797 of 5,936 products belong to at least one. Where FDA lists none, the page shows no class rather than a guess.
- Other strengths, forms and brands are the products that share an FDA nonproprietary name (the generic name on the label) with this one. They stay separate products. A drug page lists up to 24 of them, itself included, in a table with each one’s FDA dosage form, whether NADAC classes it brand or generic, what pharmacies pay per unit and its Medicare figure per unit, each with its state and source line. Products under one Part D name show the same Part D average, because Part D gives one figure for all of them.
- Compared with its class. For each of a product’s classes, its page says how many of the class’s other products priced in the same unit (EA, ML or GM) have a higher or lower NADAC price, and which product is in the middle. Each product is represented by the middle of its own NDCs’ NADAC prices, and the class by the middle of those; when there is an even number of prices, the lower of the two middle ones is taken, so the price shown is always one real filed NADAC price, never an average of two. A product whose NDCs are priced in different units, or whose class has fewer than 3 other products in its unit, gets no comparison. A unit is one tablet, capsule, milliliter or gram whatever its strength, so this compares prices per unit, not per dose.
- Shared explanations live here. What NADAC and Part D measure, how the starting quantity, the spread and the cost-plus estimate are worked out, and the rules every price follows are the same for every product, so a drug page links to them on this page instead of repeating them; the page keeps what is particular to its product. Its questions answer only what the rest of the page does not already say.
- Related drugs are up to five other drugs in the same classes, ranked by how many classes they share and then by Medicare Part D claims. A class says how a drug works. It does not mean one drug can replace another; that is a question for a doctor or pharmacist.
- Structured data. Each page describes its figures to search engines as costs of the drug (schema.org DrugCost), never as an offer to sell it, and every number there equals the one on the page.
Sources
Every number comes from one of these files. data/drugs.json records each file’s sha256, so you can check that you have the same bytes. The licences and terms, the fields read and the caveats of each file are in DATA_SOURCES.md.
CMS NADAC
The national average price retail pharmacies pay to buy a drug from their suppliers (their acquisition cost), per unit, from a CMS survey of pharmacy invoices.
| Dataset | NADAC (National Average Drug Acquisition Cost) 2026 |
|---|---|
| Published by | Centers for Medicare & Medicaid Services, on data.medicaid.gov |
| Publisher in the file’s metadata | data.medicaid.gov |
| Dataset page | data.medicaid.gov |
| File | nadac- |
| sha256 | 15335686bbc0… |
| Period | none: not period data |
| File date (as of) | 2026-10-14 |
| Retrieved (UTC) | 2026-10-17 |
| New file | every week |
| Stale after | 14 days, counted from the file date. NADAC is published every week. A file more than 14 days old means at least one weekly update was missed, so the price may have changed. |
| Licence | www. |
| Weekly files in the download | 41; OpenBill uses the newest |
FDA NDC Directory
The FDA listing of each drug package: its NDC, brand and generic names, dosage form, strength, labeler and package description. It identifies drugs; it has no prices.
| Dataset | National Drug Code Directory (NDC database file, text version) |
|---|---|
| Published by | U.S. Food and Drug Administration |
| Publisher in the file’s metadata | U.S. Food and Drug Administration (FDA), Center for Drug Evaluation and Research |
| Dataset page | www.fda.gov |
| File | ndctext. |
| sha256 | ee609cef6e4e… |
| Period | none: not period data |
| File date (as of) | 2026-10-16 |
| Retrieved (UTC) | 2026-10-17 |
| New file | every day |
| Stale after | 30 days, counted from the file date. The FDA updates the directory every business day. It only supplies names and package details, which change slowly, so it is shown as stale after 30 days rather than after one missed day. |
| Licence | www. |
| Directory size | 116,607 products, 219,166 packages |
Medicare Part D
What Medicare Part D spent on a drug in one calendar year, averaged per dosage unit across every strength and form sold under the same brand and generic name.
| Dataset | Medicare Part D Spending by Drug, data year 2024 |
|---|---|
| Published by | Centers for Medicare & Medicaid Services, on data.cms.gov |
| Publisher in the file’s metadata | Centers for Medicare & Medicaid Services (CMS), Office of Enterprise Data and Analytics (OEDA) |
| Dataset page | data.cms.gov |
| File | DSD_ |
| sha256 | 5dcc9d7bd7d7… |
| Period | 2024-01-01 to 2024-12-31 |
| File date (as of) | 2026-06-25 |
| Retrieved (UTC) | 2026-10-18 |
| New file | once a year |
| Stale after | 913 days, counted from the last day of the data year. Part D spending is published once a year, about 18 months after the data year ends (about 548 days), and the next year follows about a year after that. So a data year is the newest one available until roughly 2.5 years (913 days) after it ends; older than that, a newer year should exist. |
| Licence | www. |
| CMS documentation | methodology, data dictionary |
Medicare Part B
The most Medicare Part B pays for a drug given in a clinic or doctor’s office, per HCPCS billing unit, for one calendar quarter, and the coinsurance percentage for it.
| Dataset | October 2026 Medicare Part B Payment Limit Files |
|---|---|
| Published by | Centers for Medicare & Medicaid Services, on cms.gov |
| Publisher in the file’s metadata | Centers for Medicare & Medicaid Services (CMS) |
| Dataset page | www.cms.gov |
| File | october- |
| sha256 | a9b7afa80aae… |
| Period | 2026-10-01 to 2026-12-31 |
| File date (as of) | 2026-09-17 |
| Retrieved (UTC) | 2026-10-18 |
| New file | every quarter |
| Stale after | 100 days, counted from the first day of the quarter. Part B payment limits are set for one calendar quarter (about 91 days). Once more than 100 days have passed since the quarter began, the next quarter’s file should be in use. |
| Licence | www. |
Medicare Part B crosswalk
Which HCPCS billing code each NDC is paid under in Medicare Part B, and how many billing units are in one package of that NDC.
| Dataset | October 2026 NDC-HCPCS Crosswalk |
|---|---|
| Published by | Centers for Medicare & Medicaid Services, on cms.gov |
| Publisher in the file’s metadata | Centers for Medicare & Medicaid Services (CMS) |
| Dataset page | www.cms.gov |
| File | october- |
| sha256 | e6bbf7c61e6d… |
| Period | 2026-10-01 to 2026-12-31 |
| File date (as of) | 2026-09-17 |
| Retrieved (UTC) | 2026-10-18 |
| New file | every quarter |
| Stale after | 100 days, counted from the first day of the quarter. The NDC-HCPCS crosswalk is published with each quarter’s payment limits. Once more than 100 days have passed since the quarter began, the next quarter’s file should be in use. |
| Licence | www. |
How the files are joined
The NADAC file is the spine: one row per NDC in its newest week, 30,031 rows in this build. The other files are joined to it on exact keys. A join that is not certain is left empty rather than guessed; each empty join is counted below and explained on the card.
Identity: the FDA NDC Directory
Each NDC is looked up, digit for digit, in the FDA NDC Directory’s package list. FDA writes package NDCs with hyphens in three layouts (4-4-2, 5-3-2 and 5-4-1); the short segment is padded with a zero to make the 11 digits NADAC uses. A match gives the brand and generic names, dosage form, route, strength, labeler and package description.
27,083 of 30,031 rows (90.2%) match. The other 2,948 rows have no identity: their NDC is not in the directory’s package list (discontinued products leave it, for example), its package has no product record, or its FDA entries conflict with each other (0 NDCs in this build).
For the Part D name join only, a row with no identity can be matched using FDA’s record for its 9-digit product code, when that code has one product record and a sibling NDC of the same description is in the directory, or the NADAC description begins with FDA’s trade name or prints its name and strength. That record is not an identity: the row’s identity stays empty. Its route is used for one thing only: the multi-route rule below, for a Part D name marked *. In this build it was tried for 2,035 rows, and 1,385 of them joined a Part D name.
Medicare Part D: by name
Part D Spending by Drug has no NDCs. CMS lists each drug by brand name and generic name, with every strength and form together, so this join is by name: the NADAC description and, when the NDC has an FDA identity, FDA’s brand and generic names, against the names CMS uses. It is strict on purpose. A row gets no Part D figure when:
- the release forms differ: markers such as ER, XR or DR must be the same on both sides, and FDA’s dosage form must agree;
- more than one Part D row could match;
- the generic names disagree: the substances must be the same, and so must the salt where both names state one (a hydrate that is the only qualifier counts as the salt);
- a brand product has no Part D row with its own brand name;
- Part D lists the substance under several salts, or lists several presentations such as pens and vials, and neither the NADAC description nor FDA’s names say which one this NDC is;
- a combination product’s NADAC name lists a different number of ingredients from the Part D generic name;
- the product is sold over the counter under an FDA OTC monograph, or matches only a Part D row named by the generic name: Part D excludes over-the-counter drugs, so those Part D rows are a different, prescription product;
- NADAC prices the NDCs joined to one Part D name in different units and this NDC’s unit is not the one nearly all of them share, because CMS’s average would then mix tablets with milliliters;
- the Part D name carries CMS’s *, which marks one average over several routes of administration (tablets and injections, say), and this NDC is not on the route the name’s rows share. A row joins such a name only when its FDA route is known and includes the route that covers the most of the name’s rows, so every row that keeps the figure shares one route. The route is the one in the row’s FDA identity. A row the FDA NDC Directory does not list as a package of its own takes the route of FDA’s record for the same product (labeler and product code), and only of the record the name join already accepted for it with the checks above: one product record for the code, and either a listed sibling NDC of the same description, FDA’s trade name at the start of the description, or FDA’s name and strength printed in it. Such a row follows the route the rows with an FDA identity choose and does not count toward choosing it, because it is often a second package (a blister or pouch inside a carton) of a product whose listed package already counts; it keeps the figure only when its route includes the whole chosen route. Only when none of the name’s rows has an FDA identity with a route do those rows choose among themselves. A row with no known route, or on another route, is left out; if two routes cover as many rows but not the same rows, every row is left out. Routes are FDA’s own words, so PARENTERAL is not merged with INTRAVENOUS.
In this build 263 rows with no FDA identity took their route from the product record for a name marked *, and 146 of them kept the Part D figure because of it. Each of those rows says so in its provenance panel, naming the route, FDA’s product NDC as FDA writes it and, where FDA prints this NDC inside one of the product’s listed packages, that package; the name’s reason says how many of its NDCs took their route this way. The others were left out: 58 priced in a unit that is not the one nearly all of the name’s rows share, 55 on another route than the one the name’s rows share, 4 in a name whose rows tie between routes. 108 rows still have no known route and stay out.
19,277 of 30,031 rows (64.2%) match, pointing at 1,458 Part D names. For 17,111 rows CMS’s brand name is the generic name itself; for 2,166 rows it is a trade name. The exact rules are in tools/build_drugs.py, and the cases they were written for are in DATA_SOURCES.md.
| Why | Rows | Share |
|---|---|---|
| NADAC prices the NDCs joined to this Part D name in more than one unit and this NDC’s unit is not the one nearly all of them share, so CMS’s average per dosage unit is not in this NDC’s unit | 2,739 | 9.1% |
| no Part D row with this generic name and release form | 2,040 | 6.8% |
| more than one Part D row carries this product name and release form | 1,625 | 5.4% |
| OTC product sold under an FDA OTC monograph; Part D excludes OTC drugs, so a Part D row of this name is a different product | 1,320 | 4.4% |
| OTC product with no Part D row of exactly this name (Part D excludes OTC drugs) | 1,019 | 3.4% |
| the Part D name carries CMS’s * (an average over several routes of administration) and this NDC’s FDA route is not the one most NDCs joined to the name share | 501 | 1.7% |
| a Part D row has this name but its generic name disagrees with FDA’s nonproprietary name | 323 | 1.1% |
| brand product with no Part D row of this brand name and release form | 303 | 1.0% |
| FDA’s dosage form or name states a release form (extended or delayed release) the NADAC description does not, or the reverse | 296 | 1.0% |
| Part D has rows for this substance under different salts (for example Buprenorphine and Buprenorphine HCl) and neither the NADAC description nor FDA’s nonproprietary name settles which | 175 | 0.6% |
| OTC product whose name matches only a Part D row named by the generic name, which is the prescription product (Part D excludes OTC drugs) | 152 | 0.5% |
| more than one Part D generic row could match | 108 | 0.4% |
| the Part D name carries CMS’s * (an average over several routes of administration) and this NDC has no FDA identity with a route, and no accepted FDA product record for its product code with one, so its route is unknown | 108 | 0.4% |
| the Part D name carries CMS’s * (an average over several routes of administration) and the NDCs joined to it split evenly between routes, so no route is shared by most of them | 44 | 0.1% |
| Part D lists presentations of this name separately (for example vial and pen) and the NADAC description does not settle which one this NDC is | 1 | <0.1% |
CMS flags 61 Part D names as outliers: the average is substantially affected by unusual records. The 742 rows pointing at them keep the figure in the data but do not use it as the reference price. The flag is blank for 1 name (1 row); a blank flag counts as not flagged, and the provenance panel says so in the figure’s reason.
Medicare Part B: by exact NDC
The CMS NDC-HCPCS crosswalk lists which NDCs are billed to Part B under which HCPCS code, and how many billing units one package holds. An NDC is joined to it digit for digit; an NDC on crosswalk lines that disagree with each other is left out. The HCPCS code then gives the payment limit and the coinsurance percentage from the payment limit file.
To compare a payment limit with NADAC, the package has to be measured in NADAC’s unit. The crosswalk does not say which unit its package size is in, so OpenBill accepts it only when FDA’s package description for the same NDC gives the same total (milliliters, grams, or a count of tablets, vials and so on) and the crosswalk’s two billing-unit columns agree with each other. A kit that NADAC prices per EA is counted as whole kits. Where the two billing-unit columns disagree, one is used only when FDA’s strength × the package amount ÷ the HCPCS dosage gives exactly that number. Otherwise the package size is shown as the crosswalk prints it, a number with no unit, and the NDC gets no Part B price per NADAC unit.
1,427 of 30,031 rows (4.8%) are on the crosswalk, under 138 HCPCS codes; 1,338 of them (93.8%) have the package unit reconciled and can be converted to a price per NADAC unit: 1,289 because FDA’s package description gives the same total as the crosswalk; 32 because the NDC is a kit NADAC prices per EA, so the count is of whole kits; 17 because the crosswalk’s billing-unit columns disagree and FDA’s strength × amount ÷ the HCPCS dosage settles them exactly.
| Why | Rows | Share |
|---|---|---|
| the NDC is not on the current quarter’s NDC-HCPCS crosswalk (most drugs filled at a pharmacy are not billed to Part B) | 28,587 | 95.2% |
| the NDC is on more than one crosswalk line, and the lines disagree on the HCPCS code or the package figures | 17 | 0.1% |
A payment limit is one figure per code. 38 of the 138 codes have a single labeler (the company named on the label) on the crosswalk, and 77 rows point at them. For the others the limit averages every labeler’s product billed under the code (when a Part B spread counts as derived). CMS spells some labeler names more than one way on the crosswalk: 6 codes list one company under names that differ only in capitals, punctuation or a suffix such as Inc or LLC. OpenBill counts each company once, and the provenance panel quotes every spelling; 2 of those codes have a single labeler only once the spellings are counted together.
What the cards end up showing
With these joins and the reference rule: 18,535 rows (61.7%) have a Part D reference price, 767 (2.6%) a Part B one and 10,729 (35.7%) none. By product: 3,075 Part D, 180 Part B, 2,681 none, of 5,936. 571 rows with a Part D reference also show a Part B figure in the provenance panel.
Every Part D spread is estimated (18,535). Of the Part B spreads, 25 are derived and 742 estimated. 387 estimated spreads at or beyond 20× or 0.2× carry the large-gap warning; on those cards the headline is “These two figures may not measure the same thing” instead of the spread’s sentence, with the multiple beside it. The full tables are in DATA-QUALITY.md.
NADAC explanation codes
NADAC marks each price with one or more explanation codes. Their meanings below are from the NADAC data dictionary. Codes 2 and 4 mean the price was carried forward from an earlier file rather than set by the latest survey; OpenBill marks those rows and shows every code in the provenance panel.
| Code | Meaning | Rows |
|---|---|---|
| 1 | calculated from the most recently completed pharmacy survey | 29,061 |
| 2 | the latest survey was within 2% of the current NADAC, so the NADAC was carried forward from the previous file (carried forward) | 45 |
| 3 | the survey-based NADAC was adjusted for changes in published pricing, or after a help-desk inquiry | 15 |
| 4 | carried forward from the previous file (carried forward) | 910 |
| 5 | calculated based on package size | 5,962 |
| 6 | the CMS Covered Outpatient Drug Product File S/I/N drug category was not applied when grouping drugs for the calculation | 1,295 |
Known limitations
- Part D is a yearly average for a whole name. CMS averages every strength, form and maker sold under one brand and generic name, weighted by claims, for data year 2024 (2024-01-01 to 2024-12-31). It is gross spending as CMS defines it: ingredient cost, dispensing fees, sales tax and vaccine administration fees, whether the plan or the patient paid them, and before the rebates drug makers pay back, which CMS may not disclose. So it can sit far from the NADAC of one NDC, and prices may have moved since 2024-12-31. That is why every Part D spread is estimated. 2,154 rows point at names CMS marks with *, an average across routes of administration that can have different unit prices. Only NDCs on the route the name’s rows share keep such a figure (the multi-route rule), but the average itself still includes the other routes’ spending, so a tablet can be compared with a figure that is mostly injections or gels.
- The Part D unit is assumed. CMS reports spending per dosage unit but does not say which unit each drug uses. OpenBill takes it to be NADAC’s unit (EA, ML or GM) for the NDCs joined to that name. Where NADAC prices those NDCs in more than one unit, only the NDCs in the unit nearly all of them share get the figure; 2,739 rows got none for that reason. A spread far from 1× can still mean the units differ, which is why estimated spreads at or beyond 20× or 0.2× carry a warning.
- Part B pays only in some settings. Medicare Part B pays for a drug like this only in specific settings, such as a drug given in a clinic or doctor’s office or used with certain medical equipment; most prescriptions filled at a pharmacy are paid by Part D. The payment limit file’s own description of each code is in the provenance panel. A code shared by several labelers has one limit averaged across all their products, so a spread against it is estimated (100 of the 138 codes here). Billing units are whole numbers, so a price per unit can be slightly off for odd package sizes.
- Some NADAC prices are carried forward. 955 of 30,031 rows (3.2%) carry explanation code 2 or 4: CMS carried the price forward from an earlier file instead of setting it from the latest survey. They are shown with their codes, not dropped.
- Low recall, by design. 19,277 of 30,031 rows (64.2%) match a Part D name, and 10,729 (35.7%) have no Medicare price at all. A wrong Medicare price is worse than none, so an uncertain match is left empty and the card says “We don’t know yet”. Brand and generic versions of a drug are separate products, because OpenBill has no RxNorm identity yet. The 2,948 rows without an FDA identity have only the NADAC description and, for some, FDA’s record for their product code to match on, and they join a Part D name marked * only when that product record’s route is the one the name’s rows share.
- NADAC is a survey average. It is what retail community pharmacies paid on average in CMS’s survey. A given pharmacy may pay more or less, and NADAC says nothing about what a patient pays.
- The cost-plus estimate is a model. It applies one online pharmacy’s published markup and fee to NADAC, which is a survey average of what retail pharmacies pay, not what that pharmacy pays. Its real prices can be higher or lower, it charges shipping, and other pharmacies price in other ways. So the estimate says what a cost-plus price could look like, not what you will be charged (the rule).
- Dates are UTC calendar dates. The page counts every age against today’s UTC date, read from the viewer’s device clock, so the time zone never changes an age; near midnight UTC an age can differ by a day from the viewer’s local calendar. A device clock set behind can make a file look dated tomorrow; its age is then shown as 0 days. DATA-QUALITY.md gives each source’s age on the day it was retrieved.
- Older browsers cannot open it. The page carries its data compressed (deflate-raw, then base64) and unpacks it with the browser’s own DecompressionStream, which reads that format from Chrome 103, Edge 103, Firefox 113 and Safari 16.4. An older browser cannot unpack it; the page then says so in plain words and shows no figures rather than wrong ones.
Reproduce it
You need Python 3.10 or later (standard library only) and Node 20 or later. From a copy of the repository (on Windows the Python command is usually python rather than python3):
python3 tools/fetch_all.py
python3 tools/build_drugs.py
node build.js
node build.js --check
node --test
fetch_all.py downloads every source into tools/cache/ and records each file’s sha256; with --offline it only checks the files already there. build_drugs.py reads the cache, never the network, and writes data/drugs.json. build.js writes openbill.html, this page and docs/DATA-QUALITY.md. Nothing generated contains a build time, so the same files give the same bytes. build.js --check fails if a generated file is out of date, and node --test runs the unit, data, spot-check, quality and link tests.
Government sites replace their files. To reproduce these exact numbers, your 5 source files must have the sha256 values listed under sources. A newer file gives newer numbers; that is a data refresh, not a mismatch.
Report a wrong number
Every card has a “Wrong? Tell us” link. It opens the wrong-price issue form on GitHub with the card’s details filled in. You can also open the wrong-price form yourself.
The most useful report names the source file and the row in it that shows the right value. A receipt or a price from a website is useful context, but it does not show that a government file was read wrong. Please don’t include your own or anyone else’s health information.
How reports are checked and corrected: CONTRIBUTING.md and GOVERNANCE.md.