Evalgent
Back to Blog
Voice AI Testing

Dealership Service AI Scheduling: How to QA VIN Lookups

Deepesh Jayal
15 min read
Dealership Service AI Scheduling: How to QA VIN Lookups
On this page

A caller says "it's the Silverado, not the Equinox." Your agent has two vehicles under that phone number. Which record gets the appointment? This post is for product and engineering teams at fixed ops software companies who ship dealership service AI scheduling to many rooftops. It covers how vehicle capture fails, the rules and research, runnable Python and a copyable test matrix.

17
characters in every VIN, with I, O and Q never used (49 CFR 565.13)
15.2%
letter error rate on telephone alphadigit strings, vs 4.7% for digits (Hamaker et al.)
6.2%
of spoken alphanumeric IDs misread by human readers, two thirds unnoticed (SNuC, LREC 2022)
5
confusable letter pairs the VIN check digit cannot detect (computed from 49 CFR 565.15)

Why dealership service AI scheduling starts with the vehicle

A service call is not a calendar problem. It is a record-resolution problem with a calendar at the end. The agent captures an identifier, resolves it to a vehicle, checks open campaigns, prices the job and writes a repair order or appointment back to the dealer management system (DMS). Every later step trusts the vehicle the first step picked.

That makes the vehicle the single point of silent failure. A wrong time slot gets noticed when the customer arrives. A wrong vehicle often does not. The caller hears a confident confirmation, and the error surfaces days later in parts or the service lane.

Here is what one wrong VIN corrupts downstream:

Downstream stepWhat the wrong vehicle doesWhen it surfaces
Recall and campaign checkReports "no open recall" on a car that has one, or the reverseAt write-up, or never
Service menu and priceQuotes the wrong engine's oil capacity, filter or labor timeAt the counter, as a price dispute
Parts pre-pickPulls or orders parts for a different trim or engineWhen the tech opens the box
Appointment recordAttaches the visit to another customer's vehicle historyAt warranty claim or audit

The broader version of this checklist for independent shops lives in the auto repair AI receptionist QA guide. This post goes deep on the one step dealers care about most: getting the vehicle right.

Dealership service call pipeline from caller speech to DMS write-back, where one misheard VIN letter at vehicle resolution fans out into four corrupted downstream records

Four ways a caller identifies a vehicle

Most agents accept more than one input path. Each path fails differently, so each needs its own tests.

Input pathWhat the caller saysMain failure modeWhat to confirm
Full VIN17 characters, often read off a card or windshieldLetter confusions, skipped or doubled charactersYear, make, model from the decode
Last 8 of VINPositions 10 to 17Matches more than one vehicle across the rooftop groupModel and owner name
License plate and state"Texas, plate ..."State omitted, plate letters misheard, plate transferredYear, make, model
Phone number lookupNothing; caller ID does the workSeveral vehicles on file, wrong one pickedWhich vehicle, by model and year

The phone-number path looks safest because the caller says nothing hard. It hides wrong-vehicle bookings well. A household with a Silverado and an Equinox has two vehicles on one number. If the agent picks the most recent vehicle, it books the Equinox when the caller said "the truck." Test this path with callers who name the vehicle loosely: "the truck," "my wife's car," "the one we bought last spring."

The last-8 path is a middle ground. Positions 10 to 17 hold the model year, plant code and serial number. Within one rooftop they usually identify one vehicle. Across a dealer group sharing a database, collisions get likelier, so test at least one.

What the VIN rules give you for free

The VIN format is a federal standard, and it is your best validation tool.

VIN check digit: the ninth character of every VIN, computed from the other 16 using fixed letter values and position weights. It exists so a transcription error can be caught without a database lookup.

The rules come from 49 CFR 565.13 and 49 CFR 565.15:

  • Every VIN has 17 characters. Letters come only from ABCDEFGHJKLMNPRSTUVWXYZ, so I, O and Q never appear.
  • Position 9 is the check digit, 0 to 9 or X.
  • Position 10 is the model year code. The year table never uses U, Z or 0.
  • For passenger cars, and for multipurpose vehicles and trucks of 10,000 lb GVWR or less, the last five characters are numeric.

