Overview
All endpoints are mounted under /v1 and
return JSON. There is no separate sandbox — every key works against the same live, versioned
data CMS publishes.
| Param | Type | Description |
|---|---|---|
| Base URL | https://medicalfeeschedules.com/v1 | |
| Format | JSON request bodies (POST) and JSON responses | |
| Auth | Bearer token on every request — see Authentication |
Authentication
Every request — even against the free tier — needs a valid Sanctum API token in the
Authorization header.
A missing or invalid token returns 401.
curl https://medicalfeeschedules.com/v1/rates/99213 \
-H "Authorization: Bearer sk_live_…"
Create an account to get a token.
Rate limits
The API is metered in cost-weighted units, not requests: a single-code (or single-ZIP) lookup
costs 1 unit, and a bulk request costs one unit per code/ZIP in the batch — so a 500-code
bulk call can't undercut the limit by counting as one request. Both a per-minute burst budget
and a per-account daily quota apply, keyed to your account (all of an account's tokens share
one quota). Every response carries the current budget in headers; exceeding either limit
returns 429 with a Retry-After header.
| Param | Type | Description |
|---|---|---|
| X-RateLimit-Limit | header | The per-account daily unit quota. |
| X-RateLimit-Remaining | header | Units left in the current day. |
| Retry-After | header | Seconds until the exceeded window resets (429 responses only). |
Historical lookups
Releases are immutable and versioned by (schedule, year, quarter): A = Jan, B = Apr, C = Jul,
D = Oct. Every rate and ZIP endpoint accepts either an explicit year+quarter
pair or an as_of calendar date (mutually exclusive — supplying
both is a 422). Omit both for the current release.
| Param | Type | Description |
|---|---|---|
| year | integer | Release year, e.g. 2026. Requires quarter. |
| quarter | A|B|C|D | Release quarter. Requires year. |
| as_of | date | Resolve the release effective on this calendar date instead of a specific quarter. |
Citations & provenance
Every payable rate (and every resolved ZIP) carries a citations object
naming the exact CMS file and row each number came from, plus the arithmetic it was plugged
into — enough to defend a number on an appeal without re-deriving it yourself.
| Param | Type | Description |
|---|---|---|
| sources | object | One entry per CMS dataset this line's numbers came from (e.g. "rvu", "gpci", "conversion_factor", "zip_locality"). Keys vary by what the calculation needed. |
| sources.*.file | string | The specific file inside the CMS release the value was read from, e.g. "PPRRVU2026_Jan_nonQPP.csv". |
| sources.*.cms_page_url / download_url | string | The CMS page the file is published on, and a direct file link when CMS provides a stable one. |
| sources.*.row | integer | The 1-based row inside the file the value came from — cite this in an appeal alongside the file name. |
| sources.*.values | object | The raw values read from that row, keyed by field name. |
| calculation | object | The formula and the exact inputs it was evaluated with, so the amount can be reproduced by hand. |
{
"sources": {
"rvu": {
"dataset": "rvu",
"label": "Physician relative value file (PPRRVU)",
"release": "2026A",
"cms_page_url": "https://www.cms.gov/medicare/payment/fee-schedules/physician",
"file": "PPRRVU2026_Jan_nonQPP.csv",
"row": 4118,
"values": { "work_rvu": { "value": 1.30 }, "status_code": { "value": "A" } }
},
"gpci": {
"dataset": "gpci",
"label": "Geographic practice cost indices (GPCI)",
"release": "2026A",
"file": "GPCI2026.csv",
"row": 33,
"values": { "work_gpci": { "value": 1.045 } }
}
},
"calculation": {
"formula": "(work_rvu × work_gpci + pe_rvu × pe_gpci + mp_rvu × mp_gpci) × conversion_factor",
"inputs": { "work_rvu": 1.30, "work_gpci": 1.045, "pe_rvu": 1.02, "pe_gpci": 1.10, "mp_rvu": 0.09, "mp_gpci": 0.98, "conversion_factor": 33.29 }
}
}
Single rate lookup
Price one HCPCS/CPT code for a locality (or the national amount, if none is given) and setting.
/v1/rates/{hcpcs}
| Param | Type | Description |
|---|---|---|
| locality | string | Composite key "{mac}-{code}", e.g. "01182-18" — see /v1/localities. |
| locality_id | integer | Numeric locality ID, as an alternative to the composite key. |
| zip | string | A ZIP resolves the locality via the versioned crosswalk. Combined with an explicit locality, it must agree (422 otherwise). An ambiguous ZIP is rejected — see GET /v1/zip/{zip} for the same ZIP resolved on its own, which answers ambiguous ZIPs instead of erroring. |
| setting | facility|non_facility | Which practice-expense RVU to use. Default non_facility. |
| modifier | string | Up to 2 characters, e.g. "26". |
| sequestration | boolean | Apply the ~2% sequestration reduction after the formula. Default false. |
| time_minutes | number | Anesthesia only: raw minutes, to compute a real anesthesia amount for status J codes. |
| qpp | boolean | Physician QPP conversion-factor split (CY2026+). |
curl "https://medicalfeeschedules.com/v1/rates/99213?locality=01182-18&setting=non_facility" \
-H "Authorization: Bearer sk_live_…"
{
"hcpcs": "99213",
"modifier": null,
"setting": "non_facility",
"payable": true,
"amount": 97.20,
"gross_amount": 97.20,
"status_code": "A",
"sequestration_applied": false,
"conversion_factor": 33.29,
"qpp": false,
"citations": { "sources": { "rvu": { … }, "gpci": { … } }, "calculation": { … } },
"release": { "schedule": "pfs", "year": 2026, "quarter": "A", "revision": 1 },
"locality": { "mac": "01182", "code": "18", "state": "CA", "name": "Los Angeles, CA" }
}
See Citations & provenance above
for the full shape of citations. A non-payable result omits
amount/gross_amount and returns
reason instead.
Bulk rate lookup
Price up to 500 codes in one request against a shared locality/setting/release context. A code
that can't be priced doesn't fail the batch — it comes back with payable: false and
a reason.
/v1/rates/bulk
| Param | Type | Description |
|---|---|---|
| codes | array — required | Up to 500 entries. Each is a string ("99213", "71046-26") or an object { code, modifier?, time_minutes?, qpp? } for per-line anesthesia claims. |
| locality / locality_id / zip | string / integer / string | Same as the single lookup — shared across every code in the batch. |
| setting, sequestration, year, quarter, as_of | Same as the single lookup — shared across every code in the batch. |
curl -X POST https://medicalfeeschedules.com/v1/rates/bulk \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"codes": ["99213", "99214-26"], "locality": "01182-18"}'
{
"release": { "schedule": "pfs", "year": 2026, "quarter": "A", "revision": 1 },
"locality": { "mac": "01182", "code": "18", "state": "CA", "name": "Los Angeles, CA" },
"setting": "non_facility",
"sequestration_requested": false,
"count": 2,
"results": [
{ "hcpcs": "99213", "modifier": null, "amount": 97.20, "payable": true, … },
{ "hcpcs": "99214", "modifier": "26", "amount": 135.61, "payable": true, … }
]
}
Claim check
Check the lines of one claim (one patient, one date of service) against the rules that only
exist between lines: the multiple-procedure reduction (MPPR) ranks the surgical lines
and pays 100%/50%/50%…, NCCI procedure-to-procedure (PTP) edits flag pairs that bundle
(including whether a 59/X{EPSU} modifier bypasses the edit), and medically
unlikely edits (MUE) check units per line or summed per date of service, per each code's
adjudication indicator.
PTP and MUE verdicts are edit facts, not deductions — a flagged line keeps its computed amount and the flag says why a payer would deny it. Two releases answer a claim check and both are echoed: the PFS release that priced the lines and the NCCI release that supplied the edits. When no NCCI release covers the period, the edit sections are absent and a warning says so.
/v1/claims/check
| Param | Type | Description |
|---|---|---|
| lines | array — required | Up to 50 claim lines, each an object { code, modifier?, units?, time_minutes? }. The whole claim is one patient on one date of service — that scope is what makes the cross-line rules meaningful. |
| lines.*.modifier | string | The billed modifier. Informational modifiers (59, XE/XS/XP/XU, 25, 51, …) are accepted: they don't change the fee-schedule amount, but 59/X{EPSU} drive the NCCI PTP bypass check. |
| lines.*.units | integer | Units of service on the line (default 1). Units feed the MUE check only; amounts are always per single unit of service. |
| qpp | boolean | Claim-level (one claim has one rendering provider): use the QP conversion factor (CY2026+). |
| locality / locality_id / zip | string / integer / string | Same as the single lookup — shared across every line on the claim. |
| setting, sequestration, year, quarter, as_of | Same as the single lookup. as_of doubles as the claim's date of service for the PTP effective/deletion windows. |
curl -X POST https://medicalfeeschedules.com/v1/claims/check \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"lines": [{"code": "27447"}, {"code": "27369", "modifier": "59", "units": 1}], "locality": "01182-18"}'
{
"release": { "schedule": "pfs", "year": 2026, "quarter": "A", "revision": 1 },
"ncci_release": { "schedule": "ncci", "year": 2026, "quarter": "A", "revision": 1 },
"locality": { "mac": "01182", "code": "18", "state": "CA", "name": "Los Angeles, CA" },
"setting": "non_facility",
"sequestration_requested": false,
"count": 2,
"lines": [
{
"line": 0, "hcpcs": "27447", "modifier": null, "units": 1,
"payable": true, "amount": 1391.42, "gross_amount": 1391.42,
"rate": { "hcpcs": "27447", "amount": 1391.42, "payable": true, … },
"mppr": { "rank": 1, "percentage_applied": 1.0, "adjusted_amount": 1391.42, "reduction_applied": false },
"mue": { "within_limit": true, "limit": 1, "adjudication_indicator": 2, "units_counted": 1, … }
},
{
"line": 1, "hcpcs": "27369", "modifier": "59", "units": 1,
"payable": true, "amount": 103.55, "gross_amount": 103.55,
"rate": { "hcpcs": "27369", "amount": 207.10, "gross_amount": 207.10, "payable": true, … },
"mppr": { "rank": 2, "percentage_applied": 0.5, "base_allowed": 207.10, "adjusted_amount": 103.55, "reduction_applied": true },
"mue": { … }
}
],
"ptp_edits": [
{
"column1": { "line": 0, "hcpcs": "27447" },
"column2": { "line": 1, "hcpcs": "27369" },
"modifier_indicator": "1",
"bundled": true, "bypassable": true, "billable": true,
"reason": "Modifier indicator 1: bypassed by modifier 59.",
"citation": { "file": "ccipra-…csv", "row": 118204, … }
}
]
}
The per-line audit chain reads rate.gross_amount (the base allowed amount)
→ mppr.adjusted_amount (after ranking) → amount (after
sequestration, when requested). Amounts are per single unit of service; MPPR ranking for indicators
with a specialized CMS rule we don't model (endoscopy families, imaging/therapy component reductions)
is never guessed — those lines come back at full amount with a reason.
Single ZIP → locality lookup
Resolve a ZIP code to its Medicare payment locality on its own, without also pricing a code —
useful for populating a locality picker or validating an address book against the crosswalk.
Accepts a bare 5-digit ZIP, a "ZIP+4" (90210-1234 or 902101234),
or a separate plus4 parameter.
This differs from passing zip= to the rate endpoints: a rate
lookup must reject an ambiguous ZIP outright, because silently picking a locality risks
pricing at the wrong rate. This endpoint instead returns the dominant locality with
ambiguous: true and a note — a useful answer on its own, honestly labeled.
/v1/zip/{zip}
| Param | Type | Description |
|---|---|---|
| plus4 | string | The ZIP+4 extension, if not already embedded in the path. |
| year, quarter, as_of | Resolve against a historical crosswalk release instead of the most recently published one. |
curl https://medicalfeeschedules.com/v1/zip/90210 \
-H "Authorization: Bearer sk_live_…"
{
"status": "exact",
"input_zip": "90210",
"zip5": "90210",
"plus4": null,
"mac_code": "01182",
"locality_code": "18",
"locality": { "id": 1, "name": "Los Angeles, CA", "state": "CA" },
"ambiguous": false,
"crosswalk_release": { "year": 2026, "quarter": "A" },
"reason": null,
"citations": { "sources": { "zip_locality": { "file": "ZIP5_2026.csv", "row": 4, … } } }
}
status is one of exact, ambiguous,
not_found, or invalid_zip. Only
not_found and invalid_zip return a non-200 status (404).
Bulk ZIP → locality lookup
Resolve up to 500 ZIPs in one request against a shared crosswalk release. Every entry resolves
independently and always comes back in results — an unresolvable
ZIP doesn't fail the batch, it comes back with its own status and reason.
/v1/zip/bulk
| Param | Type | Description |
|---|---|---|
| zips | array — required | Up to 500 entries. Each is a string ("90210", "90210-1234") or an object { zip, plus4? }. |
| year, quarter, as_of | Resolve every ZIP in the batch against a historical crosswalk release instead of the most recently published one. |
curl -X POST https://medicalfeeschedules.com/v1/zip/bulk \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{"zips": ["90210", "35801", {"zip": "90211"}]}'
{
"count": 3,
"crosswalk_release": { "year": 2026, "quarter": "A" },
"results": [
{ "status": "exact", "zip5": "90210", "mac_code": "01182", "locality_code": "18", … },
{ "status": "exact", "zip5": "35801", "mac_code": "10112", "locality_code": "00", … },
{ "status": "ambiguous", "zip5": "90211", "mac_code": "01182", "locality_code": "18", … }
]
}
Locality directory
Every Medicare payment locality currently defined. The locality field
is the composite key the rate endpoints accept.
/v1/localities
{
"count": 112,
"localities": [
{ "id": 1, "locality": "01182-18", "mac": "01182", "code": "18", "state": "CA", "name": "Los Angeles, CA" }
]
}
Release directory
Every (schedule, year, quarter) release published, newest first — including the
zip_locality crosswalk releases the ZIP endpoints resolve against.
/v1/releases
{
"count": 6,
"releases": [
{ "schedule": "pfs", "year": 2026, "quarter": "A", "published_at": "2026-01-01" },
{ "schedule": "zip_locality", "year": 2026, "quarter": "A", "published_at": "2026-01-01" }
]
}
Errors
Errors carry a client-safe error or reason string
explaining what went wrong — never a bare status code.
| Param | Type | Description |
|---|---|---|
| 401 | Missing or invalid API token. | |
| 404 | The code, locality, ZIP, or release requested doesn't exist or isn't in any published crosswalk. | |
| 422 | Contradictory input — e.g. a ZIP and an explicit locality that disagree, or both year/quarter and as_of supplied. | |
| 429 | Per-minute or per-day rate limit exceeded. See the Retry-After header. |