ATC Code Lookup: A Developer's Guide to Using an API

You've probably hit this exact problem in the middle of an ETL task or a terminology integration sprint. A source system hands you an ATC code. Product wants a searchable drug browser. Analytics wants rollups by therapeutic class. Research wants the corresponding standard OMOP concept. And suddenly a “simple lookup” turns into vocabulary downloads, local database setup, release management, and a lot of SQL against tables you didn't want to own.
That's the old path. It still works, but it's heavy.
A modern ATC code lookup workflow is much more API-driven. Instead of treating ATC as a static reference table, it helps to treat it as a structured, versioned terminology you query, traverse, and map on demand. That shift matters when you're building applications, not just running one-off manual searches. It changes how you design search, how you cache results, and how you keep analyses reproducible across vocabulary releases.
Introduction From Manual Lookups to Modern APIs
The friction usually starts small. You want to answer one question: what does this ATC code mean? Then the next question lands immediately after it: what class is it under, what ingredient does it represent, and how do I map it into OMOP or another vocabulary?
If you solve that with spreadsheets or ad hoc exports, you'll spend more time managing vocabulary state than writing application logic. If you solve it with a local vocabulary database, you gain control, but you also inherit ingestion jobs, release synchronization, indexing, schema knowledge, and operational support.
That trade-off is why API-first terminology work has become the practical option for many teams. You can query concepts directly, search by code or text, traverse hierarchy, and request mappings without building your own vocabulary service first. For ATC work in particular, that's useful because lookup often isn't a single-row fetch. You often need parent categories, related standard concepts, and stable release-aware behavior.
The other big difference is speed of iteration. When a developer can test a code lookup in a browser, repeat it with a REST call, and then move the same logic into Python or R, the workflow gets simpler. You stop designing around vocabulary maintenance and start designing around product behavior.
Practical rule: If your team needs ATC in an app, a pipeline, or a recurring analysis, build the lookup path as a service integration from the start. Manual lookup is fine for validation, not for production.
Understanding the ATC Classification System
ATC codes are only useful if your application treats them as a hierarchy. A lookup for a level 5 substance code and a lookup for a level 1 category should not drive the same UI, the same aggregation, or the same mapping logic. The World Health Organization defines ATC as a five-level classification system with top-level groups organized around the organ or system involved, along with therapeutic, pharmacological, and chemical characteristics, in the WHO ATC classification reference.
For developers, the practical point is simple. An ATC code is a path, not just an identifier string.
How to read the levels
When you parse a full ATC code, each segment adds more specificity:
- Level 1 is the broad anatomical or pharmacological group.
- Level 2 narrows to the therapeutic main group.
- Level 3 defines a pharmacological or therapeutic subgroup.
- Level 4 specifies the chemical, therapeutic, or pharmacological subgroup.
- Level 5 identifies the chemical substance.
That structure affects implementation details quickly. If a user searches for a broad class, returning a single exact match is usually not enough. If an analyst groups claims or prescriptions by ATC, mixing higher-level class codes with substance-level codes will distort counts unless the pipeline normalizes them first.
Another detail matters when you work with product data. WHO's ATC rules assign codes based on factors such as route of administration, and combination products can receive different codes than single-ingredient products. In practice, that means an ATC code is not always a safe shortcut for ingredient equivalence. It is better to treat ATC as classification metadata and confirm substance-level identity separately when your workflow depends on it.
This is one of the places where API-based lookup helps. Instead of storing only the matched code, return the code, its level, its parents, and any linked concepts you need for downstream mapping. That extra context prevents a lot of bad assumptions later.
For a broader perspective on why category structure matters, especially when explaining it to non-terminology colleagues, consider the insights from Cleffex expertise in data analysis.
A short overview is useful before you start coding:
Why Use an API for ATC Code Lookup
The main reason is simple. Self-hosting a vocabulary stack solves access, but it doesn't solve developer time.

