sashalenz / nhtsa-api
Laravel integration package for the NHTSA vPIC API.
Requires
- php: ^8.4|^8.5
- illuminate/http: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- spatie/laravel-data: ^4.20
- spatie/laravel-package-tools: ^1.93
Requires (Dev)
- orchestra/testbench: ^11.0
- pestphp/pest: ^4.4
- pestphp/pest-plugin-laravel: ^4.1
README
A small, typed Laravel integration for the NHTSA vPIC API. It decodes VINs and WMIs into predictable DTOs, with built-in retries, optional proxying, and opt-in request-history logging.
Requirements
- PHP 8.4 or 8.5
- Laravel 12 or 13
Installation
composer require sashalenz/nhtsa-api
The service provider and the NhtsaApi facade are auto-discovered. Publish the config only if you need to change the defaults:
php artisan vendor:publish --tag="nhtsa-api-config"
Configuration
config/nhtsa-api.php:
| Key | Env | Default | Description |
|---|---|---|---|
base_url |
NHTSA_API_BASE_URL |
https://vpic.nhtsa.dot.gov/api/ |
vPIC base URL. |
timeout |
— | 10.0 |
Request and connection timeout, in seconds. |
format |
NHTSA_API_FORMAT |
json |
Response format query parameter. |
retry.times |
— | 3 |
Attempts before failing. Set 0 to disable retries. |
retry.sleep |
— | 150 |
Delay between attempts, in milliseconds. |
proxy |
NHTSA_API_PROXY |
null |
HTTP proxy: a URL string, a Closure resolved per request, or null. |
Usage
Every call goes through the NhtsaApi facade and accepts a request DTO.
Decode a VIN — flat key/value (recommended)
decodeVinValues returns one result keyed by NHTSA variable name. It is the most convenient shape for reading individual fields:
use Sashalenz\NhtsaApi\Data\Requests\DecodeVinRequestData; use Sashalenz\NhtsaApi\Facades\NhtsaApi; $result = NhtsaApi::decodeVinValues( DecodeVinRequestData::from(['vin' => '58ADA1C18MU003613']) )->firstResult(); $make = $result?->get('Make'); // "LEXUS" $model = $result?->get('Model'); // "ES" $body = $result?->get('BodyClass'); // "Sedan/Saloon" $year = $result?->get('ModelYear'); // "2021" $fuel = $result?->get('FuelTypePrimary');
get() accepts a default: $result->get('Trim', 'n/a'). Use ->results() to iterate every result row.
Decode a VIN — variable list
decodeVin returns the classic NHTSA variable list as VinDecodedVariableData items (variable, value, valueId, variableId):
$response = NhtsaApi::decodeVin(DecodeVinRequestData::from([ 'vin' => '5UXWX7C5*BA', 'modelYear' => 2011, // optional, narrows ambiguous VIN patterns ])); $make = $response->variables()->firstWhere('variable', 'Make')?->value;
Decode a WMI
use Sashalenz\NhtsaApi\Data\Requests\DecodeWmiRequestData; $wmi = NhtsaApi::decodeWmi(DecodeWmiRequestData::from(['wmi' => '5UX']));
Façade methods
| Method | Returns | Notes |
|---|---|---|
decodeVin |
DecodeVinResponseData |
Classic variable list. |
decodeVinExtended |
DecodeVinExtendedResponseData |
Adds extended / NCSA variables. |
decodeVinValues |
VinDecodeFlatResponseData |
Flat key/value result. |
decodeVinValuesExtended |
VinDecodeFlatResponseData |
Flat, extended. |
decodeWmi |
DecodeWmiResponseData |
WMI metadata. |
Service layer (request history)
NhtsaApiService wraps the client and logs every decode to the nhtsa_api_requests table — VIN, vehicle descriptor, make, model, model year, the raw decoded values, and a polymorphic link to the initiator.
use Sashalenz\NhtsaApi\Data\Requests\DecodeVinRequestData; use Sashalenz\NhtsaApi\Services\NhtsaApiService; $service = app(NhtsaApiService::class); // Resolve just the make (and log the request, optionally attributed to a model). $make = $service->determineMake( DecodeVinRequestData::from(['vin' => 'WP0AA2A7GL', 'modelYear' => 2016]), initiator: $user, // optional ); // Full flat decode, logged. $response = $service->decodeVinWithHistory( DecodeVinRequestData::from(['vin' => 'WP0AA2A7GL']) ); // How many times this VIN has been looked up. $count = $service->getVinRequestCount('WP0AA2A7GL');
Run the package migration to create the history table:
php artisan migrate
Error handling
A non-2xx response (after the configured retries are exhausted) raises NhtsaApiRequestException:
use Sashalenz\NhtsaApi\Exceptions\NhtsaApiRequestException; try { NhtsaApi::decodeVin(DecodeVinRequestData::from(['vin' => $vin])); } catch (NhtsaApiRequestException $e) { // vPIC was unavailable — fall back to manual entry, queue a retry, etc. }
Testing
The client is built on Laravel's HTTP client, so Http::fake() intercepts every request in your tests:
use Illuminate\Support\Facades\Http; Http::fake([ '*/DecodeVinValues/*' => Http::response([ 'Count' => 1, 'Message' => 'Results returned successfully', 'Results' => [['Make' => 'LEXUS', 'Model' => 'ES']], ]), ]);
Run the package's own suite:
vendor/bin/pest
License
The MIT License (MIT). See LICENSE for details.