How to Build Explainable Company Domain Matching Workflows
Build an auditable company domain matching workflow that separates lookup evidence from application decisions, tests the 59/60/84/85 boundaries, and calibrates policy from labeled outcomes.
A company domain lookup may return the right domain, yet six months later nobody can explain why the application accepted it, held it for review, or rejected it.
A company domain match is explainable only when a team can answer two questions later:
- What evidence did the lookup return?
- Why did the application allow, hold, or reject the requested action?
The domain and confidence score answer only part of the first question. They do not explain which source record triggered the lookup, whether the result was cached, which action was requested, which policy version ran, or whether a reviewer changed the decision.
This guide builds one decision record around Elvesora Company Domain Lookup. It also shows how to use the current confidence bands at their exact boundaries and how to improve policy from labeled outcomes without pretending that one threshold is correct for every workflow.
Explainability is evidence plus a decision
The Domain Lookup API resolves a company name to a likely official domain and returns evidence such as:
found;company_nameandnormalized_company_name;domain;confidence;is_live;reasonsandlower_reasons;cached;- usage fields.
Your application supplies the rest:
- source system and source record;
- requested action;
- action risk;
- accepted, review, investigate, or hold decision;
- allowed and blocked downstream actions;
- policy version;
- reviewer outcome and notes;
- decision timestamp.
Do not mix these into one undocumented “Elvesora response.” The split is the foundation of an auditable workflow.
Keep returned fields and customer-owned fields separate
Every decision record combines data returned by the lookup with fields owned by the application. Keeping those responsibilities separate makes the workflow explainable.
| Data | Source | Purpose |
|---|---|---|
found, domain, confidence |
Elvesora | Core match evidence |
is_live, cached |
Elvesora | Reachability and freshness context |
reasons, lower_reasons |
Elvesora | Human-readable supporting or lowering evidence |
| Source system and record ID | Customer application | Trace the lookup to the original record |
| Requested action | Customer application | Define what the match is being asked to change |
| Policy decision | Customer application | Allow, review, investigate, or hold the action |
| Allowed and blocked actions | Customer application | Prevent a low-risk acceptance from becoming broad permission |
| Policy version | Customer application | Reconstruct which rules produced the decision |
| Reviewer outcome | Customer application | Calibrate policy from labeled results |
If you convert returned reason text into internal reason codes for reporting, preserve the original reasons and lower_reasons as well. A code such as NAME_ALIGNMENT can be a useful application-defined mapping; it should not be described as an API code unless it is part of the documented contract.
Start with the action, not the threshold
The same match can support one action and remain unsafe for another.
| Requested action | Typical risk | What a match may safely do |
|---|---|---|
| Store a candidate beside the submitted name | Low | Preserve evidence without changing the trusted field. |
| Prepare company enrichment | Medium | Continue after a strong match; hold uncertain results. |
| Suggest an existing account | Medium | Show a possible association for review. |
| Route an inbound account | Medium to high | Require strong evidence plus routing context. |
| Overwrite a canonical company domain | High | Require strict policy and an audit trail. |
| Join or merge accounts | High | Require account context and authorization beyond the score. |
| Change ownership or reporting keys | High | Require both identity evidence and business-rule agreement. |
The same match can support one action and remain unsafe for another. Otherwise, “accepted” can be misread as permission for every downstream workflow.
Use the exact confidence boundaries
Elvesora defines the following confidence bands as operational starting points:
- high: 85–100;
- medium: 60–84;
- low: below 60.
The boundaries should be explicit in code and tests:
| Score | Starting band | Default route | Important qualification |
|---|---|---|---|
| 59 | Low | Investigate | Keep the domain as evidence only, or leave unmatched. |
| 60 | Medium | Review | Do not round it into the high band. |
| 84 | Medium | Review | It remains review even though it is one point below high. |
| 85 | High | Eligible for low-risk acceptance | Check found, live/fresh evidence, lower reasons, source context, and requested action. |
These are starting routes, not measured accuracy guarantees. A team may choose a stricter rule for account merges than for enrichment preparation. It should not silently choose a looser rule without labeled outcome evidence.
Treat a missing confidence value conservatively. Treat a cached is_live value as evidence from the cached lookup, not proof that reachability was checked again for the current request.
A complete decision record
The following fictional record demonstrates the shape. The lookup evidence is separated from the application's policy layer:
{
"decision_id": "domain_decision_01K0Q2VY7B",
"source": {
"system": "crm_import",
"record_id": "account_4831",
"submitted_company_name": "Northstar Technologies",
"requested_action": "prepare_enrichment"
},
"lookup": {
"found": true,
"company_name": "Northstar Technologies",
"normalized_company_name": "northstar technologies",
"domain": "northstar.example",
"confidence": 84,
"is_live": null,
"cached": false,
"reasons": [
"Company name aligns with the website brand"
],
"lower_reasons": [
"More than one plausible company was found"
]
},
"policy": {
"version": "company-domain-policy-2026-07",
"band": "medium",
"decision": "needs_review",
"allowed_actions": [
"store_candidate_domain"
],
"blocked_actions": [
"start_enrichment",
"overwrite_canonical_domain",
"merge_accounts"
]
},
"review": {
"status": "pending",
"reviewer_id": null,
"resolved_at": null,
"outcome": null
}
}
The .example domain and response values are illustrative; this article did not run that lookup.
The record answers:
- what was submitted;
- why the lookup ran;
- which evidence came back;
- which exact band applied;
- what the result may do now;
- what remains blocked;
- which rule version made the decision;
- what a reviewer still needs to resolve.
This separation allows the lookup evidence to remain unchanged while policy evolves over time.
Implement the decision separately
The example below intentionally keeps the lookup evidence immutable and returns a separate application policy object.
export function decideCompanyDomain({
lookup,
requestedAction,
policyVersion,
}) {
const domain =
typeof lookup.domain === 'string' ? lookup.domain.trim() : '';
if (lookup.found !== true || domain === '') {
return {
policy_version: policyVersion,
band: 'no_match',
decision: 'no_match',
allowed_actions: [],
blocked_actions: [requestedAction],
};
}
const confidence =
typeof lookup.confidence === 'number' &&
Number.isFinite(lookup.confidence) &&
lookup.confidence >= 0 &&
lookup.confidence <= 100
? lookup.confidence
: null;
if (confidence === null) {
return {
policy_version: policyVersion,
band: 'unknown',
decision: 'needs_review',
allowed_actions: ['store_candidate_domain'],
blocked_actions:
requestedAction === 'store_candidate_domain'
? []
: [requestedAction],
};
}
const band =
confidence >= 85
? 'high'
: confidence >= 60
? 'medium'
: 'low';
const hasFreshLiveEvidence =
lookup.is_live === true && lookup.cached !== true;
const hasLowerReasons =
Array.isArray(lookup.lower_reasons) &&
lookup.lower_reasons.length > 0;
const isEligibleWithoutReview =
requestedAction === 'store_candidate_domain' ||
requestedAction === 'prepare_enrichment';
if (
band === 'high' &&
hasFreshLiveEvidence &&
!hasLowerReasons &&
isEligibleWithoutReview
) {
return {
policy_version: policyVersion,
band,
decision: 'accepted_for_requested_action',
allowed_actions: [requestedAction],
blocked_actions: [
'overwrite_canonical_domain',
'merge_accounts',
],
};
}
const blockedActions = [
'overwrite_canonical_domain',
'merge_accounts',
];
if (requestedAction !== 'store_candidate_domain') {
blockedActions.unshift(requestedAction);
}
return {
policy_version: policyVersion,
band,
decision: band === 'low' ? 'investigate' : 'needs_review',
allowed_actions: ['store_candidate_domain'],
blocked_actions: blockedActions,
};
}
This policy is intentionally conservative. It is an application example, not an Elvesora guarantee and not a substitute for authorization checks around account membership or record ownership.
Keep lifecycle states explicit
Do not collapse the application decision into pass or fail. Keep states such as:
accepted, when the evidence is approved for the named action;needs_review, when the candidate remains plausible but the action is held;rejected, when policy or a reviewer rejects the candidate for that action;no_match, when the lookup returned no reliable domain;superseded, when a later evaluation or reviewer correction replaces the operational decision.
These are application workflow states, not additional Domain Lookup response fields. Preserve the evidence and policy version behind every state so a later reviewer can distinguish a rejected candidate from an unavailable lookup or a decision that was replaced.
Make the review screen answer the decision
A review screen should show information in the same order the operator needs it:
- requested action and business impact;
- submitted and normalized company names;
- selected domain, confidence band, live status, and cached status;
- returned reasons and lower reasons;
- existing trusted domain or account candidates;
- actions that are currently allowed and blocked;
- the exact effect of accept, reject, or defer.
Useful reviewer actions are narrow:
- accept for the requested action;
- store candidate only;
- correct the domain;
- reject the match;
- request more company context;
- defer without changing the trusted record.
Avoid a generic “approve” button when it is unclear whether approval starts enrichment, overwrites a field, joins an account, or merges records.
Preserve reviewer outcomes for calibration
Confidence policy should improve from labeled outcomes, not from intuition alone.
1. Choose one action and one source
Do not mix CRM merges, signup routing, and enrichment preparation into the same calibration set. Start with a bounded question such as:
For imported CRM accounts requesting enrichment preparation, which matches can proceed without review?
Record the source, requested action, policy version, and review date.
2. Build a labeled review set
For each decision, preserve:
- submitted company name and useful company-level context;
- returned domain and full lookup evidence;
- automatic band and decision;
- reviewer outcome: correct, incorrect, unresolved, or corrected;
- corrected domain when appropriate;
- reason for the review outcome.
Do not label an unresolved record as incorrect merely because the reviewer could not decide.
3. Measure the decision, not “accuracy” in the abstract
Useful action-specific measures include:
false-accept rate =
incorrect automatically accepted decisions
/ all automatically accepted decisions
review correction rate =
reviewed decisions that were corrected or rejected
/ all resolved reviewed decisions
unresolved rate =
unresolved reviewed decisions
/ all reviewed decisions
Keep the denominator, observation window, source, action, and policy version with every result. A value without those details cannot justify a threshold change.
4. Inspect the boundaries
Review outcomes around 59/60 and 84/85 separately. Ask:
- Do score-85 results for this source and action survive review?
- Are many score-84 results accepted unchanged?
- Are lower reasons concentrated in false accepts?
- Does
cachedcorrelate with corrections for workflows that require current reachability? - Does one source produce much more ambiguity than another?
The goal is not to manipulate the Elvesora score. Instead, decide which application actions each evidence pattern may support.
5. Change one policy variable at a time
If the evidence supports a change, version the policy and record:
- previous and new rule;
- affected source and action;
- effective date;
- reviewer and approval;
- expected trade-off;
- rollback condition.
Then compare equal, clearly dated windows. An uncontrolled improvement after several simultaneous workflow changes is an association, not proof that the threshold caused it.
Use source-specific policy without creating hidden rules
Different sources carry different context. Signup forms, CSV imports, CRM cleanup jobs, and webhooks rarely require the same policy.
| Source | Useful application context | Safer starting policy |
|---|---|---|
| Signup form | Email domain, country, invitation, source form | Use lookup for account search; require membership proof for an existing-account join. |
| CSV import | Batch ID, row ID, source quality | Stage results and review uncertain clusters before writes. |
| Webhook | Provider event ID, organization ID | Make processing idempotent and review company-name changes. |
| CRM cleanup | Existing trusted domain, owner, customer state | Require strict gates before canonical updates or merges. |
| Manual review | Reviewer and correction | Store the outcome for later policy evaluation. |
The decision record should show which source policy ran. A hidden source exception is difficult to review and impossible to calibrate reliably.
Version the policy
A short identifier is enough:
company-domain-policy-2026-07
Store it on every decision. When rules change, do not silently rewrite the historical decision. Create a new evaluation or retain both the original and current decision so operators can reconstruct what happened. Mark the earlier evaluation as superseded when a later evaluation replaces it for operational use, while preserving both records.
Policy versioning is particularly important when:
- the auto-accept boundary changes;
- a source receives a stricter or looser rule;
is_liveor lower reasons become required;- the set of allowed actions changes;
- reviewer corrections alter internal mappings.
Failure states belong in the audit trail too
An explainable workflow does not begin only after a domain is returned.
Record:
configuration_errorfor authentication failures;invalid_inputfor request validation problems;rate_limitedfor allowance or rate-limit responses;retry_laterfor temporary failures and timeouts;no_matchfor a successful lookup with no reliable domain;matchedplus the application decision for returned domains.
A transport failure is not a low-confidence company. A no-match result is not an infrastructure outage. Keeping those states distinct makes reporting more accurate and recovery workflows easier to automate.
Launch checklist
- Preserve the original source value.
- Store the documented lookup evidence without renaming it into undocumented API fields.
- Record the source, requested action, and policy version.
- Test scores 59, 60, 84, and 85.
- Treat missing confidence conservatively.
- Check
cachedbefore treatingis_liveas fresh. - Keep low-risk acceptance separate from merges, ownership changes, and account joins.
- Give reviewers narrow, explicit actions.
- Preserve corrected, rejected, and unresolved outcomes.
- Define denominators and windows before reporting policy performance.
- Re-run review after material policy changes.
An explainable workflow is not defined by a single confidence threshold. It comes from preserving evidence, recording application decisions, and making every automated action traceable later.