The check digit uses transliteration. Each letter maps to a number: A=1, B=2, C=3 and so on, restarting at J=1 and S=2. Multiply each value by its position weight (8, 7, 6, 5, 4, 3, 2, 10, skip, 9, 8, 7, 6, 5, 4, 3, 2). Sum, divide by 11, and take the remainder. A remainder of 10 becomes X.

These rules are free filters. "I," "O" and "Q" in an ASR transcript are always wrong, so map them to 1, 0 and 0. A letter in positions 13 to 17 of a car VIN is always wrong. A U in position 10 is always wrong.

The blind spot: letters that share a value

Here is the non-obvious part. Because 11 is prime and every weight is between 2 and 10, the check digit catches any single substitution between characters with different values. It cannot catch a swap between characters with the same value.

Several of those same-value pairs sound alike on the phone. C and T both equal 3. E and V both equal 5. G and P both equal 7. A and J both equal 1. H and 8 both equal 8. We ran every pair in the confusion groups used below. Five of 49 confusable pairs pass the check digit untouched.

So a check digit that passes does not mean the VIN is right. An E heard as V produces a perfectly valid, wrong VIN. That is why the DMS match and the spoken year-make-model readback are not optional.

Why spoken VINs break: what the research says

Spelled-letter recognition is an old speech research problem. The findings still describe your phone line.

The E-set. Mark Fanty and Ron Cole studied the "E-set": B, C, D, E, G, P, T, V and Z. These letters share one vowel. B and D differ only in the first 10 to 20 milliseconds, per ISIP's alphadigit notes. In their ICSLP 1990 paper, a dedicated E-set classifier cut E-set errors by 20% against their baseline. The letters needed special handling even in clean audio.

Clean versus telephone audio. Their NIPS 1990 system reached 96% accuracy on isolated letters from 30 test speakers. A year later, Fanty, Cole and Roginski moved to telephone speech. Speaker-independent letter classification dropped to 89%. Matching letters against a 50,000-name database recovered the correct name 91% of the time. That is the core lesson for VIN capture: constrain the search space with records you already have.

Letters versus digits on the phone. Mississippi State's ISIP group (Hamaker, Ganapathiraju, Picone and Godfrey) studied the OGI Alphadigit corpus. It holds telephone recordings from over 3,000 speakers reading six-character strings. In their alphadigit paper, the crossword triphone system had 15.2% word error on letters and 4.7% on digits. The paper reports confusion rates within each set. The E-set was worst for triphones at 17.7%. The S/F pair came next at 15.0%. The authors note S and F differ mostly in high frequencies that telephone bandwidth removes.

Humans misread identifiers too. The SNuC corpus from Sheffield (LREC 2022) recorded over 13,000 spoken alphanumeric identifiers. Readers got 93.8% right. Of the 6.2% they got wrong, about two thirds went unnoticed by the speaker. Common slips included "1" for "I" and swapped pairs like "KF" for "FK." The baseline ASR got 77% of identifiers fully right on partner data. Adapting it with SNuC data raised that to 91.7%.

What this means for your agent:

  • Your caller is a noisy channel before ASR even starts. They misread the card.
  • Digits are far safer than letters. Prefer paths that lean on digits, such as the last 8.
  • A per-character error compounds across 17 characters. At 99% per-character accuracy, a full VIN is right only 0.99^17 = 84.3% of the time. At 99.9%, it is 98.3%.
  • Closed-set matching beats open transcription. Fanty's name lookup and SNuC's adaptation both show it.

For how modern STT models handle names and codes, see our guide on evaluating STT for names and entities. For the codec side of the story, see voice call quality metrics.

Spoken letter confusion sets on phone audio with each letter's VIN transliteration value, highlighting five same-value pairs the check digit cannot detect

The code: a VIN check digit validator with confusion-aware candidates

