---
title: "Validate a single NPI number"
method: POST
path: "/v1/healthcare/npi/validate"
tags: ["healthcare"]
---

# Validate a single NPI number

`POST /v1/healthcare/npi/validate`

## Request

- Base URL: `https://api.snipget.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.snipget.ai/v1/healthcare/npi/validate`
- Auth: none declared

Validates a National Provider Identifier (NPI) using the Luhn algorithm with the CMS 80840 prefix (per the NPI Final Rule, 45 CFR Part 162). Returns structured validity info. Accepts string or integer input; Excel-style float strings like '1234567893.0' are tolerated.

## Query parameters

- `include_receipt` boolean

## Headers

- `authorization` string, nullable
- `X-API-Key` string, nullable

## Request body

- NPIValidateRequest
  - `npi` union, required: NPI value to validate. Accepts string or integer.
    - string
    - integer
  - `include_trace` boolean: Include reasoning trace in meta.trace.

## Response `200`

Successful Response

- HealthcareNpiValidateResponse: Success envelope for `/v1/healthcare/npi/validate` — `result` carries this utility's precise shape.
  - `status` string
  - `confidence` number, required
  - `result` object, required: Result of :func:`validate`. ``why_invalid`` appears on any invalid verdict — the catalog's shared key, which the descriptions used to call 'reason' without the field existing (parked polish item). Entity type is NOT derivable from the number itself; use npi_lookup (registry data) for that.
    - `npi` integer, nullable, required
    - `is_valid` boolean, required
    - `checksum_valid` boolean, required
    - `input_was_clean` boolean, required
    - `why_invalid` string
  - `meta` ResponseMeta: Metadata attached to every successful response.
    - `version` string
    - `build` string
    - `elapsed_ms` integer
    - `cost_units` integer: Cost units charged for this call: one per item processed (a batch of N items = N; live-registry lookups bill 2 per item). A call is one request; cost units are the billing measure.
    - `request_id` string, nullable
    - `trace` string[], nullable
    - `rate_limit_remaining` integer, nullable
    - `rate_limit_reset` integer, nullable
    - `quota_remaining` integer, nullable: Cost units left in the plan's monthly included bucket before this call's charge lands (None on unlimited tiers).
    - `quota_reset` integer, nullable: Unix timestamp when the monthly included bucket resets.
    - `credit_remaining_usd` number, nullable: Prepaid overage-allowance balance in USD, populated once the included cost units are used up and this call is drawing on it.
    - `receipt` ReceiptMeta: Opt-in execution receipt (ADR #68 B1) — caller-verifiable hashes. Requested with the ``include_receipt=true`` query parameter (free, like ``include_trace``). Reproduce either hash with any RFC 8785 (JCS) library: ``input_hash = sha256(jcs({"tool": <request path>, "args": <your JSON body minus include_trace/include_receipt>}))``; ``result_hash = sha256(jcs(result))``. For deterministic tools (everything except the generators) the same input on the same ``build`` reproduces ``result_hash`` — disputes are honored by replay.
      - `receipt_id` string, required
      - `build` string, required
      - `input_hash` string, nullable
      - `result_hash` string, nullable
      - `error` string, nullable
      - `message` string, nullable

## Other responses

- `400`: Input was syntactically valid but semantically unusable (`INVALID_INPUT`). Not retryable without changing the input.
- `401`: Missing or unrecognized API key. Send it as `Authorization: Bearer <key>` or `X-API-Key: <key>`. Keys are issued at https://snipget.ai.
- `403`: The key is valid but this caller may not use it: the request IP is outside the key's allowlist (`IP_NOT_ALLOWED`) or the account is deactivated (`ACCOUNT_DEACTIVATED`).
- `422`: The request body failed schema validation (`INVALID_REQUEST`). `details` lists the per-field errors. Not retryable without changing the request.
- `429`: Two distinct conditions share this status — branch on `error_code`. **`RATE_LIMITED`** is a per-second throughput throttle: retry after `retry_after_seconds` (the precise float; the `Retry-After` header is the same value rounded up). **`QUOTA_EXCEEDED`** is monthly capacity and is NOT retryable: recovery depends on `limit_type` — upgrade (`monthly_quota`), buy an overage allowance (`included_exhausted`), or top up (`overage_balance_exhausted`).
- `503`: A transient, retryable service condition — branch on `error_code`. **`MAINTENANCE_MODE`**: an admin-toggled maintenance window (auth, portal, and webhook surfaces stay up). **`UPSTREAM_UNAVAILABLE`**: a utility's external data source (PubChem, RxNorm, ClinicalTrials.gov, the FX feed, NPPES…) is down, blocking us, or timed out. **`UPSTREAM_RATE_LIMITED`**: that external source is throttling Snipget (it returned a 429) — the caller's own rate is fine, so this is NOT our `RATE_LIMITED` (429); it's a service-side throttle. All three are retryable; wait for `retry_after_seconds` / the `Retry-After` header when present.

---

[API](https://skmtc.dev/snipget/apis/snipget-api.md) · [All operations](https://skmtc.dev/snipget/apis/snipget-api/llms.txt) · [OpenAPI document](https://skmtc.dev/snipget/apis/snipget-api/revisions/ed1077fb1bcb?raw)
