Deep Match

Deep Match resolves a row of partial, messy signals to exactly one subject — or tells you it will not commit. Send a name with a region, an email, an old employer, an NPI, or any combination; every criterion is evaluated and the ones that fired come back as the row’s match trail. It is the endpoint to reach for when you have fragments rather than an identifier.

Deep Match is asynchronous and batch-only, up to 1,000 rows per submission. The mechanics of submitting and collecting a batch are in Resolve a Batch of Records; this page is about what the answers mean.

What a row resolves to#

A row resolves to one subject, or to the several it cannot be narrowed between. A subject is named by three independent fields; which ones are populated varies, and a result with a null personId can still be a match:

FieldNamesWhen it is populated
personIdA person in the AggKnowledge datasetThe row resolved to someone we hold a person record for.
socialProfileA person outside the datasetThe row resolved to a real professional identity we can name but hold no person record for. Present without a personId.
aggkHcpIdA healthcare professionalA supplied NPI, GMC, RPPS, or ORCID resolved one. Most healthcare professionals have no person record behind them, so this is usually present with a null personId.
Branch on outcome, not on personId. Treating a null personId as a failure discards every professional resolved outside the dataset and nearly every healthcare match. This is the most common Deep Match integration bug.

The slots fill independently because they answer different questions. The person index supplies candidates to be ranked; a healthcare identifier is deterministic and one-to-one, so it is not a hypothesis competing with those candidates — it is the answer to “who is the provider you named”. When the professional is also linked to a person record, one row fills both slots.

What you can send#

Every field is optional and every supplied field is evaluated. A row supplying nothing usable resolves to no_match. Full field definitions are on Submit a Deep Match batch.

SignalFieldsWeight it carries
Contactemails, phonesAn email on file is the strongest signal available and resolves a row on its own.
SocialsocialProfileDeterministic — the URL either resolves to a person or it does not.
Name & locationfullName, stateOrProvince, countryCircumstantial. A name plus a region describes someone; it rarely identifies them alone.
EmployeremployerNames, employerDomains, aggkCompanyIdsCurrent or past. A company ID or domain is worth more than a name, and a current employer more than a former one.
Healthcarenpis, gmcs, rppss, orcidsDeterministic, and resolved against the provider dataset rather than the person graph.
EducationcollegeNamesAccepted and recorded, not yet scored. Supplying a college neither improves nor degrades a row’s result today.
A malformed identifier is not an error. An unparseable phone or a LinkedIn URL with no username simply drops that criterion, and the row resolves on whatever else it supplied. A row naming two different healthcare professionals is the one exception — that is a contradiction we will not settle on your behalf, so the row names neither and falls back to its other signals.

Every criterion is evaluated#

Matching does not stop at the first hit. Every criterion is scored for every row, so the trail you get back is the complete list of what fired — not the first thing that worked. The vocabulary is MatchCriterion; the ones Deep Match can return group into six families:

  • Contact — email_on_file, phone_on_file, reverse_email.
  • Social — social_url.
  • Name — name_exact, name_normalized, name_partial, name_fuzzy, name_phonetic.
  • Location — location_region, location_country.
  • Employer — employer_id, employer_domain, employer_name_current, employer_name.
  • Healthcare — npi, gmc, rpps, orcid.

Within a family, only the strongest reading counts. The five name criteria are five readings of one piece of evidence, not five pieces of it — a row whose name matches exactly also matches normalized, and scoring both would count the same name twice. The same applies to the four healthcare identifiers: supplying both an NPI and an ORCID for one professional is one piece of evidence given twice over.

name_phonetic appears on the trail but contributes nothing to the score. Sounding alike is why a document surfaced, which is worth recording, but it is not evidence of identity — “jayne pryce” and “joana peres” share a phonetic code.

How evidence becomes an outcome#

Candidates are ranked in two tiers, not one ordering. A candidate carrying deterministic evidence — an email, a LinkedIn URL, or a healthcare identifier that is theirs — ranks above every candidate that merely resembles your input, however much circumstantial evidence the latter accumulated. A matching name, employer, and region describe someone who looks like your subject; an identifier on file is your subject.

outcomeWhat happenedWhat to doBilled
matchedResolved to exactly one subject.Store the populated identifier. Read matchedCriteria if you need to show your reviewers why.1 credit
multiple_candidatesSeveral subjects fit about equally well. Up to three come back in candidates, each with its own evidence, and none of them is asserted to be the subject.Choose between them with context we do not have, or add a signal and resubmit — an email, a phone, or an employer. Do not treat the row as unresolvable.Free
no_matchNobody cleared the bar. Different from “several did”.Check the input is a real person and is spelled as a human would write it. Re-sending the same row will return the same answer.Free
suppressedA person matched but is subject to a privacy suppression, so no identifiers are returned.Treat as a permanent decline and record it. Retrying will not change it, and the suppression is the point.Free
failedThe row could not be processed.Safe to resubmit.Free

