# Keep your data fresh

URL: https://docs.healthcare.dev/recipes/keep-data-fresh

|           |                                                                                                                                                             |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Plan      | **Every plan**: release list. **Builder**: change feed (a preview on Free). **Pro**: webhooks, and the coverage change feed (a preview on Free and Builder) |
| Endpoints | `/v1/datasets/{dataset}/releases`, [`/v1/changes`](/versioning#change-feed), [`/v1/coverage/changes`](/coverage-changes), [`/v1/webhooks`](/webhooks)       |
| Cost      | None 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

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

Free keys can page through the change feed as a preview: 50 preview calls a month on Free. Webhooks
have no preview.

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

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

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

```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](/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.

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

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

```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](/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.
