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.
No key, no account, no sign-up. Every route below is open. GET routes send access-control-allow-origin: *, so a browser app can call them directly. The price table is version 2026-09-09.1: 274 rows, 251 of them priced and addable, each with the file it was read from.
On this page — 6 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.
1 · The call the site itself makes
One request, one journey, every line traceable.
BASE=https://waypoint-ledger.pages.dev
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"}'Real response. Long coverage statements and three further segments are elided, marked with an ellipsis key:
{
"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": "Valuing a lost workday requires your actual earnings, not a national median. 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."
}What to notice. matchedOn shows the exact words in the table the phrase matched, so the mapping is never a black box. “I missed work” comes back in unpriced with 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. And 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.
Nine more worked calls
2 · Read one unit of care, with its provenance
Every row carries who it describes, who it does not, and the rules about adding it to anything else.
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.
3 · Save a journey and get a link back
Anonymous by default. A journey holds units of care, counts and the words the person typed. Nothing else.
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-99214","times":6},
{"raw":"echocardiogram","itemId":"cms-img-echo","times":1}]}'Real response — the url echoes the host you called; this run was against a local instance of this code:
{
"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.
4 · Price it for a person, not for the country
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.
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"}'Real response. Long fields already shown above are elided with an ellipsis key:
{
"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.
5 · When nothing published describes the person
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.
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.
6 · Errors are sentences, and a value we cannot honour is never ignored
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.
# 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."}7 · Take all 5,668 locality figures, with the inputs that made them
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.
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.
8 · Ask for one place, not for the whole country
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.
# 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-00Real response, one field elided and marked:
{
"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"
}
}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)."}9 · Take the whole thing, and the catalog that describes it
/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.
# 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 test
npm run devThe 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.
10 · A correction, addressed to the body that published the number
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.
curl -s $BASE/api/citation/cms-99213?format=textPUBLIC 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://waypoint-ledger.pages.dev/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 corrections export is a defect report, not a comment box. 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.
| Data | JSON | CSV |
|---|---|---|
| Everything, one call | /api/register | — |
| The whole price table, with provenance | /api/table | price-table.csv |
| All 5,668 CMS locality figures | locality-prices.json | locality-prices.csv |
| One CMS payment locality, priced | /api/localities/{key} | — |
| The open-data catalog (DCAT-US v1.1) | /data.json | — |
| The application itself, to run yourself | waypoint-ledger-source.tar.gz | — |
| The licence, as a file counsel can read | LICENSE | NOTICE |
| One figure’s provenance and counts | /api/citation/{id} | as plain text |
| The hash-chain head of every count | /api/integrity | — |
| Corrections to published figures | /api/corrections | corrections.csv · corrections.json |
| Care that no claim recorded | /api/gap | gap.csv |
| How people ranked the burdens | /api/survey | survey.csv |
| Written interviews (count only, ever) | /api/interview | — |
| What changed because someone said so | /api/changes | — |
Every route
| Method | Path | What it does |
|---|---|---|
GET | /api/health | Service and table version. |
POST | /api/health | Prove the database accepts a write, not just a read. |
GET | /api/table | The whole price table with provenance. |
GET | /api/table/{id} | One unit of care. |
GET | /api/localities | The 109 places Medicare prices separately. |
GET | /api/localities/{key} | One locality, and every figure published for it. |
POST | /api/price | Price a story, or a list of units of care. |
POST | /api/map | AI reads a story into units of care; the published table prices them. |
POST | /api/fhir | A ledger as an HL7 FHIR R4 collection Bundle. |
GET | /api/fhir/example | The demo journey, already converted. |
POST | /api/journeys | Save 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. |
GET | /api/register | Every public count in one call. |
GET | /api/corrections | Counts of right and wrong per federal figure. |
POST | /api/corrections | Say a published figure is right or wrong for you. |
GET | /api/gap | The measured shape of what claims data cannot see. |
POST | /api/gap | Report care you needed and did not get. |
GET | /api/survey | The community’s ranking of which burden weighed most, with its N. |
POST | /api/survey | Rank the five burdens. |
GET | /api/interview | How many written interviews have been received. Nothing else, ever. |
POST | /api/interview | Send a written interview. |
GET | /api/changes | The published change log: what someone said, and what changed because of it. |
GET | /api/integrity | The hash-chain head of every public count. |
GET | /api/citation/{id} | One published figure, its provenance and what the public said about it. |
GET | /data/locality-prices.csv | Every CMS payment locality figure behind this tool, as CSV. |
GET | /data/locality-prices.json | The same 5,123 figures as JSON, with the formula, the sources and the audit. |
GET | /data/price-table.csv | The whole national price table as CSV, with provenance and audit verdict. |
GET | /api/export/{kind}.csv | The de-identified register as CSV. |
GET | /api/export/corrections.csv | Corrections to published federal figures, joined to the file each figure came from. |
GET | /api/export/corrections.json | The same corrections file as JSON, shaped as a DCAT distribution, with its column contract. |
GET | /api/openapi.json | This description. |
POST | /api/auth/register/options | Begin creating a passkey. |
POST | /api/auth/register/verify | Finish creating a passkey and start a session. |
POST | /api/auth/login/options | Begin signing in with a passkey. |
POST | /api/auth/login/verify | Finish signing in; sets the session cookie. |
POST | /api/auth/logout | End the session and clear the cookie. |
GET | /api/me | Who the session belongs to, or null. |
PUT | /api/me | Set a display name. |
DELETE | /api/me | Erase the account and everything it owns. |
GET | /api/me/corrections | The corrections this account has sent. |
GET | /api/me/journeys | The journeys saved to this account. |
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.
The rules
- 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, 20 to each write route. Over the limit returns 429 with a sentence, not a code. - 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 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 application is a single download and runs on your machine in three commands, so none of that is a promise you have to take on trust.
Questions, or a row you think is wrong: bo@precisionfederal.com.