# Compare payers' prior authorization metrics

URL: https://docs.healthcare.dev/recipes/payer-prior-auth-metrics

|                  |                                                                                       |
| ---------------- | ------------------------------------------------------------------------------------- |
| Plan             | **Builder** and up, or a preview on Free (below)                                      |
| Dataset          | [Payer prior authorization metrics](/datasets/payer-pa-metrics) (CMS-0057-F postings) |
| Calls per answer | 1 to find payers, then 1 per payer                                                    |

<Callout title="Try it before you upgrade">
  Free keys can call it as a preview: 50 preview calls a month on Free, per feature. A preview
  answer is the real answer, with `meta.preview` (calls `remaining` this month and the plan that
  `unlocks` it) and `links.upgrade`. Preview calls count toward your included calls. Once they're
  used up, the `403` `plan-upgrade-required` adds `upgrade_url` and a sample answer for a fixed demo
  subject. See [Plans](/plans#previews).
</Callout>

## 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`):

```sh
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

```sh
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}'
```

```json
{
  "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**

```ts
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**

```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](/recipes/prior-auth-requirements).
