API documentation

Every number on this site is an API call. Here is how to make them yourself.

Project Crossfoot publishes two read-only JSON APIs — one per analysis. They are the same endpoints the site's own pages and charts run on: when a chart drills down to its underlying rows, the panel that opens is one of these URLs, rendered. There is no separate "API product" that can drift from what the pages show.

No key, no registration, no rate card. The data is publicly funded and legislatively mandated; the API is how it stays public in a form machines can use.

01 · Conventions

How every endpoint behaves

Both APIs follow the same contract, so anything learned on one carries to the other. Everything is a GET request returning application/json; nothing on this site writes, so there is nothing to authenticate.

Requests

Every endpoint answers GET and sends Access-Control-Allow-Origin: *, so calls work from a browser page on any origin as well as from curl, Python or a spreadsheet. Every value that reaches SQL is a bound parameter, and identifiers such as sort columns and set names are looked up in fixed lists — an unrecognised value falls back to a default or returns a 400, it is never passed through.

Pagination

List responses take &per= (rows per page; up to 500 on the prior-auth API, up to 2,000 on the prices API) and &page=. The meta object echoes the page, the page size, the total row count and the page count, so a client can walk the whole set without guessing.

Errors

A bad request is a 400 and a missing record a 404, each with a machine-readable error code and a human-readable message naming the accepted values. Queries that would return the bulk of a 150,000-row table without a filter answer 400 too_broad and say which filters would narrow them.

Responses

Numeric columns arrive as JSON numbers, not strings — counts as integers, rates and dollars as floats. Code-like fields that merely look numeric (CCNs, FIPS codes, ZIP codes, CPT and DRG codes) stay strings, because leading zeros are part of the identifier.

null never means zero

A null means the publisher printed nothing for that field. "This payer denied nothing" and "this payer published nothing" are different claims, and the API keeps them apart. Every list response carries this note in its meta, so the rule travels with the data.

Provenance

