Saltar al contenido principal

Referencia de la API

Veridia expone una API REST chica. JSON in, JSON out. Sin SOAP, sin GraphQL, sin XML.

Base URL

https://api.xxuxe.online

Todas las requests deben usar HTTPS.

Endpoints

MétodoPathAuthPropósito
POST/v1/verify/initClave de APIInicia una verificación, obtiene los slots de subida
PUT/v1/verify/upload/:verificationId/:roleToken de subidaSube una imagen
GET/v1/verify/challenge/:verificationId/nextToken de subidaPróxima baliza de prueba de vida activa (solo en flujos que la activan)
POST/v1/verify/submitClave de APICorre el pipeline sobre las imágenes subidas
GET/v1/verify/:idClave de API — solo secretaConsulta el status y el veredicto
GET/healthNingunaLiveness check

El widget usa todos estos endpoints por debajo. Vos los llamás directamente cuando estás armando un flujo server-side o un cliente móvil propio.

Dos de ellos son fáciles de pasar por alto, y los dos son estructurales:

  • PUT /v1/verify/upload/... es por donde pasa cada byte de imagen del producto. No toma bearer token. Ver POST /v1/verify/init.
  • GET /v1/verify/challenge/.../next solo existe en flujos que optaron por prueba de vida activa con activeLiveness: true en /init.

No existe /v1/verifications ni /v1/verifications/:id. Esos paths devuelven 404 not_found; el endpoint de resultados es GET /v1/verify/:id.

Autenticación

La mayoría de los endpoints toman un bearer token:

Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9

Existen dos familias de claves, y esa división es lo más importante de esta página:

FamiliaPrefijo livePrefijo testDónde corre¿Puede leer veredictos?
Publicableqv_pub_qv_pubt_Navegador, widget, app móvilNo
Secretaqv_sec_qv_sect_Solo tu servidor

Una clave publicable puede iniciar una verificación y enviarla. No puede leer el resultado — GET /v1/verify/:id la rechaza con 401 secret_key_required. Eso es deliberado: una clave publicable vive en el código fuente de tu página, donde cualquiera puede leerla, así que nunca debe poder obtener un veredicto KYC.

Prestá atención a los prefijos de test: qv_pubt_ y qv_sect_. No qv_pub_test_.

Los endpoints de subida y de reto no usan ninguna de las dos. Se autentican con el X-Veridia-Upload-Token de vida corta que /init devuelve dentro del objeto headers de cada slot de subida.

Detalle completo: Autenticación.

Versionado

La API se versiona en el path de la URL: /v1/.... Los cambios incompatibles van a un path de versión nuevo (/v2/...).

Los agregados compatibles — campos opcionales nuevos en las requests, campos nuevos en las respuestas, endpoints nuevos — pasan en /v1 sin aviso. Escribí clientes que ignoren los campos de respuesta que no reconocen.

Formato de las requests

Todos los bodies POST son JSON:

POST /v1/verify/init HTTP/1.1
Host: api.xxuxe.online
Authorization: Bearer qv_pub_...
Content-Type: application/json

{
"userRef": "customer-12345",
"country": "PY",
"documentType": "dni"
}
Los campos desconocidos se descartan en silencio

Los bodies de las requests se validan con un esquema no estricto. Una clave que no reconocemos se descarta sin error — recibís 200 OK y el valor simplemente desapareció.

Así que si te inventás un campo (tenantId, callbackUrl, metadata en /init) nada te avisa que no tuvo efecto. Revisá las tablas de campos de cada página de endpoint en lugar de asumir que un campo funcionó porque la request salió bien.

Formato de las respuestas

Toda respuesta lleva un requestId, y el mismo valor está en el header de respuesta X-Request-Id — así podés correlacionar incluso cuando falla el parseo del JSON. Registralo. Es el camino más rápido a un diagnóstico en un ticket de soporte.

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"uploads": { "docFront": { "...": "..." } },
"expiresAt": 1714604000
}

Los errores usan una forma consistente:

{
"error": "invalid_body",
"message": "Request body failed validation",
"requestId": "9f511d92ac11236d-SJC",
"detail": {
"fieldErrors": {
"country": ["expected 2 characters"]
}
}
}

Ramificá sobre error, no sobre el status HTTP. Ver Errores para el catálogo completo.

Status no es veredicto

El error más caro que permite esta API:

CampoEjeValores
status¿Corrió el pipeline?queued, processing, completed, failed
verdict¿Pasó la persona?approved, review, rejected

status: "completed" significa que el pipeline terminó. No dice nada sobre si el solicitante fue aceptado. Ramificar sobre status para dar de alta una cuenta admite a todos los solicitantes rechazados, en silencio, sin ningún error en ningún lado.

// MAL — esto da de alta a todos los que el sistema rechazó
if (result.status === 'completed') enableAccount(userId);

// BIEN
if (result.status === 'completed' && result.verdict === 'approved') enableAccount(userId);

verdict está ausente (o en null) hasta que status sea completed. Detalles en GET /v1/verify/:id.

Límites de tasa

Dos capas. La que agarra desprevenida a la gente es la capa por IP, que se verifica antes de leer tu clave:

CapaLímite
Por IP de cliente, todas las rutas100 requests / 60 s
Por tenant — /v1/verify/init60 / 60 s
Por tenant — /v1/verify/submit30 / 60 s
Por tenant — /v1/verify/:id600 / 60 s

Una verificación con prueba de vida activa hace unas 26 requests desde el dispositivo del usuario, así que un puñado de usuarios móviles detrás de una misma dirección CGNAT puede agotar el presupuesto por IP mientras tus contadores de tenant se ven ociosos. La explicación completa, y qué hacer al respecto, está en Límites de tasa — leelo antes de salir a producción con tráfico móvil.

Las respuestas 429 llevan un header Retry-After.

CORS

Las respuestas de preflight (OPTIONS) se cachean 10 minutos.

Las requests cross-origin no están restringidas por defecto. Una clave publicable puede llevar una lista de orígenes permitidos, pero toda clave se crea con esa lista vacía, y una lista vacía permite todos los orígenes. Tratala como una forma de acotar dónde corre tu widget una vez que la completes — no como un control de acceso. Ver la advertencia en Autenticación.

A dónde seguir