This script is a starting point for your VIN lookup voice agent. It validates a heard VIN, generates every valid VIN one confusable letter away, and matches the results against vehicles on file. The confusion groups follow the research above. Tune them against your own STT's confusion matrix.

# Simplified: VIN check-digit validator + confusion-aware candidate generator.
# Rules from 49 CFR 565.13 and 565.15 (Tables III, IV, V, VII).
VALUES = {**{str(d): d for d in range(10)},
    "A":1,"B":2,"C":3,"D":4,"E":5,"F":6,"G":7,"H":8,
    "J":1,"K":2,"L":3,"M":4,"N":5,"P":7,"R":9,
    "S":2,"T":3,"U":4,"V":5,"W":6,"X":7,"Y":8,"Z":9}
WEIGHTS = [8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2]
YEAR_CODES = set("ABCDEFGHJKLMNPRSTVWXY123456789")  # no I,O,Q,U,Z,0

# Confusion groups for phone audio: E-set (Fanty & Cole), A-set, nasals, S/F.
GROUPS = ["BCDEGPTVZ", "AHJK8", "MN", "SF", "59"]
CONFUSIONS = {}
for g in GROUPS:
    for c in g:
        CONFUSIONS.setdefault(c, set()).update(set(g) - {c})

def check_digit(vin: str) -> str:
    total = sum(VALUES[c] * w for c, w in zip(vin, WEIGHTS))
    return "X" if total % 11 == 10 else str(total % 11)

def problems(vin: str) -> list[str]:
    if len(vin) != 17:
        return [f"length {len(vin)}, expected 17"]
    if any(c not in VALUES for c in vin):
        return ["contains I, O or Q"]
    errs = []
    if vin[9] not in YEAR_CODES:
        errs.append("position 10 is not a model-year code")
    if not vin[12:].isdigit():
        errs.append("positions 13-17 must be numeric (car/light truck)")
    if vin[8] != check_digit(vin):
        errs.append(f"check digit {vin[8]}, computed {check_digit(vin)}")
    return errs

def normalize(heard: str) -> str:
    # "Oh" means zero; I and Q are never legal, so map them to digits.
    return heard.upper().replace(" ", "").translate(str.maketrans("OQI", "001"))

def candidates(heard: str) -> list[tuple[str, int | None]]:
    """Valid VINs within one confusable substitution of what was heard."""
    base = normalize(heard)
    found = [(base, None)] if not problems(base) else []
    for i, c in enumerate(base):
        for alt in sorted(CONFUSIONS.get(c, ())):
            cand = base[:i] + alt + base[i + 1:]
            if not problems(cand):
                found.append((cand, i + 1))
    return found

def triage(heard: str, on_file: set[str]) -> tuple[str, str]:
    cands = candidates(heard)
    in_dms = [v for v, _ in cands if v in on_file]
    if len(in_dms) == 1:
        return "ACCEPT", f"matches DMS record ...{in_dms[0][-6:]}; read back last 6"
    if not cands:
        return "REREAD", "no valid VIN within one confusable edit"
    if len(cands) == 1 and cands[0][1] is None:
        return "CONFIRM", "valid but not on file; confirm year/make/model"
    pos = sorted({p for _, p in cands if p})
    return "CONFIRM", f"{len(cands)} valid readings; spell positions {pos}"

def with_check_digit(vin16: str) -> str:
    v = vin16[:8] + "0" + vin16[8:]
    return v[:8] + check_digit(v) + v[9:]

