Autenticación
Veridia usa autenticación por bearer token:
Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9
Dos endpoints son la excepción: PUT /v1/verify/upload/... y GET /v1/verify/challenge/.../next no toman clave de API en absoluto. Se autentican con el token de subida de vida corta que reparte /init. Ver Subir las imágenes.
Familias de claves
| Familia | Usar desde | Puede | No puede |
|---|---|---|---|
| Publicable | Navegador, widget, app móvil | POST /v1/verify/init, POST /v1/verify/submit | Leer veredictos |
| Secreta | Solo tu servidor | Todo lo que puede una clave publicable, más GET /v1/verify/:id | — |
La clave publicable es segura para publicar en tu página. Está diseñada para eso. Puede iniciar verificaciones contra tu saldo, y puede enviarlas — pero nunca puede leer un resultado.
La clave secreta nunca es segura para publicar. Puede obtener el veredicto, los campos de identidad extraídos y los scores de cualquier verificación de tu tenant. Tratala como la contraseña de una base de datos.
Por qué existe la división
Una clave publicable es, por definición, legible por cualquiera que abra el código fuente de tu página. Si esa clave pudiera llamar a GET /v1/verify/:id, entonces todos los resultados KYC de tus clientes — veredicto, confianza, número de documento, fecha de nacimiento — estarían a un fetch de distancia para cualquiera con las herramientas de desarrollo abiertas.
Por eso el endpoint de resultados rechaza las claves publicables con un código de error dedicado, secret_key_required, en lugar de un fallo de auth genérico. El código existe específicamente para que no salgas a cazar un typo en una clave que es perfectamente válida.
Entornos
Cada tenant recibe dos juegos paralelos de claves. Prestá mucha atención a los prefijos de test — son qv_pubt_ y qv_sect_, con la t antes del guion bajo. No qv_pub_test_.
| Entorno | Publicable | Secreta |
|---|---|---|
| Test | qv_pubt_... | qv_sect_... |
| Live | qv_pub_... | qv_sec_... |
Una clave de test corre todo el cable, nada del trabajo. Cada request hace el mismo recorrido que producción — autenticación de subida real, una fila de verificación real, un webhook real firmado con tu secreto real y reintentado con la misma escalera, legible con tu clave secreta de test — pero no corre OCR, no corre el pipeline de ML, no se gasta ningún crédito, y el veredicto lo elegís vos, determinísticamente:
userRef contiene | Veredicto | Evento del webhook |
|---|---|---|
+reject | rejected | verification.rejected |
+review | review | verification.review |
| cualquier otra cosa | approved | verification.approved |
{ "userRef": "qa-user-17+reject", "documentType": "passport", "country": "PY" }
Resultados determinísticos significan que tu CI puede asertar sobre cada rama del webhook en vez de esperar que el pipeline opine igual dos veces. Las imágenes que subís igual tienen que ser JPEG reales dentro de los límites de tamaño — el camino de bytes se ejercita a propósito — pero su contenido se ignora: los campos extraídos vuelven como marcadores inconfundibles (TEST PERSONA, TEST-000000).
Las claves de test y de live entregan a la misma URL de webhook del tenant. Por eso cada sobre de evento lleva un campo env — "test" o "live" — y tu handler tiene que ramificar sobre él. Un verification.approved sintético que activa una cuenta real es exactamente el accidente que este campo existe para prevenir.
Flujo de trabajo recomendado: desarrollá contra claves de test, asertá sobre los tres resultados en CI, y recién ahí cambiá a claves live en producción. Nunca uses una clave live en staging.
Lista blanca de dominios (claves publicables)
Una clave publicable puede llevar una lista de orígenes permitidos. Cuando esa lista no está vacía, una request de navegador cuyo Origin no figure en ella se rechaza con origin_not_allowed (403).
Las entradas se comparan contra el hostname pelado — sin esquema, sin puerto:
yourapp.com
staging.yourapp.com
localhost
http://localhost:3000 no es una entrada válida. Nunca va a coincidir con nada, porque la verificación compara contra el hostname localhost.
Los comodines funcionan: *.yourapp.com coincide con app.yourapp.com pero deliberadamente no coincide con yourapp.com en sí. Listá ambos si necesitás ambos.
La garantía funciona al revés de lo que espera la mayoría.
- Toda clave se crea con la lista vacía, y una lista vacía permite todos los orígenes. La verificación se saltea por completo cuando la lista está vacía. Es opt-in, no opt-out. El panel hoy no expone un campo para completarla.
- Solo aplica a requests de navegador. curl, un servidor, un script o cualquiera de nuestros SDKs server-side no envían header
Origin, así que no hay nada que comparar y la request procede. Nunca puede restringir el uso fuera del navegador. - Agregar tu primera entrada da vuelta la clave de "cualquier origen" a "solo esta lista". Si agregás
https://yourapp.com— con el esquema, que no va a coincidir — pasás de todo permitido a todo bloqueado en un solo paso. Agregá el hostname pelado.
Lo que esta lista hace realmente es acotar dónde puede correr tu widget. No es un control de acceso. Lo que protege a una clave publicable es que no puede leer resultados, más tus límites de tasa y tu saldo de créditos.
Tratá una clave publicable como pública, porque lo es.
Usar la clave secreta
Solo del lado del servidor. Todos los ejemplos de abajo obtienen un veredicto.
curl
curl -X GET https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2 \
-H "Authorization: Bearer qv_sec_YOUR_SECRET"
En modo test la clave empieza con qv_sect_.
JavaScript / Node.js
const response = await fetch(`https://api.xxuxe.online/v1/verify/${id}`, {
headers: {
'Authorization': `Bearer ${process.env.VERIDIA_SECRET_KEY}`,
},
});
const data = await response.json();
Python
import os, requests
response = requests.get(
f"https://api.xxuxe.online/v1/verify/{verification_id}",
headers={"Authorization": f"Bearer {os.environ['VERIDIA_SECRET_KEY']}"},
)
data = response.json()
PHP
<?php
$ch = curl_init("https://api.xxuxe.online/v1/verify/$verificationId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $_ENV['VERIDIA_SECRET_KEY'],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
Reemplazar una clave
El panel soporta dos operaciones: crear y revocar. No hay rotación, y no hay período de gracia.
Revocar una clave la borra del edge store al instante. Toda request en vuelo que la use empieza a fallar con invalid_api_key en ese mismo instante — no hay ventana de solapamiento durante la cual la clave vieja siga funcionando.
Así que el orden seguro es: creá la clave nueva primero, desplegala en todos lados, confirmá que el tráfico está fluyendo por ella, y recién entonces revocá la vieja. Hacerlo al revés es una caída de servicio.
Si sospechás de una filtración, ese es exactamente el caso en que querés el corte inmediato — revocá primero y aceptá la interrupción. Solo hacelo a sabiendas.
Buenas prácticas
- Nunca commitees claves secretas. Variables de entorno o un gestor de secretos.
- Claves separadas por entorno. Nunca una clave live en staging.
- Crear-después-revocar al reemplazar una clave, por el motivo de arriba.
- Registrá el
requestIdde las respuestas. Convierte un ticket de soporte vago en uno trazable. - Para cualquier llamada del lado del servidor, usá la clave secreta. La clave publicable es para clientes.
Errores de autenticación
| HTTP | Código de error | Qué significa |
|---|---|---|
401 | missing_api_key | Sin header Authorization, o no es el esquema Bearer |
401 | invalid_api_key | La clave no existe, fue revocada, o el prefijo/formato no parsea |
401 | secret_key_required | Se usó una clave publicable en GET /v1/verify/:id |
402 | insufficient_credits | El saldo de créditos del tenant es cero |
403 | origin_not_allowed | Request de navegador desde un origen que no está en una lista permitida no vacía |
429 | rate_limited | Ver Límites de tasa — puede ser la capa por IP, verificada antes que tu clave |
503 | backend_unavailable | Pipeline inalcanzable — transitorio, reintentá con backoff |
Notá que insufficient_credits es 402, no 403. Además se dispara durante la autenticación, lo que significa que puede aparecer en /init antes de que hayas hecho cualquier otra cosa — una causa común del "el widget solo dice que algo salió mal" en una cuenta de prueba agotada.
Ver Errores para el catálogo completo.
Qué sigue
- POST /v1/verify/init — iniciar una verificación
- Errores — referencia completa de códigos de error
- Límites de tasa — los dos límites, y por qué los 429 pueden preceder a la autenticación