Constaia
Guías por caso

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ónDónde apareceQué hace
Código PDF417 (AAMVA)checks[]: aamva_matches_visualLee 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 permisochecks[]: id_number_formatValida el formato del número según el estado emisor (esquema us_dl con issuing_state).
Código ZIPchecks[]: id_number_formatValida el ZIP de 5 o 9 cifras (esquema us_zip).
Caducidadverdict.reasons: not_expiredActiva por defecto en estos tipos.
Edad mínimaverdict.reasons: age o min_age_yearsSolo si mandas min_age_years (por ejemplo 21).
Titularverdict.reasons: holderSolo 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):

Respuesta
{
  "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/sdk
verify-license.ts
import { 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.jpg

Ajusta 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:

verify-id.ts
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" y keep_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 en metadata. 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_suspected o edited_suspected son 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

En esta página