Saltar al contenido principal

POST /v1/verify/submit

Una vez que el cliente subió el documento y la selfie, llamá a /submit para correr el pipeline de verificación.

POST https://api.xxuxe.online/v1/verify/submit

Esta llamada:

  1. Confirma que las keys enviadas son las que /init emitió para esta verificación
  2. Confirma que las imágenes realmente llegaron al almacenamiento
  3. Corre OCR sobre el documento vía Workers AI
  4. Despacha el trabajo al backend de ML para coincidencia facial, prueba de vida y veredicto
  5. Devuelve 202 Accepted

Todo lo que sigue al paso 5 es asincrónico.

Autenticación

Bearer token. Cualquier clave que pertenezca al mismo tenant que la usada en /init.

Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9

La verificación es sobre el tenant, no sobre la clave. Así que funciona un patrón legítimo y a menudo preferible: iniciás la verificación en el navegador con tu clave publicable, y después enviás desde tu propio servidor con tu clave secreta. El envío entre tenants distintos se rechaza.

Body de la request

CampoTipoRequeridoDescripción
verificationIdstringViene de /init. Debe coincidir con ^vf_[A-Za-z0-9]{16,24}$
keys.docFrontstringLa key de init.uploads.docFront. Máx. 512 caracteres
keys.selfiestringLa key de init.uploads.selfie. Máx. 512 caracteres
keys.docBackstringNoLa key de init.uploads.docBack, si la capturaste
livenessScorenumberNoScore de prueba de vida del lado del cliente, 0-100. Se usa como señal blanda adicional
metadataobjectNoAceptado por el esquema. Ver la advertencia de abajo

keys es obligatorio, y no es decorativo

keys es lo que ata los bytes almacenados a esta verificación. No hay forma de enviar sin él.

Los valores deben coincidir exactamente con lo que devolvió /init. Tomalos de la respuesta del init en lugar de reconstruir los strings — una discrepancia se rechaza con doc_front_key_mismatch, selfie_key_mismatch o doc_back_key_mismatch.

metadata se acepta y después se descarta

El esquema valida metadata, y la request devuelve 202. Pero el campo no se reenvía al backend y no se devuelve en los webhooks. Se queda en el borde del Worker.

Este es el peor tipo de falla — toma tus datos, tiene éxito, y los tira. Si pensabas rutear en base a un ID de campaña, un bucket de A/B o una etiqueta de marca, eso no va a funcionar.

Usá userRef en /init en su lugar. Ese sí se persiste, y es el único campo que se devuelve en los payloads de webhook.

Ejemplo de request

curl

curl -X POST https://api.xxuxe.online/v1/verify/submit \
-H "Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9" \
-H "Content-Type: application/json" \
-d '{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"keys": {
"docFront": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-front.jpg",
"selfie": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/selfie.jpg"
},
"livenessScore": 92.5
}'

JavaScript / Node.js

