For developers, agencies and researchers
Price a diagnostic journey with one request
Send a sentence a person actually wrote. Get back every unit of care it names, priced and cited.
Try it now, in this tab: GET /api/table/cms-99213, one published figure with its source, basis and year.
Try it: a live call from this page
- No keyNo key, no account, no sign-up. Every route below is open.
- 274 rowsPrice table version 2026-09-09.1: 251 of them priced and addable, each with the file it was read from.
- 5,668locality prices, every one with the inputs that made it
- 51 routesin the OpenAPI 3.1 description, every request and response schema
From a sentence to a FHIR Bundle
A story
A sentence a person actually wrote.
"story"The reader
A deterministic matcher maps words to a unit of care. matchedOn shows the exact words it matched.
matchedOnThe federal table
274 rows, each with the file it was read from. A published table prices the unit; no model produces a dollar figure.
sourceUrlThe ledger
Every line priced and cited. What cannot be priced comes back unpriced, with the reason.
unpricedFHIR R4
The same ledger as an HL7 FHIR R4 Bundle of type collection.
Bundle
CORS Every public GET route, and POST /api/price and POST /api/fhir, send access-control-allow-origin: *, so a browser app on another site can call them directly. Call POST /api/map from your own server.
On this page: 8 sections
Price a story
Every response carries the published federal figure that prices each unit of care, and the year, basis, population and source URL behind that figure, so whatever you build can show any number it prints. A deterministic matcher maps words to a unit of care; a published table prices it. No model produces a dollar figure.
POST /api/price
The call the site itself makes
One request, one journey, every line traceable.
What to notice.
matchedOnshows the exact words in the table the phrase matched, so the mapping is never a black box.- “I missed work” comes back in
unpricedwith the reason it will not be priced and what would fix it: valuing a lost day needs that person’s own pay, not a national median. confidence: "DERIVED"says this figure was computed from two numbers read in the CMS file, not lifted from a dollar column that does not exist.
Show the callHide the callThe curl request and the real response
BASE=https://waypointledger.org
curl -s -X POST $BASE/api/price \
-H 'content-type: application/json' \
-d '{"story":"saw my regular doctor three times, then a cardiologist, an echocardiogram, blood work twice, and I missed work"}'{
"ok": true,
"input": "story",
"segments": [
{
"raw": "saw my regular doctor three times",
"times": 3,
"itemId": "cms-99213",
"label": "Doctor's office visit, established patient, low complexity",
"confidence": "DERIVED",
"matchedOn": "saw my regular doctor",
"matchScore": 100,
"valueUsd": 95.19,
"outOfPocketUsd": null,
"lineTotalUsd": 285.57,
"basis": "allowed",
"attribution": "gross",
"year": "2026",
"geography": "United States, national (geographic practice cost indices set to 1.000)",
"population": "Medicare Part B fee-for-service beneficiaries",
"coverage": "A follow-up visit with a doctor you have seen before, for a straightforward problem. HOW THIS NUMBER WAS MADE: …",
"sourceTitle": "CMS, CY2026 National Physician Fee Schedule Relative Value File (RVU26C, July release, published 2026-06-30)",
"sourceUrl": "https://www.cms.gov/medicare/payment/fee-schedules/physician/pfs-relative-value-files/rvu26c",
"code": "CPT 99213",
"summable": true
},
{
"…": "3 more segments, each with its own source"
}
],
"unpriced": [
{
"raw": "I missed work",
"kind": "known-unpriceable",
"unpriceableId": "lost-work",
"reason": "Shown on the workdays card, at the published median or your own pay, and never added to the medical total. The Bureau of Labor Statistics pu …",
"whatWouldFixIt": "Ask the person for their own hourly or weekly pay and multiply. That is arithmetic on a figure they supplied, …"
}
],
"totals": {
"totalUsd": 675.2,
"outOfPocketUsd": 0,
"outOfPocketReported": false,
"pricedCount": 4,
"unpricedCount": 1,
"basesUsed": [
"allowed"
]
},
"basisWarning": null,
"conflicts": [],
"nonSummable": [],
"excludedFromTotal": [],
"bundlingNote": null,
"tableVersion": "2026-09-08.1-verified",
"method": "A deterministic matcher maps words to a unit of care; a published federal table prices it. No model produces a dollar figure."
}The ledger as an HL7 FHIR R4 Bundle
Any ledger, as entries or as a story, comes back as a FHIR R4 Bundle of type collection, served as application/fhir+json.
GET /api/fhir/example
The example sentence in the journey builder’s empty box on /journey, “saw my regular doctor three times, then a cardiologist, an echo and a Holter, then the ER once when my heart was racing”, priced from the published table and returned as a FHIR R4 Bundle: 14 entries. The home page’s box shows a different one: three lines of the full example ledger.
POST /api/fhir
Send entries (the shape POST /api/journeys takes) or story (the shape POST /api/price takes). One Encounter per visit line, one Procedure per test or procedure line coded in CPT or HCPCS from the row itself, one ChargeItem per priced line whose priceOverride is the published figure and whose definitionUri is the federal file it came from, and one DocumentReference plus one Provenance per federal file.
No identity There is no Patient resource, no identifier element and no date of care in the bundle: the tool never collected them.
Not a bill Every ChargeItem carries status unknown: the figure is a published federal reference price, not a bill.
Envelope ?envelope=1 adds what was deliberately left out and why.
The example Bundle, as a tree
14 entries by resource type, one ChargeItem opened to its fields, and no Patient
- Bundle
- Encounter×3one per visit line
- Procedure×2one per test or procedure line, coded in CPT or HCPCS from the row itself
ChargeItem×5one per priced line: priceOverride is the published figure, definitionUri the federal file
- status
unknown - code
99213Doctor’s office visit, established patient, low complexity - quantity
3 - priceOverride
285.57USD, 3 x $95.19 - definitionUricms.gov, RVU26C
- subjectA person using Waypoint Ledger. No identity is recorded.
- status
- DocumentReference×2one per federal file
- Provenance×2one per federal file
- Patientnoneno Patient resource, no identifier, no date of care: the tool never collected them
Show the callHide the callTwo curl requests and the example Bundle, one ChargeItem shown
BASE=https://waypointledger.org
curl -s $BASE/api/fhir/example
# or any ledger, as entries or as a story, fitted to a person
curl -s -X POST "$BASE/api/fhir?envelope=1" \
-H 'content-type: application/json' \
-d '{"story":"saw my regular doctor three times, then an echocardiogram",
"coverage":"medicare", "state":"IA"}'{
"resourceType": "Bundle",
"meta": {
"source": "https://waypointledger.org/api/table",
"tag": [{
"system": "https://waypointledger.org/fhir/CodeSystem/price-table-version",
"code": "2026-09-09.1",
"display": "The price table this bundle was built from"
}]
},
"type": "collection",
"timestamp": "2026-09-30T22:52:42.076Z",
"entry": [
{
"fullUrl": "urn:uuid:99d5285c-ce27-4bad-94b3-3f4f9ca2d254",
"resource": {
"resourceType": "ChargeItem",
"status": "unknown",
"code": {
"coding": [{
"system": "http://www.ama-assn.org/go/cpt",
"code": "99213",
"display": "Doctor's office visit, established patient, low complexity"
}]
},
"subject": { "display": "A person using Waypoint Ledger. No identity is recorded." },
"quantity": { "value": 3 },
"priceOverride": { "value": 285.57, "currency": "USD" },
"overrideReason": "3 x $95.19 = $285.57. CMS, CY2026 National Physician Fee Schedule Relative Value File (RVU26C, July release, published 2026-06-30), 2026. Basis: allowed. … This is a published federal reference figure, not a bill …",
"definitionUri": ["https://www.cms.gov/medicare/payment/fee-schedules/physician/pfs-relative-value-files/rvu26c"],
"note": [{ "text": "The person described this as: \"saw my regular doctor three times\"" }]
}
},
{ "…": "13 more entries: 3 Encounter, 2 Procedure, 4 ChargeItem, 2 DocumentReference, 2 Provenance" }
]
}Nine more worked calls
Each with a real request and response. Open the one you need.
Read one unit of care, with its provenance
GET /api/table/cms-99213
Every row carries who it describes, who it does not, and the rules about adding it to anything else.
BASE=https://waypointledger.org
curl -s $BASE/api/table/cms-99213{
"ok": true,
"version": "2026-09-08.1-verified",
"item": {
"id": "cms-99213",
"label": "Doctor's office visit, established patient, low complexity",
"valueUsd": 95.19,
"basis": "allowed",
"year": "2026",
"population": "Medicare Part B fee-for-service beneficiaries",
"coverage": "A follow-up visit with a doctor you have seen before, for a straightforward problem. HOW THIS NUMBER WAS MADE: CMS does not publish a dollar column in …",
"sourceTitle": "CMS, CY2026 National Physician Fee Schedule Relative Value File (RVU26C, July release, pub …",
"sourceUrl": "https://www.cms.gov/medicare/payment/fee-schedules/physician/pfs-relative-value-files/rvu26c",
"confidence": "DERIVED",
"code": "CPT 99213",
"rules": {
"summable": true,
"mutuallyExclusiveWith": [],
"bundlesAncillaries": false,
"alternates": {
"…": "5 alternate measures of the same service (a CY2024 average allowed amount, an average submitted charge, the hospital outpatient facility fee), each with a note saying why it is an alternative and never an addition"
}
}
}
}What to notice. rules.summable and rules.mutuallyExclusiveWith are machine-readable, because a rule written only in prose is a wish. Ask /api/price for a whole-year figure alongside per-visit lines and it comes back priced and flagged in excludedFromTotal, with the conflict named: the year already contains the visits, so adding them counts the same care twice. The figure is never quietly dropped and never quietly summed.
Save a journey and get a link back
POST /api/journeys
Anonymous by default. A journey holds units of care, counts and the words the person typed. Nothing else.
BASE=https://waypointledger.org
curl -s -X POST $BASE/api/journeys \
-H 'content-type: application/json' \
-d '{"title":"Two years of looking",
"entries":[{"raw":"saw my regular doctor about the fatigue","itemId":"cms-99213","times":6},
{"raw":"echocardiogram","itemId":"cms-img-echo","times":1}]}'{
"ok": true,
"id": "91dec6a1-5886-417d-9078-c842aaa1d93c",
"slug": "7nvjqbdhmf",
"url": "http://localhost:8792/ledger?s=7nvjqbdhmf",
"savedTo": "link"
}GET /api/journeys/{slug} reads it back with the table version it was priced against, so a link opened next year still says which edition of the table it came from.
Price it for a person, not for the country
POST /api/price with coverage and state
Add coverage and state (or a CMS locality key) and the response carries the same answer the site shows that person: whether the published figure describes them, which figure applies instead if it does not, and what that service costs from the cheapest CMS locality to the dearest.
BASE=https://waypointledger.org
curl -s -X POST $BASE/api/price \
-H 'content-type: application/json' \
-d '{"story":"saw my regular doctor three times, then an echocardiogram",
"coverage":"uninsured", "state":"IA"}'{
"ok": true,
"input": "story",
"context": {
"coverage": "uninsured",
"locality": {
"key": "IA-00",
"name": "Iowa",
"state": "IA",
"stateName": "Iowa",
"mac": "05102",
"workGpci": 1,
"practiceExpenseGpci": 0.915,
"malpracticeGpci": 0.397
},
"localityFrom": "state",
"figureBasis": "Medicare allowed amounts for Iowa (CMS locality IA-00), CY2026 fee schedule formula"
},
"segments": [
{
"raw": "saw my regular doctor three times",
"times": 3,
"itemId": "cms-99213",
"valueUsd": 95.19,
"lineTotalUsd": 285.57,
"…": "basis, year, population, coverage, sourceTitle and sourceUrl as before",
"localityUsd": 89.23,
"localityName": "Iowa",
"nationalUsd": 95.19,
"fit": {
"verdict": "BILLED AGAINST THIS",
"why": "With no insurance you are billed the provider’s charge, not an allowed amount. This is the average charge submitted for this same service in CY2024.",
"figureUsd": 189.25,
"figureNote": "Average submitted charge, CY2024 · billed charge, never added to an allowed amount",
"which": "charge",
"offerGap": false,
"lineTotalUsd": 567.75
},
"localityRange": {
"nationalUsd": 95.19,
"lowUsd": 86.86,
"lowLocalityKey": "AR-13",
"lowLocalityName": "Arkansas",
"highUsd": 120.13,
"highLocalityKey": "CA-65",
"highLocalityName": "San Jose-Sunnyvale-Santa Clara (San Benito County)",
"localityCount": 109,
"formula": "(work RVU x work GPCI + non-facility PE RVU x PE GPCI + MP RVU x MP GPCI) x 33.4009"
}
},
{ "…": "1 more segment, priced and fitted the same way" }
],
"fitted": {
"totalUsd": 1258.3,
"suppressedReason": null,
"describedCount": 2,
"notDescribedCount": 0,
"figureKindsUsed": ["charge"],
"labels": {
"primary": "What providers billed on average for these services",
"secondary": "What the published Medicare figures add up to in Iowa"
},
"verdicts": [{ "verdict": "BILLED AGAINST THIS", "lines": 2 }],
"basisWarning": null
},
"totals": { "totalUsd": 482.3, "pricedCount": 2, "basesUsed": ["allowed"], "…": "" },
"tableVersion": "2026-09-08.1-verified"
}What to notice. valueUsd is still the published national figure and never moves. localityUsd is what Medicare allows in Iowa for the same code, from CMS’s own formula and the three geographic indices echoed in context. And because this caller is uninsured, fit.figureUsd is neither of those: it is the average charge providers submitted, because a charge is what an uninsured person is billed against. Three different true numbers for one line, each labelled with what it is.
When nothing published describes the person
POST /api/price returns null, never 0
Medicaid rates are set by each state and are in no national dataset. Roughly one in five Americans is on Medicaid, and they are over-represented in exactly the population this tool is built for. So the API does not return a zero.
BASE=https://waypointledger.org
curl -s -X POST $BASE/api/price \
-H 'content-type: application/json' \
-d '{"story":"saw my regular doctor three times, then an echocardiogram",
"coverage":"medicaid", "locality":"TX-18"}'"fitted": {
"totalUsd": null,
"suppressedReason": "No published federal figure describes this person on any line here, so there is no total to report. This is a gap in the published data, not a cost of zero. The lines carry what each figure is and who it does describe, and every one of them can be counted at POST /api/gap.",
"describedCount": 0,
"notDescribedCount": 2,
"figureKindsUsed": [],
"verdicts": [{ "verdict": "NOT DESCRIBED", "lines": 2 }]
}totalUsd is null, never 0, because a zero would be read as a price. Each line still comes back with the Medicare figure, what it is, and who it does describe. The honest output here is a counted gap, and POST /api/gap is where it goes.
Errors are sentences, and a value we cannot honour is never ignored
POST /api/price refuses, and says what to send
Before this round the API took a state, ignored it, and returned the national figure with no warning. It now refuses, and the refusal tells you what to send.
BASE=https://waypointledger.org
# a state with more than one CMS payment locality
curl -s -X POST $BASE/api/price -H 'content-type: application/json' \
-d '{"story":"a doctor visit","state":"TX"}'
{"ok":false,"error":"Texas has more than one CMS payment locality, so a state is
not enough to price a line. Send one of: TX-31 (Austin), TX-20 (Beaumont),
TX-09 (Brazoria), TX-11 (Dallas), TX-28 (Fort Worth), TX-15 (Galveston),
TX-18 (Houston), TX-99 (Rest Of Texas)."}
# a coverage we do not have a rule for
{"ok":false,"error":"coverage must be one of: employer, marketplace, medicaid,
medicare, uninsured, unsure. Send no coverage at all and every line comes back
as a reference price."}Take all 5,668 locality figures, with the inputs that made them
GET /data/locality-prices.csv
52 CMS Physician Fee Schedule codes priced for each of the 109 Medicare payment localities. Every row carries the three RVU components, the three geographic indices, the conversion factor, the formula written out, and the SHA256 of both CMS files it came from, so a state health department can filter it to their own state and recompute every figure without us.
BASE=https://waypointledger.org
curl -s $BASE/data/locality-prices.csv -o locality-prices.csv
head -1 locality-prices.csv
price_id,code,label,state,locality,locality_key,locality_name,mac,work_rvu,
pe_nonfacility_rvu,mp_rvu,pw_gpci,pe_gpci,mp_gpci,conversion_factor,allowed_usd,
national_usd,pct_of_national,formula,basis,year,population,locality_audit_status,
national_audit_status,rvu_file,rvu_file_sha256,gpci_file,gpci_file_sha256,table_version
grep '^cms-img-echo,.*,TX-18,' locality-prices.csv | cut -d, -f1,6,7,16,17
cms-img-echo,TX-18,HOUSTON,197.09,196.73Also as JSON with the formula, the sources and the audit; columns explained in locality-dictionary.csv. The generator is the audit: node scripts/gen-locality-table.mjs recomputes all 5,668 from the published RVUs and indices and exits non-zero on one cent of drift.
Ask for one place, not for the whole country
GET ?locality= or ?state= on any read route
A national Medicare figure is not what anyone is charged. Send ?locality= or ?state= to any read route and every row CMS prices geographically comes back as that place’s allowed amount, with the three relative value units, the three geographic indices and the formula that produced it: enough to re-derive the number without this API. valueUsd is never overwritten, so the national figure and the local one can never be confused for each other.
BASE=https://waypointledger.org
# one row, priced where the person actually lives
curl -s "$BASE/api/table/cms-99213?locality=IA-00"
# the whole table for one place, or a state where CMS gives it a single locality
curl -s "$BASE/api/table?locality=IA-00"
curl -s "$BASE/api/table?state=IA"
# the index of every locality, and every figure published for one of them
curl -s $BASE/api/localities
curl -s $BASE/api/localities/IA-00{
"ok": true,
"locality": {
"key": "IA-00", "name": "Iowa", "state": "IA", "stateName": "Iowa",
"mac": "05102", "workGpci": 1, "practiceExpenseGpci": 0.915, "malpracticeGpci": 0.397
},
"item": {
"id": "cms-99213",
"label": "Doctor's office visit, established patient, low complexity",
"code": "CPT 99213",
"valueUsd": 95.19,
"nationalUsd": 95.19,
"localityUsd": 89.23,
"localityGeography": "Iowa (CMS payment locality IA-00, Medicare Administrative Contractor 05102)",
"localityFormula": {
"code": "99213",
"text": "(work_rvu*pw_gpci + pe_nonfacility_rvu*pe_gpci + mp_rvu*mp_gpci) * 33.4009",
"conversionFactor": 33.4009,
"parts": [
{ "name": "Work", "rvu": 1.3, "gpci": 1, "product": 1.3 },
{ "name": "Practice expense", "rvu": 1.46, "gpci": 0.915, "product": 1.3359 },
{ "name": "Malpractice", "rvu": 0.09, "gpci": 0.397, "product": 0.03573 }
],
"rvuSum": 2.67163,
"total": 89.23
},
"…": "every other field of the row, unchanged"
},
"localityRange": {
"nationalUsd": 95.19,
"lowUsd": 86.86, "lowLocalityKey": "AR-13", "lowLocalityName": "Arkansas",
"highUsd": 120.13, "highLocalityKey": "CA-65",
"highLocalityName": "San Jose-Sunnyvale-Santa Clara (San Benito County)",
"localityCount": 109,
"formula": "(work RVU x work GPCI + non-facility PE RVU x PE GPCI + MP RVU x MP GPCI) x 33.4009"
}
}Never a silent fallback A place we cannot honour is a 400, never a silent national fallback. That was the defect worth fixing: an answer to a question nobody asked, returned with no warning, is wrong and looks right.
# a locality that does not exist: refused, never quietly national
{"ok":false,"error":"No CMS locality has the key \"IA-99\". A key is the two-letter
state and the two-digit CMS locality number, like \"IA-00\" or \"TX-31\". All 109 are
published at /data/locality-prices.json."}
# a state CMS splits into several: refused, and it names them
{"ok":false,"error":"Texas has more than one CMS payment locality, so a state is not
enough to price a line. Send one of: TX-31 (Austin), TX-20 (Beaumont), TX-09
(Brazoria), TX-11 (Dallas), TX-28 (Fort Worth), TX-15 (Galveston), TX-18 (Houston),
TX-99 (Rest Of Texas)."}Take the whole thing, and the catalog that describes it
GET /data.json and the source archive
/data.json is a DCAT-US v1.1 catalog (the metadata standard data.gov harvests) describing the price table, the locality table, the three register exports and the dictionary, each with its licence, its distributions, its data dictionary and the federal files it was built from. It validates against the government’s own published JSON Schema; the schema is vendored in this repository with its SHA-256 and the test re-hashes it before it validates anything.
The application itself is downloadable in one file. Not a description of a repository: the source, the data, the migrations, the tests and a README you can run from.
BASE=https://waypointledger.org
# the catalog, in the shape data.gov harvests
curl -s $BASE/data.json
# the whole application: source, data, migrations, tests, and a README to run from
curl -sL $BASE/waypoint-ledger-source.tar.gz | tar -xz && cd waypoint-public
npm install
npm run build:static # the static export the size budgets measure
npm test # the suite that guards every rule below
npm run dev # http://localhost:3000The code is Apache-2.0; the data is CC0 1.0, public domain. NOTICE says which is which, line by line. The five DCAT-US fields that belong to federal agencies (bureauCode, programCode, dataQuality, primaryITInvestmentUII, systemOfRecords) are absent from our catalog, as the standard’s own guidance directs for a non-federal publisher. We are not an agency, and an OMB bureau code we do not have would be exactly the kind of invented federal number this project exists to refuse.
A correction, addressed to the body that published the number
GET /api/citation/cms-99213?format=text
A thumb on a ledger line is bound to one published federal row. This is that row’s provenance and the public counts in one call: no bundle, no join, no account. Add ?format=text and there is nothing to parse.
BASE=https://waypointledger.org
curl -s "$BASE/api/citation/cms-99213?format=text"PUBLIC CORRECTION REPORT: Waypoint Ledger
Published figure: Doctor's office visit, established patient, low complexity
Row identifier: cms-99213 · code CPT 99213
Published value: $95.19 (2026)
Basis: allowed (the negotiated or fee-schedule amount)
Geography: United States, national (geographic practice cost indices set to 1.000)
Population: Medicare Part B fee-for-service beneficiaries
Published by: Centers for Medicare & Medicaid Services
Source: CMS, CY2026 National Physician Fee Schedule Relative Value File (RVU26C, July release, published 2026-06-30)
Source URL: https://www.cms.gov/medicare/payment/fee-schedules/physician/pfs-relative-value-files/rvu26c
What the public said about this row: 0 responses: 0 say the figure describes them, 0 say it does not.
Median amount respondents said they actually paid: none reported.
Sample: self-selected members of the public using a free tool. Counts are reported exactly as entered: no weighting, no imputation, no extrapolation to a population.
Counted as of: 2026-09-09
Method and every source: https://waypointledger.org/methodWithout ?format=text the same call returns JSON with the figure, the publisher, the document, the source URL and the counts as separate fields, plus this block in text. Every row of the table answers, including the rows nobody has spoken about yet.
What you can take without asking
The register is public: what people said a federal figure got wrong, what care never entered any claims file, and how the people who carried the burden ranked it. Counts and a de-identified CSV, not free text.
The survey and gap exports carry no one’s self-description
The day an answer arrived, never the time. Who answered is published only as counts.
Each row of survey.csv and gap.csv gives the day it arrived (received_on), never the time. Who answered is published only as counts, at /api/survey and /api/gap, and any value held by fewer than 11 answers is counted as “fewer than 11, not shown” on the server and never named. Their prev_hash and row_hash columns let you check the chain link by link against /api/integrity. corrections.csv carries every field its hashes cover, so every hash in it can be recomputed from the file alone.
The corrections export is a defect report, not a comment box
Every row is bound to the published federal row and the file it came from. Free text is never exported.
Every row carries the price row id, the CPT or HCPCS code (and the LOINC code where the row is a lab), the published figure with its basis, year, geography and population, the federal file by name with its URL, its SHA-256 and the day we read it, the exact line or arithmetic the figure was re-read from, the audit verdict, the direction of the correction, the counts and fit rate on that figure, the chained row hash, and a permalink that renders it as a paste-ready report for the agency that published the number. The first line of the CSV is a comment naming the price-table version and the audit; /api/export/corrections.json publishes the same rows with the column contract beside them. Free text is never exported.
- Everything, one call
- The whole price table, with provenance
- All 5,668 CMS locality figures
- One CMS payment locality, priced
- The open-data catalog (DCAT-US v1.1)
- The application itself, to run yourself
- The licence, as a file counsel can read
- One figure’s provenance and counts
- The hash-chain head of every count
- Corrections to published figures
- Care that no claim recorded
- How people ranked the burdens
- Written interviews (count only, ever)
- What changed because someone said so
Reuse it in your agency
Three things another agency, a health system or a clinic can take today, without asking us.
The HL7 FHIR R4 Bundle, checked by HL7’s own validator
On September 30, 2026 we ran the official HL7 FHIR Validator 6.10.4 (validator_cli.jar, Git 1b90fb13f77b, built 2026-09-04, from the org.hl7.fhir.core releases) on the Bundle that /api/fhir/example returns: 14 entries (3 Encounter, 5 ChargeItem, 2 Procedure, 2 DocumentReference, 2 Provenance), against FHIR 4.0.1 (hl7.fhir.r4.core#4.0.1), on OpenJDK 17.0.20.1.
Result:0 errors,14 warnings,6 information notes.
Separately, the test suite checks every Bundle we build against HL7’s official R4 JSON schema on every run.
Show the runHide the runWhat the warnings and notes say, and the two commands to check it yourself
- All 14 warnings are the same best-practice rule, dom-6: “A resource should have narrative for robust management”. The Bundle carries codes and figures, not a human-readable summary inside each resource.
- The 6 notes: five say the currency code USD was not checked, because we ran with no terminology server; one says the validator had not loaded our own code system for the price-table tag, which is served at /fhir/CodeSystem/price-table-version.
- An earlier run the same day found one error: an extension whose definition could not be fetched. Bundles no longer carry that extension, and its definition is now served at /fhir/StructureDefinition/cms-apc.
Check it yourself. The jar we ran has SHA-256 1106b9d58f9e363e47bea7c4fc065841e5fc91fe9d062775c3bfdd212bd653cc. Each Bundle gets fresh identifiers, so its own hash differs every time; the entry count and the result do not.
curl -s https://waypointledger.org/api/fhir/example > example.json
java -jar validator_cli.jar example.json -version 4.0.1 -tx n/a A button for your own website
One line of plain HTML. No script, no cookie, any content security policy.
One line of plain HTML puts a “Price your diagnostic journey” button on a clinic, patient organization or agency page. It opens Waypoint Ledger in a new tab. It runs no code on your site, sets no cookie and passes any content security policy.
<a href="https://waypointledger.org/" target="_blank" rel="noopener" style="display:inline-block;min-height:44px;box-sizing:border-box;padding:12px 20px;border-radius:999px;background:#0e2a3a;color:#fff;font:600 16px/1.2 system-ui,sans-serif;text-decoration:none">Price your diagnostic journey</a>A patient organization that wants its own survey link, QR code and printable flyer can make them in a minute, with no code, at /adopt/kit.
Run your own copy in three steps
Download it, run the tests, deploy it on your own Cloudflare account.
- Download the whole application: source, data, migrations and tests, in one archive.
- Run
npm install,npm run build:staticandnpm test. The suite checks, as tests, that no code path can produce a dollar figure. - Create the database and deploy it on your own Cloudflare account with the commands on /adopt, then add your condition, state or population.
The code is Apache-2.0 and the data is CC0 1.0. Nothing in the archive talks to us.
The rules
No model makes a dollar figure, figures that cannot be added are not, and errors are sentences. Rate limits and privacy.
No model, average or interpolation ever produces a dollar figure.
A phrase that maps to nothing stays unpriced and is returned as unpriced, with the reason.
Figures that cannot be added are not added.
Measures that answer different questions raise
basisWarning; a whole-year figure is returned outside the total inexcludedFromTotal.Rate limits, per network per hour
300 to
/api/price, 30 to/api/journeys, 300 to/api/survey(a flood guard only: a person or a clinic is never refused), 5 an hour (10 a day) to/api/interview, 300 to/api/gapand/api/corrections(corrections also take at most 3 an hour and 8 a day on any one figure from one network), 20 to each other write route. Over the limit returns 429 with a sentence that says when to try again, and aretry-afterheader. Write routes refuse requests sent from another website;/api/price,/api/mapand/api/fhiraccept them, and the answers of/api/priceand/api/fhirare readable from any origin.Privacy is the precondition.
No account is required and none is offered by these routes. No IP address is stored; rate limiting hashes it with a daily salt and forgets it within the hour. Free text sent to the register is held privately and is never served back or exported.
Errors are sentences.
Every failure is
{ ok: false, error }with the right status, written for a person reading a log.
Why reuse this
The hard part of a cost-of-illness number is knowing which published figure applies.
The hard part of a cost-of-illness number is not arithmetic. It is knowing which published figure applies, what it measures, who it leaves out, and what it must not be added to. That work is in the table and its rules, and this API hands you all of it with every response.
A state health agency costing a care pathway, a patient organization building its own tool, a researcher who needs the same unit prices we used: none of you need our interface, and none of you should have to take our word for a number.
If you want to stand the whole thing up for your own condition, your own state or your own population, /adopt is the procedure: the files to edit, in order, and the two commands that refuse a figure which does not reproduce. The whole application is one download, and it runs on your own machine in four commands, so none of that is a promise you have to take on trust.
Questions, or a row you think is wrong: Contact.
Every route
51 routes, each with its method and what it does. The full description, with every request and response schema, is at /api/openapi.json. Administrative routes take a bearer token and are not published there.
Pricing
7 routes
The price table and the pricing call.
- GET
/api/healthService and table version. - POST
/api/healthProve the database accepts a write, not just a read. - GET
/api/tableThe whole price table with provenance. - GET
/api/table/{id}One unit of care. - POST
/api/pricePrice a story, or a list of units of care. - POST
/api/mapAI reads a story into units of care; the published table prices them. - GET
/api/openapi.jsonThis description.
Journeys
4 routes
Save a ledger and read it back by link.
- POST
/api/journeysSave a journey and get a link back. - GET
/api/journeys/{id}Read a shared journey. - PUT
/api/journeys/{id}Replace the lines of a journey you own. - DELETE
/api/journeys/{id}Delete a journey, with the session that owns it, or with the delete token it was saved with.
Register
20 routes
The public counts: corrections, gaps, the burden survey, the change log.
- GET
/api/registerEvery public count in one call. - GET
/api/correctionsCounts of right and wrong per federal figure. - POST
/api/correctionsSay a published figure is right or wrong for you. - GET
/api/gapThe measured shape of what claims data cannot see. - POST
/api/gapReport care you needed and did not get. - GET
/api/surveyThe community’s ranking of which burden weighed most, with its N. - POST
/api/surveyRank the five burdens. - GET
/api/survey/channelThe answers that came in through one survey link (?c=), under the small-cell rule. - GET
/api/interviewHow many written interviews have been received. Nothing else, ever. - POST
/api/interviewSend a written interview. - GET
/api/changesThe published change log: what someone said, and what changed because of it. - GET
/api/annotationsEvery row we marked as our own team testing the form, by row_hash. Marked rows stay in the chain and the CSV, and are left out of every count. - GET
/api/useCounts of actions by week since counting began: counts of actions, never of who took them. - POST
/api/useThe browser reports one action it does on its own. No body is read. - GET
/api/policyThe policy view: burden figures per condition, the burden ranking, and corrections rolled up by agency and fee schedule. - GET
/api/integrityThe hash-chain head of every public count. - GET
/api/citation/{id}One published figure, its provenance and what the public said about it. - GET
/api/export/{kind}.csvThe de-identified register as CSV. - GET
/api/export/corrections.csvCorrections to published federal figures, joined to the file each figure came from. - GET
/api/export/corrections.jsonThe same corrections file as JSON, shaped as a DCAT distribution, with its column contract.
Account
10 routes
Optional passkey sign-in so a ledger follows a person across devices.
- POST
/api/auth/register/optionsBegin creating a passkey. - POST
/api/auth/register/verifyFinish creating a passkey and start a session. - POST
/api/auth/login/optionsBegin signing in with a passkey. - POST
/api/auth/login/verifyFinish signing in; sets the session cookie. - POST
/api/auth/logoutEnd the session and clear the cookie. - GET
/api/meWho the session belongs to, or null. - PUT
/api/meSet a display name. - DELETE
/api/meErase the account and everything it owns. - GET
/api/me/correctionsThe corrections this account has sent. - GET
/api/me/journeysThe journeys saved to this account.
Interoperability
4 routes
The ledger as HL7 FHIR R4, so another system reads it without a bespoke parser.
- POST
/api/fhirA ledger as an HL7 FHIR R4 collection Bundle. - GET
/api/fhir/exampleThe example journey, already converted. - GET
/fhir/StructureDefinition/{id}A FHIR StructureDefinition this site names, served at its own canonical URL. - GET
/fhir/CodeSystem/{id}A FHIR CodeSystem this site names, served at its own canonical URL.
Open data
6 routes
Whole files, no request body, nothing to join back to us.
- GET
/api/localitiesThe 109 places Medicare prices separately. - GET
/api/localities/{key}One locality, and every figure published for it. - GET
/api/private-rates/{code}Hospital-published negotiated rates for one code (45 CFR 180), with every hospital and file hash. - GET
/data/locality-prices.csvEvery CMS payment locality figure behind this tool, as CSV. - GET
/data/locality-prices.jsonThe same 5,668 figures as JSON, with the formula, the sources and the audit. - GET
/data/price-table.csvThe whole national price table as CSV, with provenance and audit verdict.

