VIN forms look simple: seventeen characters in, structured fields out. The hard part is what you show when the input is wrong, partial, or only half-supported by the API.
Three failure classes
Invalid: fails format or check digit. Do not call the network yet. Explain what failed (length, illegal character, check digit).
Incomplete: user is still typing. Show progress (13/17) without scary errors.
Ambiguous / sparse: API returns 200 with many blank fields. That is not a crash. Say what you know and what you do not.
Soft vs hard fail
Hard fail: block submit for impossible VINs.
Soft fail: allow decode attempts that may return sparse data, but label confidence.
type VinGate =
| { ok: true; vin: string }
| { ok: false; reason: "length" | "charset" | "checkDigit" };
function gateVin(raw: string): VinGate {
const vin = raw.trim().toUpperCase();
if (vin.length !== 17) return { ok: false, reason: "length" };
if (/[IOQ]/.test(vin)) return { ok: false, reason: "charset" };
// check digit omitted for brevity
return { ok: true, vin };
}
Copy that helps buyers
Avoid "Error 500" language. Prefer:
- "This VIN fails the check digit. Re-type from the dash plate."
- "We decoded make and model year. Trim and engine came back blank from NHTSA."
- "A free decode is not a title or salvage report."
Marketplace embed pattern
If you embed VIN checks in listings, never mark a listing "verified" on decode alone. Use decode as a consistency check against the seller's claimed year/make/model.
I maintain VIN Lookup, a free VIN decode based on NHTSA data.
Top comments (0)