Manejo de errores
Todas las superficies de la API de
watsi — servicios Fastify y rutas de API de Next.js — devuelven un envelope de error consistente. Esta página documenta la estructura estándar, todos los códigos de error y las estrategias de reintento recomendadas.
Respuesta de error estándar
Toda respuesta de error tiene el mismo envelope. El campo de nivel superior error siempre es un objeto con code y message:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "email is required",
"details": {} // optional — extra context when available
}
}| Campo | Tipo | Descripción |
|---|
error.code | string | Código de error legible por máquina (p. ej. UNAUTHORIZED) |
error.message | string | Descripción legible por humanos de lo que salió mal |
error.details | object? | Objeto opcional con errores a nivel de campo o contexto adicional |
Códigos de error
| Estado | Código | Descripción |
|---|
| 400 | VALIDATION_ERROR | El cuerpo de la solicitud o los parámetros de consulta no pasaron la validación. Revise el mensaje para más detalles. |
| 400 | BAD_REQUEST | La solicitud está mal formada o no puede procesarse en su estado actual. |
| 401 | UNAUTHORIZED | Credenciales de autenticación ausentes o inválidas. Incluya una clave de API o sesión válida. |
| 403 | FORBIDDEN | El usuario autenticado no tiene permiso para realizar esta acción. |
| 404 | NOT_FOUND | El recurso solicitado no existe o no es accesible. |
| 405 | METHOD_NOT_ALLOWED | El método HTTP no es compatible con este endpoint. |
| 409 | CONFLICT | La solicitud entra en conflicto con el estado actual (p. ej. recurso duplicado). |
| 429 | RATE_LIMITED | Demasiadas solicitudes. Reduzca el ritmo y reintente después del intervalo Retry-After. |
| 500 | INTERNAL_ERROR | Ocurrió un error inesperado en el servidor. Reintente con retroceso exponencial. |
| 502 | EXTERNAL_API_ERROR | Un servicio externo (WhatsApp, proveedor de IA) devolvió un error. |
| 503 | SERVICE_UNAVAILABLE | El servicio no está disponible temporalmente. Reintente tras una breve espera. |
Ejemplos de respuestas
Error de validación (400)
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "email is required"
}
}No autorizado (401)
HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": {
"code": "UNAUTHORIZED",
"message": "Unauthorized"
}
}No encontrado (404)
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"error": {
"code": "NOT_FOUND",
"message": "Chat not found"
}
}Límite de tasa excedido (429)
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 30 seconds."
}
}Error interno (500)
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
}
}Estrategias de reintento
| Código | ¿Reintentable? | Estrategia |
|---|
VALIDATION_ERROR | No | Corrija los parámetros de la solicitud antes de reintentar. |
UNAUTHORIZED | No | Revise su clave de API o vuelva a autenticarse. |
FORBIDDEN | No | Verifique que cuenta con los permisos requeridos (rol de administrador, etc.). |
NOT_FOUND | No | Verifique que el ID del recurso exista. |
CONFLICT | No | El recurso ya existe. Obtenga el recurso existente o use un identificador diferente. |
RATE_LIMITED | Sí | Espere la duración indicada en el encabezado Retry-After y luego reintente. Límite predeterminado: 100 sol./min por clave de API. |
INTERNAL_ERROR | Sí | Reintente con retroceso exponencial: 1s, 2s, 4s, hasta 3 intentos. |
EXTERNAL_API_ERROR | Sí | Un proveedor externo falló. Reintente con retroceso exponencial. |
SERVICE_UNAVAILABLE | Sí | El servicio está temporalmente caído. Reintente después de 5–30 segundos. |
Manejo de errores en el código
const res = await fetch('https://api.watsi.ai/api/v1/conversations', {
headers: { Authorization: `Bearer ${apiKey}` },
})
if (!res.ok) {
const body = await res.json()
const { code, message } = body.error
switch (code) {
case 'RATE_LIMITED':
const retryAfter = res.headers.get('Retry-After') ?? '30'
await sleep(Number(retryAfter) * 1000)
return retry()
case 'UNAUTHORIZED':
throw new Error('Invalid API key')
default:
throw new Error(`API error [${code}]: ${message}`)
}
}