if __name__ == "__main__":
    true_vin = with_check_digit("1GCUYDEDRZ103456")  # synthetic, not a real truck
    on_file = {true_vin, with_check_digit("3GNAXUEVPL204417")}  # synthetic
    print("true VIN:", true_vin, "| check digit:", true_vin[8])
    tests = {
        "exact": true_vin,
        "caller says 'oh'": true_vin.replace("0", "O"),
        "D heard as B (pos 6)": true_vin[:5] + "B" + true_vin[6:],
        "E heard as V (pos 7)": true_vin[:6] + "V" + true_vin[7:],
        "two errors": true_vin[:5] + "B" + true_vin[6:10] + "C" + true_vin[11:],
    }
    for label, heard in tests.items():
        status, why = triage(heard, on_file)
        ok = "pass" if not problems(normalize(heard)) else "fail"
        print(f"{label:22s} {heard} checksum={ok}  {status:7s} {why}")
    # New customer: nothing on file, so the DMS cannot break the tie.
    heard = true_vin[:6] + "V" + true_vin[7:]
    ok = "pass" if not problems(heard) else "fail"
    print(f"{'E as V, not on file':22s} {heard} checksum={ok}  ", *triage(heard, set()))
    blind = sorted({"/".join(sorted((a, b))) for a in CONFUSIONS
                    for b in CONFUSIONS[a] if VALUES[a] == VALUES[b]})
    print("confusable pairs the check digit cannot see:", blind)

Output from a real run (Python 3.13, synthetic VINs):

true VIN: 1GCUYDED0RZ103456 | check digit: 0
exact                  1GCUYDED0RZ103456 checksum=pass  ACCEPT  matches DMS record ...103456; read back last 6
caller says 'oh'       1GCUYDEDORZ1O3456 checksum=pass  ACCEPT  matches DMS record ...103456; read back last 6
D heard as B (pos 6)   1GCUYBED0RZ103456 checksum=fail  ACCEPT  matches DMS record ...103456; read back last 6
E heard as V (pos 7)   1GCUYDVD0RZ103456 checksum=pass  ACCEPT  matches DMS record ...103456; read back last 6
two errors             1GCUYBED0RC103456 checksum=fail  CONFIRM 5 valid readings; spell positions [6, 8, 11]
E as V, not on file    1GCUYDVD0RZ103456 checksum=pass   CONFIRM 4 valid readings; spell positions [2, 3, 7]
confusable pairs the check digit cannot see: ['8/H', 'A/J', 'C/T', 'E/V', 'G/P']

Read the last two lines closely. The E-as-V VIN passes the check digit. With the vehicle on file, the DMS match rescues it. For a new customer, the code still flags four valid readings, so the agent spells positions 2, 3 and 7 back. A naive validator would accept it.

The script also verifies against a known reference: `check_digit("1M8GDM9AXKP042788")` returns X, the expected value. Production code should also check positions 1 to 3 against known manufacturer codes. It should decode the VIN through NHTSA's vPIC API (`/vehicles/DecodeVinValues/{VIN}?format=json&modelyear={year}`) so the agent can say the model back.

Readback design: spelling alphabets, chunks and partial readbacks

VIN speech recognition improves when the agent shapes how the caller speaks. A dealership BDC AI agent should make four design choices.

Ask for chunks. Request the VIN in groups, such as 3, 5, 1 and 8 characters. Confirm each chunk. A missed character then costs one chunk, not the whole VIN.

Offer a spelling alphabet, and parse it. Callers say "B as in boy," "D like David" or "Bravo." Parse all three forms, including mixes mid-VIN.

Handle the spoken forms of digits. SNuC documents "oh" for zero, "double nine" for 99, and "sixty-four" for 64. "Eight" next to "A" is a classic trap. "Forty eight" can mean 48 or 408, separated only by prosody.

Read back what matters, not all 17. Reading back 17 characters invites a tired "yes." Instead, read back the decoded vehicle: "a 2024 pickup from Chevrolet, ending in 103456?" Callers catch a wrong model instantly. They rarely catch a wrong letter.

There is one trap in partial readbacks. Reading back only the last six digits skips positions 1 to 11. That is where all the letters live. Pair a last-six readback with year, make and model, never alone.

Recall checks: what the NHTSA data can and cannot tell you

Recall appointment AI has one extra job: flag open campaigns and book the remedy. A wrong VIN does visible damage here.

As of October 2026, NHTSA's documented public Recalls API takes make, model and model year (`/recalls/recallsByVehicle?make=...&model=...&modelYear=...`). It also takes a campaign number (`/recalls/campaignNumber?campaignNumber=...`). It does not document a VIN parameter. A make-model-year list says which recalls affect that model. It does not say whether this specific car has been repaired.