Two candidates carrying the same deterministic signal come back multiple_candidates on purpose. Two people holding the email you supplied means the data is dirty, which is exactly the moment a confident answer would do the most damage.

The evidence is on the candidates, not on the row. An unresolved row leaves personId, matchedCriteria and confidence empty, because each of those describes a subject and the row has not named one. Read candidates instead: each entry carries its own personId, confidence and matchedCriteria, and comparing those is usually what tells you which one you meant.
A row that fits two people
{
  "rowId": "d8b2a4c3-1e5f-4b7a-8c9d-3f4e5a6b7c8d",
  "rowIndex": 2,
  "metadata": { "crmId": "C-42" },
  "outcome": "multiple_candidates",
  "personId": null,
  "confidence": null,
  "matchedCriteria": [],
  "candidates": [
    {
      "personId": "731209846",
      "confidence": 0.95,
      "matchedCriteria": [
        { "criterion": "name_exact", "evidence": "jay patel", "personId": "731209846", "matchedAt": "2026-08-09T12:00:00Z" },
        { "criterion": "employer_name_current", "evidence": "henry schein", "personId": "731209846", "matchedAt": "2026-08-09T12:00:00Z" }
      ]
    },
    {
      "personId": "900000008",
      "confidence": 0.95,
      "matchedCriteria": [
        { "criterion": "name_exact", "evidence": "jay patel", "personId": "900000008", "matchedAt": "2026-08-09T12:00:00Z" },
        { "criterion": "employer_name_current", "evidence": "henry ford health", "personId": "900000008", "matchedAt": "2026-08-09T12:00:00Z" }
      ]
    }
  ]
}

Both Jay Patels are real, and both work somewhere called “henry”. The employer is what separates them, so a row carrying henry ford health rather than henry would have resolved outright — which is the general shape of the fix: the signal that separates the candidates is the one worth adding.

Reading the match trail#

matchedCriteria is the auditable record of why a row resolved, ordered with the criterion that contributed most confidence first. Each entry carries the criterion that fired, the evidence — the value on our side that it matched, not the value you sent — matchedAt, and the subject it fired on as personId or aggkHcpId.

A resolved row and its trail
{
  "rowId": "b6f0e2a1-9c3d-4f5e-8a7b-1d2c3e4f5a6b",
  "rowIndex": 0,
  "metadata": { "crmId": "A-17" },
  "outcome": "matched",
  "personId": "590654273",
  "socialProfile": "https://www.linkedin.com/in/danielentrup",
  "aggkHcpId": null,
  "confidence": 1.0,
  "scoringVersion": 7,
  "matchedCriteria": [
    { "criterion": "email_on_file",   "evidence": "dan.entrup@example.com", "personId": "590654273", "aggkHcpId": null, "matchedAt": "2026-08-09T12:00:00Z" },
    { "criterion": "name_exact",      "evidence": "dan entrup",             "personId": "590654273", "aggkHcpId": null, "matchedAt": "2026-08-09T12:00:00Z" },
    { "criterion": "location_region", "evidence": "new york",               "personId": "590654273", "aggkHcpId": null, "matchedAt": "2026-08-09T12:00:00Z" }
  ]
}

On a healthcare row the identifier criteria carry the aggkHcpId rather than a personId, because they resolved against the provider dataset. A row that fills both slots carries both, and the trail tells you which evidence named which subject.

Confidence and scoring version#

confidence runs from 0 to 1 and is capped there — a deterministic hit alone reaches 1.0, so a row that supplies only an email still resolves at full confidence. It is a claim about certainty, not a ranking score, and it is not the number candidates are compared on internally.

Compare outcomes only within one scoringVersion. The criteria and their weights are tuned as we measure them against real traffic, and every change bumps this number. The exact weights are deliberately not published: they will move, and scoringVersion is how you tell when they did. If you track match rates over time, group by it — a shift in your rate across a version boundary is us, not your data.

What Deep Match does not do#

  • It does not return a search result. A row that fits several people names at most three of them. It will not hand back a ranked page of everyone who resembles your input — Person Match is the endpoint for that.
  • It has no synchronous variant. Even a one-row batch is submitted and collected asynchronously.
  • It does not accept a job title. Person Match does, and is the right endpoint when you already have a strong identifier and want a single fast answer.