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 Rover vs LAND ROVER, Mercedes-Benz vs Mercedes 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.