Errores
La API responde con el código HTTP y un cuerpo JSON pequeño. El código manda; message es prosa y puede cambiar. La excepción es el 422: sus claves son nombres de campo y forman parte del contrato.
Cómo funciona
Sección titulada «Cómo funciona»La forma
Sección titulada «La forma»| Código | Cuerpo |
|---|---|
| 401 | {"message": "..."} |
| 403 | {"message": "..."} y, según el caso, module, ability, code o access |
| 404 | {"message": "..."} |
| 422 | {"errors": {"campo": ["mensaje", ...]}} |
| 429 | {"message": "..."} y cabeceras de límite |
Controller::invalid() arma el 422 con solo errors: no cuentes con un message ahí.
401 — no sé quién eres
Sección titulada «401 — no sé quién eres»Falta el token, venció o no es válido: {"message":"Unauthenticated."}. Dos variantes: el login con credenciales incorrectas devuelve 401 con Credenciales inválidas.; un Bearer que no resuelve en POST /api/leads o POST /api/track devuelve 401 con Token inválido.. Sin ninguna credencial, /leads responde 422 y pide site_key.
No reintentes con el mismo token: cambia la credencial.
403 — sé quién eres, pero no puedes
Sección titulada «403 — sé quién eres, pero no puedes»- Módulo. El sitio no tiene el módulo habilitado o su plan no se lo permite:
{"message":"El módulo 'blog' no está habilitado para este sitio.","module":"blog"}- Habilidad. El token de sitio no tiene la habilidad que la ruta exige:
{"message":"El token no tiene la habilidad 'blog'.","ability":"blog"}- Plan o sitio. Una escritura con el plan vencido trae
code(plan_read_only,plan_suspended) yaccess; la API de token de un sitio suspendido traecode: site_suspended. - Perfil o portal. Un miembro sin permiso recibe
Tu perfil no permite…; un token del área de cliente no abre el panel.
Un recurso de otro sitio no da 403: da 404. La API no confirma que exista.
404 — no está, o no es tuyo
Sección titulada «404 — no está, o no es tuyo»Puede significar que el recurso no existe, que es de otro sitio o que la ruta no existe. Esto último también aplica bajo /api/v1: una ruta inventada es 404, no un hueco.
El message no es estable. En el sitio público por slug sí es explícito (No existe ese sitio.), pero no lo uses como discriminador: decide por el código.
422 — la petición no cumple
Sección titulada «422 — la petición no cumple»Es validación. Las claves son los campos de la regla y cada valor es una lista de mensajes:
{"errors":{"email":["<mensaje de la regla>"],"password":["<mensaje de la regla>"]}}También hay 422 semánticos: falta la clave de ingest, la clave es inválida, el origen no está autorizado, el sitio está inactivo o intentas revocar el token con el que llamas.
429 — demasiadas peticiones
Sección titulada «429 — demasiadas peticiones»El limitador general es de 120 por minuto por usuario o IP. Encima hay límites por ruta: POST /api/leads 30 por minuto, POST /api/track 120 por minuto, el login 5 por minuto por IP y correo (30 por IP) y la verificación de certificados 20 por minuto por documento e IP.
Laravel devuelve {"message":"Too Many Attempts."} con Retry-After y X-RateLimit-*. POST /api/leads suma su propio límite: 15 por hora por (sitio, IP); 100 por hora por sitio cuando no hay IP. Ese 429 es un cuerpo propio, sin esas cabeceras.
Por qué el 422 es contrato
Sección titulada «Por qué el 422 es contrato»El frontend usa la clave para pintar el mensaje debajo del input. Al renombrar la regla contenido a content, errors.contenido desapareció y el formulario se quedó mudo: el 422 llegaba igual. Ningún test que compruebe el código de estado lo ve; lo caza AdditiveContractTest, porque la clave es parte de la forma grabada.
Por eso los controladores usan $this->invalid($validator): las claves salen de las reglas.
Ejemplo
Sección titulada «Ejemplo»// 401{"message":"Unauthenticated."}
// 422{"errors":{"consent_granted":["Este sitio exige la autorización de tratamiento de datos."]}}
// 429{"message":"Too Many Attempts."}En el cliente, decide por el estado: el 422 se pinta campo a campo; el 401 y el 403 piden otra credencial o otro permiso; el 429 espera el Retry-After.
Qué rompe
Sección titulada «Qué rompe»- Renombrar una clave del 422. El mensaje deja de aparecer bajo el input, aunque el error siga llegando con el mismo código.
- Parsear
message. Es prosa y cambia. Usa el código de estado y, para el 422, las claves deerrors. - Reintentar un 401 o un 403 tal cual. No se arreglan solos: falta credencial, habilidad, módulo o plan.
- Ignorar
Retry-After. Reintentar en bucle solo alarga el bloqueo. - Asumir que todo 422 trae
message.invalid()no lo incluye. - Leer un 404 como «no existe». Puede ser «no es tuyo» o «ruta mal escrita», también bajo
/api/v1.
Ver también
Sección titulada «Ver también»- Autenticación: cómo obtener la credencial que falta.
- Contrato: qué registra el archivo y qué es aditivo.
- Versionado: qué versión estás llamando.