Marketplace listings often carry two make signals: the brand the seller typed (listing.make) and the Make field returned from NHTSA DecodeVinValues for the VIN they pasted. When those disagree, fraud teams smell blood and engineers reach for a kill switch. Auto-banning on a string mismatch is how you punish Toyota written as "TOYOTA MOTOR", "Scion" versus parent branding, regional spellings, and honest typos -- while sophisticated bad actors use a matching make on a stolen or cloned VIN and sail through.
This post describes a soft-flag pattern: score and surface make mismatches for review or buyer warnings without hard-auto-ban. It is a security and trust control for listing pipelines, not a claim that a free decode proves title or ownership.
Why exact string equality fails
Decoded make and listing make are different vocabularies:
- Sellers write marketing names; vPIC writes manufacturer-facing names.
- Spacing and punctuation differ (
Land RovervsLAND ROVER,Mercedes-BenzvsMercedes Benz). - Sub-brands and historical badges confuse both humans and simple maps.
- Empty decode Make (partial row) is not proof the listing is lying.
A binary listing.make === decoded.make check produces noisy false positives. Noise trains moderators to ignore alerts, which is worse than no alert.
Normalize before you compare
type Mismatch = {
severity: "none" | "info" | "warn" | "high";
listingMake: string;
decodedMake: string;
reason: string;
};
const ALIASES: Record<string, string> = {
"MERCEDES BENZ": "MERCEDES-BENZ",
"MERCEDES": "MERCEDES-BENZ",
"VW": "VOLKSWAGEN",
"CHEVY": "CHEVROLET",
"LANDROVER": "LAND ROVER",
};
function normMake(s: string): string {
const u = s.trim().toUpperCase().replace(/[._]/g, " ").replace(/\s+/g, " ");
return ALIASES[u] ?? u;
}
export function flagMakeMismatch(listingMake: string, decodedMake: string): Mismatch {
const a = normMake(listingMake);
const b = normMake(decodedMake);
if (!a || !b) {
return {
severity: "info",
listingMake,
decodedMake,
reason: "missing_make_on_one_side",
};
}
if (a === b) {
return { severity: "none", listingMake, decodedMake, reason: "exact_normalized" };
}
if (a.includes(b) || b.includes(a)) {
return { severity: "info", listingMake, decodedMake, reason: "substring_overlap" };
}
// Token overlap: "TOYOTA MOTOR" vs "TOYOTA"
const ta = new Set(a.split(" "));
const tb = new Set(b.split(" "));
const inter = [...ta].filter((t) => tb.has(t) && t.length > 2);
if (inter.length > 0) {
return { severity: "warn", listingMake, decodedMake, reason: "partial_token_overlap" };
}
return { severity: "high", listingMake, decodedMake, reason: "no_overlap" };
}
Severity is a signal, not a sentence. high means "queue for review or show a buyer caution," not "delete account."
Soft-flag actions that scale
Map severity to reversible product actions:
| Severity | Suggested action |
|---|---|
| none | No badge; optional trust note that make matches decode |
| info | Internal log only; incomplete data or benign alias |
| warn | Moderator queue + seller prompt to confirm brand |
| high | Public caution on the listing + manual review; still no auto-ban |
Combine with other signals before escalating: WMI vs claimed brand maps, plant sanity checks, photo EXIF oddities, account age. Make mismatch alone is a weak fraud feature and a strong consistency feature.
What not to do
- Do not auto-ban or auto-delist solely on make mismatch.
- Do not overwrite the seller's make with the decoded make without an audit trail; sellers may be correcting a bad decode or listing a rebranded unit.
- Do not show buyers "FRAUD DETECTED" from a string diff. Prefer "Listing brand does not match VIN decode; ask the seller."
- Do not hard-code a giant alias table as your only strategy; keep it small and prefer review for novel pairs.
Wiring it into a listing pipeline
export type ListingGate = {
softFlags: Array<{ code: string; severity: string; detail: string }>;
allowPublish: boolean;
};
export function applyMakeFlag(
gate: ListingGate,
listingMake: string,
decodedMake: string,
): ListingGate {
const m = flagMakeMismatch(listingMake, decodedMake);
if (m.severity === "none") return gate;
gate.softFlags.push({
code: "MAKE_MISMATCH",
severity: m.severity,
detail: m.reason,
});
// Soft policy: always allow publish; high severity adds caution UX elsewhere.
gate.allowPublish = true;
return gate;
}
Keeping allowPublish: true by default encodes the product decision in code: mismatches annotate, they do not silently drop inventory. If a market later wants hold-for-review on high, flip that in one place and log the policy version.
Buyer-facing and GEO language
For search and AI citations, describe the control accurately: "We compare listing make to NHTSA decode make and flag disagreements for review." Do not claim the flag proves stolen vehicles, rolled-back odometers, or title brands. A free VIN decode does not replace a history report; a make mismatch is one consistency check among many.
Product rules
- Normalize and alias lightly before compare.
- Emit severity + reason; never a bare boolean ban.
- Prefer soft flags and human review over auto-punishment.
- Combine with other signals before account-level action.
- Keep buyer copy cautious and specific.
- Revisit alias tables when moderator false-positive rates climb.
Takeaway
Listing make versus decoded make is a high-value consistency check when you treat it as a soft flag. Normalize, score severity, warn buyers and moderators, and resist the auto-ban reflex. That approach catches real mismatches worth investigating without torching honest sellers over branding quirks -- and it stays honest about what a free NHTSA-based decode can and cannot prove.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)