VIN-level status comes from elsewhere. Under 49 CFR 573.15, manufacturers producing 25,000 or more light vehicles a year must offer a free VIN lookup. It must be updated at least every seven days. NHTSA's own recall lookup page searches by VIN or license plate. It lists exclusions: recalls over 15 years old, recalls from small manufacturers, and "customer service and other non-safety campaigns." Your integration may read open campaigns from OEM or DMS data instead. Whatever the source, test it per VIN.

Three tests follow from this:

1. No MMY overclaiming. Never say "your vehicle has a recall" from a make-model-year list. Say the model has one and the VIN will be checked.

2. Remedy not available. Under 573.15, results must show a vehicle covered by a recall whose remedy is not yet available. Test that the agent does not book a repair for a remedy without parts.

3. Safety recall versus service campaign. A customer-satisfaction campaign is not a safety recall. Test that the agent uses the right words for each.

Scheduling after the vehicle is right

Once the vehicle resolves, rolling out AI service scheduling dealership by dealership becomes a configuration problem. Each rooftop has its own menu, advisors and transport options. Vendors describe the same DMS-connected pattern. Kenect says its Voice AI schedules service appointments "integrated directly with dealership management systems" (Business Wire, September 2025). GoTo announced booking through a "direct integration with Xtime" (Business Wire, October 2025).

In dealership service AI scheduling, test four booking fields against the resolved vehicle:

  • Service type. The menu item must exist for this engine and model year.
  • Service advisor. Rooftops route by brand, fleet account or express lane. Check the rule fired.
  • Loaner or shuttle. Eligibility depends on job, warranty or customer tier. Never promise a loaner the agent cannot reserve.
  • Drop-off time. A waiter slot at 4:30 for a four-hour job is a failure.

Then pull the created appointment or repair order from the DMS and compare every field to the call. Our guide to tool call accuracy covers argument-level checks. The appointment scheduling testing guide covers date and slot logic.

The VIN capture test matrix

This is the framework. Cross three dimensions, then add the downstream checks to every cell.

Dimension 1, input path: full VIN, last 8, plate and state, phone lookup with one vehicle, phone lookup with several vehicles.

Dimension 2, audio condition: quiet handset, speakerphone in a car, shop floor noise, low-bandwidth cellular, accented speech. Our guides on background noise testing and accent robustness give setups for each.

Dimension 3, confusion set: VINs rich in E-set letters, A-set letters, M/N, S/F, same-value pairs (C/T, E/V, G/P, A/J, H/8), and digit-heavy VINs as a control.

Cell typeExamplePass rule
Full VIN, quiet, E-set heavyVIN with B, D, E, P, V in positions 4 to 8Correct vehicle, or a confirm prompt naming the right positions
Full VIN, noisy, same-value pairE and V swapped, new customerAgent spells back, never books on a check-digit pass alone
Last 8, dealer groupTwo vehicles share positions 10 to 17Agent asks for model or owner name
Plate, no stateCaller gives plate onlyAgent asks for state before lookup
Phone, two vehicles"The truck, not my wife's car"Correct vehicle by model, read back
Caller misreads cardOne character wrong at the sourceAgent catches via DMS mismatch or decode readback

Score every cell on four outcomes: vehicle, recall status, menu item and write-back. The right slot on the wrong vehicle fails all four.

VIN capture test matrix with five input paths as rows and five audio conditions as columns, each cell scored on vehicle, recall, menu and write-back

How to test VIN capture before every rollout

Run this before any prompt change, STT swap or new rooftop in your dealership service AI scheduling rollout.

1. Build a VIN fixture set. Generate synthetic VINs with valid check digits, weighted toward E-set letters and same-value pairs. Load matching vehicle records into a sandbox DMS, including households with several vehicles.

2. Script caller behaviors. Include chunked reading, NATO words, "as in" spellings, "oh," "double," self-corrections and source misreads.

