Verificar un permiso de conducir de EE. UU.
Verifica un permiso de conducir o una ID estatal de EE. UU. con el código PDF417 (AAMVA), el formato del número de cada estado, la caducidad y la edad mínima, con Node o Python.
Las altas con restricción de edad (reparto de alcohol, locales, eventos para mayores de 21) y los alquileres (coches, material, viviendas vacacionales) suelen pedir al cliente una foto de su permiso de conducir (driver's license) o de su tarjeta de identificación estatal (state ID). Quieres saber tres cosas: quién es la persona, si tiene edad suficiente y si el documento sigue vigente.
Constaia tiene dos tipos para estos documentos: us_driver_license (los 50 estados y el Distrito de Columbia, incluidas
las licencias comerciales CDL y los permisos de aprendiz) y us_state_id. Con expect recibes un verdict con la edad,
la caducidad, el formato del número y el cruce del código de barras con el anverso ya evaluados.
El flujo
El cliente sube el permiso
Tu frontend (web o móvil) envía la imagen a tu backend, con el anverso y el reverso en el mismo fichero: el
reverso lleva el código de barras PDF417. La clave de API nunca sale de tu servidor. Si usas el
widget de subida, sides="2" captura las dos caras y las une en un único JPEG, así que el análisis
cuesta 1 crédito.
Tu backend pide a Constaia que lo verifique
Llamas a POST /v1/analyze con expect: ["us_driver_license", "us_state_id"] y checks.min_age_years: 21. Constaia
extrae los campos, lee el código de barras, aplica las comprobaciones y devuelve un verdict.
Tu código decide
valid se acepta, invalid se rechaza y review pasa a una persona. Los motivos concretos están en
verdict.reasons y en checks[].
Qué comprueba Constaia
| Comprobación | Dónde aparece | Qué hace |
|---|---|---|
| Código PDF417 (AAMVA) | checks[]: aamva_matches_visual | Lee el código de barras del reverso, interpreta los datos AAMVA, completa los campos que falten en el anverso y cruza número, nombre, fecha de nacimiento y caducidad con lo impreso. |
| Número de permiso | checks[]: id_number_format | Valida el formato del número según el estado emisor (esquema us_dl con issuing_state). |
| Código ZIP | checks[]: id_number_format | Valida el ZIP de 5 o 9 cifras (esquema us_zip). |
| Caducidad | verdict.reasons: not_expired | Activa por defecto en estos tipos. |
| Edad mínima | verdict.reasons: age o min_age_years | Solo si mandas min_age_years (por ejemplo 21). |
| Titular | verdict.reasons: holder | Solo si mandas holder con el nombre, el número o la fecha de nacimiento que esperas. |
El código de barras se lee en el servidor de Constaia, sin enviarlo a ningún proveedor externo, y no cuesta nada
adicional: el análisis sigue costando 1 crédito por documento de hasta 2 páginas. Si un dato del código no coincide con
el impreso, aamva_matches_visual llega con passed: false, su mensaje nombra los campos distintos y esos campos llevan
validated: false. Una comprobación de checks[] que falla añade al veredicto un motivo error con el mismo código.
Sin reverso no hay cruce con el código
Si el fichero solo trae el anverso, Constaia extrae los campos impresos y aplica el resto de comprobaciones, pero no
hay aamva_matches_visual en checks[]. Pide las dos caras en una imagen o en un PDF de 2 páginas, o usa el widget
con sides="2". Si tu política lo exige, trata la ausencia de esa comprobación como motivo de revisión.
Los campos del tipo us_driver_license son, entre otros: issuing_state, document_number, first_name,
middle_name, last_name, name_suffix, birth_date, issue_date, expiry_date, sex, height, weight,
eye_color, hair_color, address, postal_code, real_id_compliant, dd, organ_donor, veteran,
under_21_until, class, restrictions, endorsements, commercial y permit_type. La lista completa y las
comprobaciones de cada tipo están en el catálogo de documentos y en
GET /v1/document-types/us_driver_license.
Petición con curl
curl https://api.constaia.com/v1/analyze \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F file=@license.jpg \
-F 'options={
"expect": ["us_driver_license", "us_state_id"],
"checks": { "min_age_years": 21 },
"storage": "none",
"keep_results": false
}'Respuesta (resumida):
{
"status": "completed",
"document": { "type": "us_driver_license", "label": "Licencia de conducir (EE. UU.)", "confidence": 0.97, "side": "both", "country": "USA" },
"verdict": {
"expected": ["us_driver_license", "us_state_id"],
"match": true,
"status": "valid",
"reasons": [
{ "code": "type_match", "severity": "info", "message": "El documento es Licencia de conducir (EE. UU.)." },
{ "code": "not_expired", "severity": "info", "message": "Vigente hasta el 30/07/2031." },
{ "code": "age", "severity": "info", "message": "El titular tiene 41 años." },
{ "code": "i9_list", "severity": "info", "message": "Documento aceptable para el formulario I-9: lista B." }
]
},
"fields": {
"issuing_state": { "value": "CA", "confidence": 0.99, "validated": null, "source": null },
"document_number": { "value": "I1234568", "confidence": 0.98, "validated": true, "source": null },
"birth_date": { "value": "1985-07-30", "confidence": 0.99, "validated": null, "source": null },
"expiry_date": { "value": "2031-07-30", "confidence": 0.99, "validated": null, "source": null },
"real_id_compliant": { "value": true, "confidence": 0.93, "validated": null, "source": null }
},
"checks": [
{ "code": "id_number_format", "passed": true, "message": "El identificador document_number (us_dl) es válido." },
{ "code": "id_number_format", "passed": true, "message": "El identificador postal_code (us_zip) es válido." },
{ "code": "aamva_matches_visual", "passed": true, "message": "El código PDF417 (AAMVA v10) se ha leído y coincide con los datos impresos." }
],
"warnings": []
}Cada valor llega como fields.<nombre>.value, con confidence (de 0 a 1), validated (true o false cuando una
comprobación determinista lo ha evaluado) y, cuando está disponible, la página y el recuadro de origen en source.
Decide en tu código
npm i @constaia/sdkimport { Constaia, ConstaiaError } from "@constaia/sdk";
import { fromPath } from "@constaia/sdk/node";
const constaia = new Constaia(); // lee CONSTAIA_API_KEY
const MIN_AGE = 21;
type Decision = {
decision: "approve" | "reject" | "manual_review" | "pending";
reasons: string[];
state?: string;
};
export async function verifyLicense(path: string, expectedName?: string): Promise<Decision> {
const analysis = await constaia.analyze(await fromPath(path), {
expect: ["us_driver_license", "us_state_id"],
checks: {
minAgeYears: MIN_AGE,
...(expectedName ? { holder: { fullName: expectedName } } : {}),
},
storage: "none",
keepResults: false,
metadata: { flow: "age_gate_21" },
});
// Si tarda más de 30 s la API responde 202; con keepResults: false el resultado solo llega por webhook.
if (analysis.status !== "completed" || !analysis.verdict) {
return { decision: "pending", reasons: [analysis.id] };
}
const { status, reasons } = analysis.verdict;
const errors = reasons.filter((r) => r.severity === "error").map((r) => `${r.code}: ${r.message}`);
const warnings = reasons.filter((r) => r.severity === "warning").map((r) => `${r.code}: ${r.message}`);
const state = analysis.fields.issuing_state?.value as string | undefined;
if (status === "invalid") return { decision: "reject", reasons: errors, state };
if (status === "review") return { decision: "manual_review", reasons: warnings, state };
// Política propia: exigir que se haya leído el código de barras del reverso.
const barcodeRead = analysis.checks.some((c) => c.code === "aamva_matches_visual" && c.passed);
if (!barcodeRead) return { decision: "manual_review", reasons: ["barcode_not_read"], state };
return { decision: "approve", reasons: [], state };
}
verifyLicense(process.argv[2] ?? "./license.jpg")
.then((result) => console.log(result))
.catch((err) => {
if (err instanceof ConstaiaError) {
console.error(`Constaia error ${err.status} ${err.code} (request ${err.requestId})`);
} else {
console.error(err);
}
process.exit(1);
});CONSTAIA_API_KEY=ck_live_... npx tsx verify-license.ts ./license.jpgAjusta las reglas a tu política: hay negocios que aceptan un permiso caducado dentro de un periodo de gracia (manda
not_expired: false y decide tú con expiry_date) y otros que piden un documento adicional. Para evaluar la edad en
otra fecha, por ejemplo la del evento, usa reference_date. Guarda los umbrales en configuración, no repartidos por el
código. Más en Veredictos y Comprobaciones.
Modo test
Con una clave ck_test_ el simulador no tiene escenarios específicos de EE. UU.: un permiso de conducir se responde
con el escenario generic, así que con expect: "us_driver_license" el veredicto es review con type_unknown. Te
sirve para probar la integración y la rama manual_review. Para ver resultados reales de documentos de EE. UU. usa
una clave live: el plan gratuito incluye 150 créditos al mes (los créditos live gratuitos requieren un email
verificado). Consulta Modo test.
Aceptar también pasaportes
Si el cliente no tiene permiso de conducir, amplía expect con el pasaporte de EE. UU. (libreta o tarjeta). En la
libreta, us_passport_book, Constaia comprueba además los dígitos de control de la MRZ y el cruce de la MRZ con los
datos impresos:
const analysis = await constaia.analyze(await fromPath("./id.jpg"), {
expect: ["us_driver_license", "us_state_id", "us_passport_book", "us_passport_card"],
checks: { minAgeYears: 21 },
storage: "none",
keepResults: false,
});
console.log(analysis.document?.type, analysis.verdict?.status); // p. ej. "us_passport_book" "valid"Para pasaportes extranjeros, añade el tipo genérico passport.
Si solo quieres unos campos
Si no necesitas veredicto y solo te interesan unos pocos datos, puedes pasar tu propio JSON Schema en extract sin
expect: Constaia devuelve en fields exactamente las propiedades que definas. En ese caso verdict es null y las
comprobaciones automáticas (not_expired, min_age_years…) no se aplican a tus campos. Consulta
POST /v1/analyze.
Privacidad y cumplimiento
- DPPA. La ley federal Driver's Privacy Protection Act limita cómo se obtienen y comunican los datos personales de los registros estatales de vehículos. Constaia no accede a los registros del DMV: procesa solo la imagen que aporta la propia persona. Sigue siendo un dato sensible: recógelo solo para un fin claro, explica al cliente para qué y consulta con tu asesoría jurídica cómo se aplican la DPPA y las leyes estatales a tu caso.
- Minimiza. Constaia no conserva nada por defecto con
storage: "none"ykeep_results: false. Guarda solo la decisión y los campos que de verdad necesitas (a menudo basta con "mayor de 21 verificado" y la fecha) y no pongas nunca números de permiso enmetadata. Más en Analizar sin guardar y Almacenamiento y privacidad. - Sin biometría. Constaia lee el documento; no compara la foto con un selfie ni hace reconocimiento facial, así que
no trata identificadores biométricos (BIPA, CUBI). Avisos como
screen_photo_suspectedoedited_suspectedson señales para revisar, no prueba de fraude. - CCPA. Constaia actúa como proveedor de servicios (service provider): trata los datos solo para prestarte el servicio.
- Dónde se procesan los datos. Hoy todo se procesa y se guarda en la UE, también para clientes de EE. UU. Una región de EE. UU. está prevista, próximamente, sin fecha. Consulta Residencia de datos y cumplimiento.
Esta guía no es asesoramiento jurídico.
Siguientes pasos
Documentos de EE. UU.
Todos los tipos de EE. UU. y Canadá del catálogo y sus comprobaciones.
Revisión humana
Envía los casos review y dudosos a una persona.
Residencia de datos y cumplimiento
Procesamiento en la UE, región de EE. UU., RGPD, CCPA y DPPA.
Documentos del formulario I-9
Etiqueta los documentos de las listas A, B y C.
Documentos de EE. UU.
Los tipos de documento de Estados Unidos del catálogo de Constaia, la lectura del código PDF417 (AAMVA) de permisos de conducir, las listas del formulario I-9 y la validación de SSN, EIN y otros identificadores.
Documentos del formulario I-9
Verifica los documentos de las listas A, B y C que presenta un nuevo empleado para el formulario I-9, etiquétalos por lista y prerrellena la sección 2 para RR. HH., con Node o Python.