# InPlan documentation (all pages) > Every page of https://inplan.io/docs as Markdown, in reading order. The API description is at https://inplan.io/openapi.json. # InPlan docs InPlan answers one question for your product: does this plan's network directory list this provider? These pages show how to ask it and how to read the answer. Source: https://inplan.io/docs · Updated 2026-10-30 ## Start here - [Quickstart](https://inplan.io/docs/quickstart): Get a key, make your first call and read the answer. - [Answers and evidence](https://inplan.io/docs/answers-and-evidence): What `listed` and `not_found` mean, and how to show them. - [API reference](https://inplan.io/docs/api): Every endpoint, parameter and response. ## The basics Every request goes to `https://api.inplan.io` over HTTPS, with your key in the `Authorization` header. Every response is JSON with the same envelope: `request_id`, `data_as_of`, `data`, `sources` and, on most endpoints, `notes`. curl: ```bash curl https://api.inplan.io/v1/check \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d npi=1184617268 -d plan_id=29276OH0970001 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/check"); url.search = new URLSearchParams({ npi: "1184617268", plan_id: "29276OH0970001" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.answer, body.data.evidence); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/check", params={"npi": "1184617268", "plan_id": "29276OH0970001"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["answer"], body["data"]["evidence"]) ``` The answer is `listed` when the plan's network directory lists the provider, with evidence that names the plan year, the source type and the date we read it. It is `not_found` when we find no listing. That is never a statement that the provider is out of network. Read [Answers and evidence](https://inplan.io/docs/answers-and-evidence) before you show answers to users. ## Endpoints | Endpoint | What it answers | | --- | --- | | [`GET /v1/providers/{npi}`](https://inplan.io/docs/api/providers) | Who the provider is, and how many plans list them. | | [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans) | Every plan that lists the provider. | | [`GET /v1/check`](https://inplan.io/docs/api/check) | Does this plan list this provider? | | [`POST /v1/check/batch`](https://inplan.io/docs/api/check-batch) | The same question for up to 100 pairs in one call. | | [`GET /v1/plans/{plan_id}`](https://inplan.io/docs/api/plans) | Plan details and plan years. | | [`GET /v1/plans/{plan_id}/providers`](https://inplan.io/docs/api/plan-providers) | Providers a marketplace plan lists, with filters. | | [`GET /v1/coverage`](https://inplan.io/docs/api/coverage) | Markets, states, insurers and plans with listings. | | [`GET /v1/status`](https://inplan.io/docs/api/status) | Health and data dates. No key needed. | ## Concepts and guides - [Plans and plan years](https://inplan.io/docs/plans-and-plan-years): Plan id formats and how `year` works. - [Providers and NPIs](https://inplan.io/docs/providers-and-npis): NPI checks, names and practice locations. - [Data freshness](https://inplan.io/docs/data-freshness): The dates on every answer. - [Benefits and ICHRA](https://inplan.io/docs/guides/benefits): Check every employee's doctors against the plans on offer. - [Telehealth and navigation](https://inplan.io/docs/guides/telehealth): Confirm the plan lists the clinician before you book. - [Brokers and agents](https://inplan.io/docs/guides/brokers): Quote the plans that keep a client's doctors. ## For tools and agents Every docs page is also plain Markdown: add `.md` to its address, or use **Copy page**. These files describe the whole API: - [`/openapi.json`](https://inplan.io/openapi.json): the OpenAPI 3.1 description, with real response examples. - [Postman collection](https://inplan.io/docs/inplan.postman_collection.json): every endpoint with sample values. Set `INPLAN_KEY` in the collection variables. - [`/llms.txt`](https://inplan.io/llms.txt): an index of these docs for language models. [`/llms-full.txt`](https://inplan.io/llms-full.txt) holds every page in one file. - [`/pricing.md`](https://inplan.io/pricing.md): plans, limits and prices as Markdown. - The [changelog](https://inplan.io/changelog) lists every change to the API. --- # Quickstart Get a key, check one provider against one plan, read the answer, then check many pairs in one call. Source: https://inplan.io/docs/quickstart · Updated 2026-10-30 ## 1. Get a key Sign up, open **Keys** in the dashboard and choose **Create key**. Keys start with `ip_live_`. We store only a hash of each key, so copy it when it appears. ```bash export INPLAN_KEY="ip_live_..." ``` Want to try before you sign up? The [Playground](https://inplan.io/playground) runs on a demo key that answers for a set of sample providers. ## 2. Make your first call Ask whether a plan lists a provider. Send the provider's NPI and the plan ID. Every request goes to `https://api.inplan.io` with your key in the `Authorization` header. curl: ```bash curl https://api.inplan.io/v1/check \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d npi=1184617268 -d plan_id=29276OH0970001 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/check"); url.search = new URLSearchParams({ npi: "1184617268", plan_id: "29276OH0970001" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.answer, body.data.evidence); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/check", params={"npi": "1184617268", "plan_id": "29276OH0970001"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["answer"], body["data"]["evidence"]) ``` This example checks Michael B. Williard, MD (NPI `1184617268`) against the 2026 marketplace plan Anthem Bronze Pathway 10600 (`29276OH0970001`). Medicare Advantage plan IDs look like `H2526-001-000`. ## 3. Read the answer The answer sits in `data`. The evidence block is the receipt: it says which directory listed the provider, for which plan year, and when we read it. Response (200 OK · application/json, evidence marked): ```json { "request_id": "req_ws7hhhC7uSWYqEye", "data_as_of": "2026-07-27", "data": { "npi": "1184617268", "plan_id": "29276OH0970001", "input_plan_id": "29276OH0970001", "market": "aca", "plan_years_listed": [ 2026 ], "in_network": true, "answer": "listed", "explanation": "The insurer's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ], "plan_year": 2026, "plan": { "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" }, "as_of": "2026-07-27" }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ], "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` | Field | What it tells you | | --- | --- | | `data.in_network` | `true` when the plan's directory lists the provider. `null` when we found no listing. | | `data.answer` | `listed` or `not_found`. Use this value to pick the words you show. | | `data.explanation` | One plain sentence you can show to users as it is. | | `data.evidence[]` | One entry for each listing: plan year, network tier, and for Medicare Advantage plans, whether the provider accepts new patients. | | `evidence[].source` | The source type and `as_of`, the date we read the data. | | `data.plan` | Plan name, insurer, state, metal level and plan type. | | `request_id` | Unique to this request. Quote it when you contact support. | Every check returns one of two answers: - **LISTED · in_network: true**: The plan's network directory lists the provider. Show the plan year, tier and the date from `evidence`. - **NOT FOUND · in_network: null**: No listing found in current directory data for this provider and plan. Ask the user to confirm with the plan. Do not show it as out of network. ## 4. Check many pairs in one call On Builder and above, send up to 100 provider-plan pairs to `POST /v1/check/batch` in a body with the key `items`. Each pair counts as one call. A bad pair returns its own `error` and the rest of the batch still answers. curl: ```bash curl https://api.inplan.io/v1/check/batch \ -H "Authorization: Bearer $INPLAN_KEY" \ -H "Content-Type: application/json" \ -d '{"items": [ {"npi": "1184617268", "plan_id": "29276OH0970001"}, {"npi": "1184617268", "plan_id": "H2526-001-000"} ]}' ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/check/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ items: [ { npi: "1184617268", plan_id: "29276OH0970001" }, { npi: "1184617268", plan_id: "H2526-001-000" } ] }), }); const body = await res.json(); const { summary, results } = body.data; ``` Python: ```python import os, requests res = requests.post( "https://api.inplan.io/v1/check/batch", json={ "items": [ { "npi": "1184617268", "plan_id": "29276OH0970001" }, { "npi": "1184617268", "plan_id": "H2526-001-000" } ] }, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=30, ) body = res.json() summary, results = body["data"]["summary"], body["data"]["results"] ``` Response (200 OK · response, shortened): ```json { "request_id": "req_KHtLEQJ46JLKLkZZ", "data_as_of": "2026-10-29", "data": { "count": 4, "summary": { "listed": 2, "not_found": 1, "invalid": 1 }, "results": [ { "index": 0, "npi": "1184617268", "plan_id": "29276OH0970001", "market": "aca", "in_network": true, "answer": "listed", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ] }, { "index": 1, "npi": "1184617268", "plan_id": "H2526-001-000", "market": "ma", "in_network": true, "answer": "listed", "evidence": [ { "plan_id": "H2526-001-000", "plan_year": 2027, "accepting_new_patients": true, "provider_kind": "practitioner", "source": { "type": "plan_directory", "as_of": "2026-10-29" } } ] }, { "index": 2, "npi": "1376006148", "plan_id": "13262AR0230002", "market": "aca", "in_network": null, "answer": "not_found", "evidence": [] }, { "index": 3, "npi": "1234567890", "plan_id": "29276OH0970001", "error": { "type": "invalid_npi", "title": "Invalid NPI", "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840)." } } ] } } ``` Read `data.summary` for the counts, then each item in `data.results`. The `index` matches the position of the pair you sent. See [Batch checks](https://inplan.io/docs/batch-checks). ## 5. Handle errors Errors use the `application/problem+json` format (RFC 7807). Branch on `type`, show `detail` in your logs, and keep `request_id`. | Status | Type | What to do | | --- | --- | --- | | 400 | `invalid_npi`, `invalid_plan_id`, `invalid_request` | Fix the input. The detail names the field. | | 401 | `unauthorized` | Check the Authorization header and that the key is active. | | 403 | `plan_upgrade_required`, `demo_restricted` | The endpoint needs a larger plan, or the demo key only answers for sample providers. | | 404 | `not_found`, `plan_not_found` | We hold no record for this NPI or plan ID. | | 429 | `rate_limited`, `quota_exceeded` | Wait for `Retry-After` seconds. Quota errors clear on the 1st of the month, UTC. | | 503 | `busy`, `timeout` | Retry after `Retry-After` seconds. | Every lookup response also carries rate-limit and quota headers. Calls to `/v1/status` do not count against your quota. See [Rate limits and quotas](https://inplan.io/docs/rate-limits) and [Errors](https://inplan.io/docs/errors). ## 6. Next steps - [Answers and evidence](https://inplan.io/docs/answers-and-evidence): What each field means and how to show it. - [API reference](https://inplan.io/docs/api): Every endpoint, parameter and response. - [Playground](https://inplan.io/playground): Send real requests from your browser. --- # Authentication Send your key in the `Authorization` header of every request, as a bearer token. Only `/v1/status` works without a key. Source: https://inplan.io/docs/authentication · Updated 2026-10-30 ## The Authorization header Put the key after the word `Bearer` and one space. The API reads no other header, cookie or query parameter for keys. ```text Authorization: Bearer ip_live_... ``` curl: ```bash curl https://api.inplan.io/v1/providers/1184617268 \ -H "Authorization: Bearer $INPLAN_KEY" ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/providers/1184617268", { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.name.display); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268", headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["name"]["display"]) ``` ## Key format A key is `ip_live_` followed by 40 letters and digits. The dashboard shows the start of each key so you can tell keys apart. We store only a hash of the full key, so we cannot show it again: copy it when you create it. ## Keep keys on your server Call the API from your backend and keep the key in an environment variable or a secret store. Never put a key in browser or mobile app code, in a public repository, or in a URL. The API does not send CORS headers on `/v1` endpoints, so browsers block direct calls from web pages. If a key leaks, create a new key, switch your servers to it, then revoke the old one in the dashboard. A revoked key stops working within a minute. ## When the key is missing or wrong A request without a valid key gets `401` with the type `unauthorized` and a `WWW-Authenticate` header. A missing header and an unknown or revoked key have different `title` values. Response (401 Unauthorized · application/problem+json): ```http HTTP/1.1 401 Unauthorized Content-Type: application/problem+json; charset=utf-8 X-Request-Id: req_pctJoQwOrN-xK9Wp WWW-Authenticate: Bearer realm="inplan" ``` ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/check", "request_id": "req_pctJoQwOrN-xK9Wp", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` If the account behind a key is not active, the API answers `403` with the type `account_suspended`. Write to us if you see it: see [Support](https://inplan.io/docs/support). ## Endpoints without a key `GET /v1/status` needs no key and does not count against any quota. Use it for health checks and to read the date of each data source. See [Status](https://inplan.io/docs/api/status). --- # Keys and the demo key Each key belongs to your account and carries its plan. The Playground uses a demo key that answers for a small set of sample providers. Source: https://inplan.io/docs/keys · Updated 2026-10-30 ## Create a key 1. Sign in and open **Keys** in the dashboard. 2. Choose **Create key** and name it after the system that will use it. 3. Copy the key when it appears. We show it once. Create one key for each service or environment, so you can revoke one without stopping the others. ## Rotate and revoke To rotate a key, create the new key, deploy it, then revoke the old key. A revoked key answers `401 unauthorized` within a minute. ## Limits for keys and accounts The per-second limit applies to each key. The monthly call quota belongs to your account, and all its keys share it. The plan of your account sets both. Every lookup response tells you where you stand in its `RateLimit-*` and `X-Quota-*` headers. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). ## The demo key The [Playground](https://inplan.io/playground) sends your requests with a demo key that stays on our server. It answers only for the sample providers listed in the Playground, allows 20 requests a minute for each visitor, and does not include plan-to-provider search. Any other NPI answers `403 demo_restricted`: Response (403 Forbidden · application/problem+json): ```json { "type": "demo_restricted", "title": "Demo key restriction", "status": 403, "detail": "The playground key works only for the sample NPIs. Create a free key for any NPI.", "instance": "/api/v1/check", "request_id": "req_mW3y_sM0mgptQFPy", "docs": "https://inplan.io/docs/errors#demo_restricted", "sample_npis": [ "1376006148", "1427468453", "1306032347", "1538462239", "1629458435" ] } ``` A free key answers for any NPI, with 500 calls a month. --- # Rate limits and quotas Two limits apply: requests a second for each key, and calls a month for your account, shared by all its keys. Every lookup response reports both in its headers. Calls over the quota are blocked, never billed. Source: https://inplan.io/docs/rate-limits · Updated 2026-10-30 ## Limits by plan | Plan | Requests a second | Calls a month | Batch pairs | | --- | --- | --- | --- | | Free | 2 | 500 | Not included | | Builder | 10 | 25,000 | 100 | | Growth | 25 | 150,000 | 100 | | Scale | Set with you | Set with you | 100 | Short bursts above the per-second rate are allowed. The `RateLimit-Policy` header states the rate and the burst size of your key. Prices are on the [pricing page](https://inplan.io/pricing). ## Headers Every response that passes the key and plan checks carries these headers. A `401` or `403 plan_upgrade_required` response carries only `X-Request-Id`. | Header | Meaning | | --- | --- | | `RateLimit-Limit` | Requests a second this key allows. | | `RateLimit-Remaining` | Requests you can send right now before the limit applies. | | `RateLimit-Reset` | Seconds until the allowance refills (at least 1). | | `RateLimit-Policy` | The rate and burst size, for example `10;w=1;burst=20`. | | `X-Quota-Limit` | Calls included this month for your account (all its keys share them). | | `X-Quota-Remaining` | Calls your account has left this month. | | `X-Quota-Reset` | Unix time of the monthly reset: the 1st of next month, 00:00 UTC. | | `X-Request-Id` | Unique to this request. Same as `request_id` in the body. | | `Retry-After` | On `429` and `503` only: seconds to wait before you retry. | ## What counts as a call - Each request to a lookup endpoint is one call. - Each accepted item in a batch is one call, so a batch of 100 valid pairs uses 100 calls. Items that fail validation cost nothing, and a batch refused as a whole (more than 100 items or an invalid body) costs nothing. - A request that reaches the lookup counts even when it answers `400` or `404`. - These do not count: `GET /v1/status`, a request rejected for its key (`401`) or plan (`403 plan_upgrade_required`), a request over a limit (`429`), and a server error (`5xx`). ## When you hit the per-second limit The API answers `429` with the type `rate_limited` and a `Retry-After` header. Wait that many seconds, then retry. Spread steady traffic below `RateLimit-Limit` instead of sending it in bursts. Response (429 Too Many Requests · application/problem+json): ```http HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json; charset=utf-8 Retry-After: 1 RateLimit-Limit: 2 RateLimit-Remaining: 0 RateLimit-Reset: 1 RateLimit-Policy: 2;w=1;burst=5 X-Request-Id: req_TrHd8LO8HNbLoZ_D ``` ```json { "type": "rate_limited", "title": "Too many requests", "status": 429, "detail": "This key allows 2 requests per second.", "instance": "/v1/coverage", "request_id": "req_TrHd8LO8HNbLoZ_D", "docs": "https://inplan.io/docs/errors#rate_limited" } ``` ## When you use the monthly quota The API answers `429` with the type `quota_exceeded` until the quota resets on the 1st of the month, UTC. The body adds `quota`, `used`, `cost` and `resets_at`, and `Retry-After` holds the seconds until the reset. We never bill calls over the quota. To keep going sooner, move to a larger plan. A batch that needs more calls than you have left is refused as a whole, so nothing in it is answered or counted. ## When the API is busy Under heavy load the API answers `503` with the type `busy`, and a lookup that takes too long answers `503 timeout`. Both carry `Retry-After`. They never count against your quota. Retry after the delay, with backoff if it repeats. --- # Answers and evidence Every check answers one question: does the plan's network directory list this provider? The answer is `listed` with evidence, or `not_found`. We never turn a missing listing into a "no". Source: https://inplan.io/docs/answers-and-evidence · Updated 2026-10-30 ## The answer values | Answer | In_network | Meaning | | --- | --- | --- | | `listed` | `true` | The network directory of this plan lists the provider. `evidence` holds each listing. | | `not_found` | `null` | No listing found in current directory data for this provider and plan. It is not a statement that the provider is out of network. | | `no_provider_network` | `null` | The plan id is a prescription drug plan (it starts with S). Drug plans have no provider network. | `in_network: false` is kept for an explicit exclusion that an insurer publishes. v1 does not return it today. Handle it anyway: treat `false` as "the plan says this provider is not in its network". ## How to show it - **LISTED · in_network: true**: Say "Listed" or "In this plan's directory". Show the plan year and the date from `evidence[].source.as_of`. - **NOT FOUND · in_network: null**: Say "Not found" or "We could not find a listing". Ask the user to confirm with the plan or the provider. Never show it as "out of network". `data.explanation` is one plain sentence for the answer. You can show it as it is. `notes` holds short sentences to show next to the answer, for example that directories change and that users should confirm before care. ## The evidence block `data.evidence[]` holds one entry for each listing we found, newest plan year first. It is empty when the answer is `not_found`. Each entry carries its own source: the source type and the date we read it. | Field | Description | | --- | --- | | `plan_id` / `plan_year` | The plan and the plan year of this listing. | | `network_tier` | Marketplace plans: the tier the directory names, for example `PARTICIPATING` or `PREFERRED`. | | `accepting_new_patients` | Medicare Advantage plans: whether the directory says the provider accepts new patients. | | `provider_kind` | Medicare Advantage plans: the kind of provider listed, for example `practitioner`. | | `source.type` | `insurer_directory` for marketplace plans, `plan_directory` for Medicare Advantage plans. | | `source.as_of` | The date we read that directory. | A Medicare Advantage answer carries `accepting_new_patients` and `provider_kind` instead of a tier: Response (200 OK · Medicare Advantage, listed): ```json { "data": { "npi": "1184617268", "plan_id": "H2526-001-000", "input_plan_id": "H2526-001-000", "market": "ma", "plan_years_listed": [ 2027 ], "in_network": true, "answer": "listed", "explanation": "The plan's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "H2526-001-000", "plan_year": 2027, "accepting_new_patients": true, "provider_kind": "practitioner", "source": { "type": "plan_directory", "as_of": "2026-10-29" } } ], "plan_year": 2027, "plan": { "plan_name": "DEVOTED CHOICE 001 OH (PPO)", "organization": "Devoted Health", "plan_type": "PPO", "service_area_states": [ "OH" ] }, "source": { "type": "plan_directory", "as_of": "2026-10-29" }, "as_of": "2026-10-29" } } ``` ## A not_found answer A `not_found` answer still returns the plan details we hold, so you can show which plan was checked. `evidence` is empty and `plan_years_listed` is `[]`. Response (200 OK · not_found): ```json { "data": { "npi": "1376006148", "plan_id": "13262AR0230002", "input_plan_id": "13262AR0230002", "market": "aca", "plan_years_listed": [], "in_network": null, "answer": "not_found", "explanation": "No listing found in current directory data for this provider and plan.", "evidence": [], "plan_year": 2026, "plan": { "plan_name": "HA Silver AH", "issuer": { "id": "13262", "name": "Health Advantage" }, "state": "AR", "metal_level": "Silver", "plan_type": "POS", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-10-28" }, "as_of": "2026-10-28" }, "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ## Source types We name sources by type. The `sources` array of every response lists the types behind it, each with the date it is current to. | Type | What it is | | --- | --- | | `insurer_directory` | Provider network directories published by health insurers (marketplace plans). | | `plan_directory` | Provider network directories published by Medicare Advantage plans. | | `provider_registry` | National provider registry and practice records: names, credentials, specialties, locations. | | `plan_catalog` | Published health plan catalogs: plan names, insurers, metal levels, plan types. | --- # Plans and plan years The API takes marketplace plan ids and Medicare Advantage contract-plan-segment ids. Each answer names the plan year it is about. Source: https://inplan.io/docs/plans-and-plan-years · Updated 2026-10-30 ## Plan id formats | Market | Format | Example | | --- | --- | --- | | Marketplace | 5-digit insurer id, 2-letter state, 7 digits (14 characters) | `29276OH0970001` | | Marketplace variant | The 14 characters plus a 2-digit variant, with or without a dash | `29276OH0970001-01` | | Medicare Advantage | Contract (H or R and 4 digits), plan number, segment | `H2526-001-000` | Ids are not case sensitive. A Medicare Advantage id without a segment (`H2526-001`) means segment `000`. Any other format answers `400 invalid_plan_id`. ## Marketplace variants Insurer directories list providers under the 14-character plan id, so a variant id is checked against its plan. `plan_id` in the answer is the 14-character id, `input_plan_id` echoes what you sent, and `plan.variant` holds the variant. Response (200 OK · variant id): ```json { "data": { "npi": "1184617268", "plan_id": "29276OH0970001", "input_plan_id": "29276OH0970001-01", "market": "aca", "plan_years_listed": [ 2026 ], "in_network": true, "answer": "listed", "explanation": "The insurer's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ], "plan_year": 2026, "plan": { "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "variant": "01" }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" }, "as_of": "2026-07-27" } } ``` ## Plan years A plan can have listings for more than one plan year. Without `year`, a check looks at every year we hold and `plan_year` is the newest year listed. With `year`, the answer covers that year only. curl: ```bash curl https://api.inplan.io/v1/check \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d npi=1184617268 -d plan_id=29276OH0970001 -d year=2026 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/check"); url.search = new URLSearchParams({ npi: "1184617268", plan_id: "29276OH0970001", year: "2026" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.plan_year, body.data.answer); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/check", params={"npi": "1184617268", "plan_id": "29276OH0970001", "year": "2026"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["plan_year"], body["data"]["answer"]) ``` `year` must be a 4-digit year from 2025 to 2030. `plan_years_listed` lists every year in which the directory lists the provider. `GET /v1/plans/{plan_id}` returns `available_years` for a plan. ## Plan details `GET /v1/plans/{plan_id}` returns the name, insurer or organization, plan type and, for marketplace plans, metal level and state. Medicare Advantage plans add the service area, star rating and premium range. `listings.has_listings` tells you whether we hold provider listings for the plan. Response (200 OK · Medicare Advantage plan): ```json { "data": { "market": "ma", "plan_id": "H2526-001-000", "plan_year": 2027, "plan_name": "DEVOTED CHOICE 001 OH (PPO)", "contract_id": "H2526", "contract_name": "DEVOTED HEALTH PLAN OF OHIO INC", "organization": "Devoted Health", "parent_organization": "Devoted Health, Inc.", "plan_type": "PPO", "category": "MA-PD", "snp": { "is_snp": false, "type": "Not Applicable" }, "service_area": { "states": [ "OH" ], "counties": 78 }, "star_rating": 4.5, "monthly_premium": { "min": 0, "max": 0 }, "plan_details_available": true, "listings": { "has_listings": true, "as_of": "2026-10-29" }, "input_plan_id": "H2526-001-000", "available_years": [ 2027, 2026 ], "links": { "providers": "/v1/plans/H2526-001-000/providers", "check": "/v1/check?plan_id=H2526-001-000&npi={npi}" } } } ``` When we hold listings for a plan but not its details, `plan_details_available` is `false` and the name fields are `null`. ## Prescription drug plans Ids that start with S are prescription drug plans. They have no provider network, so a check answers `no_provider_network` with `in_network: null`. --- # Providers and NPIs Every provider is identified by a 10-digit NPI (National Provider Identifier). Send it as a string, and keep the leading digit. Source: https://inplan.io/docs/providers-and-npis · Updated 2026-10-30 ## NPI format An NPI has 10 digits and starts with 1 or 2. The last digit is a check digit (Luhn, with the prefix 80840). We validate it before any lookup, so a typo answers `400 invalid_npi` instead of a wrong answer. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/check", "request_id": "req_tyeV9VkOEHSgXbbW", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` ## Provider profiles `GET /v1/providers/{npi}` returns the name, credential, specialties, practice locations, group practices and facility affiliations from national provider registry records, plus `plan_summary`: how many marketplace and Medicare Advantage plans list the provider. Response (200 OK · shortened): ```json { "data": { "npi": "1184617268", "profile_found": true, "name": { "first": "Michael", "middle": "Buttimer", "last": "Williard", "suffix": null, "display": "Michael B. Williard, MD" }, "credential": "MD", "gender": "male", "status": "active", "specialties": [ { "name": "Orthopedic Surgery", "primary": true } ], "education": { "medical_school": "Ohio State University College of Medicine", "graduation_year": 1987 }, "locations": [ { "address_line_1": "605 Crescent Pl", "address_line_2": null, "city": "Gahanna", "state": "OH", "zip": "43230", "phone": "6145457900", "latitude": null, "longitude": null, "group": { "name": "Orthopedic One, Inc.", "pac_id": "3072598283" }, "telehealth": null } ], "groups": [ { "name": "Orthopedic One, Inc.", "pac_id": "3072598283", "members": 282 } ], "affiliations": [ { "facility_type": "Home health agency", "ccn": "368154", "name": null, "city": null, "state": null }, { "facility_type": "Hospital", "ccn": "360035", "name": "Mount Carmel East & West", "city": "Columbus", "state": "OH" } ], "privacy": { "contact_masked": false }, "plan_summary": { "aca": { "plans": 77, "issuers": 4, "states": [ "OH" ], "plan_years": [ 2025, 2026, 2027 ], "as_of": "2026-10-28" }, "ma": { "plans": 36, "organizations": 4, "plan_years": [ 2027 ], "accepting_new_patients_any": true, "as_of": "2026-10-29" } }, "links": { "plans": "/v1/providers/1184617268/plans" } } } ``` ## Names and credentials `name.display` joins the name and the credential as published, for example "Michael B. Williard, MD". We never add titles such as "Dr.": show the credential, not a title. ## Listings without a profile Some NPIs appear in plan directories but not in our registry records. Then `profile_found` is `false`, the identity fields are absent, and `plan_summary` still counts the plans. An NPI with neither answers `404 not_found`. ## Privacy Providers can ask us to hide their street address and phone. Then `privacy.contact_masked` is `true` and those fields are `null`. City, state and ZIP stay. We hold no patient data. --- # Data freshness Every answer carries the date we read the data behind it. Show that date to your users, and store it with any answer you keep. Source: https://inplan.io/docs/data-freshness · Updated 2026-10-30 ## Dates in every response | Field | What date it is | | --- | --- | | `evidence[].source.as_of` | The date we read the directory that lists this provider for this plan year. | | `data.as_of` | For a check: the date of the directory that gave the answer. | | `data_as_of` | The newest date among the data behind the whole response. | | `sources[].as_of` | The date each source type is current to. | Dates are `YYYY-MM-DD`. Different insurers publish on different schedules, so two answers in one batch can carry different dates. That is expected: each date is the truth for its own listing. ## Check the dates without a key `GET /v1/status` returns each source type with its date. It needs no key and does not count against your quota. curl: ```bash curl https://api.inplan.io/v1/status ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/status"); const body = await res.json(); console.log(body.data.status, body.data.datasets); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/status", timeout=10, ) body = res.json() print(body["data"]["status"], body["data"]["datasets"]) ``` Response (200 OK · application/json): ```json { "data": { "status": "ok", "api_enabled": true, "version": "d470109", "time": "2026-10-30T09:28:11.713Z", "checks": { "database": { "ok": true, "latency_ms": 0 }, "cache": { "ok": true } }, "datasets": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" }, { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ] } } ``` ## Directories change We read insurer and plan directories on a regular cycle. Networks can change between reads. Tell users to confirm with the plan or the provider before care. Every check includes a note that says so. If you cache answers, keep `as_of` with them and refresh them on your own schedule. --- # Batch checks Send up to 100 provider-plan pairs in one request to `POST /v1/check/batch`. Each pair gets the same answer a single check would give. Batch checks are on Builder and above. Source: https://inplan.io/docs/batch-checks · Updated 2026-10-30 ## The request body Send JSON with `Content-Type: application/json`. Put the pairs in `items`. Each item has `npi`, `plan_id` and an optional `year`. A top-level `year` applies to every item without its own. curl: ```bash curl https://api.inplan.io/v1/check/batch \ -H "Authorization: Bearer $INPLAN_KEY" \ -H "Content-Type: application/json" \ -d '{"items": [ {"npi": "1184617268", "plan_id": "29276OH0970001"}, {"npi": "1184617268", "plan_id": "H2526-001-000"}, {"npi": "1376006148", "plan_id": "13262AR0230002"}, {"npi": "1234567890", "plan_id": "29276OH0970001"} ]}' ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/check/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ items: [ { npi: "1184617268", plan_id: "29276OH0970001" }, { npi: "1184617268", plan_id: "H2526-001-000" }, { npi: "1376006148", plan_id: "13262AR0230002" }, { npi: "1234567890", plan_id: "29276OH0970001" } ] }), }); const body = await res.json(); console.log(body.data.summary); ``` Python: ```python import os, requests res = requests.post( "https://api.inplan.io/v1/check/batch", json={ "items": [ { "npi": "1184617268", "plan_id": "29276OH0970001" }, { "npi": "1184617268", "plan_id": "H2526-001-000" }, { "npi": "1376006148", "plan_id": "13262AR0230002" }, { "npi": "1234567890", "plan_id": "29276OH0970001" } ] }, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=30, ) body = res.json() print(body["data"]["summary"]) ``` ## Read the results `data.summary` counts the answers. `data.results` holds one result for each item, in the order you sent them; `index` is the position in `items`. An item that fails validation carries an `error` instead of an answer, and the rest of the batch still answers. Response (200 OK · response, shortened): ```json { "request_id": "req_KHtLEQJ46JLKLkZZ", "data_as_of": "2026-10-29", "data": { "count": 4, "summary": { "listed": 2, "not_found": 1, "invalid": 1 }, "results": [ { "index": 0, "npi": "1184617268", "plan_id": "29276OH0970001", "market": "aca", "in_network": true, "answer": "listed", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ] }, { "index": 1, "npi": "1184617268", "plan_id": "H2526-001-000", "market": "ma", "in_network": true, "answer": "listed", "evidence": [ { "plan_id": "H2526-001-000", "plan_year": 2027, "accepting_new_patients": true, "provider_kind": "practitioner", "source": { "type": "plan_directory", "as_of": "2026-10-29" } } ] }, { "index": 2, "npi": "1376006148", "plan_id": "13262AR0230002", "market": "aca", "in_network": null, "answer": "not_found", "evidence": [] }, { "index": 3, "npi": "1234567890", "plan_id": "29276OH0970001", "error": { "type": "invalid_npi", "title": "Invalid NPI", "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840)." } } ] } } ``` ## How a batch counts - Each item is one call against your monthly quota, including items that fail validation and repeated pairs. - The per-second limit counts the request once. - If your quota cannot cover every item, the whole batch answers `429 quota_exceeded` and nothing is counted. - If a lookup fails on our side (`503`), the whole batch fails and nothing is counted. Retry it. ## Limits and errors | Status | Type | When | | --- | --- | --- | | 400 | `batch_too_large` | More than 100 items. Split them. A rejected batch still counts its first 100 items. | | 400 | `invalid_request` | No `items` array, or an empty one, or a body that is not JSON. | | 403 | `plan_upgrade_required` | The key is on the Free plan. | | 413 | `payload_too_large` | The body is over 64 KB. | | 415 | `unsupported_media_type` | The `Content-Type` is not `application/json`. | --- # Pagination Two endpoints return long lists. A provider's plans page by `offset`; a plan's providers page by `cursor`. Every other endpoint returns its whole answer at once. Source: https://inplan.io/docs/pagination · Updated 2026-10-30 ## A provider's plans: offset and limit `GET /v1/providers/{npi}/plans` takes `limit` (default 500, at most 2,000) and `offset` (default 0). `total` counts every plan that matches your filters. `next_offset` is the `offset` for the next page, and `null` on the last page. curl: ```bash curl https://api.inplan.io/v1/providers/1184617268/plans \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d year=2026 -d limit=2 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/providers/1184617268/plans"); url.search = new URLSearchParams({ year: "2026", limit: "2" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.total, body.data.next_offset); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268/plans", params={"year": "2026", "limit": "2"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["total"], body["data"]["next_offset"]) ``` Response (200 OK · shortened): ```json { "data": { "npi": "1184617268", "provider_states": [ "OH" ], "filters": { "market": null, "state": null, "year": 2026 }, "total": 46, "counts": { "aca": 46, "ma": 0 }, "offset": 0, "limit": 2, "next_offset": 2, "plans": [ { "market": "aca", "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "network_tiers": [ "PARTICIPATING" ], "source": { "type": "insurer_directory", "as_of": "2026-07-27" } }, { "market": "aca", "plan_id": "29276OH0970002", "plan_year": 2026, "plan_name": "Anthem Heart Healthy Bronze Pathway 6000 ($0 Virtual PCP + $0 Select Drugs)", "network_tiers": [ "PARTICIPATING" ], "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ] } } ``` ## A plan's providers: cursors `GET /v1/plans/{plan_id}/providers` (Growth and above) takes `limit` (default 50, at most 100). Pass `page.next_cursor` from one response as `cursor` in the next request, unchanged, until it is `null`. A cursor belongs to one plan and plan year. Response (200 OK · page 1): ```json { "data": { "page": { "limit": 2, "rows_scanned": 2, "scanned_through": 2, "max_depth": 5000, "next_cursor": "eyJvIjoyLCJ5IjoyMDI2LCJwIjoiMjkyNzZPSDA5NzAwMDEifQ" } } } ``` With filters (`state`, `zip`, `radius`, `specialty`), a page can hold fewer than `limit` providers, even none, while `next_cursor` is not `null`. Keep following the cursor. Paging reads up to `page.max_depth` (5,000) listings of a plan; past that the API answers `400 depth_limit`. Narrow the filters, or ask about data files for whole plan networks. ## Paging errors | Type | When | | --- | --- | | `invalid_cursor` | The cursor was changed, or belongs to another plan. | | `depth_limit` | Paging reached `max_depth` listings for this plan. | | `invalid_request` | `limit` or `offset` is not a whole number in range. | --- # Guide: benefits and ICHRA For each plan on offer, show which of an employee's doctors the plan's directory lists. One batch call answers up to 100 doctor-plan pairs. Source: https://inplan.io/docs/guides/benefits · Updated 2026-10-30 ## 1. Build the pairs Collect the NPIs of the doctors each employee wants to keep, and the plan ids on offer for that employee. Every doctor-plan combination is one pair. Three doctors and eight plans make 24 pairs: one batch call, 24 calls against your quota. Need the NPI of a doctor your user names? Store the NPI when the user picks the doctor in your own search, then send it here. ## 2. Check them in one call curl: ```bash curl https://api.inplan.io/v1/check/batch \ -H "Authorization: Bearer $INPLAN_KEY" \ -H "Content-Type: application/json" \ -d '{"year":2026,"items":[{"npi":"1184617268","plan_id":"29276OH0970001"},{"npi":"1184617268","plan_id":"29276OH0970002"}]}' ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/check/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ year: 2026, items: [ { npi: "1184617268", plan_id: "29276OH0970001" }, { npi: "1184617268", plan_id: "29276OH0970002" } ] }), }); const body = await res.json(); const listed = body.data.results.filter((r) => r.answer === "listed"); ``` Python: ```python import os, requests res = requests.post( "https://api.inplan.io/v1/check/batch", json={ "year": 2026, "items": [ { "npi": "1184617268", "plan_id": "29276OH0970001" }, { "npi": "1184617268", "plan_id": "29276OH0970002" } ] }, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=30, ) body = res.json() listed = [r for r in body["data"]["results"] if r.get("answer") == "listed"] ``` Group the results by `plan_id`. A plan that lists every doctor keeps the employee's care team; show it first. ## 3. Show the answer - For `listed`, show "Listed" with the plan year and the date from `evidence[].source.as_of`. - For `not_found`, show "Not found" and ask the employee to confirm with the plan. Never show "out of network". - Put the note from `notes` near the results: directories change, so confirm before care. ## 4. Plan for volume Each pair is one call. An open-enrollment week can be your busiest month, so size your plan on that month. `X-Quota-Remaining` on every response tells you how many calls are left. Batch checks need Builder or above. See [Batch checks](https://inplan.io/docs/batch-checks). --- # Guide: telehealth and navigation Before you book, check that the member's plan lists the clinician. One call answers, with the date of the directory behind it. Source: https://inplan.io/docs/guides/telehealth · Updated 2026-10-30 ## 1. Check before you book Send the clinician's NPI and the member's plan id to `GET /v1/check`. Add `year` when the visit falls in a known plan year. curl: ```bash curl https://api.inplan.io/v1/check \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d npi=1184617268 -d plan_id=H2526-001-000 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/check"); url.search = new URLSearchParams({ npi: "1184617268", plan_id: "H2526-001-000" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); const { answer, evidence } = body.data; ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/check", params={"npi": "1184617268", "plan_id": "H2526-001-000"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() answer, evidence = body["data"]["answer"], body["data"]["evidence"] ``` ## 2. Read the new-patient flag For Medicare Advantage plans, `evidence[].accepting_new_patients` says whether the directory marks the clinician as open to new patients. It is `null` when the directory does not say. Response (200 OK · shortened): ```json { "data": { "answer": "listed", "evidence": [ { "plan_id": "H2526-001-000", "plan_year": 2027, "accepting_new_patients": true, "provider_kind": "practitioner", "source": { "type": "plan_directory", "as_of": "2026-10-29" } } ] } } ``` ## 3. Route on the answer - `listed`: book, and show the plan year and date in the confirmation. - `not_found`: offer the visit with a note to confirm coverage with the plan, or offer a clinician whose answer is `listed`. - To find clinicians a marketplace plan lists near the member, use `GET /v1/plans/{plan_id}/providers` with `zip` and `radius` (Growth and above). --- # Guide: brokers and agents Start from the client's doctors. List the plans that list each doctor, keep the plans every doctor shares, and quote those first. Source: https://inplan.io/docs/guides/brokers · Updated 2026-10-30 ## 1. List the plans for each doctor Call `GET /v1/providers/{npi}/plans` for each doctor. Filter by `market`, `state` and `year` to match the client. curl: ```bash curl https://api.inplan.io/v1/providers/1184617268/plans \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d market=aca -d state=OH -d year=2026 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/providers/1184617268/plans"); url.search = new URLSearchParams({ market: "aca", state: "OH", year: "2026" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); const planIds = new Set(body.data.plans.map((p) => p.plan_id)); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268/plans", params={"market": "aca", "state": "OH", "year": "2026"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() plan_ids = {p["plan_id"] for p in body["data"]["plans"]} ``` ## 2. Keep the plans they share Intersect the `plan_id` sets of all the doctors. The plans left list every doctor. For each plan, `plan_name`, `metal_level`, `plan_type` and `network_tiers` help you compare. Response (200 OK · plans, shortened): ```json { "data": { "plans": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "metal_level": "Bronze", "plan_type": "HMO", "network_tiers": [ "PARTICIPATING" ] }, { "plan_id": "29276OH0970002", "plan_year": 2026, "plan_name": "Anthem Heart Healthy Bronze Pathway 6000 ($0 Virtual PCP + $0 Select Drugs)", "metal_level": "Expanded Bronze", "plan_type": "HMO", "network_tiers": [ "PARTICIPATING" ] } ] } } ``` ## 3. Medicare Advantage clients Use `market=ma`. Each Medicare Advantage plan carries `service_area_states` and `provider_state_in_service_area`, so you can drop plans that the client cannot buy where they live. ## 4. Confirm the quote Before you send the quote, run `GET /v1/check` (or one batch) for the chosen plan and every doctor, and show the date from `evidence`. --- # Guide: health tech platforms Add network status where your users already pick a provider: search results, matching and intake forms. Source: https://inplan.io/docs/guides/health-tech · Updated 2026-10-30 ## 1. Show who the provider is `GET /v1/providers/{npi}` returns the name with credential, specialties, practice locations and a count of the plans that list the provider. curl: ```bash curl https://api.inplan.io/v1/providers/1184617268 \ -H "Authorization: Bearer $INPLAN_KEY" ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/providers/1184617268", { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); const { name, specialties, plan_summary } = body.data; ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268", headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() d = body["data"]; print(d["name"]["display"], d["plan_summary"]) ``` ## 2. Search inside a plan On Growth and above, `GET /v1/plans/{plan_id}/providers` pages through the providers a marketplace plan lists. Filter by `state`, `zip` with `radius` in miles, or `specialty` (a case-insensitive part of the specialty name). curl: ```bash curl https://api.inplan.io/v1/plans/29276OH0970001/providers \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d specialty=cardio -d limit=2 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/plans/29276OH0970001/providers"); url.search = new URLSearchParams({ specialty: "cardio", limit: "2" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); const { providers, page } = body.data; ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/plans/29276OH0970001/providers", params={"specialty": "cardio", "limit": "2"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() providers, page = body["data"]["providers"], body["data"]["page"] ``` Response (200 OK · shortened): ```json { "data": { "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "filters": { "state": null, "zip": null, "radius": null, "specialty": "CARDIO" }, "providers": [ { "npi": "1023120250", "name": "Jayanta T. Mukherjee, MD", "credential": "MD", "primary_specialty": "Cardiovascular Disease (cardiology)", "network_tiers": [ "PARTICIPATING" ], "locations": [ { "city": "Kettering", "state": "OH", "zip": "45429", "distance_miles": null } ], "profile_found": true } ], "page": { "limit": 2, "rows_scanned": 200, "scanned_through": 200, "max_depth": 5000, "next_cursor": "eyJvIjoyMDAsInkiOjIwMjYsInAiOiIyOTI3Nk9IMDk3MDAwMSJ9" }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } } ``` Follow `page.next_cursor` until it is `null`. See [Pagination](https://inplan.io/docs/pagination). ## 3. Check at intake When a user enters a plan, check it against their chosen provider with `GET /v1/check`, or check a whole roster with `POST /v1/check/batch`. Store `as_of` with the answer. --- # API reference Eight endpoints over HTTPS, JSON in and out. Every endpoint has its own page with parameters, examples in curl, JavaScript and Python, and real responses. Source: https://inplan.io/docs/api · Updated 2026-10-30 ## Base URL and key ```text https://api.inplan.io ``` Send `Authorization: Bearer ` on every request except `/v1/status`. See [Authentication](https://inplan.io/docs/authentication). ## Endpoints | Method | Path | Plan | | --- | --- | --- | | GET | [`/v1/providers/{npi}`](https://inplan.io/docs/api/providers) | Free and above | | GET | [`/v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans) | Free and above | | GET | [`/v1/check`](https://inplan.io/docs/api/check) | Free and above | | POST | [`/v1/check/batch`](https://inplan.io/docs/api/check-batch) | Builder and above | | GET | [`/v1/plans/{plan_id}`](https://inplan.io/docs/api/plans) | Free and above | | GET | [`/v1/plans/{plan_id}/providers`](https://inplan.io/docs/api/plan-providers) | Growth and above | | GET | [`/v1/coverage`](https://inplan.io/docs/api/coverage) | Free and above | | GET | [`/v1/status`](https://inplan.io/docs/api/status) | No key needed | ## The response envelope Every successful response has the same top level. Errors use the [problem format](https://inplan.io/docs/errors). Response (200 OK · envelope, data shortened): ```json { "request_id": "req_ws7hhhC7uSWYqEye", "data_as_of": "2026-07-27", "data": { "answer": "listed" }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ], "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` | Field | Type | Description | | --- | --- | --- | | `request_id` | string | Unique to this request. Also sent as the `X-Request-Id` header. Quote it when you contact support. | | `data_as_of` | date \| null | The date of the newest data behind this response. | | `data` | object | The answer. Its fields depend on the endpoint and are listed below. | | `sources` | object[] | The types of data behind the response, each with its date. | | `sources[].type` | string | The source type: `insurer_directory`, `plan_directory`, `provider_registry` or `plan_catalog`. | | `sources[].description` | string | The source type in plain words. | | `sources[].as_of` | date \| null | The date this type of data is current to. | | `notes` | string[] | Short notes to show next to the answer. Present on most endpoints. | ## Files for tools and agents - [`/openapi.json`](https://inplan.io/openapi.json): OpenAPI 3.1, with real response examples. - [Postman collection](https://inplan.io/docs/inplan.postman_collection.json): import it, then set `INPLAN_KEY` in the collection variables. - [`/llms.txt`](https://inplan.io/llms.txt) and [`/llms-full.txt`](https://inplan.io/llms-full.txt): these docs for language models. --- # MCP server for AI agents AI agents can call InPlan through our remote MCP server at `https://api.inplan.io/mcp`. It gives an agent 7 tools, from "is this provider in this plan?" to "which plans list this provider?", and every answer keeps its source type and date. Add the server with your API key and the agent can use it at once. Source: https://inplan.io/docs/mcp · Updated 2026-10-30 ## Endpoint ```text https://api.inplan.io/mcp ``` The server speaks the Model Context Protocol over **Streamable HTTP**. It is stateless: every JSON-RPC request is a `POST` to the endpoint and gets a JSON answer. It serves protocol version `2026-07-28` and the earlier versions that start with `initialize` (`2025-11-25`, `2025-06-18`, `2025-03-26`), so current clients connect without extra settings. ## API key Send your InPlan API key as a bearer token, the same key you use for the API: `Authorization: Bearer ip_live_...`. The free plan gives 500 calls a month with no credit card. [Get a free key](https://inplan.io/signup). Without a key, an agent can still connect and list the tools. A tool call without a key returns a tool error that tells the agent where to get a free key. Tool calls count exactly like API calls. Each call costs 1 call of your monthly quota (a batch costs 1 per accepted pair), and your plan, per-second limit and quota apply. Errors come back as tool errors with the same `type` values as the API, for example `quota_exceeded` or `plan_upgrade_required`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits) and [Errors](https://inplan.io/docs/errors). ## Tools | Tool | What it answers | Same as | Plan | | --- | --- | --- | --- | | check_network | Is this provider in this plan? `npi`, `plan_id`, optional `year`. | [`GET /v1/check`](https://inplan.io/docs/api/check) | Free and above | | check_network_batch | The same answer for up to 100 pairs in one call. | [`POST /v1/check/batch`](https://inplan.io/docs/api/check-batch) | Builder and above | | plans_for_provider | Every plan whose directory lists the provider. Filters: `market`, `state`, `year`; pages: `limit`, `offset`. | [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans) | Free and above | | lookup_provider | Who an NPI is: name, credential, specialties, locations, plan summary. | [`GET /v1/providers/{npi}`](https://inplan.io/docs/api/providers) | Free and above | | get_plan | Plan details by plan ID. | [`GET /v1/plans/{plan_id}`](https://inplan.io/docs/api/plans) | Free and above | | search_plan_providers | Providers a Marketplace plan lists. Filters: `state`, `specialty`, `zip`, `radius`; pages: `cursor`. | [`GET /v1/plans/{plan_id}/providers`](https://inplan.io/docs/api/plan-providers) | Growth and above | | coverage | The markets, states and insurers we hold listings for, with plan counts and dates. | [`GET /v1/coverage`](https://inplan.io/docs/api/coverage) | Free and above | Each tool result has a short plain-text summary, then the full API answer: as JSON text, and as `structuredContent` for clients that read structured output. The answer has the same fields as the API endpoint, with `sources` and `data_as_of`. ## How agents should read answers - **listed**: The plan's network directory lists the provider. The evidence names the plan year, the network tier and the source type with its date. - **not_found**: No listing was found in current directory data for this provider and plan. It is not a statement that the provider is out of network. - **no_provider_network**: The plan is a prescription drug plan; it has no provider network. The tool descriptions and the server instructions tell the agent the same thing, and to ask users to confirm with the plan before care. See [Answers and evidence](https://inplan.io/docs/answers-and-evidence). ## Claude Code Put your key in an environment variable, then add the server once: ```text claude mcp add --transport http inplan https://api.inplan.io/mcp \ --header "Authorization: Bearer $INPLAN_KEY" ``` Run `/mcp` in Claude Code to see the server and its 7 tools. Then ask, for example: "Is NPI 1184617268 in plan 29276OH0970001?" ## Claude Desktop Claude Desktop reads local servers from `claude_desktop_config.json` (Settings, Developer, Edit Config). The `mcp-remote` bridge connects it to our remote server with your key: ```text { "mcpServers": { "inplan": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.inplan.io/mcp", "--header", "Authorization:${INPLAN_AUTH}"], "env": { "INPLAN_AUTH": "Bearer ip_live_..." } } } } ``` Restart Claude Desktop after you save the file. The bridge runs with `npx`, so Node.js must be installed. ## Cursor Add the server to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` (one project). Cursor reads `INPLAN_KEY` from your environment: ```text { "mcpServers": { "inplan": { "url": "https://api.inplan.io/mcp", "headers": { "Authorization": "Bearer ${env:INPLAN_KEY}" } } } } ``` ## VS Code and other clients VS Code asks for the key once and stores it securely with this `.vscode/mcp.json`: ```text { "servers": { "inplan": { "type": "http", "url": "https://api.inplan.io/mcp", "headers": { "Authorization": "Bearer ${input:inplan-key}" } } }, "inputs": [ { "type": "promptString", "id": "inplan-key", "description": "InPlan API key", "password": true } ] } ``` Any client that supports remote servers over Streamable HTTP needs three things: the URL, the transport and the header. ```text { "type": "streamable-http", "url": "https://api.inplan.io/mcp", "headers": { "Authorization": "Bearer ip_live_..." } } ``` ## Test with curl List the tools (no key needed): ```text curl https://api.inplan.io/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Call a tool with your key: ```text curl https://api.inplan.io/mcp \ -H "Authorization: Bearer $INPLAN_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"check_network", "arguments":{"npi":"1184617268","plan_id":"29276OH0970001"}}}' ``` --- # Errors Errors use `application/problem+json` (RFC 7807). Branch your code on `type`, log `detail`, and keep `request_id` for support. Source: https://inplan.io/docs/errors · Updated 2026-10-30 ## The problem format Response (400 Bad Request · application/problem+json): ```http HTTP/1.1 400 Bad Request Content-Type: application/problem+json; charset=utf-8 RateLimit-Limit: 60 RateLimit-Remaining: 106 RateLimit-Reset: 1 RateLimit-Policy: 60;w=1;burst=120 X-Quota-Limit: 2000000 X-Quota-Remaining: 1997025 X-Quota-Reset: 1793491200 X-Request-Id: req_tyeV9VkOEHSgXbbW ``` ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/check", "request_id": "req_tyeV9VkOEHSgXbbW", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` | Field | Type | Description | | --- | --- | --- | | `type` | string | Short problem type. Branch your code on this value. See [Errors](https://inplan.io/docs/errors). | | `title` | string | A short, readable summary. | | `status` | integer | The HTTP status code. | | `detail` | string | What went wrong with this request, in one sentence. | | `instance` | string | The path you called. | | `request_id` | string | Unique to this request. Quote it to support. | | `docs` | string | A link to the matching entry on the errors page. | | `current_plan` | string | `plan_upgrade_required` only: the plan of the key you sent. | | `required_plan` | string | `plan_upgrade_required` only: the smallest plan that includes the endpoint. | | `sample_npis` | string[] | `demo_restricted` only: the NPIs the demo key answers for. | ## Statuses at a glance | Status | Types | Retry? | | --- | --- | --- | | 400 | [`invalid_request`](#invalid_request), [`invalid_npi`](#invalid_npi), [`invalid_plan_id`](#invalid_plan_id), [`invalid_cursor`](#invalid_cursor), [`batch_too_large`](#batch_too_large), [`depth_limit`](#depth_limit) | No. Fix the request. | | 401 | [`unauthorized`](#unauthorized) | No. Fix the key. | | 403 | [`account_suspended`](#account_suspended), [`plan_upgrade_required`](#plan_upgrade_required), [`demo_restricted`](#demo_restricted) | No. | | 404 | [`not_found`](#not_found), [`plan_not_found`](#plan_not_found) | No. | | 413, 415 | [`payload_too_large`](#payload_too_large), [`unsupported_media_type`](#unsupported_media_type) | No. Fix the body. | | 429 | [`rate_limited`](#rate_limited), [`quota_exceeded`](#quota_exceeded) | Yes, after `Retry-After`. | | 500, 501 | [`internal_error`](#internal_error), [`not_implemented`](#not_implemented) | 500: yes. 501: no. | | 503 | [`busy`](#busy), [`timeout`](#timeout) | Yes, after `Retry-After`. | In a batch, an item that fails validation does not fail the call: it carries its own `error` with `type`, `title` and `detail`. See [Batch checks](https://inplan.io/docs/batch-checks). ## Every problem type ### 400 invalid_request **When:** A parameter is missing or invalid: no `npi` or `plan_id`, a bad `year`, `state`, `market`, `zip`, `limit` or `offset`, or a batch body that is not `{"items": [...]}` JSON. **What to do:** Fix the request. `detail` names the parameter. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_request", "title": "Missing parameter", "status": 400, "detail": "`plan_id` is required.", "instance": "/v1/check", "request_id": "req_xS54q-lq-95-CNO7", "docs": "https://inplan.io/docs/errors#invalid_request" } ``` ### 400 invalid_npi **When:** The NPI is not 10 digits, or fails its check digit. **What to do:** Check the NPI for a typo. Send it as a string. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/check", "request_id": "req_tyeV9VkOEHSgXbbW", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` ### 400 invalid_plan_id **When:** The plan id matches neither the marketplace nor the Medicare Advantage format. **What to do:** See [Plans and plan years](https://inplan.io/docs/plans-and-plan-years#ids) for the formats. ### 400 invalid_cursor **When:** The `cursor` was changed, or belongs to another plan. **What to do:** Pass `page.next_cursor` from the previous page unchanged. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_cursor", "title": "Invalid cursor", "status": 400, "detail": "Pass the next_cursor value from the previous page unchanged.", "instance": "/v1/plans/29276OH0970001/providers", "request_id": "req_kicZeaYEQF_Xcf0U", "docs": "https://inplan.io/docs/errors#invalid_cursor" } ``` ### 400 batch_too_large **When:** A batch has more than 100 items. **What to do:** Split it into batches of 100 or fewer. A rejected batch still counts its first 100 items. Response (400 Bad Request · application/problem+json): ```json { "type": "batch_too_large", "title": "Batch too large", "status": 400, "detail": "At most 100 pairs per call; got 101.", "instance": "/v1/check/batch", "request_id": "req_hxtwDDAeyVY4wVMP", "docs": "https://inplan.io/docs/errors#batch_too_large" } ``` ### 400 depth_limit **When:** Paging through a plan's providers reached 5,000 listings. **What to do:** Narrow with `state`, `zip` or `specialty`, or ask us about data files. ### 401 unauthorized **When:** The `Authorization` header is missing or malformed, or the key is unknown or revoked. **What to do:** Send `Authorization: Bearer ip_live_...` with an active key. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Invalid API key", "status": 401, "detail": "The key is unknown or revoked.", "instance": "/v1/check", "request_id": "req_zH72k5VrnZm8eUI0", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 403 account_suspended **When:** The account behind the key is not active. **What to do:** Write to support. ### 403 plan_upgrade_required **When:** The endpoint is not in the plan of this key: batch needs Builder, plan-to-provider search needs Growth. The body adds `current_plan` and `required_plan`. **What to do:** Move to a larger plan, or use single checks. Response (403 Forbidden · application/problem+json): ```json { "type": "plan_upgrade_required", "title": "Plan upgrade required", "status": 403, "detail": "This endpoint needs the builder plan or higher; this key is on free.", "instance": "/v1/check/batch", "request_id": "req_RL1ifh22It4kUXjz", "docs": "https://inplan.io/docs/errors#plan_upgrade_required", "current_plan": "free", "required_plan": "builder" } ``` ### 403 demo_restricted **When:** The Playground demo key was used for an NPI outside its sample list. **What to do:** Use a sample NPI, or create a free key for any NPI. Response (403 Forbidden · application/problem+json): ```json { "type": "demo_restricted", "title": "Demo key restriction", "status": 403, "detail": "The playground key works only for the sample NPIs. Create a free key for any NPI.", "instance": "/api/v1/check", "request_id": "req_mW3y_sM0mgptQFPy", "docs": "https://inplan.io/docs/errors#demo_restricted", "sample_npis": [ "1376006148", "1427468453", "1306032347", "1538462239", "1629458435" ] } ``` ### 404 not_found **When:** We hold no provider record and no plan listing for this NPI. **What to do:** Check the NPI. A valid NPI can still be unknown to us. Response (404 Not Found · application/problem+json): ```json { "type": "not_found", "title": "Provider not found", "status": 404, "detail": "No provider found for NPI 1999999984.", "instance": "/v1/providers/1999999984", "request_id": "req_eHYrIWCixJ4RIl52", "docs": "https://inplan.io/docs/errors#not_found" } ``` ### 404 plan_not_found **When:** We hold no plan with this id (or no plan year you asked for) on `/v1/plans`. **What to do:** Check the id and `year`. `/v1/check` answers `not_found` instead of 404 for an unknown plan. Response (404 Not Found · application/problem+json): ```json { "type": "plan_not_found", "title": "Plan not found", "status": 404, "detail": "No plan found for 99999OH0010001.", "instance": "/v1/plans/99999OH0010001", "request_id": "req_yJz9v1z9Xg6vHNi9", "docs": "https://inplan.io/docs/errors#plan_not_found" } ``` ### 413 payload_too_large **When:** A request body is over 64 KB. **What to do:** Send fewer items in each batch. ### 415 unsupported_media_type **When:** A batch request does not have `Content-Type: application/json`. **What to do:** Set the header. Response (415 Unsupported Media Type · application/problem+json): ```json { "type": "unsupported_media_type", "title": "JSON required", "status": 415, "detail": "Send `Content-Type: application/json`.", "instance": "/v1/check/batch", "request_id": "req_zdyooaUKchC18Eiy", "docs": "https://inplan.io/docs/errors#unsupported_media_type" } ``` ### 429 rate_limited **When:** More requests a second than the key allows. **What to do:** Wait `Retry-After` seconds, then retry. Not counted against your quota. Response (429 Too Many Requests · application/problem+json): ```json { "type": "rate_limited", "title": "Too many requests", "status": 429, "detail": "This key allows 2 requests per second.", "instance": "/v1/coverage", "request_id": "req_TrHd8LO8HNbLoZ_D", "docs": "https://inplan.io/docs/errors#rate_limited" } ``` ### 429 quota_exceeded **When:** Your account used its calls for this month (all its keys share them), or a batch needs more calls than are left. The body adds `quota`, `used`, `cost` and `resets_at`. **What to do:** Wait for the reset on the 1st of the month, UTC (`Retry-After` holds the seconds), or move to a larger plan. Never billed. ### 500 internal_error **When:** Something failed on our side. **What to do:** Retry. If it repeats, send us the `request_id`. Not counted against your quota. ### 501 not_implemented **When:** Plan-to-provider search was called with a Medicare Advantage plan id. **What to do:** Use `/v1/check` or `/v1/providers/{npi}/plans` for Medicare Advantage plans. Response (501 Not Implemented · application/problem+json): ```json { "type": "not_implemented", "title": "Available for Marketplace plans", "status": 501, "detail": "Provider search by plan is available for Marketplace plans. For Medicare Advantage plans use /v1/check or /v1/providers/{npi}/plans.", "instance": "/v1/plans/H2526-001-000/providers", "request_id": "req_91oTGyIEJwKXhH2R", "docs": "https://inplan.io/docs/errors#not_implemented" } ``` ### 503 busy **When:** The API is under heavy load or paused for maintenance. **What to do:** Retry after `Retry-After` seconds. Not counted against your quota. ### 503 timeout **When:** A lookup took too long. **What to do:** Retry after `Retry-After` seconds. Not counted against your quota. --- # Data dictionary Every field the API returns, with its type and meaning. The paths and types come from real responses for each schema in `/openapi.json`, so this page cannot fall behind the API. Source: https://inplan.io/docs/data-dictionary · Updated 2026-10-30 ## Envelope Every successful response has these top-level fields. `data` changes by endpoint. | Field | Type | Description | | --- | --- | --- | | `request_id` | string | Unique to this request. Also sent as the `X-Request-Id` header. Quote it when you contact support. | | `data_as_of` | date \| null | The date of the newest data behind this response. | | `data` | object | The answer. Its fields depend on the endpoint and are listed below. | | `sources` | object[] | The types of data behind the response, each with its date. | | `sources[].type` | string | The source type: `insurer_directory`, `plan_directory`, `provider_registry` or `plan_catalog`. | | `sources[].description` | string | The source type in plain words. | | `sources[].as_of` | date \| null | The date this type of data is current to. | | `notes` | string[] | Short notes to show next to the answer. Present on most endpoints. | ## Check `data` of [`GET /v1/check`](https://inplan.io/docs/api/check). | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you checked. | | `plan_id` | string | The plan we checked. Marketplace ids are normalized to the 14-character plan id. | | `input_plan_id` | string | The plan id exactly as you sent it. | | `market` | string | `aca` for marketplace plans, `ma` for Medicare Advantage plans. | | `plan_years_listed` | integer[] | Plan years in which the directory lists the provider for this plan. | | `in_network` | true \| null | `true`: the directory lists the provider for this plan. `null`: no listing found in current directory data. It is never a statement that the provider is out of network. | | `answer` | string | `listed`, `not_found`, or `no_provider_network` for a prescription drug plan id (contract ids that start with S). | | `explanation` | string | One plain sentence that states the answer. Safe to show to users as it is. | | `evidence` | object[] | One entry for each listing we found. Empty when the answer is `not_found`. | | `evidence[].plan_id` | string | The plan of this listing. | | `evidence[].plan_year` | integer | The plan year of this listing. | | `evidence[].network_tier` | string | Marketplace plans: the tier the directory names, for example `PARTICIPATING` or `PREFERRED`. Several tiers are joined with commas. | | `evidence[].source` | object | Where this listing comes from. | | `evidence[].source.type` | string | `insurer_directory` for marketplace plans, `plan_directory` for Medicare Advantage plans. | | `evidence[].source.as_of` | date | The date we read that directory. | | `evidence[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients (`null` when it does not say). | | `evidence[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed, for example `practitioner`. | | `plan_year` | integer \| null | The plan year this answer is about: the `year` you sent, else the newest year listed, else the newest plan year we hold. | | `plan` | object \| null | Plan details. `null` when we hold no details for this plan id. | | `plan.plan_name` | string | The plan name as the insurer publishes it. | | `plan.issuer` | object | Marketplace plans: the insurer. | | `plan.issuer.id` | string | The 5-digit insurer id (the first 5 characters of the plan id). | | `plan.issuer.name` | string | The insurer name. | | `plan.state` | string | Marketplace plans: the state the plan is sold in. | | `plan.metal_level` | string | Marketplace plans: Bronze, Expanded Bronze, Silver, Gold, Platinum or Catastrophic. | | `plan.plan_type` | string | HMO, PPO, EPO, POS and similar. | | `plan.variant` | string \| null | Marketplace plans: the 2-digit variant you sent (for example `01`), else `null`. | | `plan.organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `plan.service_area_states` | string[] | Medicare Advantage plans: the states in the plan's service area. | | `source` | object | The source type of the answer and its date. | | `source.type` | string | `insurer_directory` or `plan_directory`. | | `source.as_of` | date \| null | The date we read the directory that gave this answer. | | `as_of` | date \| null | The date we read the directory that gave this answer (same as `source.as_of`). | ## Batch check `data` of [`POST /v1/check/batch`](https://inplan.io/docs/api/check-batch). Each item in `results` has the fields of a check, plus `index`. | Field | Type | Description | | --- | --- | --- | | `count` | integer | The number of items you sent. | | `summary` | object | Counts of the answers in this batch. | | `summary.listed` | integer | Items answered `listed`. | | `summary.not_found` | integer | Items answered with `in_network: null`. | | `summary.invalid` | integer | Items that failed validation and carry an `error`. | | `results` | object[] | One result for each item, in the order you sent them. | | `results[].index` | integer | The position of the item in your `items` array, starting at 0. | | `results[].npi` | string | The NPI you checked. | | `results[].plan_id` | string | The plan we checked. Marketplace ids are normalized to the 14-character plan id. | | `results[].input_plan_id` | string | The plan id exactly as you sent it. | | `results[].market` | string | `aca` for marketplace plans, `ma` for Medicare Advantage plans. | | `results[].plan_years_listed` | integer[] | Plan years in which the directory lists the provider for this plan. | | `results[].in_network` | true \| null | `true`: the directory lists the provider for this plan. `null`: no listing found in current directory data. It is never a statement that the provider is out of network. | | `results[].answer` | string | `listed`, `not_found`, or `no_provider_network` for a prescription drug plan id (contract ids that start with S). | | `results[].explanation` | string | One plain sentence that states the answer. Safe to show to users as it is. | | `results[].evidence` | object[] | One entry for each listing we found. Empty when the answer is `not_found`. | | `results[].evidence[].plan_id` | string | The plan of this listing. | | `results[].evidence[].plan_year` | integer | The plan year of this listing. | | `results[].evidence[].network_tier` | string | Marketplace plans: the tier the directory names, for example `PARTICIPATING` or `PREFERRED`. Several tiers are joined with commas. | | `results[].evidence[].source` | object | Where this listing comes from. | | `results[].evidence[].source.type` | string | `insurer_directory` for marketplace plans, `plan_directory` for Medicare Advantage plans. | | `results[].evidence[].source.as_of` | date | The date we read that directory. | | `results[].evidence[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients (`null` when it does not say). | | `results[].evidence[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed, for example `practitioner`. | | `results[].plan_year` | integer \| null | The plan year this answer is about: the `year` you sent, else the newest year listed, else the newest plan year we hold. | | `results[].plan` | object \| null | Plan details. `null` when we hold no details for this plan id. | | `results[].plan.plan_name` | string | The plan name as the insurer publishes it. | | `results[].plan.issuer` | object | Marketplace plans: the insurer. | | `results[].plan.issuer.id` | string | The 5-digit insurer id (the first 5 characters of the plan id). | | `results[].plan.issuer.name` | string | The insurer name. | | `results[].plan.state` | string | Marketplace plans: the state the plan is sold in. | | `results[].plan.metal_level` | string | Marketplace plans: Bronze, Expanded Bronze, Silver, Gold, Platinum or Catastrophic. | | `results[].plan.plan_type` | string | HMO, PPO, EPO, POS and similar. | | `results[].plan.variant` | string \| null | Marketplace plans: the 2-digit variant you sent (for example `01`), else `null`. | | `results[].plan.organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `results[].plan.service_area_states` | string[] | Medicare Advantage plans: the states in the plan's service area. | | `results[].source` | object | The source type of the answer and its date. | | `results[].source.type` | string | `insurer_directory` or `plan_directory`. | | `results[].source.as_of` | date \| null | The date we read the directory that gave this answer. | | `results[].as_of` | date \| null | The date we read the directory that gave this answer (same as `source.as_of`). | | `results[].error` | object | Only on an item that failed validation. The other fields of a check are then absent. | | `results[].error.type` | string | The problem type, for example `invalid_npi`. | | `results[].error.title` | string | A short summary. | | `results[].error.detail` | string | What is wrong with this item. | ## Provider profile `data` of [`GET /v1/providers/{npi}`](https://inplan.io/docs/api/providers). | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you asked for. | | `profile_found` | boolean | `true` when we hold identity details for this NPI. When `false`, only `npi`, `plan_summary` and `links` are present. | | `name` | object | The name as published, with a display form. | | `name.first` | string | First name. | | `name.middle` | string | Middle name. | | `name.last` | string | Last name. | | `name.suffix` | string \| null | Suffix, for example Jr. | | `name.display` | string | Name and credential ready to show, for example "Michael B. Williard, MD". We never add titles. | | `credential` | string | The credential as published, for example MD, DO, NP or PA. | | `gender` | string | `male`, `female`, or `null` when not published. | | `status` | string | `active` or `deactivated` in the national provider registry. | | `specialties` | object[] | Specialties, primary first. | | `specialties[].name` | string | The specialty name. | | `specialties[].primary` | boolean | `true` for the primary specialty. | | `education` | object | Training details, or `null` when not published. | | `education.medical_school` | string | The school named in the registry. | | `education.graduation_year` | integer | The graduation year. | | `locations` | object[] | Practice locations. | | `locations[].address_line_1` | string | Street address. `null` when the provider asked us to hide contact details. | | `locations[].address_line_2` | string \| null | Suite or second address line. | | `locations[].city` | string | City. | | `locations[].state` | string | 2-letter state code. | | `locations[].zip` | string | 5-digit ZIP code. | | `locations[].phone` | string \| null | Phone number, digits only. `null` when not published or hidden at the provider's request. | | `locations[].latitude` | number \| null | Latitude of the location, when known. | | `locations[].longitude` | number \| null | Longitude of the location, when known. | | `locations[].group` | object | The group practice at this location, or `null`. | | `locations[].group.name` | string | The group name. | | `locations[].group.pac_id` | string | The group identifier in the national registry. | | `locations[].telehealth` | boolean \| null | `true` when the registry marks telehealth at this location, else `null`. | | `groups` | object[] | Group practices the provider belongs to. | | `groups[].name` | string | The group name. | | `groups[].pac_id` | string | The group identifier in the national registry. | | `groups[].members` | integer | Number of providers in the group. | | `affiliations` | object[] | Facilities the provider is affiliated with. | | `affiliations[].facility_type` | string | For example Hospital or Home health agency. | | `affiliations[].ccn` | string | The facility's 6-character certification number. | | `affiliations[].name` | string \| null | Facility name, when we hold it. | | `affiliations[].city` | string \| null | Facility city. | | `affiliations[].state` | string \| null | Facility state. | | `privacy` | object | Privacy settings for this record. | | `privacy.contact_masked` | boolean | `true` when the provider asked us to hide street address and phone. Those fields are then `null`. | | `plan_summary` | object | Counts of the plans that list this provider. Get the plans from `links.plans`. | | `plan_summary.aca` | object | Marketplace plans. | | `plan_summary.aca.plans` | integer | Plan and plan-year pairs that list the provider. | | `plan_summary.aca.issuers` | integer | Insurers among those plans. | | `plan_summary.aca.states` | string[] | States of those plans. | | `plan_summary.aca.plan_years` | integer[] | Plan years with a listing. | | `plan_summary.aca.as_of` | date | The newest date we read a directory that lists the provider. | | `plan_summary.ma` | object | Medicare Advantage plans. | | `plan_summary.ma.plans` | integer | Plan and plan-year pairs that list the provider. | | `plan_summary.ma.organizations` | integer | Organizations among those plans. | | `plan_summary.ma.plan_years` | integer[] | Plan years with a listing. | | `plan_summary.ma.accepting_new_patients_any` | boolean | `true` when at least one directory says the provider accepts new patients, else `null`. | | `plan_summary.ma.as_of` | date | The newest date we read a directory that lists the provider. | | `links` | object | Related endpoints. | | `links.plans` | string | The path of the full plan list for this provider. | ## Plans that list a provider `data` of [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans). | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you asked for. | | `provider_states` | string[] | States of the provider's practice locations. | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.market` | string \| null | `aca`, `ma` or `null`. | | `filters.state` | string \| null | The state filter. | | `filters.year` | integer \| null | The plan year filter. | | `total` | integer | Plans that match the filters, across all pages. | | `counts` | object | The total, split by market. | | `counts.aca` | integer | Marketplace plans that match. | | `counts.ma` | integer | Medicare Advantage plans that match. | | `offset` | integer | The offset of this page. | | `limit` | integer | The page size. | | `next_offset` | integer \| null | The `offset` for the next page, or `null` on the last page. | | `plans` | object[] | The plans on this page: marketplace plans first, then Medicare Advantage plans. | | `plans[].market` | string | `aca` or `ma`. | | `plans[].plan_id` | string | The plan id. | | `plans[].plan_year` | integer | The plan year of this listing. | | `plans[].plan_name` | string \| null | The plan name, when we hold plan details. | | `plans[].issuer` | object | Marketplace plans: the insurer. | | `plans[].issuer.id` | string | The 5-digit insurer id. | | `plans[].issuer.name` | string \| null | The insurer name, when we hold it. | | `plans[].state` | string | Marketplace plans: the plan state. | | `plans[].metal_level` | string \| null | Marketplace plans: the metal level. | | `plans[].plan_type` | string \| null | HMO, PPO, EPO, POS and similar. | | `plans[].network_tiers` | string[] | Marketplace plans: the tiers the directory names for this provider. | | `plans[].plan_details_available` | boolean | `true` when plan name, metal level and plan type are included. | | `plans[].source` | object | Where this listing comes from. | | `plans[].source.type` | string | `insurer_directory` or `plan_directory`. | | `plans[].source.as_of` | date \| null | The date we read that directory. | | `plans[].organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `plans[].snp` | object | Medicare Advantage plans: special needs plan details. | | `plans[].snp.is_snp` | boolean | `true` for a special needs plan. | | `plans[].snp.type` | string | The special needs plan type, as published. | | `plans[].service_area_states` | string[] | Medicare Advantage plans: states in the plan's service area. | | `plans[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients. | | `plans[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed. | | `plans[].taxonomy` | string | Medicare Advantage plans: the provider taxonomy code listed with the network entry. | | `plans[].provider_state_in_service_area` | boolean \| null | Medicare Advantage plans: `true` when one of the provider's states is in the service area, `null` when either side is unknown. | ## Plan details `data` of [`GET /v1/plans/{plan_id}`](https://inplan.io/docs/api/plans). Marketplace and Medicare Advantage plans share some fields and add their own. | Field | Type | Description | | --- | --- | --- | | `market` | string | `aca` or `ma`. | | `plan_id` | string | The normalized plan id. | | `plan_year` | integer | The plan year of these details: the `year` you sent, else the newest year we hold. | | `plan_name` | string | The plan name as published. | | `issuer` | object | Marketplace plans: the insurer. | | `issuer.id` | string | The 5-digit insurer id. | | `issuer.name` | string | The insurer name. | | `state` | string | Marketplace plans: the plan state. | | `metal_level` | string | Marketplace plans: the metal level. | | `plan_type` | string | HMO, PPO, EPO, POS and similar. | | `markets` | string[] | Marketplace plans: the markets the plan is sold in, for example Individual. | | `network_id` | string | Marketplace plans: the insurer's network id for this plan. | | `variant_ids` | string[] | Marketplace plans: every variant id of this plan. | | `plan_details_available` | boolean | `true` when plan name and plan details are included. | | `listings` | object | Whether we hold provider listings for this plan. | | `listings.has_listings` | boolean | `true` when we hold provider listings for this plan. | | `listings.as_of` | date | The date we read its directory. | | `variant` | string \| null | Marketplace plans: the variant you sent, else `null`. | | `input_plan_id` | string | The plan id exactly as you sent it. | | `available_years` | integer[] | Every plan year we hold for this plan. | | `links` | object | Related endpoints. | | `links.providers` | string | The path of the provider list for this plan. | | `links.check` | string | A check path for this plan. Put an NPI in place of `{npi}`. | | `contract_id` | string | Medicare Advantage plans: the contract id (the part before the first dash). | | `contract_name` | string | Medicare Advantage plans: the contract name as published. | | `organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `parent_organization` | string | Medicare Advantage plans: the parent organization. | | `category` | string | Medicare Advantage plans: the plan category, for example MA-PD. | | `snp` | object | Medicare Advantage plans: special needs plan details. | | `snp.is_snp` | boolean | `true` for a special needs plan. | | `snp.type` | string | The special needs plan type, as published. | | `service_area` | object | Medicare Advantage plans: the service area. | | `service_area.states` | string[] | States in the service area. | | `service_area.counties` | integer | Number of counties in the service area. | | `star_rating` | number \| null | Medicare Advantage plans: the overall star rating published for the plan. | | `monthly_premium` | object | Medicare Advantage plans: the monthly premium range in US dollars. | | `monthly_premium.min` | integer | Lowest monthly premium. | | `monthly_premium.max` | integer | Highest monthly premium. | ## Providers in a plan `data` of [`GET /v1/plans/{plan_id}/providers`](https://inplan.io/docs/api/plan-providers). | Field | Type | Description | | --- | --- | --- | | `plan_id` | string | The plan id. | | `plan_year` | integer | The plan year of these listings. | | `plan_name` | string | The plan name. | | `issuer` | object | The insurer. | | `issuer.id` | string | The 5-digit insurer id. | | `issuer.name` | string | The insurer name. | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.state` | string \| null | State filter. | | `filters.zip` | string \| null | ZIP filter. | | `filters.radius` | integer \| null | Radius in miles around `zip`. | | `filters.specialty` | string \| null | Specialty filter, in upper case. | | `providers` | object[] | Providers on this page, in directory order. | | `providers[].npi` | string | The NPI. | | `providers[].name` | string \| null | Display name with credential. | | `providers[].credential` | string \| null | The credential as published. | | `providers[].primary_specialty` | string \| null | The primary specialty. | | `providers[].network_tiers` | string[] | Tiers the directory names for this provider in this plan. | | `providers[].locations` | object[] | Up to 5 practice locations that match your filters. | | `providers[].locations[].city` | string | City. | | `providers[].locations[].state` | string | State. | | `providers[].locations[].zip` | string | ZIP code. | | `providers[].locations[].distance_miles` | number \| null | Miles from the `zip` you sent, when you sent `radius`. Else `null`. | | `providers[].profile_found` | boolean | `true` when we hold identity details for this NPI. | | `page` | object | Paging state. See [Pagination](https://inplan.io/docs/pagination). | | `page.limit` | integer | The page size you asked for. | | `page.rows_scanned` | integer | Listing rows read for this page. | | `page.scanned_through` | integer | Listing rows read so far, across pages. | | `page.max_depth` | integer | The most listing rows paging reads for one plan. | | `page.next_cursor` | string \| null | Pass it as `cursor` for the next page. `null` on the last page. | | `source` | object | The source type and date of these listings. | | `source.type` | string | Always `insurer_directory`. | | `source.as_of` | date | The date we read the directory. | ## Coverage `data` of [`GET /v1/coverage`](https://inplan.io/docs/api/coverage). | Field | Type | Description | | --- | --- | --- | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.market` | string \| null | `aca`, `ma` or `null`. | | `filters.state` | string | The state filter. | | `filters.year` | integer \| null | The plan year filter. | | `markets` | string[] | Markets with listings for these filters. | | `summary` | object | Totals by market, then by plan year. | | `summary.{market}` | object | `aca` or `ma`. | | `summary.{market}.{year}` | object | Totals for one plan year. | | `summary.{market}.{year}.states` | integer | States with listings. | | `summary.{market}.{year}.insurers` | integer | Insurers or organizations with listings. | | `summary.{market}.{year}.plans` | integer | Plans with listings. | | `summary.{market}.{year}.as_of` | date | The newest date we read a directory in this group. | | `rows` | object[] | One row for each market, state, insurer and plan year with listings. | | `rows[].market` | string | `aca` or `ma`. | | `rows[].state` | string | 2-letter state code. | | `rows[].insurer` | string | The insurer or organization name. | | `rows[].plan_year` | integer | The plan year. | | `rows[].plans` | integer | Plans with listings in this row. | | `rows[].as_of` | date \| null | The date we read the directory for this row. | ## Status `data` of [`GET /v1/status`](https://inplan.io/docs/api/status). | Field | Type | Description | | --- | --- | --- | | `status` | string | `ok`, `degraded` (answers still served), `busy` or `paused`. `busy` and `paused` come with HTTP 503. | | `api_enabled` | boolean | `false` while the API is paused for maintenance. | | `version` | string | The running API build. | | `time` | string | Server time, ISO 8601 UTC. | | `checks` | object | Health of the parts behind the API. | | `checks.database` | object | The database check. | | `checks.database.ok` | boolean | `true` when the database answered. | | `checks.database.latency_ms` | integer \| null | Time the database check took, in milliseconds. | | `checks.cache` | object | The cache check. | | `checks.cache.ok` | boolean | `true` when the cache answered. | | `datasets` | object[] | Every source type with the date it is current to. | | `datasets[].type` | string | The source type: `insurer_directory`, `plan_directory`, `provider_registry` or `plan_catalog`. | | `datasets[].description` | string | The source type in plain words. | | `datasets[].as_of` | date | The date this type of data is current to. | ## Problem The body of every error. See [Errors](https://inplan.io/docs/errors). | Field | Type | Description | | --- | --- | --- | | `type` | string | Short problem type. Branch your code on this value. See [Errors](https://inplan.io/docs/errors). | | `title` | string | A short, readable summary. | | `status` | integer | The HTTP status code. | | `detail` | string | What went wrong with this request, in one sentence. | | `instance` | string | The path you called. | | `request_id` | string | Unique to this request. Quote it to support. | | `docs` | string | A link to the matching entry on the errors page. | | `current_plan` | string | `plan_upgrade_required` only: the plan of the key you sent. | | `required_plan` | string | `plan_upgrade_required` only: the smallest plan that includes the endpoint. | | `sample_npis` | string[] | `demo_restricted` only: the NPIs the demo key answers for. | --- # Versioning The major version is in the path: every endpoint starts with `/v1`. Changes inside v1 add things; they do not break what you built. Source: https://inplan.io/docs/versioning · Updated 2026-10-30 ## What can change inside v1 - New endpoints, new optional parameters and new response fields. - New values in `notes`, and new problem types for new kinds of errors. - Wording of `title`, `detail` and `explanation`. Branch on `type` and `answer`, never on text. Write clients that ignore fields they do not know. A change that would break a v1 client ships under a new path, such as `/v2`. ## Follow the changes The [changelog](https://inplan.io/changelog) lists every change. `/openapi.json` always describes the API that is running, and `GET /v1/status` returns its build in `data.version`. --- # Support Write to contact@inplan.io. Include the `request_id` of a response that surprised you, and we can find the exact request. Source: https://inplan.io/docs/support · Updated 2026-10-30 ## What to send - The `request_id` from the body, or the `X-Request-Id` header. - The endpoint and parameters you sent. Never send your key. - What you expected, and what you got. ## Check status first The [status page](https://inplan.io/status) and `GET /v1/status` show whether the API is up and the date of each data source. ## Support by plan | Plan | Support | | --- | --- | | Free | Documentation and Playground. | | Builder | Email support. | | Growth | Priority support. | | Scale | Dedicated support and a service level agreement. | Listing looks wrong for a provider you know? Send the NPI and the plan id to contact@inplan.io. Directories come from insurers and plans, so we also suggest the provider confirm with the plan. --- # Provider profile `GET https://api.inplan.io/v1/providers/{npi}` Who the provider is, from national provider registry records: name and credential, specialties, practice locations, groups and facility affiliations. `plan_summary` counts the plans that list the provider. Free plan and up · 1 call. Source: https://inplan.io/docs/api/providers ## When to use it Use it to show a provider before you check plans, or to see in one call how many marketplace and Medicare Advantage plans list them. To list those plans, call [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `npi` | path | string | Required | The provider's 10-digit National Provider Identifier. We validate the check digit. Example: `1184617268`. | ## Request curl: ```bash curl https://api.inplan.io/v1/providers/1184617268 \ -H "Authorization: Bearer $INPLAN_KEY" ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/providers/1184617268", { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.name.display, body.data.plan_summary); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268", headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["name"]["display"], body["data"]["plan_summary"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you asked for. | | `profile_found` | boolean | `true` when we hold identity details for this NPI. When `false`, only `npi`, `plan_summary` and `links` are present. | | `name` | object | The name as published, with a display form. | | `name.first` | string | First name. | | `name.middle` | string | Middle name. | | `name.last` | string | Last name. | | `name.suffix` | string \| null | Suffix, for example Jr. | | `name.display` | string | Name and credential ready to show, for example "Michael B. Williard, MD". We never add titles. | | `credential` | string | The credential as published, for example MD, DO, NP or PA. | | `gender` | string | `male`, `female`, or `null` when not published. | | `status` | string | `active` or `deactivated` in the national provider registry. | | `specialties` | object[] | Specialties, primary first. | | `specialties[].name` | string | The specialty name. | | `specialties[].primary` | boolean | `true` for the primary specialty. | | `education` | object | Training details, or `null` when not published. | | `education.medical_school` | string | The school named in the registry. | | `education.graduation_year` | integer | The graduation year. | | `locations` | object[] | Practice locations. | | `locations[].address_line_1` | string | Street address. `null` when the provider asked us to hide contact details. | | `locations[].address_line_2` | string \| null | Suite or second address line. | | `locations[].city` | string | City. | | `locations[].state` | string | 2-letter state code. | | `locations[].zip` | string | 5-digit ZIP code. | | `locations[].phone` | string \| null | Phone number, digits only. `null` when not published or hidden at the provider's request. | | `locations[].latitude` | number \| null | Latitude of the location, when known. | | `locations[].longitude` | number \| null | Longitude of the location, when known. | | `locations[].group` | object | The group practice at this location, or `null`. | | `locations[].group.name` | string | The group name. | | `locations[].group.pac_id` | string | The group identifier in the national registry. | | `locations[].telehealth` | boolean \| null | `true` when the registry marks telehealth at this location, else `null`. | | `groups` | object[] | Group practices the provider belongs to. | | `groups[].name` | string | The group name. | | `groups[].pac_id` | string | The group identifier in the national registry. | | `groups[].members` | integer | Number of providers in the group. | | `affiliations` | object[] | Facilities the provider is affiliated with. | | `affiliations[].facility_type` | string | For example Hospital or Home health agency. | | `affiliations[].ccn` | string | The facility's 6-character certification number. | | `affiliations[].name` | string \| null | Facility name, when we hold it. | | `affiliations[].city` | string \| null | Facility city. | | `affiliations[].state` | string \| null | Facility state. | | `privacy` | object | Privacy settings for this record. | | `privacy.contact_masked` | boolean | `true` when the provider asked us to hide street address and phone. Those fields are then `null`. | | `plan_summary` | object | Counts of the plans that list this provider. Get the plans from `links.plans`. | | `plan_summary.aca` | object | Marketplace plans. | | `plan_summary.aca.plans` | integer | Plan and plan-year pairs that list the provider. | | `plan_summary.aca.issuers` | integer | Insurers among those plans. | | `plan_summary.aca.states` | string[] | States of those plans. | | `plan_summary.aca.plan_years` | integer[] | Plan years with a listing. | | `plan_summary.aca.as_of` | date | The newest date we read a directory that lists the provider. | | `plan_summary.ma` | object | Medicare Advantage plans. | | `plan_summary.ma.plans` | integer | Plan and plan-year pairs that list the provider. | | `plan_summary.ma.organizations` | integer | Organizations among those plans. | | `plan_summary.ma.plan_years` | integer[] | Plan years with a listing. | | `plan_summary.ma.accepting_new_patients_any` | boolean | `true` when at least one directory says the provider accepts new patients, else `null`. | | `plan_summary.ma.as_of` | date | The newest date we read a directory that lists the provider. | | `links` | object | Related endpoints. | | `links.plans` | string | The path of the full plan list for this provider. | ## Responses ### 200 The profile, shortened here to one location. Response (200 OK · application/json): ```json { "request_id": "req_fjj3DnMpm3d4ZZNK", "data_as_of": "2026-10-29", "data": { "npi": "1184617268", "profile_found": true, "name": { "first": "Michael", "middle": "Buttimer", "last": "Williard", "suffix": null, "display": "Michael B. Williard, MD" }, "credential": "MD", "gender": "male", "status": "active", "specialties": [ { "name": "Orthopedic Surgery", "primary": true } ], "education": { "medical_school": "Ohio State University College of Medicine", "graduation_year": 1987 }, "locations": [ { "address_line_1": "605 Crescent Pl", "address_line_2": null, "city": "Gahanna", "state": "OH", "zip": "43230", "phone": "6145457900", "latitude": null, "longitude": null, "group": { "name": "Orthopedic One, Inc.", "pac_id": "3072598283" }, "telehealth": null } ], "groups": [ { "name": "Orthopedic One, Inc.", "pac_id": "3072598283", "members": 282 } ], "affiliations": [ { "facility_type": "Home health agency", "ccn": "368154", "name": null, "city": null, "state": null }, { "facility_type": "Hospital", "ccn": "360035", "name": "Mount Carmel East & West", "city": "Columbus", "state": "OH" } ], "privacy": { "contact_masked": false }, "plan_summary": { "aca": { "plans": 77, "issuers": 4, "states": [ "OH" ], "plan_years": [ 2025, 2026, 2027 ], "as_of": "2026-10-28" }, "ma": { "plans": 36, "organizations": 4, "plan_years": [ 2027 ], "accepting_new_patients_any": true, "as_of": "2026-10-29" } }, "links": { "plans": "/v1/providers/1184617268/plans" } }, "sources": [ { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" }, { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" } ], "notes": [ "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 400 The NPI is not 10 digits or fails its check digit. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/providers/1234567890", "request_id": "req_RYHURQjbqOzSrK9m", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/providers/1184617268", "request_id": "req_eQx8WCqv-SXJ-RZw", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 404 We hold no provider record and no plan listing for this NPI. Response (404 Not Found · application/problem+json): ```json { "type": "not_found", "title": "Provider not found", "status": 404, "detail": "No provider found for NPI 1999999984.", "instance": "/v1/providers/1999999984", "request_id": "req_eHYrIWCixJ4RIl52", "docs": "https://inplan.io/docs/errors#not_found" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `403` (plan, account or demo-key limit), `429` (rate limit or quota) and `503` (busy or timed out; retry after `Retry-After`). See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Plans that list a provider `GET https://api.inplan.io/v1/providers/{npi}/plans` Every plan whose network directory lists the provider, marketplace plans first, each with its plan year, source type and date. Filter by market, state and year; page with `offset`. Free plan and up · 1 call per page. Source: https://inplan.io/docs/api/provider-plans ## When to use it Use it to show every plan that keeps a provider, for example to quote plans that keep a client's doctors. To answer for one known plan, call [`GET /v1/check`](https://inplan.io/docs/api/check). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `npi` | path | string | Required | The provider's 10-digit National Provider Identifier. We validate the check digit. Example: `1184617268`. | | `market` | query | string · `aca` or `ma` | Optional | `aca` for marketplace plans, `ma` for Medicare Advantage plans. Omit for both. Example: `ma`. | | `state` | query | string | Optional | 2-letter state. Marketplace plans: the plan's state. Medicare Advantage plans: a state in the plan's service area. Example: `OH`. | | `year` | query | integer | Optional | Only plans for this plan year (2025 to 2030). Example: `2026`. | | `limit` | query | integer · default 500 · max 2,000 | Optional | Plans per page. | | `offset` | query | integer · default 0 | Optional | Plans to skip. Use `next_offset` from the previous page. | ## Request curl: ```bash curl https://api.inplan.io/v1/providers/1184617268/plans \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d year=2026 -d limit=2 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/providers/1184617268/plans"); url.search = new URLSearchParams({ year: "2026", limit: "2" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.total, body.data.plans); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/providers/1184617268/plans", params={"year": "2026", "limit": "2"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["total"], body["data"]["plans"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you asked for. | | `provider_states` | string[] | States of the provider's practice locations. | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.market` | string \| null | `aca`, `ma` or `null`. | | `filters.state` | string \| null | The state filter. | | `filters.year` | integer \| null | The plan year filter. | | `total` | integer | Plans that match the filters, across all pages. | | `counts` | object | The total, split by market. | | `counts.aca` | integer | Marketplace plans that match. | | `counts.ma` | integer | Medicare Advantage plans that match. | | `offset` | integer | The offset of this page. | | `limit` | integer | The page size. | | `next_offset` | integer \| null | The `offset` for the next page, or `null` on the last page. | | `plans` | object[] | The plans on this page: marketplace plans first, then Medicare Advantage plans. | | `plans[].market` | string | `aca` or `ma`. | | `plans[].plan_id` | string | The plan id. | | `plans[].plan_year` | integer | The plan year of this listing. | | `plans[].plan_name` | string \| null | The plan name, when we hold plan details. | | `plans[].issuer` | object | Marketplace plans: the insurer. | | `plans[].issuer.id` | string | The 5-digit insurer id. | | `plans[].issuer.name` | string \| null | The insurer name, when we hold it. | | `plans[].state` | string | Marketplace plans: the plan state. | | `plans[].metal_level` | string \| null | Marketplace plans: the metal level. | | `plans[].plan_type` | string \| null | HMO, PPO, EPO, POS and similar. | | `plans[].network_tiers` | string[] | Marketplace plans: the tiers the directory names for this provider. | | `plans[].plan_details_available` | boolean | `true` when plan name, metal level and plan type are included. | | `plans[].source` | object | Where this listing comes from. | | `plans[].source.type` | string | `insurer_directory` or `plan_directory`. | | `plans[].source.as_of` | date \| null | The date we read that directory. | | `plans[].organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `plans[].snp` | object | Medicare Advantage plans: special needs plan details. | | `plans[].snp.is_snp` | boolean | `true` for a special needs plan. | | `plans[].snp.type` | string | The special needs plan type, as published. | | `plans[].service_area_states` | string[] | Medicare Advantage plans: states in the plan's service area. | | `plans[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients. | | `plans[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed. | | `plans[].taxonomy` | string | Medicare Advantage plans: the provider taxonomy code listed with the network entry. | | `plans[].provider_state_in_service_area` | boolean \| null | Medicare Advantage plans: `true` when one of the provider's states is in the service area, `null` when either side is unknown. | ## Responses ### 200 A page of plans. `total` counts every match; `next_offset` is `null` on the last page. Response (200 OK · application/json): ```json { "request_id": "req_EEsgYcNXpuC_ijmU", "data_as_of": "2026-10-29", "data": { "npi": "1184617268", "provider_states": [ "OH" ], "filters": { "market": null, "state": null, "year": 2026 }, "total": 46, "counts": { "aca": 46, "ma": 0 }, "offset": 0, "limit": 2, "next_offset": 2, "plans": [ { "market": "aca", "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "network_tiers": [ "PARTICIPATING" ], "plan_details_available": true, "source": { "type": "insurer_directory", "as_of": "2026-07-27" } }, { "market": "aca", "plan_id": "29276OH0970002", "plan_year": 2026, "plan_name": "Anthem Heart Healthy Bronze Pathway 6000 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Expanded Bronze", "plan_type": "HMO", "network_tiers": [ "PARTICIPATING" ], "plan_details_available": true, "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ] }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" } ], "notes": [ "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 200 ma With `market=ma`: Medicare Advantage plans add service area, new-patient and provider-kind fields. Response (200 OK · application/json): ```json { "request_id": "req_lUbO6VDvdYSs2XWz", "data_as_of": "2026-10-29", "data": { "npi": "1184617268", "provider_states": [ "OH" ], "filters": { "market": "ma", "state": null, "year": null }, "total": 36, "counts": { "aca": 0, "ma": 36 }, "offset": 0, "limit": 1, "next_offset": 1, "plans": [ { "market": "ma", "plan_id": "H2526-001-000", "plan_year": 2027, "plan_name": "DEVOTED CHOICE 001 OH (PPO)", "organization": "Devoted Health", "plan_type": "PPO", "snp": { "is_snp": false, "type": "Not Applicable" }, "service_area_states": [ "OH" ], "accepting_new_patients": true, "provider_kind": "practitioner", "taxonomy": "207X00000X", "plan_details_available": true, "source": { "type": "plan_directory", "as_of": "2026-10-29" }, "provider_state_in_service_area": true } ] }, "sources": [ { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ], "notes": [ "Network directories change. Confirm with the plan or the provider before care.", "A plan's network can include providers outside the plan's service area. Compare service_area_states with the provider's location." ] } ``` ### 400 An invalid NPI or filter. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/providers/1234567890/plans", "request_id": "req_tJFUtTPOxNSXJtXb", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/providers/1184617268/plans", "request_id": "req_ItpJcJKIUQZ1dBDx", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 404 We hold no provider record and no plan listing for this NPI. Response (404 Not Found · application/problem+json): ```json { "type": "not_found", "title": "Provider not found", "status": 404, "detail": "No provider found for NPI 1999999984.", "instance": "/v1/providers/1999999984/plans", "request_id": "req_HVipN6sy7kXyvBYe", "docs": "https://inplan.io/docs/errors#not_found" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `403` (plan, account or demo-key limit), `429` (rate limit or quota) and `503` (busy or timed out). See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Check a provider and plan `GET https://api.inplan.io/v1/check` Answers one question: does this plan's network directory list this provider? The answer comes with its evidence: the plan year, the network tier, the source type and the date we read it. Free plan and up · 1 call. Source: https://inplan.io/docs/api/check ## When to use it Use it when you already know the plan, for example to confirm a booking or to mark the doctors a quoted plan keeps. To list every plan for a provider, call [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans). To check many pairs, call [`POST /v1/check/batch`](https://inplan.io/docs/api/check-batch). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `npi` | query | string | Required | The provider's 10-digit National Provider Identifier. We validate the check digit. Example: `1184617268`. | | `plan_id` | query | string | Required | A marketplace plan id (14 characters, or 16 with the variant) or a Medicare Advantage contract-plan-segment id. Example: `29276OH0970001 · H2526-001-000`. | | `year` | query | integer | Optional | A plan year. Narrows the answer to that year. Example: `2026`. | ## Request curl: ```bash curl https://api.inplan.io/v1/check \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d npi=1184617268 -d plan_id=29276OH0970001 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/check"); url.search = new URLSearchParams({ npi: "1184617268", plan_id: "29276OH0970001" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.answer, body.data.evidence); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/check", params={"npi": "1184617268", "plan_id": "29276OH0970001"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["answer"], body["data"]["evidence"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `npi` | string | The NPI you checked. | | `plan_id` | string | The plan we checked. Marketplace ids are normalized to the 14-character plan id. | | `input_plan_id` | string | The plan id exactly as you sent it. | | `market` | string | `aca` for marketplace plans, `ma` for Medicare Advantage plans. | | `plan_years_listed` | integer[] | Plan years in which the directory lists the provider for this plan. | | `in_network` | true \| null | `true`: the directory lists the provider for this plan. `null`: no listing found in current directory data. It is never a statement that the provider is out of network. | | `answer` | string | `listed`, `not_found`, or `no_provider_network` for a prescription drug plan id (contract ids that start with S). | | `explanation` | string | One plain sentence that states the answer. Safe to show to users as it is. | | `evidence` | object[] | One entry for each listing we found. Empty when the answer is `not_found`. | | `evidence[].plan_id` | string | The plan of this listing. | | `evidence[].plan_year` | integer | The plan year of this listing. | | `evidence[].network_tier` | string | Marketplace plans: the tier the directory names, for example `PARTICIPATING` or `PREFERRED`. Several tiers are joined with commas. | | `evidence[].source` | object | Where this listing comes from. | | `evidence[].source.type` | string | `insurer_directory` for marketplace plans, `plan_directory` for Medicare Advantage plans. | | `evidence[].source.as_of` | date | The date we read that directory. | | `evidence[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients (`null` when it does not say). | | `evidence[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed, for example `practitioner`. | | `plan_year` | integer \| null | The plan year this answer is about: the `year` you sent, else the newest year listed, else the newest plan year we hold. | | `plan` | object \| null | Plan details. `null` when we hold no details for this plan id. | | `plan.plan_name` | string | The plan name as the insurer publishes it. | | `plan.issuer` | object | Marketplace plans: the insurer. | | `plan.issuer.id` | string | The 5-digit insurer id (the first 5 characters of the plan id). | | `plan.issuer.name` | string | The insurer name. | | `plan.state` | string | Marketplace plans: the state the plan is sold in. | | `plan.metal_level` | string | Marketplace plans: Bronze, Expanded Bronze, Silver, Gold, Platinum or Catastrophic. | | `plan.plan_type` | string | HMO, PPO, EPO, POS and similar. | | `plan.variant` | string \| null | Marketplace plans: the 2-digit variant you sent (for example `01`), else `null`. | | `plan.organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `plan.service_area_states` | string[] | Medicare Advantage plans: the states in the plan's service area. | | `source` | object | The source type of the answer and its date. | | `source.type` | string | `insurer_directory` or `plan_directory`. | | `source.as_of` | date \| null | The date we read the directory that gave this answer. | | `as_of` | date \| null | The date we read the directory that gave this answer (same as `source.as_of`). | ## Responses ### 200 listed The plan's directory lists the provider. The evidence block carries the plan year, tier, source type and date. Response (200 OK · application/json): ```json { "request_id": "req_ws7hhhC7uSWYqEye", "data_as_of": "2026-07-27", "data": { "npi": "1184617268", "plan_id": "29276OH0970001", "input_plan_id": "29276OH0970001", "market": "aca", "plan_years_listed": [ 2026 ], "in_network": true, "answer": "listed", "explanation": "The insurer's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ], "plan_year": 2026, "plan": { "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" }, "as_of": "2026-07-27" }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ], "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 200 not_found No listing found in current directory data for this provider and plan. `in_network` is `null` and `evidence` is empty. Show it as unknown, not as out of network. Response (200 OK · application/json): ```json { "request_id": "req_p-wTVhm9dU50UsKb", "data_as_of": "2026-10-28", "data": { "npi": "1376006148", "plan_id": "13262AR0230002", "input_plan_id": "13262AR0230002", "market": "aca", "plan_years_listed": [], "in_network": null, "answer": "not_found", "explanation": "No listing found in current directory data for this provider and plan.", "evidence": [], "plan_year": 2026, "plan": { "plan_name": "HA Silver AH", "issuer": { "id": "13262", "name": "Health Advantage" }, "state": "AR", "metal_level": "Silver", "plan_type": "POS", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-10-28" }, "as_of": "2026-10-28" }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ], "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 400 The NPI fails its check digit, the plan id has an unknown format, or a parameter is invalid. The `type` is `invalid_npi`, `invalid_plan_id` or `invalid_request`. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_npi", "title": "Invalid NPI", "status": 400, "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840).", "instance": "/v1/check", "request_id": "req_tyeV9VkOEHSgXbbW", "docs": "https://inplan.io/docs/errors#invalid_npi" } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/check", "request_id": "req_pctJoQwOrN-xK9Wp", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 429 You sent more requests a second than your key allows (`rate_limited`), or used this month's calls (`quota_exceeded`). Wait for `Retry-After` seconds. Calls over the quota are never billed. Response (429 Too Many Requests · application/problem+json): ```http HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json; charset=utf-8 Retry-After: 1 RateLimit-Limit: 2 RateLimit-Remaining: 0 RateLimit-Reset: 1 RateLimit-Policy: 2;w=1;burst=5 X-Request-Id: req_TrHd8LO8HNbLoZ_D ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `403` (plan, account or demo-key limit) and `503` (busy or timed out; retry after `Retry-After`). See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Batch check `POST https://api.inplan.io/v1/check/batch` The check answer for up to 100 provider-plan pairs in one call. Each pair is answered as a single check would be; a bad pair gets its own `error` and the rest still answer. Builder plan and up · 1 call per item. Source: https://inplan.io/docs/api/check-batch ## When to use it Use it for rosters: every doctor of an employee against every plan on offer, or a nightly re-check of saved pairs. See [Batch checks](https://inplan.io/docs/batch-checks) for counting and limits. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `items` | body | object[] · 1 to 100 | Required | The pairs to check. Each item: `npi` (string, required), `plan_id` (string, required), `year` (integer, optional). | | `year` | body | integer | Optional | Default plan year for items without their own `year`. Example: `2026`. | ## Request curl: ```bash curl https://api.inplan.io/v1/check/batch \ -H "Authorization: Bearer $INPLAN_KEY" \ -H "Content-Type: application/json" \ -d '{"items": [ {"npi": "1184617268", "plan_id": "29276OH0970001"}, {"npi": "1184617268", "plan_id": "H2526-001-000"} ]}' ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/check/batch", { method: "POST", headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ items: [ { npi: "1184617268", plan_id: "29276OH0970001" }, { npi: "1184617268", plan_id: "H2526-001-000" } ] }), }); const body = await res.json(); console.log(body.data.summary, body.data.results); ``` Python: ```python import os, requests res = requests.post( "https://api.inplan.io/v1/check/batch", json={ "items": [ { "npi": "1184617268", "plan_id": "29276OH0970001" }, { "npi": "1184617268", "plan_id": "H2526-001-000" } ] }, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=30, ) body = res.json() print(body["data"]["summary"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `count` | integer | The number of items you sent. | | `summary` | object | Counts of the answers in this batch. | | `summary.listed` | integer | Items answered `listed`. | | `summary.not_found` | integer | Items answered with `in_network: null`. | | `summary.invalid` | integer | Items that failed validation and carry an `error`. | | `results` | object[] | One result for each item, in the order you sent them. | | `results[].index` | integer | The position of the item in your `items` array, starting at 0. | | `results[].npi` | string | The NPI you checked. | | `results[].plan_id` | string | The plan we checked. Marketplace ids are normalized to the 14-character plan id. | | `results[].input_plan_id` | string | The plan id exactly as you sent it. | | `results[].market` | string | `aca` for marketplace plans, `ma` for Medicare Advantage plans. | | `results[].plan_years_listed` | integer[] | Plan years in which the directory lists the provider for this plan. | | `results[].in_network` | true \| null | `true`: the directory lists the provider for this plan. `null`: no listing found in current directory data. It is never a statement that the provider is out of network. | | `results[].answer` | string | `listed`, `not_found`, or `no_provider_network` for a prescription drug plan id (contract ids that start with S). | | `results[].explanation` | string | One plain sentence that states the answer. Safe to show to users as it is. | | `results[].evidence` | object[] | One entry for each listing we found. Empty when the answer is `not_found`. | | `results[].evidence[].plan_id` | string | The plan of this listing. | | `results[].evidence[].plan_year` | integer | The plan year of this listing. | | `results[].evidence[].network_tier` | string | Marketplace plans: the tier the directory names, for example `PARTICIPATING` or `PREFERRED`. Several tiers are joined with commas. | | `results[].evidence[].source` | object | Where this listing comes from. | | `results[].evidence[].source.type` | string | `insurer_directory` for marketplace plans, `plan_directory` for Medicare Advantage plans. | | `results[].evidence[].source.as_of` | date | The date we read that directory. | | `results[].evidence[].accepting_new_patients` | boolean \| null | Medicare Advantage plans: whether the directory says the provider accepts new patients (`null` when it does not say). | | `results[].evidence[].provider_kind` | string | Medicare Advantage plans: the kind of provider listed, for example `practitioner`. | | `results[].plan_year` | integer \| null | The plan year this answer is about: the `year` you sent, else the newest year listed, else the newest plan year we hold. | | `results[].plan` | object \| null | Plan details. `null` when we hold no details for this plan id. | | `results[].plan.plan_name` | string | The plan name as the insurer publishes it. | | `results[].plan.issuer` | object | Marketplace plans: the insurer. | | `results[].plan.issuer.id` | string | The 5-digit insurer id (the first 5 characters of the plan id). | | `results[].plan.issuer.name` | string | The insurer name. | | `results[].plan.state` | string | Marketplace plans: the state the plan is sold in. | | `results[].plan.metal_level` | string | Marketplace plans: Bronze, Expanded Bronze, Silver, Gold, Platinum or Catastrophic. | | `results[].plan.plan_type` | string | HMO, PPO, EPO, POS and similar. | | `results[].plan.variant` | string \| null | Marketplace plans: the 2-digit variant you sent (for example `01`), else `null`. | | `results[].plan.organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `results[].plan.service_area_states` | string[] | Medicare Advantage plans: the states in the plan's service area. | | `results[].source` | object | The source type of the answer and its date. | | `results[].source.type` | string | `insurer_directory` or `plan_directory`. | | `results[].source.as_of` | date \| null | The date we read the directory that gave this answer. | | `results[].as_of` | date \| null | The date we read the directory that gave this answer (same as `source.as_of`). | | `results[].error` | object | Only on an item that failed validation. The other fields of a check are then absent. | | `results[].error.type` | string | The problem type, for example `invalid_npi`. | | `results[].error.title` | string | A short summary. | | `results[].error.detail` | string | What is wrong with this item. | ## Responses ### 200 One result for each item, in order. `summary` counts `listed`, `not_found` and `invalid` items. Response (200 OK · application/json): ```json { "request_id": "req_KHtLEQJ46JLKLkZZ", "data_as_of": "2026-10-29", "data": { "count": 4, "summary": { "listed": 2, "not_found": 1, "invalid": 1 }, "results": [ { "index": 0, "npi": "1184617268", "plan_id": "29276OH0970001", "input_plan_id": "29276OH0970001", "market": "aca", "plan_years_listed": [ 2026 ], "in_network": true, "answer": "listed", "explanation": "The insurer's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "29276OH0970001", "plan_year": 2026, "network_tier": "PARTICIPATING", "source": { "type": "insurer_directory", "as_of": "2026-07-27" } } ], "plan_year": 2026, "plan": { "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" }, "as_of": "2026-07-27" }, { "index": 1, "npi": "1184617268", "plan_id": "H2526-001-000", "input_plan_id": "H2526-001-000", "market": "ma", "plan_years_listed": [ 2027 ], "in_network": true, "answer": "listed", "explanation": "The plan's network directory lists this provider for this plan.", "evidence": [ { "plan_id": "H2526-001-000", "plan_year": 2027, "accepting_new_patients": true, "provider_kind": "practitioner", "source": { "type": "plan_directory", "as_of": "2026-10-29" } } ], "plan_year": 2027, "plan": { "plan_name": "DEVOTED CHOICE 001 OH (PPO)", "organization": "Devoted Health", "plan_type": "PPO", "service_area_states": [ "OH" ] }, "source": { "type": "plan_directory", "as_of": "2026-10-29" }, "as_of": "2026-10-29" }, { "index": 2, "npi": "1376006148", "plan_id": "13262AR0230002", "input_plan_id": "13262AR0230002", "market": "aca", "plan_years_listed": [], "in_network": null, "answer": "not_found", "explanation": "No listing found in current directory data for this provider and plan.", "evidence": [], "plan_year": 2026, "plan": { "plan_name": "HA Silver AH", "issuer": { "id": "13262", "name": "Health Advantage" }, "state": "AR", "metal_level": "Silver", "plan_type": "POS", "variant": null }, "source": { "type": "insurer_directory", "as_of": "2026-10-28" }, "as_of": "2026-10-28" }, { "index": 3, "npi": "1234567890", "plan_id": "29276OH0970001", "error": { "type": "invalid_npi", "title": "Invalid NPI", "detail": "\"1234567890\" fails the NPI check digit (Luhn with prefix 80840)." } } ] }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" } ], "notes": [ "in_network=null means no listing was found in current directory data. It is not a statement that the provider is out of network.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 400 More than 100 items (`batch_too_large`), or a body without an `items` array (`invalid_request`). Response (400 Bad Request · application/problem+json): ```json { "type": "batch_too_large", "title": "Batch too large", "status": 400, "detail": "At most 100 pairs per call; got 101.", "instance": "/v1/check/batch", "request_id": "req_hxtwDDAeyVY4wVMP", "docs": "https://inplan.io/docs/errors#batch_too_large" } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/check/batch", "request_id": "req_GeDGBDA_JgNwi3Hq", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 403 The key is on the Free plan. Batch checks start on Builder. Response (403 Forbidden · application/problem+json): ```json { "type": "plan_upgrade_required", "title": "Plan upgrade required", "status": 403, "detail": "This endpoint needs the builder plan or higher; this key is on free.", "instance": "/v1/check/batch", "request_id": "req_RL1ifh22It4kUXjz", "docs": "https://inplan.io/docs/errors#plan_upgrade_required", "current_plan": "free", "required_plan": "builder" } ``` ### 415 The `Content-Type` is not `application/json`. Response (415 Unsupported Media Type · application/problem+json): ```json { "type": "unsupported_media_type", "title": "JSON required", "status": 415, "detail": "Send `Content-Type: application/json`.", "instance": "/v1/check/batch", "request_id": "req_zdyooaUKchC18Eiy", "docs": "https://inplan.io/docs/errors#unsupported_media_type" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `413` (body over 64 KB), `429` (rate limit or quota; a batch is refused whole when the quota cannot cover it) and `503` (busy or timed out; the batch is not counted). See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Plan details `GET https://api.inplan.io/v1/plans/{plan_id}` Plan details for a marketplace or Medicare Advantage plan: name, insurer or organization, plan type, the plan years we hold, and whether we hold its provider listings. Free plan and up · 1 call. Source: https://inplan.io/docs/api/plans ## When to use it Use it to show which plan a user picked, to validate a plan id before you store it, or to read `available_years`. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `plan_id` | path | string | Required | A marketplace plan id or a Medicare Advantage contract-plan-segment id. Example: `29276OH0970001 · H2526-001-000`. | | `year` | query | integer | Optional | The plan year of the details. Default: the newest year we hold. Example: `2026`. | ## Request curl: ```bash curl https://api.inplan.io/v1/plans/29276OH0970001 \ -H "Authorization: Bearer $INPLAN_KEY" ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/plans/29276OH0970001", { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.plan_name, body.data.available_years); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/plans/29276OH0970001", headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["plan_name"], body["data"]["available_years"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `market` | string | `aca` or `ma`. | | `plan_id` | string | The normalized plan id. | | `plan_year` | integer | The plan year of these details: the `year` you sent, else the newest year we hold. | | `plan_name` | string | The plan name as published. | | `issuer` | object | Marketplace plans: the insurer. | | `issuer.id` | string | The 5-digit insurer id. | | `issuer.name` | string | The insurer name. | | `state` | string | Marketplace plans: the plan state. | | `metal_level` | string | Marketplace plans: the metal level. | | `plan_type` | string | HMO, PPO, EPO, POS and similar. | | `markets` | string[] | Marketplace plans: the markets the plan is sold in, for example Individual. | | `network_id` | string | Marketplace plans: the insurer's network id for this plan. | | `variant_ids` | string[] | Marketplace plans: every variant id of this plan. | | `plan_details_available` | boolean | `true` when plan name and plan details are included. | | `listings` | object | Whether we hold provider listings for this plan. | | `listings.has_listings` | boolean | `true` when we hold provider listings for this plan. | | `listings.as_of` | date | The date we read its directory. | | `variant` | string \| null | Marketplace plans: the variant you sent, else `null`. | | `input_plan_id` | string | The plan id exactly as you sent it. | | `available_years` | integer[] | Every plan year we hold for this plan. | | `links` | object | Related endpoints. | | `links.providers` | string | The path of the provider list for this plan. | | `links.check` | string | A check path for this plan. Put an NPI in place of `{npi}`. | | `contract_id` | string | Medicare Advantage plans: the contract id (the part before the first dash). | | `contract_name` | string | Medicare Advantage plans: the contract name as published. | | `organization` | string | Medicare Advantage plans: the organization that offers the plan. | | `parent_organization` | string | Medicare Advantage plans: the parent organization. | | `category` | string | Medicare Advantage plans: the plan category, for example MA-PD. | | `snp` | object | Medicare Advantage plans: special needs plan details. | | `snp.is_snp` | boolean | `true` for a special needs plan. | | `snp.type` | string | The special needs plan type, as published. | | `service_area` | object | Medicare Advantage plans: the service area. | | `service_area.states` | string[] | States in the service area. | | `service_area.counties` | integer | Number of counties in the service area. | | `star_rating` | number \| null | Medicare Advantage plans: the overall star rating published for the plan. | | `monthly_premium` | object | Medicare Advantage plans: the monthly premium range in US dollars. | | `monthly_premium.min` | integer | Lowest monthly premium. | | `monthly_premium.max` | integer | Highest monthly premium. | ## Responses ### 200 aca A marketplace plan. Response (200 OK · application/json): ```json { "request_id": "req_qekrIghme3OYo8pW", "data_as_of": "2026-10-29", "data": { "market": "aca", "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "state": "OH", "metal_level": "Bronze", "plan_type": "HMO", "markets": [ "Individual" ], "network_id": "OHN004", "variant_ids": [ "29276OH0970001-00", "29276OH0970001-01", "29276OH0970001-02", "29276OH0970001-03" ], "plan_details_available": true, "listings": { "has_listings": true, "as_of": "2026-07-27" }, "variant": null, "input_plan_id": "29276OH0970001", "available_years": [ 2026 ], "links": { "providers": "/v1/plans/29276OH0970001/providers", "check": "/v1/check?plan_id=29276OH0970001&npi={npi}" } }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ] } ``` ### 200 ma A Medicare Advantage plan, with service area, star rating and premium range. Response (200 OK · application/json): ```json { "request_id": "req_QQiaxVHE9rtUs9XV", "data_as_of": "2026-10-29", "data": { "market": "ma", "plan_id": "H2526-001-000", "plan_year": 2027, "plan_name": "DEVOTED CHOICE 001 OH (PPO)", "contract_id": "H2526", "contract_name": "DEVOTED HEALTH PLAN OF OHIO INC", "organization": "Devoted Health", "parent_organization": "Devoted Health, Inc.", "plan_type": "PPO", "category": "MA-PD", "snp": { "is_snp": false, "type": "Not Applicable" }, "service_area": { "states": [ "OH" ], "counties": 78 }, "star_rating": 4.5, "monthly_premium": { "min": 0, "max": 0 }, "plan_details_available": true, "listings": { "has_listings": true, "as_of": "2026-10-29" }, "input_plan_id": "H2526-001-000", "available_years": [ 2027, 2026 ], "links": { "providers": "/v1/plans/H2526-001-000/providers", "check": "/v1/check?plan_id=H2526-001-000&npi={npi}" } }, "sources": [ { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ] } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/plans/29276OH0970001", "request_id": "req_fyedh_HTRJB21DW8", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 404 We hold no plan with this id, or not the `year` you asked for. Response (404 Not Found · application/problem+json): ```json { "type": "plan_not_found", "title": "Plan not found", "status": 404, "detail": "No plan found for 99999OH0010001.", "instance": "/v1/plans/99999OH0010001", "request_id": "req_yJz9v1z9Xg6vHNi9", "docs": "https://inplan.io/docs/errors#plan_not_found" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `400` (an id in neither format: `invalid_plan_id`), `403`, `429` and `503`. See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Providers in a plan `GET https://api.inplan.io/v1/plans/{plan_id}/providers` The providers a marketplace plan lists, page by page, with filters for state, ZIP and radius, and specialty. Each provider carries the network tiers the directory names. Growth plan and up · 1 call per page. Source: https://inplan.io/docs/api/plan-providers ## When to use it Use it to build provider search inside a plan, for example "cardiologists near 43230 in this plan". For Medicare Advantage plans, use [`GET /v1/check`](https://inplan.io/docs/api/check) or [`GET /v1/providers/{npi}/plans`](https://inplan.io/docs/api/provider-plans). Paging is described in [Pagination](https://inplan.io/docs/pagination). ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `plan_id` | path | string | Required | A marketplace plan id. Medicare Advantage ids answer `501`. Example: `29276OH0970001`. | | `state` | query | string | Optional | Only providers with a practice location in this state. Example: `OH`. | | `specialty` | query | string | Optional | Part of a specialty name, any case, for example `cardio`. Example: `cardio`. | | `zip` | query | string | Optional | A 5-digit ZIP. Without `radius`: providers with a location in this ZIP. Example: `43230`. | | `radius` | query | integer | Optional | Miles from the center of `zip`, 1 to 100. Needs `zip`. Example: `10`. | | `year` | query | integer | Optional | The plan year of the listings. Default: the newest year we hold. Example: `2026`. | | `limit` | query | integer · default 50 · max 100 | Optional | Providers per page. With filters a page can hold fewer. | | `cursor` | query | string | Optional | `page.next_cursor` from the previous page, unchanged. | ## Request curl: ```bash curl https://api.inplan.io/v1/plans/29276OH0970001/providers \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d limit=2 ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/plans/29276OH0970001/providers"); url.search = new URLSearchParams({ limit: "2" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.providers, body.data.page.next_cursor); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/plans/29276OH0970001/providers", params={"limit": "2"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["providers"], body["data"]["page"]["next_cursor"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `plan_id` | string | The plan id. | | `plan_year` | integer | The plan year of these listings. | | `plan_name` | string | The plan name. | | `issuer` | object | The insurer. | | `issuer.id` | string | The 5-digit insurer id. | | `issuer.name` | string | The insurer name. | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.state` | string \| null | State filter. | | `filters.zip` | string \| null | ZIP filter. | | `filters.radius` | integer \| null | Radius in miles around `zip`. | | `filters.specialty` | string \| null | Specialty filter, in upper case. | | `providers` | object[] | Providers on this page, in directory order. | | `providers[].npi` | string | The NPI. | | `providers[].name` | string \| null | Display name with credential. | | `providers[].credential` | string \| null | The credential as published. | | `providers[].primary_specialty` | string \| null | The primary specialty. | | `providers[].network_tiers` | string[] | Tiers the directory names for this provider in this plan. | | `providers[].locations` | object[] | Up to 5 practice locations that match your filters. | | `providers[].locations[].city` | string | City. | | `providers[].locations[].state` | string | State. | | `providers[].locations[].zip` | string | ZIP code. | | `providers[].locations[].distance_miles` | number \| null | Miles from the `zip` you sent, when you sent `radius`. Else `null`. | | `providers[].profile_found` | boolean | `true` when we hold identity details for this NPI. | | `page` | object | Paging state. See [Pagination](https://inplan.io/docs/pagination). | | `page.limit` | integer | The page size you asked for. | | `page.rows_scanned` | integer | Listing rows read for this page. | | `page.scanned_through` | integer | Listing rows read so far, across pages. | | `page.max_depth` | integer | The most listing rows paging reads for one plan. | | `page.next_cursor` | string \| null | Pass it as `cursor` for the next page. `null` on the last page. | | `source` | object | The source type and date of these listings. | | `source.type` | string | Always `insurer_directory`. | | `source.as_of` | date | The date we read the directory. | ## Responses ### 200 A page of providers. Pass `page.next_cursor` as `cursor` for the next page. Response (200 OK · application/json): ```json { "request_id": "req_k-SKujLq8DN5VkZE", "data_as_of": "2026-10-29", "data": { "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "filters": { "state": null, "zip": null, "radius": null, "specialty": null }, "providers": [ { "npi": "1457438566", "name": "Pamella J. Kibbe, NP", "credential": "NP", "primary_specialty": "Nurse Practitioner", "network_tiers": [ "PARTICIPATING" ], "locations": [ { "city": "Columbus", "state": "OH", "zip": "43214", "distance_miles": null } ], "profile_found": true }, { "npi": "1154167203", "name": "Morgan E. Brockhouse, DMD", "credential": "DMD", "primary_specialty": "Dentist", "network_tiers": [ "PARTICIPATING" ], "locations": [ { "city": "East Peoria", "state": "IL", "zip": "61611", "distance_miles": null } ], "profile_found": true } ], "page": { "limit": 2, "rows_scanned": 2, "scanned_through": 2, "max_depth": 5000, "next_cursor": "eyJvIjoyLCJ5IjoyMDI2LCJwIjoiMjkyNzZPSDA5NzAwMDEifQ" }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" } }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" } ], "notes": [ "With filters a page can hold fewer than `limit` providers. Follow next_cursor until it is null.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 200 filtered With `specialty=cardio`. Response (200 OK · application/json): ```json { "request_id": "req_LIWkkbLXmx_TZSaD", "data_as_of": "2026-10-29", "data": { "plan_id": "29276OH0970001", "plan_year": 2026, "plan_name": "Anthem Bronze Pathway 10600 ($0 Virtual PCP + $0 Select Drugs)", "issuer": { "id": "29276", "name": "Anthem Blue Cross and Blue Shield" }, "filters": { "state": null, "zip": null, "radius": null, "specialty": "CARDIO" }, "providers": [ { "npi": "1023120250", "name": "Jayanta T. Mukherjee, MD", "credential": "MD", "primary_specialty": "Cardiovascular Disease (cardiology)", "network_tiers": [ "PARTICIPATING" ], "locations": [ { "city": "Kettering", "state": "OH", "zip": "45429", "distance_miles": null } ], "profile_found": true } ], "page": { "limit": 2, "rows_scanned": 200, "scanned_through": 200, "max_depth": 5000, "next_cursor": "eyJvIjoyMDAsInkiOjIwMjYsInAiOiIyOTI3Nk9IMDk3MDAwMSJ9" }, "source": { "type": "insurer_directory", "as_of": "2026-07-27" } }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" } ], "notes": [ "With filters a page can hold fewer than `limit` providers. Follow next_cursor until it is null.", "Network directories change. Confirm with the plan or the provider before care." ] } ``` ### 400 A changed or foreign cursor (`invalid_cursor`), paging past 5,000 listings (`depth_limit`), or an invalid filter. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_cursor", "title": "Invalid cursor", "status": 400, "detail": "Pass the next_cursor value from the previous page unchanged.", "instance": "/v1/plans/29276OH0970001/providers", "request_id": "req_kicZeaYEQF_Xcf0U", "docs": "https://inplan.io/docs/errors#invalid_cursor" } ``` ### 403 The key is on Free or Builder. Plan-to-provider search starts on Growth. Response (403 Forbidden · application/problem+json): ```json { "type": "plan_upgrade_required", "title": "Plan upgrade required", "status": 403, "detail": "This endpoint needs the growth plan or higher; this key is on free.", "instance": "/v1/plans/29276OH0970001/providers", "request_id": "req_qgViOYVxgNYeqbMb", "docs": "https://inplan.io/docs/errors#plan_upgrade_required", "current_plan": "free", "required_plan": "growth" } ``` ### 501 A Medicare Advantage plan id. Response (501 Not Implemented · application/problem+json): ```json { "type": "not_implemented", "title": "Available for Marketplace plans", "status": 501, "detail": "Provider search by plan is available for Marketplace plans. For Medicare Advantage plans use /v1/check or /v1/providers/{npi}/plans.", "instance": "/v1/plans/H2526-001-000/providers", "request_id": "req_91oTGyIEJwKXhH2R", "docs": "https://inplan.io/docs/errors#not_implemented" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `401`, `404` (`plan_not_found`), `429` and `503`. The Playground demo key cannot call this endpoint. See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Coverage `GET https://api.inplan.io/v1/coverage` What we hold: for each market, state, insurer and plan year, how many plans have provider listings, and the date we read them. Filter by market, state and year. Free plan and up · 1 call. Source: https://inplan.io/docs/api/coverage ## When to use it Use it to decide which states and insurers to offer checks for, or to show users how current the data is for their state. ## Parameters | Name | In | Type | Required | Description | | --- | --- | --- | --- | --- | | `market` | query | string · `aca` or `ma` | Optional | `aca` or `ma`. Omit for both. Example: `ma`. | | `state` | query | string | Optional | Only this state (2-letter code). Example: `OH`. | | `year` | query | integer | Optional | Only this plan year. Example: `2026`. | ## Request curl: ```bash curl https://api.inplan.io/v1/coverage \ -H "Authorization: Bearer $INPLAN_KEY" \ -G -d state=OH ``` JavaScript: ```javascript const url = new URL("https://api.inplan.io/v1/coverage"); url.search = new URLSearchParams({ state: "OH" }); const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.INPLAN_KEY}` }, }); const body = await res.json(); console.log(body.data.summary); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/coverage", params={"state": "OH"}, headers={"Authorization": f"Bearer {os.environ['INPLAN_KEY']}"}, timeout=10, ) body = res.json() print(body["data"]["summary"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `filters` | object | The filters you sent (`null` when not sent). | | `filters.market` | string \| null | `aca`, `ma` or `null`. | | `filters.state` | string | The state filter. | | `filters.year` | integer \| null | The plan year filter. | | `markets` | string[] | Markets with listings for these filters. | | `summary` | object | Totals by market, then by plan year. | | `summary.{market}` | object | `aca` or `ma`. | | `summary.{market}.{year}` | object | Totals for one plan year. | | `summary.{market}.{year}.states` | integer | States with listings. | | `summary.{market}.{year}.insurers` | integer | Insurers or organizations with listings. | | `summary.{market}.{year}.plans` | integer | Plans with listings. | | `summary.{market}.{year}.as_of` | date | The newest date we read a directory in this group. | | `rows` | object[] | One row for each market, state, insurer and plan year with listings. | | `rows[].market` | string | `aca` or `ma`. | | `rows[].state` | string | 2-letter state code. | | `rows[].insurer` | string | The insurer or organization name. | | `rows[].plan_year` | integer | The plan year. | | `rows[].plans` | integer | Plans with listings in this row. | | `rows[].as_of` | date \| null | The date we read the directory for this row. | ## Responses ### 200 Totals in `summary`, one row per market, state, insurer and plan year in `rows` (shortened here). Response (200 OK · application/json): ```json { "request_id": "req_0U9QnpvGhkSal2Qg", "data_as_of": "2026-10-29", "data": { "filters": { "market": null, "state": "OH", "year": null }, "markets": [ "aca", "ma" ], "summary": { "aca": { "2026": { "states": 1, "insurers": 11, "plans": 189, "as_of": "2026-10-29" } }, "ma": { "2027": { "states": 1, "insurers": 6, "plans": 45, "as_of": "2026-10-29" } } }, "rows": [ { "market": "aca", "state": "OH", "insurer": "Ambetter from Buckeye Health Plan", "plan_year": 2026, "plans": 16, "as_of": null }, { "market": "aca", "state": "OH", "insurer": "Anthem Blue Cross and Blue Shield", "plan_year": 2026, "plans": 11, "as_of": "2026-07-27" }, { "market": "aca", "state": "OH", "insurer": "Antidote Health Plan of Ohio, Inc.", "plan_year": 2026, "plans": 15, "as_of": "2026-10-28" } ] }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" } ] } ``` ### 400 An invalid `market`, `state` or `year`. Response (400 Bad Request · application/problem+json): ```json { "type": "invalid_request", "title": "Invalid state", "status": 400, "detail": "`state` must be a 2-letter US state code; got \"XX\".", "instance": "/v1/coverage", "request_id": "req_O2YuV9GXYt2-y6DK", "docs": "https://inplan.io/docs/errors#invalid_request" } ``` ### 401 The `Authorization` header is missing or malformed, or the key is unknown or revoked. Response (401 Unauthorized · application/problem+json): ```json { "type": "unauthorized", "title": "Missing API key", "status": 401, "detail": "Send `Authorization: Bearer ip_live_...`.", "instance": "/v1/coverage", "request_id": "req_BylbsNzGqRikrP1Y", "docs": "https://inplan.io/docs/errors#unauthorized" } ``` ### 429 More requests a second than the key allows. Response (429 Too Many Requests · application/problem+json): ```http HTTP/1.1 429 Too Many Requests Content-Type: application/problem+json; charset=utf-8 Retry-After: 1 RateLimit-Limit: 2 RateLimit-Remaining: 0 RateLimit-Reset: 1 RateLimit-Policy: 2;w=1;burst=5 X-Request-Id: req_TrHd8LO8HNbLoZ_D ``` ```json { "type": "rate_limited", "title": "Too many requests", "status": 429, "detail": "This key allows 2 requests per second.", "instance": "/v1/coverage", "request_id": "req_TrHd8LO8HNbLoZ_D", "docs": "https://inplan.io/docs/errors#rate_limited" } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `403` and `503`. See [Errors](https://inplan.io/docs/errors). ## Rate-limit headers Every response that passes the key and plan checks carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, `RateLimit-Policy`, `X-Quota-Limit`, `X-Quota-Remaining`, `X-Quota-Reset` and `X-Request-Id`. See [Rate limits and quotas](https://inplan.io/docs/rate-limits). --- # Status `GET https://api.inplan.io/v1/status` Whether the API is up, and the date each source type is current to. No key needed, and it never counts against a quota. No key · not counted. Source: https://inplan.io/docs/api/status ## When to use it Use it for health checks and uptime monitors, and to read data dates without spending calls. Each client may call it up to 5 times a second. ## Request curl: ```bash curl https://api.inplan.io/v1/status ``` JavaScript: ```javascript const res = await fetch("https://api.inplan.io/v1/status"); const body = await res.json(); console.log(body.data.status, body.data.datasets); ``` Python: ```python import os, requests res = requests.get( "https://api.inplan.io/v1/status", timeout=10, ) body = res.json() print(body["data"]["status"]) ``` ## Response fields Every response has the envelope `request_id`, `data_as_of`, `data`, `sources` and `notes`. Fields of `data`: | Field | Type | Description | | --- | --- | --- | | `status` | string | `ok`, `degraded` (answers still served), `busy` or `paused`. `busy` and `paused` come with HTTP 503. | | `api_enabled` | boolean | `false` while the API is paused for maintenance. | | `version` | string | The running API build. | | `time` | string | Server time, ISO 8601 UTC. | | `checks` | object | Health of the parts behind the API. | | `checks.database` | object | The database check. | | `checks.database.ok` | boolean | `true` when the database answered. | | `checks.database.latency_ms` | integer \| null | Time the database check took, in milliseconds. | | `checks.cache` | object | The cache check. | | `checks.cache.ok` | boolean | `true` when the cache answered. | | `datasets` | object[] | Every source type with the date it is current to. | | `datasets[].type` | string | The source type: `insurer_directory`, `plan_directory`, `provider_registry` or `plan_catalog`. | | `datasets[].description` | string | The source type in plain words. | | `datasets[].as_of` | date | The date this type of data is current to. | ## Responses ### 200 `status` is `ok`, or `degraded` while answers are still served. Response (200 OK · application/json): ```json { "request_id": "req_me9mgrJWQxf7oYGP", "data_as_of": "2026-10-29", "data": { "status": "ok", "api_enabled": true, "version": "d470109", "time": "2026-10-30T09:28:11.713Z", "checks": { "database": { "ok": true, "latency_ms": 0 }, "cache": { "ok": true } }, "datasets": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" }, { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ] }, "sources": [ { "type": "insurer_directory", "description": "Provider network directories published by health insurers", "as_of": "2026-10-29" }, { "type": "plan_directory", "description": "Provider network directories published by Medicare Advantage plans", "as_of": "2026-10-29" }, { "type": "provider_registry", "description": "National provider registry and practice records", "as_of": "2026-10-29" }, { "type": "plan_catalog", "description": "Published health plan catalogs", "as_of": "2026-10-28" } ] } ``` ## Errors Errors use `application/problem+json` (RFC 7807): `type`, `title`, `status`, `detail`, `instance`, `request_id`, `docs`. Other statuses this endpoint can return: `503` with `status` `busy` or `paused` and a `Retry-After` header, and `429` above 5 requests a second from one client. See [Errors](https://inplan.io/docs/errors). ---