# Insurance Plan Interop: A Cautionary Tale
A CMS mandate went into effect in 2021 to ensure that health plans offer #FHIR APIs for their Provider Directory. Let's explore how things are going.
First off, the requirements. CMS publishes a FAQ that's probably the easiest way to understand their regulatory intent: [https://www.hhs.gov/guidance/document/cms-9115-f-faqs#:~:text=Back%20to%20top-,Provider%20Directory%20API%C2%A0,-Question.%20Are](https://www.hhs.gov/guidance/document/cms-9115-f-faqs#:~:text=Back%20to%20top-,Provider%20Directory%20API%C2%A0,-Question.%20Are) . Briefly, the requirements apply to MA organizations, Medicaid plans, and a few others. These orgs need to offer FHIR API with provider names, addresses, phone numbers, and specialties. ["Da Vinci Plan Net"](https://hl7.org/fhir/us/davinci-pdex-plan-net/) is a FHIR implementation guide designed to support this requirement. CMS recommends but does not require that plans follow the Plan Net IG.
## Imagine a universe where everything just worked
Let's take a brief aside on non-FHIR approaches to publishing provider network data. As it turns out, plans on any federally facilitated exchange are exempt from the FHIR requirement because of a long-standing obligation to use a _much simpler and more tightly specified publication format_ for provider directories as well as formulary data: [https://developer.cms.gov/marketplace-api/coverage-portal/#/schema/healthplan](https://developer.cms.gov/marketplace-api/coverage-portal/#/schema/healthplan) . This approach has the advantage of clear simple JSON schema representing a flat understandable data model. Assessing (and achieving!) compliance is orders of magnitude simpler than for Plan Net. With ~100 lines of code and a single well-known entrypoint that CMS hosts, I can download every provider directory for every plan on every public exchange (https://github.com/jmandel/cms-plans-fetch). It's about 100GB in total -- small enough to fetch in an afternoon and process on my laptop. And every file is correctly formatted and easy to understand. There's even a contact person responsible for each file in case of problems.

