Verificación facial
Módulo opcional: selfie con prueba de vida activa y pasiva comparada 1:1 con la foto del documento de identidad. Modelos abiertos en nuestros servidores de la UE, sin guardar imágenes ni plantillas faciales. Solo cuentas del EEE.
La verificación facial comprueba que quien envía un documento de identidad es su titular: la persona se hace una selfie con dos gestos (prueba de vida) y la comparamos 1:1 con la foto de su DNI, NIE/TIE, pasaporte o permiso de conducir. Es un módulo opcional, desactivado por defecto.
Es biometría (RGPD, art. 9)
Solo se ofrece a cuentas del Espacio Económico Europeo (país de facturación de la UE, Islandia, Liechtenstein o Noruega). No está disponible en Estados Unidos ni para cuentas en dólares o con país EE. UU. (BIPA de Illinois, CUBI de Texas y otras leyes estatales). Lo activa el propietario de la cuenta aceptando el anexo de tratamiento de datos biométricos, que queda en el registro de auditoría.
Qué guardamos (y qué no)
| Dato | ¿Se guarda? |
|---|---|
| Fotogramas de la selfie | No. Se procesan en memoria y se borran en cuanto termina la comparación (segundos). |
| Plantilla o vector facial | No. Nunca se guarda ni se devuelve. |
| Foto del documento | No se guarda para esto. Usamos el original solo si tu almacenamiento ya lo conserva; si no, la página (o tu integración) reenvía el mismo fichero, que comprobamos por su huella SHA-256. |
Resultado (face_match) | Sí: estado, similitud, puntuación de prueba de vida, gestos y motivos. Sigue la retención de los resultados (cuenta o plantilla) y se vacía, junto con el consentimiento, con DELETE /v1/analyses?metadata[...] (también en los enlaces y expedientes con esos metadatos), al cancelar el enlace o al borrar el expediente (results_deleted_at). |
| Consentimiento | Sí: fecha, IP, navegador, versión y huella del texto mostrado. |
No hacemos identificación 1:N (buscar a alguien entre otras personas) ni usamos las caras para entrenar modelos.
Precio
1 crédito por selfie comparada en modo real (variable FACE_VERIFICATION_CREDITS); gratis en modo test. No se
cobra si la persona no hace la selfie (sin cámara o sin consentimiento), si hay que repetirla por calidad (sin cara, poca
luz, imagen movida) ni si falla por nuestra parte. Cada intento que llega a compararse cuenta (máximo 3 por enlace).
Modelos
Todos son abiertos, con licencia que permite uso comercial, y se ejecutan en CPU en nuestros servidores de la UE (sin terceros):
| Paso | Modelo | Licencia |
|---|---|---|
| Detección de caras y 5 puntos | YuNet (OpenCV Zoo) | MIT |
| Comparación (vector de 128 dimensiones, coseno) | SFace (OpenCV Zoo) | Apache-2.0 |
| Prueba de vida pasiva (foto impresa, pantalla, máscara) | MiniFASNet V2 + V1SE (Silent-Face-Anti-Spoofing) | Apache-2.0 |
| Guía de gestos en el navegador | MediaPipe Face Landmarker (servido desde nuestro dominio) | Apache-2.0 |
Tiempo en servidor: menos de 1,5 s por verificación (unos 150 ms con 6 fotogramas en nuestras pruebas).
En los enlaces de verificación
Añade face_verification al crear el enlace (o a la
plantilla):
{
"documents": [{ "key": "id", "label": "DNI", "expect": ["es_dni", "es_nie", "passport"] }],
"face_verification": { "enabled": true, "required": true }
}- Después de los documentos, la página muestra el paso Selfie con el texto de consentimiento explícito (finalidad, base legal: consentimiento, borrado inmediato, responsable y encargado).
- La persona abre la cámara frontal y hace 2 gestos al azar que elige el servidor: girar la cabeza a un lado, acercarse o parpadear. El navegador solo guía; el servidor vuelve a medirlos sobre los fotogramas.
- Si no tiene cámara o no quiere, puede seguir sin selfie: el resultado queda
review(no_cameraodeclined) y lo revisas tú. - Hasta 3 intentos. Los fallos de calidad (sin cara, poca luz, imagen movida) no gastan intento.
El enlace no se completa hasta cerrar el paso de la selfie. required: true hace que el
expediente quede incomplete mientras falte, review si queda a revisar e invalid si no
coincide. El webhook verification_link.completed y el GET del enlace incluyen:
{
"face_verification": { "enabled": true, "required": true, "status": "completed", "attempts": 1, "max_attempts": 3, "consent_accepted": true },
"face_match": {
"status": "match",
"similarity": 0.61,
"liveness": {
"status": "passed",
"score": 0.93,
"challenges": [{ "type": "turn_left", "passed": true }, { "type": "blink", "passed": true }]
},
"quality": { "frames": 6, "face_size_px": 88, "sharpness": 142.3, "brightness": 131, "document_face_size_px": 74 },
"reasons": [],
"thresholds": { "match": 0.42, "review_band": 0.12, "liveness": 0.6 },
"document": { "analysis_id": "an_…", "document_key": "id" },
"checked_at": "2026-09-30T10:12:03Z"
}
}La persona nunca ve la similitud ni las puntuaciones (con show_result solo ve el estado).
Cómo decidimos
match: similitud ≥ umbral y prueba de vida superada. Nunca haymatchsin prueba de vida superada.no_match: similitud por debajo deumbral − franja(different_person).review: todo lo demás: similitud en la franja (similarity_borderline), prueba de vida sin confirmar, sin cara en el documento, sin cámara…
La prueba de vida es passed si los dos gestos se ven en los fotogramas y la puntuación pasiva supera su
umbral; failed si falta un gesto, hay más de una cara, la cara cambia durante la captura o los puntos que envía el
navegador no cuadran con las imágenes.
| Variable (servidor) | Defecto | Qué es |
|---|---|---|
FACE_MATCH_THRESHOLD | 0.42 | Similitud coseno desde la que hay coincidencia. OpenCV recomienda 0,363 para SFace en LFW (fotos del mismo tipo); lo subimos porque documento contra selfie es más difícil y preferimos pocos falsos positivos. |
FACE_REVIEW_BAND | 0.12 | Franja por debajo del umbral que va a revisión (0,30–0,42). |
LIVENESS_THRESHOLD | 0.6 | Puntuación mínima de MiniFASNet. |
Los umbrales se calibran con pnpm --filter @constaia/api face:eval sobre pares con licencia y consentimiento
(documento, selfie, misma persona sí/no): el arnés calcula FAR/FRR, EER y el umbral para FAR ≤ 0,1 %.
Con tu propia captura: POST /v1/face-verifications
Si capturas la selfie en tu app, envía los fotogramas y el analysis_id del documento (un análisis completado de un
documento de identidad con foto). Solo funciona con el módulo activado; tú recoges el consentimiento explícito.
# 1. Retos al azar (recomendado): sesión de un solo uso, 10 minutos
curl -X POST https://api.constaia.com/v1/face-verifications/sessions \
-H "Authorization: Bearer $CONSTAIA_API_KEY"
# → { "session_id": "fvs_…", "challenges": ["turn_left", "blink"], "expires_at": "…" }
# 2. 5–8 fotogramas en orden: de frente, los gestos y de frente otra vez
curl https://api.constaia.com/v1/face-verifications \
-H "Authorization: Bearer $CONSTAIA_API_KEY" \
-F analysis_id=an_01J9Z8Q3K4M5N6P7Q8R9S0T1V2 \
-F frames=@f1.jpg -F frames=@f2.jpg -F frames=@f3.jpg -F frames=@f4.jpg -F frames=@f5.jpg -F frames=@f6.jpg \
-F document=@dni.jpg \
-F 'meta={"session_id":"fvs_…","frames":[{"phase":"neutral","t":0},{"phase":"neutral","t":400},{"phase":"turn_left","t":1500},{"phase":"turn_left","t":1600},{"phase":"blink","t":2600},{"phase":"neutral","t":3400}]}'| Campo | Descripción |
|---|---|
analysis_id | Análisis del documento (DNI, NIE/TIE, pasaporte, permiso). Otro tipo → 422 face_document_not_identity. |
frames | 5–8 JPEG/PNG/WEBP (≤ 2 MB, ~640 px), sin espejo. |
meta | JSON con session_id (o challenges si no usas sesión), y por fotograma phase (neutral, turn_left, turn_right, move_closer, blink), t (ms) y, opcional, landmarks (nose, left_eye, right_eye, normalizados 0–1) y blink (0–1). |
document | El mismo fichero del documento si ya no lo guardamos (storage: "none" o review sin revisión). Si falta → 422 face_document_unavailable. |
dossier_id | Opcional: el resultado cuenta en ese expediente. |
Responde 201 con { id: "fv_…", object: "face_verification", status, face_match, … }; GET /v1/face-verifications/{id} lo recupera. Sin gestos declarados no hay prueba de vida superada (no_active_challenge),
así que el resultado máximo es review.
Errores
| Código | Cuándo |
|---|---|
403 face_verification_not_enabled | La cuenta no ha activado el módulo. |
403 face_verification_unavailable | Cuenta fuera del EEE, de EE. UU. o en dólares. |
503 face_verification_disabled | El servicio no tiene el módulo disponible ahora. |
422 face_retry | No se ve bien la cara (sin cara, oscuro, movido): repetir. |
422 face_document_unavailable / face_document_mismatch | Falta el documento o no es el mismo fichero. |
409 face_session_invalid | Sesión de retos caducada o ya usada. |
429 face_attempts_exhausted | Sin intentos en el enlace. |
Privacidad avanzada
Copia pixelada del documento con redact, campos enmascarados con mask_fields, borrado y exportación por metadatos (derechos de supresión y de acceso) y retención de resultados por plantilla o cuenta.
Residencia de datos y cumplimiento
Dónde procesa y guarda Constaia los documentos, los perfiles de procesamiento sovereign y standard, la futura región de EE. UU. y cómo se aplican el RGPD, la CCPA y la DPPA.