Errores

Contra qué campo conviene programar, y por qué no es el cuerpo de la respuesta.

Programá contra el status HTTP. Es el único dato que viene siempre, que significa lo mismo en todos los endpoints y que no va a cambiar sin anunciarse en el changelog.

El cuerpo sirve para mostrarle algo a una persona y para depurar, pero su forma no es única: hoy conviven varias, según el endpoint y según qué falló. Si tu integración ramifica lógica leyendo el cuerpo, se va a romper al cambiar de endpoint.

Qué significa cada status

StatusCuándo apareceQué hacer
400El request es inválido para la lógica de negocioCorregir los datos. Reintentar igual no sirve.
401Faltan credenciales, o no son válidasRevisar que vayan los dos headers
403Credenciales válidas, sin permiso sobre ese recursoRevisar el alcance de tus credenciales
404El recurso no existe, o no pertenece a tu organizaciónVerificar el identificador
409Conflicto: el recurso ya existe o está en un estado incompatibleConsultar el estado actual antes de reintentar
422El cuerpo no cumple el esquema, o la operación no afectó a nadieVer el detalle
500Error inesperado del lado nuestroReintentar con backoff; si persiste, reportarlo

El 422 tiene dos orígenes distintos. Uno es la validación de esquema. El otro es de negocio: los endpoints de asignación masiva devuelven 422 cuando la llamada no terminó asignando a ningún usuario, aunque el cuerpo fuera válido. Si tu integración asigna contenido, tratá ese caso aparte.

Las formas del cuerpo

Estas son las tres que vas a encontrar en la práctica. Las tres traen el detalle bajo detail, y ahí se termina el parecido.

Una lista de mensajes

La más común. Es lo que devuelven los 400, 401, 403 y 404 de la mayoría de los endpoints:

1{
2 "detail": {
3 "errors": ["Centralized organization with slug acme does not exist"]
4 }
5}

La validación de esquema

Cuando un parámetro o un campo del cuerpo no cumple el tipo o el rango. detail acá es una lista, no un objeto, y cada elemento dice qué campo falló:

1{
2 "detail": [
3 {
4 "loc": ["query", "limit"],
5 "msg": "ensure this value is less than or equal to 50",
6 "type": "value_error.number.not_le"
7 }
8 ]
9}

loc es el camino al campo: ["query", "limit"] es un parámetro de query, ["body", "email"] un campo del cuerpo. Es la forma más útil para mostrarle al usuario qué corregir.

Un texto suelto

En los 500, detail puede venir como un string, o como un objeto con una sola clave:

1{ "detail": "The request could not be processed." }

No trae información accionable a propósito. Si te llega uno, reintentá con backoff y si persiste reportalo con la hora y la ruta.

Cómo leer las tres con el mismo código

Alcanza con normalizar detail a una lista de textos antes de tocarlo:

1def error_messages(payload: dict) -> list[str]:
2 """Devuelve los mensajes de error de cualquier respuesta de la API."""
3 detail = payload.get("detail")
4
5 if isinstance(detail, str):
6 return [detail]
7 if isinstance(detail, list):
8 return [item.get("msg", str(item)) for item in detail]
9 if isinstance(detail, dict):
10 if "errors" in detail:
11 return list(detail["errors"])
12 return [str(value) for value in detail.values()]
13
14 # Algunas operaciones devuelven el detalle en `message`.
15 return [payload["message"]] if "message" in payload else []

Los textos que salen de acá son para un log o para mostrarle a una persona. No los compares en tu código: no son estables y pueden cambiar sin aviso. La decisión de qué hacer sale del status HTTP.

Reintentos

Reintentá solo 500 y errores de red, con backoff exponencial. Los 4xx describen un problema del request: reintentarlos sin cambiar nada da el mismo resultado.

Las operaciones de escritura no son idempotentes salvo que se indique lo contrario, así que antes de reintentar una que pudo haber llegado, consultá el estado actual del recurso.

Que hoy convivan varias formas de cuerpo es deuda, no diseño. Si en algún momento se unifican, se anuncia en el changelog como cualquier otro cambio del contrato. Es la razón de fondo para ramificar por status y no por cuerpo: el código que ya hace eso no se toca el día que cambie.