3. Run every matrix cell over the phone. Text-only tests miss the failure entirely, because the letters are lost in the audio. Evalgent's simulator calls your agent over PSTN, SIP or WebRTC with caller profiles that vary accent, pace, interruptions and background noise.

4. Judge from the audio and the record. Compare the spoken readback, the tool-call arguments and the written appointment against the fixture. Evalgent evaluates from the call audio, not only the transcript, and you can add a custom metric such as "vehicle on appointment equals vehicle in fixture."

5. Size the run honestly. If you want 95% confidence that wrong-vehicle bookings stay under 1%, you need about 300 clean calls with zero failures (the rule of three: 3/n). For under 0.5%, you need about 600.

6. Gate the release. Block rollout if any cell books a wrong vehicle without a confirm prompt. Rerun the full set after every model or prompt update; see regression testing for model updates.

7. Monitor production. Sample live calls per rooftop. Flag appointments where the decoded model differs from what the caller said. Evalgent's production monitoring takes pushed call recordings and flags tool-call failures; our production monitoring guide covers the rest.

Fold these checks into the per-rooftop go-live checklist, because each new dealership brings a new DMS configuration.

Frequently asked questions

How does an AI agent look up a VIN over the phone?

An AI agent looks up a VIN over the phone by transcribing the spoken characters, normalizing illegal letters, and validating the check digit. It then matches candidates against vehicles in the DMS and decodes year, make and model through a VIN decoder. Finally, it reads the decoded vehicle back to the caller before checking recalls or booking service.

Why does speech recognition confuse B and D?

Speech recognition confuses B and D because both letter names end in the same "ee" vowel. They differ only in a brief consonant burst at the start, which phone audio blurs. Researchers call B, C, D, E, G, P, T, V and Z the "E-set." In OGI Alphadigit telephone tests, it was the largest confusion group for triphone models.

What is a VIN check digit?

A VIN check digit is the ninth character of a 17-character VIN, defined in 49 CFR 565.15. Each other character gets a numeric value and a position weight. The weighted sum is divided by 11, and the remainder becomes the digit, with 10 written as X. It catches most single-character errors, but not swaps between same-value letters like E and V.

Can the NHTSA API check recalls by VIN?

NHTSA's documented public Recalls API checks recalls by make, model and model year, or by campaign number, as of October 2026. It does not document a VIN parameter. VIN-level open recall status comes from NHTSA's recall lookup website, manufacturer VIN tools required under 49 CFR 573.15, or OEM data reached through the dealer's systems.

Should a voice agent ask for the full VIN or the last 8?

A voice agent should ask for the last 8 when the customer is already in the DMS, because positions 10 to 17 are mostly digits and recognize more reliably. It should ask for the full VIN for new customers or when the last 8 matches several vehicles. Either way, confirm by reading back the decoded year, make and model.

How do you confirm a VIN on a phone call?

Confirm a VIN on a phone call by reading back the decoded vehicle, such as model year, make and model, plus the last six characters. Callers spot a wrong model far more easily than a wrong letter. When several valid readings exist, spell only the ambiguous positions back using a spelling alphabet.

What happens if an AI books service on the wrong vehicle?

When an AI books service on the wrong vehicle, the appointment carries the wrong recall status, service menu, price and parts list. It may attach the visit to another customer's vehicle history. The caller usually hears a correct-sounding confirmation, so the error surfaces later at the counter, in parts, or during a warranty claim.

How do you test a dealership service AI before rollout?

Test a dealership service AI before rollout with a matrix of input paths, audio conditions and letter-confusion sets. Use synthetic VINs and a sandbox DMS. Place real phone calls, then score each call on correct vehicle, recall status, menu item and written record. Block release if any call books the wrong vehicle without a confirmation prompt.

The bottom line

Dealership service AI scheduling is only as accurate as the vehicle it resolves first. Validate the VIN, match it against records on file, read back the decoded vehicle, and test every input path over real phone audio before each rollout.

Related Articles