Qué es vPIC y cuándo usarlo
vPIC (Vehicle Product Information Catalog) es la API pública del National Highway Traffic Safety Administration de Estados Unidos. Es la fuente oficial de información de vehículos que los fabricantes están obligados a reportar por ley federal desde 1981. Por eso, para cualquier vehículo vendido en EE.UU. en las últimas cuatro décadas, los datos de vPIC son la referencia más confiable disponible públicamente.
El tutorial siguiente cubre el flujo completo desde la primera llamada hasta el despliegue en producción. Si necesitás contexto estratégico sobre cuándo conviene vPIC vs una API comercial vs el EPC oficial del fabricante, leé primero la guía pillar NHTSA vs API comercial.
Endpoints disponibles en vPIC
vPIC expone más de 20 endpoints, pero para decodificar VINs hay tres que importan en el 95% de los casos: DecodeVin, DecodeVinValues y DecodeVinValuesBatch. Los tres aceptan format=json, format=xml o format=csv y retornan la misma información en estructuras distintas.
- DecodeVin/{VIN}?format=json — devuelve un array de objetos {Variable, Value, ValueId, VariableId}. Más verboso, ideal para debugging.
- DecodeVinValues/{VIN}?format=json — devuelve un único objeto plano con todas las variables como propiedades. 30% menos bytes, parse trivial.
- DecodeVinValuesBatch?format=json — POST con hasta 50 VINs separados por ; en el body. Indispensable a partir de 1.000 VINs/día.
- Parámetro opcional modelyear={YYYY} — útil cuando el VIN no incluye año modelo en la posición 10 (vehículos pre-1980).
Validar el VIN antes de gastar la llamada
Cada VIN inválido que mandás a vPIC es una llamada perdida, latencia agregada al usuario y ruido en tus logs. La validación es trivial: 17 caracteres alfanuméricos, sin las letras I, O y Q (para evitar confusión con 1 y 0), y un dígito verificador en la posición 9 calculable según ISO 3779.
El dígito verificador usa pesos fijos por posición y un mapeo de letra → valor numérico. La suma ponderada módulo 11 debe igualar el carácter en la posición 9 (donde 10 se representa como X). Esta validación detecta el ~95% de los typos sin tocar la red.
- Regex base: ^[A-HJ-NPR-Z0-9]{17}$
- Pesos ISO 3779 por posición (1-17): 8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2
- Mapeo letras: 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
- Si la suma % 11 === 10 → check digit debe ser 'X'.
Tutorial práctico en JavaScript / Node
Este es el patrón mínimo que usamos en producción para el AutoParts AI Agent: una función pura validateVin, una función decodeVin con timeout y abort, y un wrapper con cache en memoria (en producción, Redis con TTL de 30 días).
El timeout de 4 segundos cubre el p99 observado de vPIC (~600ms) con holgura. Si vPIC tarda más, asumimos que está degradado y devolvemos un fallback (en nuestro caso, intentamos con la API comercial de respaldo). Nunca dejes la llamada sin timeout: una request colgada bloquea el worker.
- Usá fetch nativo (Node 18+) o undici — evitá axios para esta llamada, no agrega valor.
- Pasá AbortSignal.timeout(4000) para cortar después de 4s.
- Parseá solo los ~12 campos que de verdad usás (Make, Model, ModelYear, Trim, BodyClass, EngineConfiguration, DisplacementL, FuelTypePrimary, TransmissionStyle, DriveType, PlantCountry, ErrorCode).
- ErrorCode === '0' significa decodificación exitosa; cualquier otro código (1, 6, 7, 8...) indica decodificación parcial o VIN inválido.
Decodificar un VIN con la NHTSA vPIC API en 6 pasos
Flujo de producción listo para copiar: validar, llamar, parsear, cachear, registrar métricas y degradar a fallback si vPIC falla.
- 1
Validar el VIN en el cliente
Verificá longitud 17, regex sin I/O/Q y dígito verificador ISO 3779 antes de gastar la red.
- 2
Buscar en cache
Si el VIN está en Redis con TTL vigente, devolvé el JSON cacheado en <5 ms. Hit rate típico >70%.
- 3
Llamar a DecodeVinValues con timeout 4s
GET https://vpic.nhtsa.dot.gov/api/vehicles/DecodeVinValues/{VIN}?format=json con AbortSignal.timeout(4000).
- 4
Parsear y normalizar
Quedate con los 10-15 campos que importan (Make, Model, ModelYear, Trim, EngineConfiguration, FuelTypePrimary, etc.) y normalizá strings a tu schema interno.
- 5
Cachear el resultado
Guardá en Redis con SETEX vin:{VIN} 2592000 (30 días). vPIC actualiza su catálogo ~2 veces/año; revalidá manualmente cuando publiquen release notes.
- 6
Emitir métricas y aplicar fallback
Registrá latencia y ErrorCode. Si fallan 3 llamadas seguidas o devolvés timeout, ruteá temporalmente a una API comercial (DataOne, VinAudit) y alertá al canal de operaciones.
Tutorial en Python (requests + asyncio)
Para integraciones que ya viven en Python (data pipelines, scripts internos del concesionario, jobs de ETL nocturnos), recomendamos httpx con asyncio antes que requests síncrono. La diferencia es brutal a partir de los 100 VINs: httpx + asyncio.gather paraleliza bien y respeta el connection pool sin que tengas que hilar threads manualmente.
Para una sola llamada interactiva (ej. una API interna que decodifica on-demand), requests es perfectamente válido y más simple de leer. La regla práctica: <50 VINs/llamada y latencia no crítica → requests; cualquier otra cosa → httpx async.
- pip install httpx pydantic (Pydantic v2 valida la respuesta y te da typings reales)
- Definí un BaseModel con los 10-15 campos que te importan y usá .model_validate(response.json()).
- Timeout recomendado: httpx.Timeout(connect=2.0, read=4.0) — vPIC connect rápido pero read variable.
- Para batch >50 VINs, dividí en chunks y procesá con asyncio.Semaphore(10) para no saturar tu propio worker.
Decodificar VINs en batch (>50 VINs)
El endpoint DecodeVinValuesBatch acepta hasta 50 VINs por POST. Por encima de eso necesitás chunking en el cliente. La sintaxis del body es un único campo data con VINs separados por ; — opcionalmente cada VIN puede llevar su año modelo: 1HGBH41JXMN109186,2021;5YJSA1E26HF000337,2017.
En nuestro AutoParts AI Agent, cuando el cliente sube un Excel con la flota completa, partimos en chunks de 50 VINs y los procesamos con backoff exponencial. Una flota de 1.200 vehículos baja de ~12 minutos (uno a uno) a ~45 segundos (24 chunks paralelos con semáforo de 4 concurrentes).
- Body Content-Type: application/x-www-form-urlencoded
- data=VIN1;VIN2;VIN3...;VIN50
- Para >1.000 VINs/día: implementá cola con BullMQ (Node) o Celery (Python) + retries con jitter.
- El batch endpoint usa el mismo rate limit que las llamadas individuales — 50 VINs = 1 request, no 50.
Manejo de errores y rate limiting
vPIC no documenta un rate limit explícito, pero observacionalmente devuelve HTTP 429 (Too Many Requests) cuando sostenés >5 req/s desde una sola IP. La respuesta no incluye Retry-After, así que tu lógica de retry tiene que asumirlo: empezá con 500ms y duplicá hasta 8s, con jitter aleatorio del ±20%.
Los errores semánticos viven en el campo ErrorCode de la respuesta (HTTP 200, ¡no error de red!). Es crítico inspeccionarlo: ErrorCode === '0' es éxito; '1' significa 'check digit incorrecto pero decodificamos lo que pudimos'; '6' es 'VIN incompleto'. Tratar todos los códigos != '0' como error completo es un anti-patrón: muchas veces tenés datos útiles en una decodificación parcial.
- HTTP 200 + ErrorCode '0' → éxito limpio
- HTTP 200 + ErrorCode '1'/'6'/'7' → decodificación parcial, usá los campos no vacíos
- HTTP 429 → backoff exponencial con jitter, máximo 5 intentos
- HTTP 5xx → retry una vez, luego degradar a API comercial si tenés fallback
Pasar a producción: cache, observabilidad y fallback
Una vez que el tutorial funciona en tu laptop, faltan tres cosas para producción: cache, métricas y plan B. Cache porque la respuesta nunca cambia para un VIN dado, así que pagar latencia más de una vez es derroche. Métricas porque sin saber cuántas decodificaciones fallan no podés decidir si necesitás un fallback comercial. Plan B porque vPIC es 99.9% confiable, pero cuando se cae tu funnel de venta se cae con él si no tenés alternativa.
Patrón recomendado: cache key = vin, TTL infinito (revalidate manual si NHTSA actualiza el catálogo, lo cual hacen ~2 veces/año). Métricas: latencia p50/p95/p99, tasa de éxito por ErrorCode, tasa de hits del cache. Fallback: cuando vPIC devuelve 5xx o timeout por 3 minutos consecutivos, ruteá a una API comercial (DataOne, VinAudit) y emití una alerta a Slack.