@pipeworx/drug-prices

Connect: https://gateway.pipeworx.io/drug-prices/mcp · Install: one-click buttons

Tools: 4

US drug price benchmarks: what a pharmacy pays to acquire a drug, and what Medicare Part B pays out for one administered by a provider. Keyless.

These are two different numbers that are routinely confused for each other, and keeping them apart is most of what this pack does.

NADACASP payment limit
Answerswhat a retail pharmacy pays a wholesalerwhat Medicare pays a clinic
Cadenceweeklyquarterly
Keyed byNDCHCPCS (J-code)
Typical druggeneric tablets, retail productsoncology, biologics, infusions, vaccines
SourceCMS via data.medicaid.govCMS ASP Pricing Files (ZIP)

A generic tablet has a NADAC and no ASP. An infused oncology drug has an ASP and usually no retail NADAC. drug_price_compare reports whichever exist and says why the other is missing, rather than returning an empty result.

Tools

  • nadac_price(drug | ndc, as_of?, limit?) — current acquisition cost per unit
  • nadac_history(ndc, weeks?) — weekly price series and % change
  • asp_payment_limit(hcpcs | drug, quarter?, limit?) — Part B limit per dosage unit
  • drug_price_compare(drug, ndc?) — both benchmarks side by side, with vintages

Plumbing

NADAC is read live — data.medicaid.gov exposes a real query API (filter, sort, paginate) and the dataset holds ~20 months, so there is nothing to mirror. ASP has no API at all: CMS publishes one ZIP per quarter, so it is ingested into asp_payment_limits by scripts/ingest-asp-payment-limits.mjs and read from Supabase.

node scripts/ingest-asp-payment-limits.mjs            # current quarter
node scripts/ingest-asp-payment-limits.mjs --back=5   # backfill

Traps

Every one of these was hit while building the pack, and each produced a confident wrong answer rather than an error.

The newest NADAC week is usually a correction file, not a week. NADAC publishes the full weekly file plus tiny off-cycle corrections sharing the same effective_date column. Measured 2026-08-28: 2026-08-26 → 77 rows, 2026-08-19 → 58,163 rows, 2026-08-12 → 6 rows. The obvious design — take max(effective_date), then filter — lands on the 77-row file and reports “no price found” for almost every drug, which is indistinguishable from a drug NADAC does not cover. Filter for the drug first, then take the newest date in the matched set.

The datastore API silently ignores unknown parameters. ?sort=-effective_date returns HTTP 200 with unsorted rows — byte-identical to ?zzznonsense=x. The only spelling that sorts is sorts[0][property] + sorts[0][order]. A typo here does not fail; it quietly answers with an arbitrary week’s price.

The NADAC dataset id rotates every January, and the year in the title is not the range it covers. The dataset called “NADAC … 2026” holds effective dates from 2025-01-01 to 2026-08-26. The id is resolved by title at call time, with the last known id as a fallback, so January does not break the pack.

NADAC carries two dates. effective_date is the week the price applies to; as_of_date is when CMS published it. Prices key on the first; both are returned.

The ASP payment limit’s vintage is not its quarter. The CMS file states it: the file effective 2026-07-01 is “based on 1Q26 ASP data”. The limit in force is derived from ASP data roughly two quarters earlier, so comparing it against this week’s NADAC spans about six months. Both quarters are always returned.

The ASP CSV is windows-1252, not UTF-8, and carries a 0xA0 inside a real value ("1 million\xa0PFU"). Reading it as UTF-8 throws — and worse, grep silently aborts on the illegal byte, which is how J9271 first appeared to be missing from a file that contains it.

The ASP CSV header is not row 1 (nine rows of notes precede it, and the count varies by quarter), and the inner filename carries a per-quarter build date (…File 061626.csv). Both are matched by pattern, never by offset or literal name. CMS is also inconsistent about a -final-file suffix on the ZIP, so both spellings are tried.

NADAC names brand products by their BRAND, not their ingredient. APIXABAN matches 0 rows; ELIQUIS matches 204. A generic-name search for a still-branded drug therefore returns a confident empty, and the tempting explanation — “must be clinic-administered” — is simply wrong for that case. The empty-result note offers both possibilities in order of likelihood rather than asserting one.

NDCs travel in three shapes — dashed 5-4-2, 11-digit, and 10-digit variants missing a leading zero — and they are not interchangeable as strings. All are normalised to canonical 11-digit.

payment_limit is per its dosage unit. J9271 is $60.645 per 1 MG, so a 200 MG administration is 200×. Returning the limit without the unit invites a 200-fold error, so the two always travel together.

drug_price_compare deliberately does no arithmetic between the two benchmarks. NADAC is per pricing unit (EA/ML/GM) and ASP is per HCPCS dosage — different denominators, measured months apart. Dividing one by the other would produce a confident, meaningless “margin”.

Not included

340B covered entities. Specified in fleet #618 but not shipped: HRSA OPAIS (340bopais.hrsa.gov) is a Blazor client-rendered application with no static export link in its HTML, and probes of the obvious API and bulk-file paths all returned 404. It needs its own investigation and is tracked separately.

Tools

  • nadac_price — What a US retail pharmacy actually PAYS to acquire a drug — the National Average Drug Acquisition Cost (NADAC), surveyed and published weekly by CMS. Look up by drug name (“atorvastatin 20 mg”, “Eliqu
  • nadac_history — Weekly price history for one drug NDC — how the pharmacy acquisition cost (NADAC) moved over time. Use for “has the price of this generic gone up”, shortage-driven price spikes, or generic erosion aft
  • asp_payment_limit — What Medicare Part B PAYS for a drug administered by a provider — the quarterly ASP payment limit, set by statute at 106% of the manufacturer-reported Average Sales Price. Look up by HCPCS J-code (“J9
  • drug_price_compare — Put the two US drug price benchmarks side by side for one drug — what a pharmacy pays to acquire it (NADAC, weekly) against what Medicare Part B pays a provider for it (ASP limit, quarterly). Use to s

Tools

  • asp_payment_limit — What Medicare Part B PAYS for a drug administered by a provider — the quarterly ASP payment limit, set by statute at 106% of the manufacturer-reported Average Sales Price. Look up by HCPCS J-code ( J9
  • drug_price_compare — Put the two US drug price benchmarks side by side for one drug — what a pharmacy pays to acquire it (NADAC, weekly) against what Medicare Part B pays a provider for it (ASP limit, quarterly). Use to s
  • nadac_history — Weekly price history for one drug NDC — how the pharmacy acquisition cost (NADAC) moved over time. Use for has the price of this generic gone up , shortage-driven price spikes, or generic erosion afte
  • nadac_price — What a US retail pharmacy actually PAYS to acquire a drug — the National Average Drug Acquisition Cost (NADAC), surveyed and published weekly by CMS. Look up by drug name ( atorvastatin 20 mg , Eliqui

Regenerated from source · build August 29, 2026