So we get not only provider networks, but also drug formulary data, with a single entrypoint and near-complete spec compliance (other than a few 404s and JSON formatting errors across thousands of API requests).
## Meanwhile, back in FHIR Land, almost everything is broken.
CMS mandates that insurers publish FHIR data, but there's no process for insurers to report that they've done it, and no directory for developers to learn which insurers are participating, or where to find the data. Developers have to search through poorly indexed documentation websites, cobbling together lists and perhaps sharing their discoveries in git repos of CSV files. If you do find a FHIR endpoint, it's likely to have some kind of bespoke authentication process in place or some "token" gating access, apparently violating the terms of CMS's mandate. But again, there's no validation -- not even a voluntary process to follow. This means that even developers who want to do the right thing may not be able to tell if they're meeting expectations.
### IGs are supposed to make FHIR easy and consistent
In the analysis below, I'll be lookging for data that complies with Da Vinci Plan Net IG, but it's worth saying that this IG is not perfect. It appears that developers have a hard time correctly mapping their data into the prescribed FHIR structues, with many cross-resource references forming a rather [tangled web](https://build.fhir.org/ig/HL7/davinci-pdex-plan-net/#overview-of-payer-directory-resource-relationships):

It can be challenging to understand why each resource exists -- e.g. [a given Practitioner may have 17 PractitionerRoles](https://p-hi2.digitaledge.cigna.com/ProviderDirectory/v1/PractitionerRole?practitioner=Practitioner/jnXfbwxWklxNGayZCbxlRHdHKYXz7DRGSNlb0Ax1up), many of which state that the practitioner belongs to the same specific network. Why do so many roles exist? What does each one uniquely represent? There are no "display" affordances or other sign-posts.
Several additional kinds of content are introduced in Plan Net beyond what the CMS JSON structures require, including the idea of publishing a set of "Healthcare Services" offered by organizations... but critical capabilities like "fetch the full data set" or "expose a drug formulary list" are left out of scope (for future work, or for other IGs to handle), leading to a proliferation of data while core problems remain unsolved.
### How it's going in Medicare Advantage
Let's attempt to quantify the "public access" challenges by looking at Medicare Advantage plans. There are too many to review all of them, but we'll take advantage of CMS's data file on plan enrollment at [https://www.cms.gov/research-statistics-data-and-systems/statistics-trends-and-reports/mcradvpartdenroldata/monthly/monthly-enrollment-plan-2023-09](https://www.cms.gov/research-statistics-data-and-systems/statistics-trends-and-reports/mcradvpartdenroldata/monthly/monthly-enrollment-plan-2023-09), and to find the organizations with the largest total enrollment. Together, the following organizations appear to cover over over 21M individuals in HMO and PPO plans, representing about 2 in 3 Medicare Advantage beneficiaries:
* Aetna
* Cigna
* Humana
* Kaiser Permanente
* United Health care
These are large organizations with sophisticated technology teams, and their implementation likely represents "best in class". We'll use them to estimate the overall state of FHIR Provider Directory APIs.
Here's a 10-point rubric based on my initial perusal:
* Docs are easy to find
* Endpoint is easy to find
* Endpoint is openly accessible
* Data are valid FHIR
* Data indicate which plans exist
* Data indicate which networks exist
* Data indicate which networks are associated with which plans
* Data indicate which organizations participate in which networks
* Data indicate which providers participate in which networks
* API queries allow rapid download
Note that even losing a couple of points can render this whole exercise useless, Especially once you get past item #2, any missed points are essentially "game over" for a developer trying to connect.
## The MA Ratings
With this in mind, **only two of the five organizations are hosting "working" APIs according to my evaluation criteria**.
(*Please consider this rating a first-pass effort. It's somewhat arbitrary, and I'd love to hear from server developers if I'm doing something dumb here, or u
if my rating scheme is off. And I would be very happy to make adjustments as I learn more!*)
* Cigna: 9.5/10, **no showstoppers**
* Docs are easy to find: 1/1. Top hit in a web search for "cigna fhir provider directory"
* Endpoint is easy to find: 1/1. Endpoint is listed on the landing page, zero clicks required.
* Endpoint is openly accessible: 1/1.
* Data are valid FHIR: 1/1.
* Data indicate which plans exist: 1/1. Search for InsurancePlan returns the list.
* Data indicate which networks exist: 1/1. Search for Organization?type=ntwk returns the list.
* Data indicate which networks are associated with which plans: 1/1. InsurancePlans explicitly link to networks.
* Data indicate which organizations participate in which networks: 1/1. OrganizationAffiliations explicitly link to networks.
* Data indicate which providers participate in which networks: 1/1. PractitionerRoles explicitly link to networks.
* API queries allow rapid download: .5/1. Limited to 100 results per page, and each query takes ~1s.
* Kaiser Permanente: 8.75 / 10, **no showstoppers**
* Docs are easy to find: 1/1. Developer portal at [https://developer.kp.org/#/home](https://developer.kp.org/#/home) states clearly that some APIs are open, meaning they "are available to use without an account."
* Endpoint is easy to find: .5/1. But the detailed specifications are misleading, with a "Use this API" button that leads to *"Create an Account or Login to use this Secure API". At one point, clicking a link in the "[Supplement Instructions for CA](https://developer.kp.org/documents/external-services-documents/50923454/KP%20Formulary%20API%20Developer%20Supplemental%20Instructions%20for%20CA%20Plans.pdf?sv=2023-01-03&st=2023-09-22T22%3A24%3A38Z&se=2023-09-23T22%3A24%3A38Z&sr=b&sp=r&sig=rJzTysea%2B3Yhj%2BFRqPAD3YFcyMswQ7Rm5WgECvlSV2w%3D)" documentation took me to

But if you ignore the warnings, the API documentation at [https://developer.kp.org/#/apis](https://developer.kp.org/#/apis) does in fact provide an endpoint of [https://kpx-service-bus.kp.org/service/hp/mhpo/healthplanproviderv1rc/](https://kpx-service-bus.kp.org/service/hp/mhpo/healthplanproviderv1rc/)
* Endpoint is openly accessible: 1/1.
* Data are valid FHIR: 1/1.
* Data indicate which plans exist: .75/1. InsurancePlan resources are exposed but inconsistently coded (e.g., .type is sometimes missing its code). I am not sure how there can be 250,000 InsurancePlans.
* Data indicate which networks exist: 1/1
* Data indicate which networks are associated with which plans: 0.5/1. About half of InsurancePlans have no "network" associated.
* Data indicate which organizations participate in which networks: 1/1
* Data indicate which providers participate in which networks: 1/1
* API queries allow rapid download: 0.5/1. Searching returns at most 250 results per page. Some resource types have over 300,000 total results. In my testing, after 8 pages of results, the API slows down and begins to returns 500 errors, yielding about one actual page per minute. This may be an intermittent issue.
* United: 8/10 **with show-stoppers**
* Docs are easy to find: 1/1. [https://www.uhc.com/legal/interoperability-apis](https://www.uhc.com/legal/interoperability-apis) appears as a top search result.
* Endpoint is easy to find: .5/1. The data endpoint is collapsed under a section of the docs called "Authorization Endpoints," even though it is not an authorization endpoint.
* Endpoint is openly accessible: 1/1
* Data are valid FHIR: 1/1
* Data indicate which plans exist: 1/1. The InsurancePlan endpoint returns plans with valid types and sensible names.
* Data indicate which networks exist: .5/1. While "Organization?type=ntwk" returns results that are annotated as insurance networks, there is essentially no information by which these plans can be understood. The results include properties like
```
"name": "203",
"partOf": {
"reference": "Organization/0a6855574cd276f5d80d924a604fb7f8f15eb4781b2e2f0c8fb82df254089687" }
```
... but the name is not meaningful and the `partOf` reference fails to resolve.
* Data indicate which networks are associated with which plans: 1/1, InsurancePlan "network" arrays are populated and the references resolve.
* Data indicate which organizations participate in which networks: 1/1. OrganizationAffiliation "network" arrays are populated and references resolve.
* Data indicate which providers participate in which networks: 1/1. PractitionerRole "network-reference" extensions are populated and references resolve.
* API queries allow rapid download: 0/1. Searching for Organizations, the API server seems to limit a search to 10000 results, in pages of 100. I received the following message in place of my 96th page:
```
{
"resourceType":"OperationOutcome"
"issue":[{
"severity":"information",
"code":"processing",
"diagnostics":"Narrow down search criteria"
},{
"severity":"information",
"code":"processing",
"diagnostics":"TrackingUuid = 544d8a1a7ffe67183a954c41154436bf"}]}.
```
It is unclear how many Organizations exist or how I could retrieve all of them, given my goal to fetch the full data set.
* Humana: 5.5/10 **with show-stoppers**
* Docs are easy to find: 1/1. Results are right at the top of a web search for "humana fhir api provider directory"
* Endpoint is easy to find: 1/1. The page lists the endpoint straight off, zero clicks required, and a working example is provided.
* Endpoint is openly accessible: 1/1
* Data are valid FHIR: 1/1
* Data indicate which plans exist: .5/1. The InsurancePlan endpoint works but "types" are inconsistently populated at the InsurancePlan.type level. Where types are populated, they do not use the code system required by Da Vinci Plan Net.
* Data indicate which networks exist: 0/1. No Organizations exist for the "?type=ntwk" query, which would indicate that there are no Networks published. It's unclear if networks are published in some other way.
* Data indicate which organizations participate in which plans: .5/1. While InsurancePlan includes ".network" entries, these are populated with identifiers that do not resolve. For example:
```
"network": [{
"identifier": {
"system": "https://fhir.humana.com/documentation/glossary/PharmacyNetworkId",
"value": "13" },
"display": "Humana Premier Rx Plan (PDP) Network" }
```
but [https://fhir.humana.com/api/Organization?identifier=13](https://fhir.humana.com/api/Organization?identifier=13) returns no results.
* Data indicate which networks are associated with which plans. 0/1. No OrganizationAffiliation resource is exposed.
* Data indicate which providers participate in which networks. .5/1. There appears to be a set of undocumented Humana-specific tags on PractitionerRole resources, instead of the "network" extension defined by Da Vinci Plan Net.
* API queries allow rapid download: 0/1. Queries are limited to ten results per page, and do not indicate the total number of results available. Given server response times, this allows downloading only ~30k resources per hour, meaning that downloading the data set could take days.
* Aetna: 2/10** with show-stoppers**
* Docs are easy to find: 1/1. [https://developerportal.aetna.com/fhirapiasegregation](https://developerportal.aetna.com/fhirapiasegregation) shows up at the top of a search.
* Endpoint is easy to find: 1/1. [https://developerportal.aetna.com/fhir/apis/swagger/_v1_providerdirectory_InsurancePlan.yaml](https://developerportal.aetna.com/fhir/apis/swagger/_v1_providerdirectory_InsurancePlan.yaml) lists [https://apif1.aetna.com/fhir/v1/providerdirectory](https://apif1.aetna.com/fhir/v1/providerdirectory) as a base URL.
* Endpoint is openly accessible: 0/1. Querying the base endpoint for InsurancePlan yields
```
$ curl https://apif1.aetna.com/fhir/v1/providerdirectory/InsurancePlan
{"httpCode":"401","httpMessage":"Unauthorized","moreInformation":"Invalid client id or secret."
```
The documentation page has a cryptic note stating "Note: The Provider Directory does not have an Authorize endpoint as it does not contain member data. Since Provider Directory APIs process publicly available data only an application token is required but not authorization." The term "application token" is not defined, but it appears to require creation of a developer account and registration of an client.
* Data are valid FHIR: N/A
* Data indicate which plans exist: N/A
* Data indicate which networks exist: N/A
* Data indicate which networks are associated with which plans: N/A
* Data indicate which organizations participate in which networks: N/A
* Data indicate which providers participate in which networks: N/A
* API queries allow rapid download: N/A \
### What about State Medicaid plans?
Now looking beyond MA Plans toward the set of State Medicaid plans in [https://github.com/CMSgov/SMA-Endpoint-Directory](https://github.com/CMSgov/SMA-Endpoint-Directory), it's certainly good news that CMS is actively tracking participation at [https://github.com/CMSgov/SMA-Endpoint-Directory](https://github.com/CMSgov/SMA-Endpoint-Directory). Indeed, the tracker shows 30 of 56 states/territories/districts have an endpoint online. These are managed by a small set of vendors. But from my sampling, only a tiny fraction are managing data in a structure that follows the FHIR REST API and the DaVinci Plan Net IG. Other vendors appear to be hosting services with bespoke variations in the API surface area, missing data, and unclear meaning for the data that are indeed present.
* Safhir: appears to have data structured according to DaVinci Plan Net IG. Will review further.
* 1up (Gainwell): Data does not appear to match documentation at [https://1up.health/docs/start/cms-patient-access-rule-for-developers/connecting-to-payer-endpoints](https://1up.health/docs/start/cms-patient-access-rule-for-developers/connecting-to-payer-endpoints) (e.g., the resources indicate they follow the CARIN Blue Button implementation guide for patient access, rather than the Da Vinci implementation guide for plan networks. There are no "ntwk" or other plan types, no networks or affiliations are provided, and no way to discover a practitioners "roles", so it's unclear what the data means).
* Maine: special case, spreadsheet notes "1up" but URL is at verityanalytics.org. OrganizationAffiliations are populated without any "network", and with unbounded chains of affiliation. Does not provide a clear way to understand network participation.
* Michigan / "Salesforce": invalid references in FHIR data ("Location/mdhhs_cnsi_provider|75618909"), broken handling of search params. Adding any param like `_count=1000` or `_include=OrganizationAffiliation:*` causes the response to provide 0 results but a "success" status, as in
```
curl 'https://api.interopstation.com/mdhhs/fhir/OrganizationAffiliation?_count=1000'
```
The docs appear to be at [https://mihin.org/wp-content/uploads/2021/11/InterOp-Station-Third-party-Developer-Portal-User-Guide-v3-9-10-21-1.pdf](https://mihin.org/wp-content/uploads/2021/11/InterOp-Station-Third-party-Developer-Portal-User-Guide-v3-9-10-21-1.pdf) but do not explain these reference structures.