If you have ever built or maintained a healthcare application that handles diagnosis codes, you know the pain of working with ICD-10. The code set contains over 70,000 entries, updates annually, and carries strict specificity requirements that can make or break a claim. Manual lookups are slow, error-prone, and do not scale.
The ICD-10 Data Lookup API from VBC Risk Analytics addresses these challenges by providing RESTful access to the full ICD-10-CM and ICD-10-PCS code sets. Rather than maintaining a local database that you need to update every October, you get a set of healthcare apis that covers code search, tabular data, external causes, neoplasm tables, drug indices, and procedure codes — all kept current by the provider. For developers building EHR integrations, claims processing systems, or coding assistance tools, this kind of medical coding lookup service can significantly reduce development time and improve data quality downstream.
Quick Start
The API follows standard REST conventions. Base URL, API key in the header, JSON responses:
import requests
BASE_URL = "https://restapi.npidataservices.com/icd10/api/v1"
HEADERS = {"ApiKey": "YOUR_API_KEY"}
**Search for a condition**
response = requests.get(
f"{BASE_URL}/getICD10Code",
params={"query": "type 2 diabetes with chronic kidney disease"},
headers=HEADERS
)
results = response.json()
That is all the boilerplate you need. Every endpoint works the same way — GET request, query parameters, API key header, JSON back.
The Seven Endpoints
The API exposes seven endpoints. Together they cover the entire ICD-10 reference — the same content that coders traditionally navigate through print manuals, encoder software, or the CMS website, but accessible programmatically.
/getICD10Code — Iterative Code Search
This is the endpoint most developers will use first. Given a clinical term like "chronic obstructive pulmonary disease" or "hip fracture," it returns matching codes and index entries. The search is iterative — you can start broad, examine results, and refine by passing an index parameter to drill into a specific branch.
Think of it as the programmatic equivalent of a coder typing a term into an encoder's search box.
**Broad search**
results = requests.get(f"{BASE_URL}/getICD10Code",
params={"query": "pneumonia"}, headers=HEADERS).json()
# Narrow into a specific index branch
refined = requests.get(f"{BASE_URL}/getICD10Code",
params={"query": "pneumonia", "index": "aspiration"},
headers=HEADERS).json()
/getICD10Index — Alphabetic Index Browse
The ICD-10-CM Alphabetic Index is the traditional starting point for code lookup. This endpoint mirrors that experience — browse by letter, then by main term. Useful for building autocomplete interfaces where a user types a condition name and gets progressively filtered suggestions.
**All index entries under "D"**
requests.get(f"{BASE_URL}/getICD10Index",
params={"letter": "D"}, headers=HEADERS)
**Narrow to "Diabetes" specifically**
requests.get(f"{BASE_URL}/getICD10Index",
params={"letter": "D", "main_term": "Diabetes"}, headers=HEADERS)
/getICD10Details — Tabular Data
Once you have a code or code range from the index, this endpoint gives you the full tabular detail — descriptions, Includes notes, Excludes1 and Excludes2 rules, coding guidelines, and specificity requirements. You can query at three levels of granularity: entire chapter, section range, or specific diagnosis code.
**Full detail for a specific code**
requests.get(f"{BASE_URL}/getICD10Details",
params={"diag_code": "E11.22"}, headers=HEADERS)
**Browse the entire diabetes section**
requests.get(f"{BASE_URL}/getICD10Details",
params={"section_range": "E08-E13"}, headers=HEADERS)
**Pull an entire chapter**
requests.get(f"{BASE_URL}/getICD10Details",
params={"chapter": "9"}, headers=HEADERS)
This is the endpoint you use for validation — confirming whether a code is a billable terminal code or a header that requires additional specificity.
/getICD10EIndex — External Causes Index
Injury and trauma applications need external cause codes (V00-Y99) to describe how an injury occurred. This endpoint provides the External Causes of Morbidity index — essential for emergency department systems and workers' compensation platforms.
**Look up fall-related external cause codes**
requests.get(f"{BASE_URL}/getICD10EIndex",
params={"letter": "F", "main_term": "Fall"}, headers=HEADERS)
/getICD10Drug — Table of Drugs and Chemicals
Maps substances to their ICD-10 codes for poisoning, adverse effects, and underdosing. Valuable for pharmacy applications, poison control integrations, and any system that needs to code substance-related encounters.
**Browse drug entries starting with "A" (Aspirin, Acetaminophen, etc.)**
requests.get(f"{BASE_URL}/getICD10Drug",
params={"letter": "A"}, headers=HEADERS)
/getICD10Neo — Neoplasm Table
Oncology coding requires mapping anatomical sites to neoplasm codes across six behavior categories (malignant primary, malignant secondary, in situ, benign, uncertain, unspecified). This endpoint provides that specialized lookup.
**Browse neoplasm entries (Breast, Brain, Bone, etc.)**
requests.get(f"{BASE_URL}/getICD10Neo",
params={"letter": "B"}, headers=HEADERS)
/getICD10PCSTabular — Procedure Codes
For inpatient procedure coding, this endpoint provides ICD-10-PCS code details. PCS uses a fundamentally different structure from ICD-10-CM — seven-character codes built from tables rather than hierarchical categories — and this endpoint handles that complexity.
**Look up a PCS code section**
requests.get(f"{BASE_URL}/getICD10PCSTabular",
params={"code": "0210"}, headers=HEADERS)
Putting It Together: A Practical Client
The most common integration pattern combines search and detail into a two-step workflow — find the code, then get the full context:
class ICD10Client:
def __init__(self, api_key):
self.base = "https://restapi.npidataservices.com/icd10/api/v1"
self.session = requests.Session()
self.session.headers.update({"ApiKey": api_key})
def search(self, query, index=None):
params = {"query": query}
if index:
params["index"] = index
return self.session.get(
f"{self.base}/getICD10Code", params=params, timeout=5
).json()
def details(self, diag_code):
return self.session.get(
f"{self.base}/getICD10Details",
params={"diag_code": diag_code}, timeout=5
).json()
def index(self, letter, main_term=None):
params = {"letter": letter}
if main_term:
params["main_term"] = main_term
return self.session.get(
f"{self.base}/getICD10Index", params=params, timeout=5
).json()
# Usage
client = ICD10Client("YOUR_API_KEY")
results = client.search("heart failure")
detail = client.details("I50.9")
browse = client.index("H", "Heart")
Note the use of requests.Session() — this reuses the underlying TCP connection, which makes a meaningful difference when you are making many sequential calls.
Caching Strategy
ICD-10 codes change once a year on October 1. This makes aggressive caching both safe and highly effective:
from functools import lru_cache
@lru_cache(maxsize=5000)
def get_code_detail(client, diag_code):
return client.details(diag_code)
For production systems, use Redis with a TTL that expires around the next annual update:
import redis, json
from datetime import datetime
cache = redis.Redis()
def cached_details(client, code):
key = f"icd10:{code}"
hit = cache.get(key)
if hit:
return json.loads(hit)
data = client.details(code)
# TTL until next October 1
now = datetime.now()
next_oct = datetime(now.year if now.month < 10 else now.year + 1, 10, 1)
cache.setex(key, int((next_oct - now).total_seconds()), json.dumps(data))
return data
Concurrent Lookups
When you need to look up multiple codes at once — common in claims processing and batch validation — use async to parallelize:
import httpx, asyncio
async def batch_lookup(api_key, codes):
async with httpx.AsyncClient() as client:
tasks = [
client.get(
"https://restapi.npidataservices.com/icd10/api/v1/getICD10Details",
params={"diag_code": c},
headers={"ApiKey": api_key}, timeout=10
) for c in codes
]
responses = await asyncio.gather(*tasks, return_exceptions=True)
return {
c: r.json() if not isinstance(r, Exception) else None
for c, r in zip(codes, responses)
}
codes = ["E11.22", "I50.9", "J44.1", "N18.3", "F32.1"]
results = asyncio.run(batch_lookup("YOUR_API_KEY", codes))
Error Handling
Healthcare APIs demand careful error handling because incorrect data can affect patient care and financial outcomes. Key considerations:
-
401 means missing or invalid
ApiKey— do not retry, fix the credential - 429 means rate limited — implement exponential backoff
- Timeouts — set 5s for interactive workflows, 30s for batch, and retry on transient failures
- Ambiguous results — when search returns multiple matches, surface all options to the user rather than auto-selecting. In healthcare coding, the difference between two similar codes can have significant clinical and financial implications
import time
def safe_call(client, code, retries=3):
for attempt in range(retries):
try:
return client.details(code)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 401:
raise ValueError("Invalid ApiKey")
if e.response.status_code == 429:
time.sleep(2 ** attempt)
continue
raise
except requests.exceptions.Timeout:
if attempt < retries - 1:
continue
raise
Why Use an API Instead of a Local Database?
You could download the CMS ICD-10 files and build your own lookup. Many organizations do. But maintaining it means:
- Parsing CMS release files every October and applying updates
- Building and maintaining search, hierarchy navigation, and validation logic
- Handling the separate structures for CM (diagnosis), PCS (procedures), drugs, neoplasms, and external causes
- Testing every update cycle for regressions
An ICD-10 code lookup API from NPI Data Services externalizes all of that. You get a stable interface, current data, and seven focused endpoints that cover the full ICD-10 reference. The trade-off is API dependency and latency — which caching largely mitigates.
Getting Started
If you are building healthcare applications that need diagnosis or procedure code lookup, integrating the ICD-10 data lookup service early saves significant time. The integration is straightforward — standard REST, JSON responses, API key auth. Most developers can have a working prototype within a couple of hours.
Start with /getICD10Code for search and /getICD10Details for validation. Add the specialty endpoints (Drug, Neoplasm, External Causes, PCS) as your application requires.
Full Swagger documentation: ICD10 Data Lookup API
Top comments (1)
Hey everyone — I built this article based on our team's experience integrating the VBC Risk Analytics' ICD-10 Data API into a claims processing pipeline in our RADV Scrubber. A few things we learned the hard way that didn't make it into the article:
Would love to hear from others who have integrated ICD-10 data into their apps — what patterns worked for you? Any gotchas I missed?