When you host vocabulary data locally, you're responsible for much more than lookup. You have to load releases, understand the schema, maintain database performance, and expose search patterns your application needs. Exact code lookup is the easy part. Prefix search, fuzzy search, hierarchy traversal, and cross-vocabulary mapping are where teams end up writing infrastructure they didn't originally plan for.
What self-hosting gets wrong for many teams
Self-hosting still makes sense in some environments. Air-gapped deployments, strict no-external-call policies, and proprietary local extensions are real constraints.
But for the average health data product or research engineering team, local ownership creates recurring friction:
- Release handling becomes a project: Someone has to update vocabularies and verify downstream effects.
- Search quality is usually minimal: Exact matches work. Human input doesn't.
- Every mapping becomes custom logic: The data is there, but you still have to wrap it.
- Developer onboarding slows down: New engineers need vocabulary and schema context before they can ship features.
That's why an API often wins on total effort, even when raw data access is technically possible either way.
What works better in practice
A service layer is usually the right boundary. It gives application teams stable endpoints for code lookup, search, mapping, and hierarchy operations without requiring each team to understand ATHENA internals.
One option in this category is OMOPHub's guide to working with an OHDSI ATHENA API, which shows the basic API-first model for standardized vocabulary access. The useful idea isn't “replace SQL everywhere.” It's “stop making each application own terminology plumbing.”
Treat vocabulary as an external dependency with explicit interfaces. That gives you cleaner application code and fewer hidden data management tasks.
A practical pattern that works well is hybrid. Develop and iterate against an API. Cache stable lookup results where needed. Keep local mirrors only where policy requires them. That keeps the operational center of gravity small while preserving control over critical paths.
Core Search Strategies for ATC Codes
Developers usually start with exact code lookup. That's necessary, but it's not enough. Real inputs are messy. Users search by ingredient, by product name, by partial category, and by misspelling. Your ATC code lookup layer should support all of those.
Exact lookup when you already have the code
If your source feed already carries ATC codes, exact code lookup is the cleanest path. It's deterministic, easy to cache, and a good fit for ETL validation.
Typical REST shape:
- search by vocabulary code within the ATC vocabulary
- return concept metadata
- include parent or relationship expansion if your use case needs context
This is also the fastest way to confirm whether a source code is present as expected before you start mapping.
Search by meaning, not only by literal code
When the input is a drug name or a fragment of one, code lookup needs search behavior. That usually means full-text or semantic search over concept names and synonyms, then filtering to the ATC vocabulary.
A browser-based way to test this before writing code is the OMOPHub Concept Lookup tool.

That kind of tool is useful because it lets you quickly inspect whether a term resolves to an ATC concept, whether there are multiple nearby matches, and whether your intended user query needs autocomplete or fuzzy support.
A strong search implementation usually combines a few modes:
- Exact code search for deterministic ETL and validation
- Full-text search for names and synonyms
- Fuzzy matching for user typos
- Autocomplete for interactive search boxes
If you're deciding between strict keyword matching and more flexible retrieval for vocabulary search, keyword search vs semantic search in healthcare vocabularies is a useful comparison.
A practical request pattern
For app development, I usually separate search into two stages:
- Search broadly within ATC with text input.
- Fetch details only for the selected concept.
That avoids over-fetching and keeps the UI responsive. It also lets you display lightweight suggestion rows first, then load hierarchy and mapping details after the user commits.
Working advice: Don't put hierarchy traversal inside your first keystroke search. Search fast first. Enrich after selection.
For fuzzy search, keep expectations realistic. It helps with small spelling errors. It won't fix ambiguous business logic. If a user types a brand name but you need ingredient-level ATC grouping, you still need a second-stage resolution step.
Mapping ATC to Other Vocabularies
Most production workflows don't stop at ATC. They start there.
You may receive ATC from a source feed, but your analytics model may need standard OMOP concepts. Your downstream study may need RxNorm ingredients. A clinical workflow may need another terminology entirely. The hard part isn't finding an ATC code. It's moving from that code to the representation your platform uses.

