healthcare.dev docs

Compare payers' prior authorization metrics

Approval, denial, appeal and turnaround rates for prior authorization, as each health plan posts them every year, side by side and compared like with like.

PlanBuilder and up (403 plan-upgrade-required on Free)
DatasetPayer prior authorization metrics (CMS-0057-F postings)
Calls per answer1 to find payers, then 1 per payer

1. Find the payers

Filter by the level a payer reports at (type: ma_contract for Medicare Advantage contracts), by state (payers with a reporting unit there), or by name or unit id (q):

curl "https://api.healthcare.dev/v1/payers?type=ma_contract" \
  -H "Authorization: Bearer $HDEV_API_KEY" | jq '[.data[] | {id, name}]'

# One payer's reporting units and the years each posted
curl https://api.healthcare.dev/v1/payers/humana -H "Authorization: Bearer $HDEV_API_KEY"

2. Get each payer's metrics

curl "https://api.healthcare.dev/v1/payers/humana/prior-auth-metrics?unit=H1036" \
  -H "Authorization: Bearer $HDEV_API_KEY" | jq '.data.reports[0] | {unit: .unit.key, year, report_status, approved: .standard.approved, mean_time: .standard.mean_time, derivation, evidence}'
{
  "unit": "H1036",
  "year": 2025,
  "report_status": "available",
  "approved": {
    "value": 92.98,
    "unit": "percent",
    "count": 1212346,
    "denominator": 1303837,
    "denominator_basis": "total_requests",
    "as_reported": null
  },
  "mean_time": { "value": 24, "unit": "hours", "as_reported": "1 day(s)", "...": "..." },
  "derivation": "source_stated",
  "evidence": [{ "url": "https://assets.humana.com/...", "anchor": "page=2" }]
}

Without year you get the latest year the payer posted (years_available lists the others); without unit, every unit we hold for that payer and year.

Compare them

Payers don't all divide by the same thing, so compare a percentage only with another of the same denominator_basis, and skip reports that are still pending_review. A payer's metrics cover every unit it reports (a Medicare Advantage organization may also post Medicaid or exchange units), so keep only the unit type you compare.

TypeScript

import { HealthcareDev } from "@healthcare.dev/sdk";

const hdev = new HealthcareDev();

const payers = await hdev.payers.list({ type: "ma_contract" });
const metrics = await Promise.all(
  payers.data.slice(0, 5).map((p) => hdev.payers.priorAuthMetrics(p.id)),
);

const rows = metrics.flatMap(({ data }) =>
  data.reports
    .filter((r) => r.unit.type === "ma_contract" && r.report_status === "available")
    .map((r) => {
      const approved = r.standard.approved;
      return {
        payer: r.payer_name,
        unit: r.unit.key,
        year: r.year,
        approved_pct: approved.value,
        basis: approved.value === null ? null : approved.denominator_basis,
        mean_hours: r.standard.mean_time.value,
        posting: r.evidence[0]?.url,
      };
    }),
);
console.table(rows.filter((r) => r.basis === "total_requests"));

Python

from healthcare_dev import HealthcareDev

hdev = HealthcareDev()

rows = []
for payer in hdev.payers.list(type="ma_contract")["data"][:5]:
    for r in hdev.payers.prior_auth_metrics(payer["id"])["data"]["reports"]:
        if r["unit"]["type"] != "ma_contract" or r["report_status"] != "available":
            continue
        approved = r["standard"]["approved"]
        rows.append({
            "payer": r["payer_name"],
            "unit": r["unit"]["key"],
            "year": r["year"],
            "approved_pct": approved["value"],
            "basis": approved.get("denominator_basis"),
            "mean_hours": r["standard"]["mean_time"]["value"],
            "posting": r["evidence"][0]["url"] if r["evidence"] else None,
        })

for row in rows:
    if row["basis"] == "total_requests":
        print(row)

Ask an agent

Compare prior authorization approval rates and average decision times across Medicare Advantage contracts. Only compare percentages with the same denominator basis, note any metric a payer didn't report, and link each payer's posting.

The agent uses find_payers and get_payer_prior_auth_metrics.

Read the answer

  • Every metric is a value or a gap, never an estimate. A gap is { value: null, not_reported_reason }: not_reported (the posting doesn't give it), not_applicable (the payer says it doesn't apply) or unparseable_pending_review.
  • denominator_basis says what a percentage is out of: total_requests, total_requests_all_priorities, total_appeals, extended_requests or not_stated.
  • derivation: source_stated means fixed rules read the numbers straight from the posting and they passed our checks (approved plus denied at most 100%, counts no larger than their totals, units stated, the right year and unit); reviewed means a person checked them against the posting. confidence is 1 for both and 0 while a report is pending_review, when every metric is null.
  • evidence links the payer's posting and where in it the unit's numbers are. Show it with any comparison.

What it is not

Values are as each payer reported them, not audited: payers count, round and label differently. This is not the list of services that need prior authorization (the payer's link is in items_requiring_pa_url); for that, see Check a prior auth requirement.

On this page