Errores¶
Cómo llega un error¶
Siempre con el mismo envoltorio, un código HTTP que corresponde y nunca con un 2xx:
{
"status": "error",
"data": null,
"message": "Se encontraron 1 error(es) de validación",
"errors": [
{
"code": "VALIDATION_ERROR",
"message": "Cuerpo de la petición requerido: 'titulo'",
"field": "titulo",
"details": { "error_type": "missing", "location_type": "body" }
}
],
"meta": null
}
errors[].codees estable y es lo que conviene mirar;messagees para las personas.- En un 422 hay un error por campo, con
field: es lo que un formulario necesita. - Cuando hay un mensaje pensado para el usuario final, va en
details.user_friendly_message. - Lo que llegó en el cuerpo no se repite en la respuesta ni en el log —puede ser una
contraseña—. De un parámetro de consulta o de ruta sí va el valor, en
details.input. - Una restricción de la base de datos dice cuál fue en
details.constraint, cuando el motor lo sabe:uq_autor_email,fk_publicacion_autor. UnNOT NULLes un 422 confield.
Qué código tiene cada situación¶
| HTTP | code |
Cuándo |
|---|---|---|
| 422 | VALIDATION_ERROR |
Un parámetro o un campo que no vale; un masivo sin filtro y sin confirm_all; relaciones en un PATCH masivo; un NOT NULL o un CHECK de la base de datos; en el modo estricto, un parámetro, un order_by o un includes que no existen |
| 422 | BUSINESS_RULE_VIOLATION |
Un trigger del schema lo detuvo con t.abort(mensaje): el mensaje es el del trigger |
| 400 | INVALID_INPUT |
Una petición mal formada |
| 404 | RECORD_NOT_FOUND |
No existe, o el RLS no deja verlo; también una fila hija de una escritura anidada que no existe, no se alcanza o es de otro padre |
| 409 | DUPLICATE_RECORD |
Viola una restricción de unicidad |
| 409 | FOREIGN_KEY_VIOLATION |
Apunta a algo que no existe, o se borra algo de lo que otros dependen |
| 401 | UNAUTHORIZED_ACCESS |
Sin token, o sin sesión |
| 401 | INVALID_CREDENTIALS |
Usuario o contraseña incorrectos (sin decir cuál) |
| 401 | TOKEN_EXPIRED |
El access token ha caducado: se arregla refrescando |
| 401 | INVALID_TOKEN |
Un token manipulado, de otro emisor o de otro realm |
| 401 | SESSION_INVALIDATED |
La sesión se cerró: otro login (reason: "concurrent_login") o un refresh reutilizado ("refresh_reused") |
| 401 | SESSION_EXPIRED |
La sesión caducó |
| 403 | INSUFFICIENT_PERMISSIONS |
Con token válido, sin el permiso que pide el endpoint, o el de una tabla hija en una escritura anidada (details.required_permission, y field dice cuál); con owner(), una fila que no sería del usuario (details.reason: "OWNER") |
| 500 | DATABASE_ERROR · INTERNAL_SERVER_ERROR |
Un fallo del servidor; el detalle va al log, no a la respuesta |
| 503 | SERVICE_UNAVAILABLE |
Una dependencia —Keycloak, la base de datos— no responde |
field dice dónde. En un 422 es el campo; en una escritura anidada, el camino hasta la fila hija
(etiquetas.create.0, publicaciones.update.1.values.etiquetas.delete.0); en /bulk, con la
posición delante (3.nombre); en POST /{schema}/_tx, con la operación delante
(operations.1.values.titulo, o operations.2 si el error es de toda la operación).
401 y 403 no son lo mismo. Ante un 401 el cliente tiene que volver a iniciar sesión (o
refrescar, si es TOKEN_EXPIRED); ante un 403, enseñar que no tiene permiso. La lista completa de
códigos está en la referencia.