# Python SDK

URL: https://docs.healthcare.dev/guides/python

The Python SDK has the same surface as the [TypeScript SDK](/guides/typescript), with a sync and an
asyncio client. Python 3.9 or later.

**Not on PyPI yet.** It will be published as `healthcare-dev` (import `healthcare_dev`); until
then, install it from the repository's `packages/sdk-py`.

```python
from healthcare_dev import HealthcareDev, NotFoundError

hdev = HealthcareDev()  # reads HDEV_API_KEY

r = hdev.codes.get("icd10cm", "E11.9")
print(r["data"]["display"], r["data"]["billable"], r["meta"]["data_version"])

# One page, or every result
page = hdev.codes.search("icd10cm", q="type 2 diabetes kidney", billable=True)
for code in hdev.codes.iterate("hcpcs", q="wheelchair"):
    print(code["code"])

# Hierarchy, batch (any length, split into requests of 100) and drugs
tree = hdev.codes.hierarchy("icd10cm", "E11.22")
many = hdev.codes.batch("icd10cm", ["E11.9", "I10", "NOPE"])
product = hdev.drugs.ndc("0002-0152-01")  # includes linked RxNorm concepts

# Medicare enrollment, opt-out and OIG exclusions for an NPI; each answer has a `note`
enrolled = hdev.providers.enrollment("1881018208")
excl = hdev.providers.exclusions("1922348218")  # matched by NPI as listed by OIG
by_name = hdev.exclusions.search(q="dunn medical", state="MS")

try:
    hdev.codes.get("icd10cm", "XYZ")
except NotFoundError as err:
    print(err.detail)
```

```python
import asyncio
from healthcare_dev import AsyncHealthcareDev


async def main() -> None:
    async with AsyncHealthcareDev() as hdev:
        r = await hdev.drugs.full("0006-3026-02", view="compact")
        print(r["data"]["flags"])
        async for change in hdev.changes.iterate(dataset="icd10cm", order="desc"):
            print(change["kind"], change["key"])


asyncio.run(main())
```

Method names are the TypeScript ones in snake case (`care_categories.iterate_codes`,
`drugs.search_asp`), and parameters are keyword arguments: `billable=True`,
`fields=["code", "display"]`, and `from_="2026-01-01"` for `from`. [Child keys](/child-keys) are
`hdev.keys.create`, `list`, `get`, `update` and `revoke`; a route outside a key's scopes raises
`PermissionDeniedError` with `required_scope`.

## Webhooks

Pro plan and up. Subscribe once and store the secret (shown only in the create response), then
verify every delivery against the raw body with `verify_webhook`. It checks the signature in
constant time and the timestamp within five minutes, and raises `WebhookVerificationError`
otherwise. See [Webhooks](/webhooks).

```python
from healthcare_dev import HealthcareDev, verify_webhook

created = HealthcareDev().webhooks.create(
    {
        "url": "https://example.com/hooks/healthcare-dev",
        "events": ["dataset.released", "record.changed"],
        "subjects": ["E11.9", "J1817"],
    }
)
# save created["data"]["secret"]

# In your handler (Flask shown): the raw body, not re-serialized JSON
event = verify_webhook(SECRET, request.headers, request.get_data())
```

## Behavior

- **Retries:** 429 `rate-limited`, 5xx and network errors are retried twice with exponential
  backoff and jitter, honoring `Retry-After`. `QuotaExceededError` is never retried. Writes
  (POST and DELETE: reporting an issue, creating, replaying or deleting a webhook) are sent once;
  batch lookups are reads and are retried.
- **Timeout:** a deadline for each attempt, request and body together (in the sync client, one
  stalled network read can add up to one more `timeout`).
- **Errors:** every failure is a `HealthcareDevError`; API problems are `ApiError` subclasses with
  `status`, the stable problem `code`, `detail` and `request_id`. Network failures and timeouts
  are `healthcare_dev.ConnectionError`, which also subclasses the built-in one.
- **Responses:** dicts typed as `Response[T]`, `{"data", "meta", "links"}`, as in
  [Responses](/responses).
- **Options:** `api_key`, `base_url`, `max_retries` (2), `timeout` (30 seconds), `user_agent`,
  `http_client` (your own `httpx` client).
- `is_valid_npi`, `to_ndc11`, `ndc_prefix` and `ndc_product_key` work offline, with the same rules
  as the API.

Types are generated from the same OpenAPI spec as this reference, so they never drift from the API.