Every dataset traces to the bytes its publisher served: source URL, fetch time, size and SHA-256. The prices API exposes this directly (set=source for the central files, set=mrf for each hospital's own price file), so a figure can be audited back to the original document without asking anyone's permission.

Courtesy

The site runs on ordinary shared hosting. Page through large sets rather than requesting them in one call, and cache what you fetch — the underlying data changes on the cadence of regulatory publishing, not by the minute. For bulk work, the full CSVs in the repository are the better tool.

02 · Prior authorization

api.php — the CY2025 prior authorization filings

One row per plan-level disclosure under CMS-0057-F: requests received, approved and denied, appeal outcomes and turnaround times, alongside the payer's own printed denial rate and the rate recomputed from its counts. The API takes exactly the filters the explorer takes — any explorer URL becomes an API call by swapping explorer.php for api.php.

List filings

GET api.php GET api.php?coverage=Medicare%20Advantage&minvol=1000&sort=rate

Returns meta (pagination, sort, reporting year, the null rule) and filings, an array of full filing rows.

ParameterAcceptsWhat it filters
qfree textPayer, plan and contract-ID search.
coverageexact coverage typee.g. Medicare Advantage, Marketplace QHP — the distinct values are in any list response.
statetwo-letter codeFiling state.
orgexact parent organizationOne payer's filings.
qualityclean · error · warn · flaggedValidation status: no findings, has errors, has warnings, has either.
countsyes · noWhether the filing published raw counts, or only percentages.
minvol / maxvolintegerStandard-request volume band, [minvol, maxvol).
minrate / maxratepercentStandard denial-rate band — how the histogram bars drill down.
appealsyesOnly filings that reported appeal outcomes.
sortrate · vol · org · findings · plan · coverage · state · overturn · expdenial · tatSort key; text keys default ascending, numeric keys descending.
dirasc · descOverride the sort direction.
per / page1–500 · 1+Page size and page number.

One filing, with its findings

GET api.php?id=f0042

The full filing row plus every validation finding recorded against it — the rule that fired, its severity, and the numbers that triggered it. 404 with error: not_found if the id is unknown.

The same rows as CSV

GET export.php?coverage=Marketplace%20QHP&quality=error

export.php takes the same filters and streams the filtered set as a CSV download — the portable subset of the same table.

03 · Healthcare prices

costs_api.php — prices, price files, debt and county profiles

The pricing analysis is many datasets — Medicare's inpatient, outpatient and physician files, a 3,109-hospital sample of the price files hospitals must publish under 45 CFR 180, county-level medical debt and health outcomes, a code dictionary, and the validation findings over all of it. One endpoint serves them all: set= selects the dataset, and each set takes its own filters. Called with no set, it returns an index of every set, current row counts, and the provenance of every source file.

GET costs_api.php

The index. Everything below paginates with &per= (max 2,000) and &page=, and most sets accept &state=NY.

SetFiltersWhat comes back
basketstate · code + typeThe 49-service comparison basket by state (state=US for the national row): Medicare charge and payment beside the sampled files' gross, cash and negotiated medians.
countyfips · state · by + lo/hiCounty profiles: medical debt in collections, uninsured share, health outcomes, price ratios. by= cuts a band of one whitelisted column — how the debt page's quartile bars drill down.
hospitalccn · state · county · systemEvery hospital in the Medicare files, with location, system membership and its price-file status where sampled.
systemid · stateHealth systems (AHRQ linkage) with Medicare charges summed across member hospitals.
inpatientccn · drg · state (one required)Medicare inpatient rows: one hospital's every DRG, or one DRG across hospitals — discharges, average covered charge, average total payment.
outpatientccn · apc · state (one required)Medicare outpatient rows by APC.
physiciancode · stateThe physician fee file, every HCPCS code, state by state, office and facility.
mrfseed · stateThe sampled hospital price files. With seed=NY-04: the full file record (URL, bytes, SHA-256, outcome), every coded charge aggregated across payers, and the file's findings.
findingscope · severity · ref · ruleValidation findings over the pricing data — every place a published file contradicts itself.
modeltarget · featureThe county models: fit quality, permutation importance, partial dependence. Descriptive, not causal — the run's own note fields say so.
hospital_modelseed · stateThe hospital fair-price index: every scored file with its index, interval and unexplained premium. With seed=: that file's card plus its model-estimated prices for unpublished basket items — model output, never published prices.
codecode · q · status · typeThe code dictionary: 85,000+ CPT/HCPCS and MS-DRG codes with ranked, synonym-widened full-text search over descriptors and hospital wordings.
suggestqThe same search, top 8 — built for a typeahead.
code_statecode + type · stateThe catalog: one non-basket code, every state where any source prices it.
sourcekeyProvenance of every central source file: URL fetched, when, bytes, SHA-256, license.
mapcode + typeEverything the price map draws in one round trip: state medians, Medicare hospital pins (MS-DRG), and each sampled file's published prices, all with canvas coordinates.
zip / nearzip · lat + lonZCTA centroids on the map canvas — one ZIP, or the nearest to a coordinate.

Worked examples

GET costs_api.php?set=basket&state=NY

What the basket costs in New York, service by service.

GET costs_api.php?set=inpatient&drg=470&state=TX

Knee and hip replacement (MS-DRG 470) at every Texas hospital in the Medicare file.

GET costs_api.php?set=mrf&seed=NY-04

Bellevue's own published price file: the document record, every coded charge, and its findings.

GET costs_api.php?set=county&by=medical_debt_pct&lo=20

Every county where more than a fifth of people with credit records have medical debt in collections.

GET costs_api.php?set=code&q=knee%20replacement

Ranked code search — the same lookup behind the codes page.

04 · From a terminal

Three-line recipes

The API needs nothing but an HTTP client. These use curl and jq; the same calls work from Python, R, JavaScript or a spreadsheet's "from web" import.

# The ten highest computed denial rates among high-volume Medicare Advantage plans
curl -s 'https://ryangomez.nyc/crossfoot/api.php?coverage=Medicare%20Advantage&minvol=10000&sort=rate&per=10' \
  | jq -r '.filings[] | [.parent_org, .plan_name, .std_denial_rate] | @tsv'

# Walk every page of a filtered set
pages=$(curl -s 'https://ryangomez.nyc/crossfoot/api.php?quality=error&per=100' | jq .meta.pages)
for p in $(seq 1 "$pages"); do
  curl -s "https://ryangomez.nyc/crossfoot/api.php?quality=error&per=100&page=$p" | jq -c '.filings[]'
done

# What one hospital publishes for one code, straight from its mandated file
curl -s 'https://ryangomez.nyc/crossfoot/costs_api.php?set=mrf&seed=IL-03' \
  | jq '.charges[] | select(.code == "470")'

# Verify a source file is the one the pipeline read: compare the SHA-256
curl -s 'https://ryangomez.nyc/crossfoot/costs_api.php?set=source&key=inpatient' | jq -r '.rows[0].sha256'

Questions the API doesn't answer, or a field that doesn't behave as documented here? Open an issue — the documentation is part of the product, and a wrong page is a bug.