Todos los errores de aplicación de la plataforma se devuelven como application/problem+json (RFC 7807), con la misma forma:
{
"type": "https://docs.kyc.ar/errores#validation_failed",
"title": "validation_failed",
"status": 422,
"code": "validation_failed",
"detail": "project_id: el proyecto no existe"
}code es estable y público: programá contra él. detail es texto para humanos y puede cambiar de redacción sin aviso; status tampoco alcanza para decidir, porque hay códigos distintos que comparten status (rate_limited y usage_limit_exceeded son 429; session_expired y link_consumed son 410).
El campo type apunta al ancla de esta página: el type de cualquier error te trae exactamente a su sección. Algunos errores agregan campos propios —miembros de extensión de RFC 7807, como required y balance en un 402— y nunca pisan los campos reservados.
Todos los códigos
| Código | Status | Dónde aparece | Qué significa |
|---|---|---|---|
unauthorized | 401 | API pública | Credenciales inválidas |
forbidden | 403 | Dashboard | Operación no permitida para esta identidad |
insufficient_scope | 403 | API pública | La clave no tiene ese permiso |
not_found | 404 | API pública | El recurso no existe |
validation_failed | 422 | API pública | El request no cumple el contrato |
conflict | 409 | API pública | El estado actual no admite la operación |
insufficient_tokens | 402 | API pública | Saldo insuficiente |
rate_limited | 429 | API pública | Demasiadas operaciones en poco tiempo |
session_expired | 410 | QR y flujo | El link de verificación venció |
link_consumed | 410 | QR y flujo | El link ya fue utilizado |
step_not_current | 409 | Flujo del usuario final | El paso no está esperando esa respuesta |
usage_limit_exceeded | 429 | Reservado | Límite de uso excedido |
internal | 500 | Toda la plataforma | Error interno |
Detalle por código
unauthorized 401
Credenciales inválidas
El contexto de autorización de la request llegó ausente o malformado. En la práctica es un error de plataforma, no de tu integración: una clave inválida se corta antes, en el authorizer, y devuelve un 403 del gateway.
Si lo ves de forma sostenida, escribinos con el momento exacto.
forbidden 403
Operación no permitida para esta identidad
La identidad es válida pero no puede hacer esa operación: cuenta suspendida, cuenta pendiente de aprobación intentando operar en modo live, o un rol de sólo lectura intentando una mutación.
Ojo con no confundirlo con el 403 del gateway de la API pública, que tiene cuerpo {"message":"Forbidden"} y no es problem+json.
insufficient_scope 403
La clave no tiene ese permiso
La clave es válida, pero los permisos con los que se emitió no habilitan esa operación. Reintentar no sirve y volver a autenticarse tampoco: hay que usar (o emitir) una clave con el permiso que falta.
El cuerpo trae el miembro de extensión required_scope con el permiso exacto que se necesitaba, para que no tengas que deducirlo del detail. La lista completa está en Autenticación → Permisos.
Las claves emitidas antes de que existieran los permisos no ven nunca este error: conservan acceso completo.
not_found 404
El recurso no existe
La sesión, el endpoint de webhook o la entrega no existen dentro de tu cuenta.
Un id malformado y un id de otra cuenta devuelven exactamente esto mismo. Es deliberado: la API no confirma la existencia de recursos ajenos ni sirve para enumerar ids.
validation_failed 422
El request no cumple el contrato
Body inválido, campo desconocido (los cuerpos son estrictos), query param fuera de rango, cursor de paginación corrupto. El detail nombra los campos: project_id: el proyecto no existe.
También se usa para condiciones de negocio detectables en el request: un proyecto sin configuración publicada en el modo de tu clave, un mes que supera el máximo exportable en línea, o una entrega de prueba que se intenta re-entregar.
conflict 409
El estado actual no admite la operación
Los tres casos habituales:
- cancelar una sesión que ya estaba en un estado terminal;
- reutilizar una
Idempotency-Keycon un payload distinto al original; - disparar una prueba o una re-entrega sobre un endpoint
disabled.
Reintentar sin cambiar nada va a dar el mismo 409: es un error de estado, no transitorio.
insufficient_tokens 402
Saldo insuficiente
No hay tokens para cubrir el costo de la sesión. No se crea nada: ni sesión, ni link, ni asiento contable.
El cuerpo trae miembros de extensión para que no tengas que parsear el detail: required (lo que cuesta la sesión), balance (lo que hay, puede faltar en una carrera de saldo) y top_up_url (a dónde ir a recargar).
Tratalo como incidente operativo, no como rechazo de identidad: la persona del otro lado no hizo nada mal.
rate_limited 429
Demasiadas operaciones en poco tiempo
Superaste el cupo de una operación con límite propio: prueba y re-entrega de webhook comparten 10 por minuto por cuenta.
Los intentos rechazados también cuentan para la ventana: reintentar en bucle no acerca el desbloqueo. Esperá a la ventana siguiente.
El throttling general del stage (20 rps, ráfaga 40) lo aplica API Gateway y devuelve su propio 429, sin cuerpo problem+json.
session_expired 410
El link de verificación venció
El link pasó su expires_at. No hay forma de extenderlo: la salida es crear una sesión nueva.
Lo vas a ver en los endpoints de QR y en el flujo del usuario final.
link_consumed 410
El link ya fue utilizado
El link de verificación es de un solo uso: una vez que la persona lo abre y lo canjea, el token deja de servir y el QR responde este error.
step_not_current 409
El paso no está esperando esa respuesta
Interno del flujo que corre la persona: se intentó responder un paso que no es el actual, o que ya está siendo procesado.
No aparece en la API pública. Está documentado porque el código es público y podés verlo en trazas de soporte.
usage_limit_exceeded 429
Límite de uso excedido
Código reservado del contrato de errores. Hoy ningún endpoint lo emite; se documenta para que un cliente que haga un switch exhaustivo sobre code lo contemple sin sorpresas.
internal 500
Error interno
Algo falló de nuestro lado. El cuerpo nunca expone detalle interno: si necesitás soporte, mandanos el momento exacto y, si lo tenés, el external_ref o el id del recurso.
Es reintentable con backoff. En POST de creación de sesión, reintentá con la misma Idempotency-Key para no crear duplicados.