const response = await fetch('https://api.xxuxe.online/v1/verify/submit', {
method: 'POST',
headers: {
'Authorization': `Bearer ${publishableKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
verificationId: init.verificationId,
keys: {
docFront: init.uploads.docFront.key,
selfie: init.uploads.selfie.key,
// docBack: init.uploads.docBack.key, // solo si la capturaste
},
livenessScore: 92.5,
}),
});

const data = await response.json(); // 202 Accepted
console.log('Enviado. Consultar:', data.statusUrl);

Python

import os
import requests

response = requests.post(
"https://api.xxuxe.online/v1/verify/submit",
headers={
"Authorization": f"Bearer {os.environ['VERIDIA_PUBLISHABLE_KEY']}",
"Content-Type": "application/json",
},
json={
"verificationId": init["verificationId"],
"keys": {
"docFront": init["uploads"]["docFront"]["key"],
"selfie": init["uploads"]["selfie"]["key"],
},
"livenessScore": 92.5,
},
)
response.raise_for_status()
print("Consultar:", response.json()["statusUrl"])

Respuesta

202 Accepted

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"status": "queued",
"statusUrl": "https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2"
}
CampoTipoDescripción
verificationIdstringEl mismo ID que enviaste
statusstringqueued, processing, o completed
statusUrlstringDónde hacer polling para obtener el veredicto
Acá status no es un veredicto

status: "completed" en esta respuesta — o en cualquier consulta posterior — significa que el pipeline terminó. No significa que la persona pasó. El resultado vive en un campo separado, verdict, y no está en esta respuesta en absoluto.

Leer el veredicto requiere una clave secreta en GET /v1/verify/:id. La clave publicable que hizo esta llamada no puede obtenerlo.

Idempotencia

Llamar a /submit dos veces con el mismo verificationId es seguro.

El backend deduplica: si la verificación ya está en processing o completed, el duplicado se ignora y recibís el status actual en lugar de una segunda corrida del pipeline. Una verificación que sigue en queued o failed puede volver a encolarse.

El resultado del OCR se cachea por 30 minutos, así que un reintento tampoco vuelve a golpear a Workers AI.

Entonces, si tu cliente recibe un error de red después de mandar /submit, reintentá con el mismo body. No vas a crear una verificación duplicada.

Sobre la facturación

La autenticación rechaza requests cuando el saldo de créditos del tenant es cero (insufficient_credits, 402), pero este endpoint actualmente no descuenta ese saldo por verificación. No construyas proyecciones de uso ni un medidor de consumo asumiendo que un submit equivale a un crédito menos en el contador — reconciliá contra tus propios registros.

Tiempos

El 202 típicamente vuelve en un par de segundos; la llamada de OCR domina. El veredicto en sí llega unos segundos después, de forma asincrónica.

En lugar de hacer polling para obtenerlo, usá webhooks. Si tenés que hacer polling, GET /v1/verify/:id tiene el patrón — y tené en cuenta que hacer polling desde un solo servidor toca el límite de tasa por IP mucho antes que el de por tenant.

Errores

HTTPCódigo de errordetail.reasonCuándo
400invalid_bodyEl body falló la validación. Ver detail.fieldErrors
400invalid_bodydoc_front_key_mismatchkeys.docFront no es lo que devolvió /init
400invalid_bodyselfie_key_mismatchkeys.selfie no es lo que devolvió /init
400invalid_bodydoc_back_key_mismatchkeys.docBack no es lo que devolvió /init
400invalid_bodydoc_front_not_uploadedNo hay objeto en esa key — el cliente nunca subió
400invalid_bodyselfie_not_uploadedLo mismo, para la selfie
400invalid_bodydoc_front_disappearedEl objeto existía al momento de la verificación pero desapareció antes del OCR (muy raro)
401missing_api_key / invalid_api_keyVer Autenticación
402insufficient_creditsEl saldo del tenant es cero
404verification_not_foundNunca creada vía /init, expirada después de 1 hora, o pertenece a otro tenant
429rate_limited30/min por tenant, o el límite por IP
500internal_errorReportá el requestId
503backend_unavailablePipeline inalcanzable. Reintentá con backoff

insufficient_credits es 402, no 403. El código de error interno es internal_error, no internal. Catálogo completo: Errores.

Si recibís doc_front_not_uploaded creyendo que subiste la imagen, la causa habitual es una subida que falló con missing_upload_token porque el cliente rearmó los headers en lugar de reenviar slot.headers textual. Ver Subir las imágenes.

Notas

  • La verificación tiene que haber sido creada dentro de la última hora. La intención expira después de eso y recibís verification_not_found.
  • livenessScore es opcional. Es una señal blanda que empuja levemente la confianza ponderada; nunca decide el resultado por sí sola.
  • La procedencia de la captura se lee de la metadata estampada por el servidor cuando llegaron los bytes, no del body de esta request. /submit no puede reescribirla.

Qué sigue

GET /v1/verify/:id — obtener el veredicto