medicalfeeschedules.com

API reference

A REST API over computed, versioned Medicare Physician Fee Schedule rates. Every response echoes the release that answered it and cites the CMS file and row behind every number.

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.
citations — abbreviated
{
  "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.

GET /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+).
request
curl "https://medicalfeeschedules.com/v1/rates/99213?locality=01182-18&setting=non_facility" \
  -H "Authorization: Bearer sk_live_…"
response — 200
{
  "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.

POST /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.
request
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"}'
response — 200
{
  "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.

POST /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.
request
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"}'
response — 200
{
  "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.

GET /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.
request
curl https://medicalfeeschedules.com/v1/zip/90210 \
  -H "Authorization: Bearer sk_live_…"
response — 200
{
  "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.

POST /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.
request
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"}]}'
response — 200
{
  "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.

GET /v1/localities
response — 200
{
  "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.

GET /v1/releases
response — 200
{
  "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.