Skip to main content

Patient $match

The $match operation implements a Master Patient Index (MPI) matching algorithm. It accepts a (possibly partial) Patient resource and returns a Bundle of candidate matches ordered from most to least likely, each annotated with a confidence score and match-grade extension.

Use Cases

  • Duplicate Prevention: Check for existing patients before registering a new one to avoid creating duplicate records
  • Patient Reconciliation: Match incoming patient demographics from an external system against records in your Medplum project
  • Identity Resolution: Find a patient when only partial demographics are known (e.g., name and date of birth without an identifier)

Invocation

POST [base]/Patient/$match

Input Parameters

ParameterCardinalityTypeDescription
resource1..1PatientThe patient resource to match against (may be partial). Must include at least one of: identifier, name, birthDate, telecom, or gender.
onlyCertainMatches0..1booleanIf true, only returns results graded as certain. Defaults to false.
count0..1integerMaximum number of results to return. Defaults to the server's default search count.

Output

Returns a Bundle of type searchset. Each entry contains a matched Patient resource with:

  • entry.search.score — Normalized confidence score from 0 to 1
  • entry.search.extension — A match-grade extension indicating match quality

Match Grades

Each result is annotated with the match-grade extension (http://hl7.org/fhir/StructureDefinition/match-grade):

GradeScore RangeMeaning
certain≥ 0.90High confidence match; likely the same patient
probable0.65 – 0.89Strong but not definitive match
possible0.40 – 0.64Partial match that warrants manual review
certainly-not< 0.40Not a match; excluded from results

Scoring Algorithm

The operation uses a weighted demographic comparison across fields present on both the input patient and each candidate. Fields not present on either side do not affect the score.

FieldWeightNotes
Identifier40%Exact match on value; optionally also on system
Phone30%Normalized to digits only (e.g., (555) 867-53095558675309)
Email30%Normalized to lowercase
Family name20%Exact match scores full weight; prefix/substring match scores 50%
Birth date20%Exact ISO date string match
Given name15%Any given name in common scores full weight
Gender5%Exact string match

The final score is normalized by the total weight of fields compared. Only fields present on both the input patient and the candidate contribute to the score.

Candidate Search Strategies

Before scoring, the operation gathers candidates by running FHIR searches for:

  1. Identifier — searches Patient?identifier=<system>|<value> for each identifier on the input
  2. Birth date — searches Patient?birthdate=<date> if birthDate is present
  3. Telecom — searches Patient?telecom=<value> for each phone or email on the input

Candidates from all strategies are deduplicated by patient ID before scoring.

Example

Request

POST /fhir/R4/Patient/$match
Content-Type: application/fhir+json

{
"resourceType": "Parameters",
"parameter": [
{
"name": "resource",
"resource": {
"resourceType": "Patient",
"name": [{ "family": "Smith", "given": ["John"] }],
"birthDate": "1970-03-15",
"telecom": [{ "system": "phone", "value": "555-867-5309" }]
}
},
{
"name": "onlyCertainMatches",
"valueBoolean": false
}
]
}

Response

{
"resourceType": "Bundle",
"type": "searchset",
"total": 2,
"entry": [
{
"resource": {
"resourceType": "Patient",
"id": "patient-abc",
"name": [{ "family": "Smith", "given": ["John"] }],
"birthDate": "1970-03-15",
"telecom": [{ "system": "phone", "value": "5558675309" }]
},
"search": {
"mode": "match",
"score": 1.0,
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/match-grade",
"valueCode": "certain"
}
]
}
},
{
"resource": {
"resourceType": "Patient",
"id": "patient-xyz",
"name": [{ "family": "Smith", "given": ["Jon"] }],
"birthDate": "1970-03-15"
},
"search": {
"mode": "match",
"score": 0.57,
"extension": [
{
"url": "http://hl7.org/fhir/StructureDefinition/match-grade",
"valueCode": "possible"
}
]
}
}
]
}

Error Responses

Status CodeDescription
400 Bad Requestresource parameter is missing or is not a Patient
400 Bad RequestInput Patient has no matchable fields (identifier, name, birthDate, telecom, or gender)
403 ForbiddenInsufficient permissions to search Patient resources