Why mapping matters more than lookup
A plain lookup tells you what a code is. Mapping tells you what to do with it.
In OMOP-oriented pipelines, that often means following source-to-standard relationships so your ETL writes standardized concepts rather than preserving every source vocabulary as-is. For drugs, a common pattern is translating an ATC concept into a standard concept used for analysis or storage, depending on your target design.
That shift is where API abstractions help a lot. The underlying vocabulary relationships can be multi-step and not always obvious from the source code alone.
A reliable workflow
A practical ATC mapping workflow usually looks like this:
- Resolve the ATC concept first: Confirm you have the intended source concept.
- Inspect standard status: Determine whether the ATC concept itself is standard for your use case or whether you need a mapped target.
- Traverse mapping relationships: Follow the source-to-standard relationship server-side where possible.
- Return target metadata: Include concept ID, vocabulary, domain, and target table information if your ETL needs it.
If you're doing this often for drug data, RxNorm code lookup in OMOP workflows is a useful companion reference because many ATC mapping tasks eventually land there.
Here's the practical trade-off. If you write this mapping logic yourself against local vocabulary tables, you get flexibility. You also have to encode relationship rules correctly and keep them aligned with release changes. If you push that into an API call, your application code gets much simpler, and your failure modes get easier to reason about.
A good mapping response should let you answer these questions immediately:
| Question | Why it matters |
|---|---|
| What ATC concept did I match? | Confirms the source input was interpreted correctly |
| Is it standard or source-side? | Tells ETL whether another mapping step is required |
| What is the target standard concept? | Provides the concept to write or analyze against |
| What domain or table does it belong to? | Helps route data into the correct OMOP structure |
That's the difference between “I found the code” and “I can use this code in production.”
Practical ATC Lookup in Python
Python is where a lot of vocabulary work becomes real. You stop exploring concepts and start wiring them into ETL, QA scripts, notebooks, and backend jobs.
The cleanest pattern is search, then detail lookup, then mapping. That mirrors how people work and keeps your code modular.
Install and authenticate
The Python SDK lives at the OMOPHub Python repository. Start by installing the package from PyPI and loading your API key from an environment variable.
import os
from omophub import OMOPHub
client = OMOPHub(api_key=os.environ["OMOPHUB_API_KEY"])
If you don't want to depend on environment management in a quick script, you can pass the key directly while testing. For team code, environment variables or a secret manager are the safer default.
Search for an ATC concept
This example searches for a concept by text and restricts the search to the ATC vocabulary. The exact method names can change over time, so check the current SDK docs before copying into production, but this is the right shape.
import os
from omophub import OMOPHub
client = OMOPHub(api_key=os.environ["OMOPHUB_API_KEY"])
results = client.search_concepts(
query="Metformin",
vocabulary=["ATC"],
limit=10
)
for concept in results.get("items", []):
print(
concept.get("concept_id"),
concept.get("concept_code"),
concept.get("concept_name"),
concept.get("vocabulary_id")
)
Keep the first call narrow. Filter to ATC when you know that's the source vocabulary. Otherwise you'll mix in RxNorm and other vocabularies and make ranking harder to interpret.
Fetch detail and map to a standard target
Once you have the concept ID, fetch the full concept, then request mappings. Separating those calls keeps debugging straightforward.
import os
from omophub import OMOPHub
client = OMOPHub(api_key=os.environ["OMOPHUB_API_KEY"])
search = client.search_concepts(
query="Metformin",
vocabulary=["ATC"],
limit=1
)
items = search.get("items", [])
if not items:
raise ValueError("No ATC concept found for query")
concept_id = items[0]["concept_id"]
concept = client.get_concept(concept_id)
print("Matched concept:")
print(concept.get("concept_name"))
print(concept.get("concept_code"))
print(concept.get("vocabulary_id"))
print(concept.get("domain_id"))
print(concept.get("standard_concept"))
mappings = client.map_concept(
concept_id=concept_id,
target_vocabularies=["RxNorm"]
)
print("\nMappings:")
for item in mappings.get("items", []):
print(
item.get("relationship_id"),
item.get("target_concept_id"),
item.get("target_concept_name"),
item.get("target_vocabulary_id")
)
A few habits save time here:
- Log both concept ID and concept code: You'll need both when investigating edge cases.
- Expect zero mappings sometimes: Don't assume every source concept has the target you want.
- Cache stable lookups in batch jobs: Vocabulary calls are cheap, but repeated identical calls still add noise.
- Persist release context if reproducibility matters: More on that in the best practices section.
If you're building a pipeline, wrap the three operations into a single internal helper, but keep the raw calls available for debugging. That balance makes production code clean without turning troubleshooting into archaeology.
ATC Lookups in R and TypeScript
The useful part of a terminology API is consistency across languages. Search, lookup, and mapping should behave the same whether you're in an R notebook, a Python ETL job, or a TypeScript service.
R for research and validation
The R client is available in the OMOPHub R repository. The exact function names depend on the package version, but the workflow remains straightforward.
library(omophub)
client <- omophub_client(api_key = Sys.getenv("OMOPHUB_API_KEY"))
search_res <- search_concepts(
client = client,
query = "Metformin",
vocabulary = c("ATC"),
limit = 5
)
print(search_res)
first_id <- search_res$items[[1]]$concept_id
concept_res <- get_concept(
client = client,
concept_id = first_id
)
print(concept_res)
map_res <- map_concept(
client = client,
concept_id = first_id,
target_vocabularies = c("RxNorm")
)
print(map_res)
R users often do this inside exploratory workflows first, then move the same logic into data preparation scripts. That's a good pattern because it exposes vocabulary ambiguity early.
TypeScript for apps and services
For frontend or Node.js teams, the same basic flow applies. Search first, then fetch detail, then map.
import { OMOPHub } from "@omophub/omophub-sdk";
const client = new OMOPHub({
apiKey: process.env.OMOPHUB_API_KEY!,
});
async function run() {
const search = await client.searchConcepts({
query: "Metformin",
vocabulary: ["ATC"],
limit: 5,
});
const first = search.items?.[0];
if (!first) {
throw new Error("No ATC concept found");
}
const concept = await client.getConcept(first.concept_id);
console.log({
concept_id: concept.concept_id,
concept_code: concept.concept_code,
concept_name: concept.concept_name,
vocabulary_id: concept.vocabulary_id,
standard_concept: concept.standard_concept,
});
const mappings = await client.mapConcept({
concept_id: first.concept_id,
target_vocabularies: ["RxNorm"],
});
console.log(mappings.items);
}
run().catch(console.error);
For browser UIs, debounce the search request and keep detail retrieval behind item selection. For Node services, centralize vocabulary calls behind a domain service so your controllers don't become terminology-aware.
MCP and AI-assisted workflows
If you're building agentic tooling or developer assistants, the OMOPHub MCP server is worth a look. It gives AI tools a grounded interface to concept search and lookup rather than asking them to guess codes from free text.
That doesn't replace review. It just moves the model away from hallucinated terminology and toward retrievable vocabulary state.
The best language for ATC lookup is the one your team already uses. The important part is keeping the terminology logic consistent across environments.
Best Practices and Troubleshooting
A lookup that works in a notebook can still fail in production. The common problems are predictable: teams mix hierarchy levels, assume every ATC concept has a clean downstream mapping, or forget that terminology changes across releases.
Handle ATC as a versioned reference
ATC changes over time, and your application should treat it that way. The WHO toolkit states that the searchable ATC/DDD Index 2026 supports searches across all ATC levels and can return higher levels up to the first level. The same WHO toolkit context also references the 2025 version of the index with alterations implemented from January 2025, as summarized in the WHO ATC/DDD toolkit.
For engineering work, the rule is simple. Store the vocabulary release or snapshot identifier anywhere reproducibility matters. If a pipeline generates cohorts, reports, or review queues from ATC lookups, persist the version metadata with the output so you can explain differences later.
This saves time during audits and bug triage.
Design for hierarchy-aware behavior
ATC codes at different levels answer different questions. A therapeutic class is useful for grouping. A substance-level code is useful for specific labeling, mapping, or downstream review. Pushing both through the same logic usually creates noisy results.
Use hierarchy operations on purpose:
- Parent expansion for UI context: show the class path above the selected concept so users can confirm they picked the right branch.
- Descendant expansion for analytics or cohort logic: fetch all lower-level concepts under a class instead of relying on string prefix shortcuts.
- Level checks before aggregation: reject or separate mixed-level inputs before counts, dashboards, or rules engines consume them.
Prefix matching looks convenient, but hierarchy traversal is safer when the vocabulary service already knows the parent-child structure.
Expect imperfect mappings and ambiguous inputs
Mapping failures are normal. They can come from release differences, vocabulary scope, or a source concept that does not map cleanly to the target you asked for.
When a lookup or mapping result looks wrong, check the boring things first:
- Confirm the source concept: many failed mappings start with the wrong concept_id or the wrong ATC level.
- Inspect the relationship returned: a related concept is not always the same thing as a standardization target.
- Try a different path: if a very specific concept does not map the way you expect, retry from a broader class or map through another supported vocabulary.
- Log nulls and partial results: unresolved concepts should be reviewable, not dropped without logging.
For batch ETL, keep an exception table. Store the input term, the selected concept, the release version, the mapping target, and the failure reason. That gives analysts and terminology reviewers something concrete to inspect instead of a generic "no match" bucket.
Frequently Asked Questions
How should I handle combination products in ATC lookup
Don't assume a combination product will share the same code behavior as a single-ingredient product. WHO notes that combination products receive different ATC codes from single-ingredient products, so your lookup logic should treat them as distinct concepts rather than trying to infer a simple ingredient match from the code alone.
How do I get all descendants of an ATC class
Use hierarchy traversal, not string prefix tricks. Prefixes can look useful, but hierarchy services are more reliable because they respect the vocabulary's actual parent-child structure. In OMOP-style terminology workflows, descendant expansion is the right way to collect all concepts beneath a class for cohort logic or grouped analytics.
What's the difference between standard and non-standard ATC concepts in OMOP workflows
A non-standard concept is often a source-side representation that points to another concept for standardized use. A standard concept is the one you usually store or analyze against in the common data model. In ETL, that means source concepts are often lookup inputs, while standard concepts are your operational targets.
Should I build ATC lookup directly into my app or isolate it behind a service
Isolate it. Even if the first use case is simple, terminology logic grows quickly. A thin internal service gives you one place to handle caching, retries, version constraints, and auditability.
Is manual lookup ever enough
Yes, for spot checks and validation. No, for production systems, recurring ETL, or user-facing search. Once a workflow repeats, code it and make the behavior explicit.
If your team needs ATC lookup, cross-vocabulary mapping, or OMOP-aware terminology access without standing up local vocabulary infrastructure, OMOPHub is one API-first option to evaluate. It exposes ATC alongside other OMOP vocabularies through REST, FHIR, and SDKs, which makes it practical for ETL jobs, research scripts, and application backends that need repeatable terminology workflows rather than manual lookups.


