
Simplifies Vehicle Identification Number tasks with validation, information extraction, and NHTSA database integration for vehicle details like type, make, and model. Offers extension functions for streamlined API usage.
A pure Kotlin Multiplatform library for VIN (Vehicle Identification Number) decoding, validation, and NHTSA API integration.
Targets: Android · iOS (arm64, x64, simulatorArm64) · JVM · Linux x64
For production-grade VIN processing, complement this library with your own business logic or country-specific databases. VIN standards vary by region and manufacturer.
VinInfo implements AutoCloseable; use .use {} or withVinInfo {}
// commonMain
implementation("io.github.kabirnayeem99:kvininfo:2.0.0")dependencies {
implementation("io.github.kabirnayeem99:kvininfo:2.0.0")
}dependencies {
implementation "io.github.kabirnayeem99:kvininfo:2.0.0"
}val result = VinInfo.fromNumber("WBA3A5G59DNP26082")
result.onSuccess { vin ->
println(vin.vinNumber) // WBA3A5G59DNP26082
println(vin.wmi) // WBA
println(vin.vds) // 3A5G59
println(vin.vis) // DNP26082
println(vin.year) // 2013
println(vin.region) // Europe
println(vin.country) // Germany
println(vin.manufacturer) // BMW AG
println(vin.assemblyPlant) // N
println(vin.serialNumber) // P26082
println(vin.isValid) // true
}
result.onFailure { e ->
println("Invalid VIN: ${e.message}")
}"WBA3A5G59DNP26082".withVinInfo {
println(year) // 2013
println(region) // Europe
println(manufacturer) // BMW AG
}
// VinInfo is automatically closed after the blockVinInfo.fromNumber("WBA3A5G59DNP26082").getOrThrow().use { vin ->
println(vin.year)
println(vin.manufacturer)
}VinInfo.fromNumber("1HGCM82633A004352").getOrThrow().use { vin ->
println(vin.getMakeFromNhtsa()) // HONDA
println(vin.getModelFromNhtsa()) // Accord
println(vin.getVehicleTypeFromNhtsa()) // PASSENGER CAR
println(vin.getBodyClassFromNhtsa()) // Sedan/Saloon
println(vin.isValidByNhtsa()) // Result<String>
println(vin.toJsonString()) // full JSON from NHTSA
}NHTSA functions are
suspend— call from a coroutine orrunBlocking. Optimized for US VINs; accuracy varies for other regions.
val vin = VinInfo.random()
println(vin.vinNumber) // e.g. "WBA4X3C09K1234567"
println(vin.isValid) // trueFor reproducible output (tests):
val vin = VinInfo.fromNumber(VinGenerator.generate(Random(seed = 42))).getOrThrow()| Member | Type | Description |
|---|---|---|
vinNumber |
String |
Normalized 17-char VIN |
wmi |
String |
World Manufacturer Identifier (chars 1–3) |
vds |
String |
Vehicle Descriptor Section (chars 4–9) |
vis |
String |
Vehicle Identification Section (chars 10–17) |
year |
Int |
Model year decoded from position 10 |
region |
String |
Region name (e.g. "Europe", "North America") |
regionCode |
String |
Region code (e.g. "EU", "NA") |
country |
String? |
Country derived from WMI, or null if unknown |
manufacturer |
String? |
Manufacturer name from WMI, or null if unknown |
isSmallVolumeManufacturer |
Boolean |
True if WMI 3rd char is 9 (<500 vehicles/year) |
assemblyPlant |
Char |
Assembly plant code (position 11) |
serialNumber |
String |
Production serial (positions 12–17, or 15–17 for small-volume) |
checksum |
Char |
Check digit at position 9 (throws for EU region) |
calculatedChecksum |
Char |
ISO 3779 computed check digit |
isFormatValid |
Boolean |
17 chars, valid alphabet (no I/O/Q) |
isCheckDigitRequired |
Boolean |
True for NA and China regions |
isCheckDigitValid |
Boolean |
Computed check digit matches position 9 |
isValid |
Boolean |
Format valid + check digit valid where required |
isValidByNhtsa() |
suspend Result<String> |
Validates against NHTSA database |
getMakeFromNhtsa() |
suspend String |
Make (e.g. "BMW") |
getModelFromNhtsa() |
suspend String |
Model (e.g. "328i") |
getVehicleTypeFromNhtsa() |
suspend String |
Vehicle type (e.g. "PASSENGER CAR") |
getBodyClassFromNhtsa() |
suspend String |
Body class (e.g. "Sedan/Saloon") |
toJsonString() |
suspend String |
Full NHTSA response as JSON |
| Member | Description |
|---|---|
VinInfo.fromNumber(vin: String): Result<VinInfo> |
Parse a VIN; returns Result.failure with a typed exception on error |
VinInfo.random(): VinInfo |
Generate a random valid VIN |
String.withVinInfo { } |
Extension: parse + run block + auto-close |
VinGenerator.generate() // random valid VIN
VinGenerator.generate(random = Random(seed)) // seeded, reproducibleVinInfo.fromNumber returns Result.failure — never throws. The exception type indicates what failed:
| Exception | Cause |
|---|---|
InvalidVinLengthException |
Not exactly 17 characters |
InvalidVinException |
Invalid characters (I, O, Q or non-alphanumeric) |
InvalidVinRegionCharException |
First character is not a valid region code |
InvalidVinYearException |
Position 10 is not a valid model-year code |
VinChecksumMismatchException |
Check digit wrong for NA/China VINs |
NoChecksumForEuException |
Accessing checksum on a European VIN |
NhtsaDatabaseFailedException |
NHTSA API returned no result for the requested field |
NhtsaDatabaseAlreadyClosedException |
NHTSA client used after VinInfo.close()
|
VinInfo.fromNumber("BADINPUT").onFailure { e ->
when (e) {
is InvalidVinLengthException -> println("Wrong length")
is VinChecksumMismatchException -> println("Bad check digit")
else -> println(e.message)
}
} W B A 3 A 5 G 5 9 D N P 2 6 0 8 2
↑──────↑ ↑──────────↑ ↑───────────────────────↑
WMI VDS VIS
[1-3] [4-9] [10-17]
Position 1 : Region code
Position 1–3 : WMI — World Manufacturer Identifier
Position 4–8 : VDS — Vehicle Descriptor Section
Position 9 : Check digit (mandatory: NA, China)
Position 10 : Model year code
Position 11 : Assembly plant
Position 12–17 : Serial number (12–14 = extended WMI for small-volume makers)
MIT License © 2026 Naimul Kabir
Inspired by vin-decoder-dart by Adaptant Labs and vindecoder.js by Kevin Boutin.
A pure Kotlin Multiplatform library for VIN (Vehicle Identification Number) decoding, validation, and NHTSA API integration.
Targets: Android · iOS (arm64, x64, simulatorArm64) · JVM · Linux x64
For production-grade VIN processing, complement this library with your own business logic or country-specific databases. VIN standards vary by region and manufacturer.
VinInfo implements AutoCloseable; use .use {} or withVinInfo {}
// commonMain
implementation("io.github.kabirnayeem99:kvininfo:2.0.0")dependencies {
implementation("io.github.kabirnayeem99:kvininfo:2.0.0")
}dependencies {
implementation "io.github.kabirnayeem99:kvininfo:2.0.0"
}val result = VinInfo.fromNumber("WBA3A5G59DNP26082")
result.onSuccess { vin ->
println(vin.vinNumber) // WBA3A5G59DNP26082
println(vin.wmi) // WBA
println(vin.vds) // 3A5G59
println(vin.vis) // DNP26082
println(vin.year) // 2013
println(vin.region) // Europe
println(vin.country) // Germany
println(vin.manufacturer) // BMW AG
println(vin.assemblyPlant) // N
println(vin.serialNumber) // P26082
println(vin.isValid) // true
}
result.onFailure { e ->
println("Invalid VIN: ${e.message}")
}"WBA3A5G59DNP26082".withVinInfo {
println(year) // 2013
println(region) // Europe
println(manufacturer) // BMW AG
}
// VinInfo is automatically closed after the blockVinInfo.fromNumber("WBA3A5G59DNP26082").getOrThrow().use { vin ->
println(vin.year)
println(vin.manufacturer)
}VinInfo.fromNumber("1HGCM82633A004352").getOrThrow().use { vin ->
println(vin.getMakeFromNhtsa()) // HONDA
println(vin.getModelFromNhtsa()) // Accord
println(vin.getVehicleTypeFromNhtsa()) // PASSENGER CAR
println(vin.getBodyClassFromNhtsa()) // Sedan/Saloon
println(vin.isValidByNhtsa()) // Result<String>
println(vin.toJsonString()) // full JSON from NHTSA
}NHTSA functions are
suspend— call from a coroutine orrunBlocking. Optimized for US VINs; accuracy varies for other regions.
val vin = VinInfo.random()
println(vin.vinNumber) // e.g. "WBA4X3C09K1234567"
println(vin.isValid) // trueFor reproducible output (tests):
val vin = VinInfo.fromNumber(VinGenerator.generate(Random(seed = 42))).getOrThrow()| Member | Type | Description |
|---|---|---|
vinNumber |
String |
Normalized 17-char VIN |
wmi |
String |
World Manufacturer Identifier (chars 1–3) |
vds |
String |
Vehicle Descriptor Section (chars 4–9) |
vis |
String |
Vehicle Identification Section (chars 10–17) |
year |
Int |
Model year decoded from position 10 |
region |
String |
Region name (e.g. "Europe", "North America") |
regionCode |
String |
Region code (e.g. "EU", "NA") |
country |
String? |
Country derived from WMI, or null if unknown |
manufacturer |
String? |
Manufacturer name from WMI, or null if unknown |
isSmallVolumeManufacturer |
Boolean |
True if WMI 3rd char is 9 (<500 vehicles/year) |
assemblyPlant |
Char |
Assembly plant code (position 11) |
serialNumber |
String |
Production serial (positions 12–17, or 15–17 for small-volume) |
checksum |
Char |
Check digit at position 9 (throws for EU region) |
calculatedChecksum |
Char |
ISO 3779 computed check digit |
isFormatValid |
Boolean |
17 chars, valid alphabet (no I/O/Q) |
isCheckDigitRequired |
Boolean |
True for NA and China regions |
isCheckDigitValid |
Boolean |
Computed check digit matches position 9 |
isValid |
Boolean |
Format valid + check digit valid where required |
isValidByNhtsa() |
suspend Result<String> |
Validates against NHTSA database |
getMakeFromNhtsa() |
suspend String |
Make (e.g. "BMW") |
getModelFromNhtsa() |
suspend String |
Model (e.g. "328i") |
getVehicleTypeFromNhtsa() |
suspend String |
Vehicle type (e.g. "PASSENGER CAR") |
getBodyClassFromNhtsa() |
suspend String |
Body class (e.g. "Sedan/Saloon") |
toJsonString() |
suspend String |
Full NHTSA response as JSON |
| Member | Description |
|---|---|
VinInfo.fromNumber(vin: String): Result<VinInfo> |
Parse a VIN; returns Result.failure with a typed exception on error |
VinInfo.random(): VinInfo |
Generate a random valid VIN |
String.withVinInfo { } |
Extension: parse + run block + auto-close |
VinGenerator.generate() // random valid VIN
VinGenerator.generate(random = Random(seed)) // seeded, reproducibleVinInfo.fromNumber returns Result.failure — never throws. The exception type indicates what failed:
| Exception | Cause |
|---|---|
InvalidVinLengthException |
Not exactly 17 characters |
InvalidVinException |
Invalid characters (I, O, Q or non-alphanumeric) |
InvalidVinRegionCharException |
First character is not a valid region code |
InvalidVinYearException |
Position 10 is not a valid model-year code |
VinChecksumMismatchException |
Check digit wrong for NA/China VINs |
NoChecksumForEuException |
Accessing checksum on a European VIN |
NhtsaDatabaseFailedException |
NHTSA API returned no result for the requested field |
NhtsaDatabaseAlreadyClosedException |
NHTSA client used after VinInfo.close()
|
VinInfo.fromNumber("BADINPUT").onFailure { e ->
when (e) {
is InvalidVinLengthException -> println("Wrong length")
is VinChecksumMismatchException -> println("Bad check digit")
else -> println(e.message)
}
} W B A 3 A 5 G 5 9 D N P 2 6 0 8 2
↑──────↑ ↑──────────↑ ↑───────────────────────↑
WMI VDS VIS
[1-3] [4-9] [10-17]
Position 1 : Region code
Position 1–3 : WMI — World Manufacturer Identifier
Position 4–8 : VDS — Vehicle Descriptor Section
Position 9 : Check digit (mandatory: NA, China)
Position 10 : Model year code
Position 11 : Assembly plant
Position 12–17 : Serial number (12–14 = extended WMI for small-volume makers)
MIT License © 2026 Naimul Kabir
Inspired by vin-decoder-dart by Adaptant Labs and vindecoder.js by Kevin Boutin.