Translating between vocabularies
Your clinicians code a diagnosis once. Some payers want ICD-11, others are still on ICD-10, and what bridges those two facts is a concept map, not a column in your database.
Everything below elaborates this sentence.
The situation this solves
A clinician records cholera. Your system stores 1A00, the ICD-11 code, because that is the
vocabulary the rail codes diagnoses in. Then a claim goes to a payer whose adjudication engine only
understands ICD-10, where the same condition is A00.9.
There are three ways to handle that. Two of them go wrong later.
| Call | What it does | What you keep |
|---|---|---|
Store both codes | Two columns, filled at capture time. It works until either vocabulary is revised, at which point your stored pairs disagree with the published crosswalk and nobody can say which is right. | don't |
Maintain your own mapping table | A spreadsheet somebody owns. It is correct on the day it is written and quietly wrong after that. It is also the same work the terminology owner is already doing, done less well. | don't |
Store the primary code, translate on demand | One code in your record. When a payer needs the other vocabulary, ask the service. The mapping stays owned by the people who curate it. | the ICD-11 code |
What a concept map is
A concept map is a named, versioned set of rows with an owner. Each row says that this source code corresponds to that target code, and how strong the correspondence is.
The strength is the part people skip, and it is what decides whether a translation is safe to apply unattended.
| Call | What it does | What you keep |
|---|---|---|
equivalent | The same thing. Safe to substitute without anyone looking. | use it |
narrower | The source is more specific than the target. Translating loses detail, which is usually acceptable because the target still covers the case. | use it, know what was lost |
broader | The source is more general than the target. Translating adds specificity nobody recorded, so surface this one rather than applying it silently. | flag it |
related | Connected, but not a substitution. Not a translation you can bill on. | a human decides |
notrelated | Explicitly not a match. Recorded so nobody proposes it again. | do not use |
nomap | A deliberate statement that this code has no target in this map, with a reason. Somebody looked and decided. See the warning below. | the reason |
unmapped | Nobody has decided yet. The work has not been done, which is not the same as a conclusion. | chase it |
wider and unmatched are accepted spellings of broader and notrelated, kept for callers written
against the older vocabulary. Send the current names.
Doing it
What the clinician sees is one code, chosen once. The crosswalk stays out of the way until it needs attention.
1A00 · CholeraA00.9 · Cholera, unspecifiedbroaderFind the map once
Maps are configuration rather than runtime state. Resolve the map when you set the integration up and put its short code in your configuration. Resolving it per claim adds a round trip to every submission for an answer that changes about once a year.
Use the short code rather than the numeric id. ICD11-TO-ICD10 says what it is in a code review and
means the same thing in every environment. 7 says nothing and is deployment specific.
Published maps are listed without a credential. Filter with source_code_system= and
target_code_system=, which take numeric ids from the catalogue because they are filters rather
than path lookups, or resolve one straight from the canonical_url a FHIR payload carries.
More than one map can share a canonical url, so this can legitimately return several rows and the
caller picks. Keep the short_code from the row you pick, not the id.
the map id, and its canonical_urlcurl -sS "https://ilm-dev.dha.go.ke/dev/ts/api/v2/concept-maps/?page_size=20"Translate a code
Edit the short code and the code. Every concept map route takes the map's short_code in place of
its numeric id, so a request stays readable. The answer is a FHIR Parameters resource: result, then one
match per target, each carrying an equivalence and the target Coding. Read both, because a
target code without its equivalence is half the answer.
the target code, and its equivalencecurl -sS "https://ilm-dev.dha.go.ke/dev/ts/api/v2/concept-maps/ICD11-TO-ICD10/$translate/?code=1A00&version=serving" \
-H "authorization: Bearer $TS_TOKEN"Full parameters, response shape and the failure cases in the reference.
the target codeOmitting code returns a 400 carrying a FHIR OperationOutcome rather than a plain error body,
which is worth knowing if your client only parses one error shape.
When result: false means two things
$translate deliberately excludes rows declared no_map, and rows whose target is blank. They are
not translations, so they cannot be returned as matches.
The consequence is that result: false covers two completely different situations, and this
operation cannot tell you which one you are in:
| Call | What it does | What you keep |
|---|---|---|
Nobody has mapped this code | Missing work. Raise it, because a code your clinicians use that has no crosswalk row will fail at every payer on the other vocabulary. | escalate |
Somebody decided it maps to nothing | Completed work, with a recorded reason. There is no target and there is not going to be one, so chasing it only wastes an afternoon. | the reason |
To tell them apart, read the rows:
The entry listing shows what $translate hides. A row with no_map: true carries a
no_map_reason, which is a decision rather than a gap. No row at all means nobody has looked yet.
You can also filter the whole map by equivalence=unmapped to see how much work is outstanding
before you depend on it.
no_map, no_map_reason, equivalencecurl -sS "https://ilm-dev.dha.go.ke/dev/ts/api/v2/concept-maps/ICD11-TO-ICD10/entries/?search=1A00" \
-H "authorization: Bearer $TS_TOKEN"Loading a whole crosswalk instead
Per-code translation is right for a claim. It is wrong for a bulk reconciliation of forty thousand historical encounters, because that is forty thousand round trips.
The whole map as a FHIR R4 ConceptMap resource, for loading into your own terminology client.
the bundleIf you take this route, cache the map against the version you fetched and re-check it on a schedule. A crosswalk copied into your database drifts in exactly the way this page opened by warning about. The difference is that you now own the drift deliberately, with a version recorded, rather than by accident.
What breaks here
| Where it surfaces | What the desk sees | What actually went wrong |
|---|---|---|
| Submission to one payer only | "Claims to this payer reject on the diagnosis, everyone else is fine" | Either no translation is happening for the payer on the other vocabulary, or the map id in configuration points at the wrong pair of systems. |
| A handful of codes, repeatedly | "These five diagnoses always fail and we keep re-raising them" | They are almost certainly `no_map` rows with a recorded reason. Read /entries/ once and stop chasing them. |
| An audit or a payer query | "The claim says one thing and the clinical record says another" | A broader translation applied silently. Log the equivalence alongside the translated code so you can explain the difference. |
| A bulk reconciliation | "The job takes nine hours and gets throttled" | One $translate per row. Fetch the map once as FHIR and translate locally. |
| Months after a vocabulary revision | "Our stored ICD-10 codes no longer match the crosswalk" | The second code was stored at capture time. Store the primary code and derive the other one when it is needed. |
Next: Getting the data you need · Validating a code · Pinning to a release

