Saltar a contenido

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[].code es estable y es lo que conviene mirar; message es 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. Un NOT NULL es un 422 con field.

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.