healthcare.dev docs

Keep your data fresh

Know when a dataset releases, what changed in it, and when a code, NDC, NPI or Medicare coverage document you rely on changes: poll the feeds or get signed webhooks.

PlanEvery plan: release list. Builder: change feed. Pro: webhooks and the coverage change feed
Endpoints/v1/datasets/{dataset}/releases, /v1/changes, /v1/coverage/changes, /v1/webhooks
CostNone of these count as billable calls (the change feeds on the plans that include them)

Use this when you cache answers or keep a local copy of a dataset: a code retired in the October ICD-10-CM release or an NDC that changed yesterday should never sit in your cache unnoticed.

Every plan: watch releases

curl https://api.healthcare.dev/v1/datasets/icd10cm/releases \
  -H "Authorization: Bearer $HDEV_API_KEY" | jq '.data[0] | {version, published_at, added, changed, retired}'

Each release says how many records it added, changed and retired. When version differs from the one you cached, refresh. Every response's meta.data_version names the release it came from, so you can also tag each cached answer with it.

Builder: page through what changed

Keep the last release you applied (its version), and for each newer release page through its changes:

curl "https://api.healthcare.dev/v1/changes?dataset=icd10cm&version=fy2027" \
  -H "Authorization: Bearer $HDEV_API_KEY"

Each item has key, kind (added, changed or retired), data_version and published_at, oldest first. Tracking the release version means a run that stops halfway can apply the same release again from the start, and a release is never skipped or applied twice.

TypeScript

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

const hdev = new HealthcareDev();
let applied = "fy2026"; // the last release you applied: load it from your store

// Releases come newest first: take the ones after `applied`, then apply them oldest first.
const { data: releases } = await hdev.datasets.releases("icd10cm");
const i = releases.findIndex((r) => r.version === applied);
const newer = (i === -1 ? releases.slice(0, 1) : releases.slice(0, i)).reverse();

for (const release of newer) {
  for await (const change of hdev.changes.iterate({
    dataset: "icd10cm",
    version: release.version,
  })) {
    // re-fetch change.key, or mark it retired, in your cache
    console.log(change.kind, change.key);
  }
  applied = release.version; // save it now
}

Python

from healthcare_dev import HealthcareDev

hdev = HealthcareDev()
applied = "fy2026"  # the last release you applied: load it from your store

releases = hdev.datasets.releases("icd10cm")["data"]  # newest first
versions = [r["version"] for r in releases]
newer = list(reversed(versions[: versions.index(applied)])) if applied in versions else versions[:1]

for version in newer:
    for change in hdev.changes.iterate(dataset="icd10cm", version=version):
        print(change["kind"], change["key"])  # re-fetch or retire it in your cache
    applied = version  # save it now

If the release you stored isn't listed (the first run, say), start from a full copy: page through /v1/datasets/{dataset}/keys and look the records up, then store the newest version.

Retired records stay answerable with status: retired and, where the source names one, replaced_by. Medicare coverage has its own feed of material document changes on Pro: GET /v1/coverage/changes (Coverage changes).

Pro: get pushed, signed

Subscribe once and we POST each change to your endpoint. record.changed needs the codes, NDCs, RxCUIs or NPIs you care about (up to 1,000); coverage.changed filters to procedure codes when you list them.

curl -X POST https://api.healthcare.dev/v1/webhooks \
  -H "Authorization: Bearer $HDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/healthcare-dev",
    "events": ["dataset.released", "record.changed", "coverage.changed"],
    "datasets": ["icd10cm", "hcpcs"],
    "subjects": ["E11.9", "I10", "J1817", "11720"]
  }'

The response has secret (whsec_...), shown once: store it in your secret manager. Then verify every delivery against the raw body before trusting it.

TypeScript

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

const hdev = new HealthcareDev();
const { data } = await hdev.webhooks.create({
  url: "https://example.com/hooks/healthcare-dev",
  events: ["dataset.released", "record.changed", "coverage.changed"],
  datasets: ["icd10cm", "hcpcs"],
  subjects: ["E11.9", "I10", "J1817", "11720"],
});
console.log(data.id); // store data.secret now

// Your endpoint
export async function POST(req: Request) {
  const body = await req.text(); // the raw body, not re-serialized JSON
  try {
    const event = await verifyWebhook(process.env.HDEV_WEBHOOK_SECRET!, req.headers, body);
    // deduplicate by event.id, answer fast, do the work after
    console.log(event.type, event.id);
    return new Response(null, { status: 204 });
  } catch (err) {
    if (err instanceof WebhookVerificationError) return new Response(null, { status: 400 });
    throw err;
  }
}

Python

import os
from healthcare_dev import HealthcareDev, WebhookVerificationError, verify_webhook

hdev = HealthcareDev()
hook = hdev.webhooks.create({
    "url": "https://example.com/hooks/healthcare-dev",
    "events": ["dataset.released", "record.changed", "coverage.changed"],
    "datasets": ["icd10cm", "hcpcs"],
    "subjects": ["E11.9", "I10", "J1817", "11720"],
})["data"]
print(hook["id"])  # store hook["secret"] now


# In your endpoint (any framework): pass the headers and the raw body
def handle(headers, raw_body: bytes) -> int:
    try:
        event = verify_webhook(os.environ["HDEV_WEBHOOK_SECRET"], headers, raw_body)
    except WebhookVerificationError:
        return 400
    print(event["type"], event["id"])  # deduplicate by id
    return 204

Deliveries are retried for about a day, can arrive more than once and out of order: deduplicate by id and order by published_at (or detected_at for coverage). Details: Webhooks.

Ask an agent

What changed in the latest ICD-10-CM release? List the retired codes with their replacements, and cite the release version.

The agent uses list_datasets and get_dataset_changes (Builder and up for the changes).

What it is not

A change says that a record we serve was added, changed or retired, not what it means for your claims, formulary or directory: re-read the record (and, for coverage, the linked CMS document) before acting on it. Datasets whose license keeps them out of the API produce no changes or webhook events.

On this page