## El envelope

Todo error de `/public/v1/*` responde con el mismo cuerpo, sea cual sea el código HTTP:

```json
{
  "error": "PERMISSION_DENIED",
  "message": "You do not have the required permission for this action",
  "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "details": {}
}
```

- **`error`** — el código de la tabla de abajo; el único campo que tu integración debería usar para decidir
  qué hacer. Nunca cambia de forma dentro de `/public/v1` (ver [Versionado](/api/versioning/)).
  - **`message`** — texto para humanos, en inglés, orientado a depurar — no lo muestres tal cual a tu usuario
    final ni lo uses para decidir lógica de negocio, puede cambiar de redacción sin previo aviso.
- **`request_id`** — inclúyelo si escribes a soporte por un error concreto; ayuda a localizar la petición
  exacta en nuestros logs.
- **`details`** — contexto adicional (p. ej. los campos que fallaron una validación); nunca es la parte
  estable del contrato, trátalo como informativo.

## Catálogo de códigos

| HTTP | `error` | Cuándo aparece |
| --- | --- | --- |
| 401 | `INVALID_CREDENTIALS` | La clave no existe, está mal copiada o está revocada. |
| 401 | `TOKEN_EXPIRED` | La clave superó su fecha de caducidad. |
| 403 | `PERMISSION_DENIED` | La clave no tiene el scope exacto que exige el endpoint — ver [Autenticación](/api/authentication/). |
| 403 | `PLAN_NOT_ELIGIBLE` | El tenant dueño de la clave no está en un plan Pro o Enterprise — ver [Planes y límites](/api/plans-and-limits/). |
| 404 | `NOT_FOUND` | El recurso no existe, o pertenece a otro tenant — misma respuesta en ambos casos, para no confirmar la existencia de datos ajenos. |
| 409 | `WHATSAPP_SESSION_WINDOW_CLOSED` | `POST /conversations/{id}/messages` a un contacto de WhatsApp con la ventana de 24h cerrada — usa `POST /conversations/start` con una plantilla aprobada en su lugar. Nunca se llega a intentar el envío real. |
| 422 | `VALIDATION_ERROR` | El cuerpo o los parámetros de la petición no cumplen el schema del endpoint — revisa `details`. |
| 422 | `IDEMPOTENCY_KEY_REUSE_MISMATCH` | Solo si envías el header opcional `Idempotency-Key`: el mismo valor ya se usó con un cuerpo DIFERENTE en una petición anterior. Genera una clave nueva para una petición realmente distinta. |
| 429 | `RATE_LIMIT_EXCEEDED` | Superaste tu cupo de peticiones — ver [Planes y límites](/api/plans-and-limits/) para los headers `X-RateLimit-*`. |

Un `422` de validación de FastAPI (cuerpo malformado, tipo incorrecto) usa el mismo envelope, con `details`
listando cada campo que falló.