Gestionar errores
Qué trae un UtiliaSDKError, cómo se traduce cada respuesta HTTP, qué se reintenta y cómo reaccionar a cada caso en JavaScript y Python.
Actualizado el . 5 min de lectura
Incluido en todos los planes. Ver precios
Todo lo que falla en el SDK llega como un UtiliaSDKError: una respuesta de error de la API, un fallo de red o un dato que el propio SDK rechaza antes de enviarlo. Así puedes tratar todos los casos con un único bloque de captura y decidir qué hacer mirando un código, no el texto del mensaje.
Qué trae el error
| JavaScript | Python | Qué contiene |
|---|---|---|
code | code | Tipo de error, del enumerado ErrorCode (tabla siguiente). |
message | message | Texto legible. Siempre es una cadena. |
statusCode | status_code | Código HTTP de la respuesta, o vacío si la petición no llegó a enviarse. |
errorCode | error_code | Código de negocio del servidor, por ejemplo TICKET_NOT_FOUND. Es lo que debes comparar para casos concretos. |
requestId | request_id | Identificador de la petición (x-request-id), para buscarla en los registros del servidor. |
timestamp | timestamp | Momento del error (Date en JavaScript; datetime en UTC en Python). |
details | details | Detalle estructurado que mande el servidor. No lo enseñes tal cual a tus usuarios. |
validationErrors | No existe | Solo JavaScript: lista normalizada de errores de validación por campo, para resaltar campos o registrar. |
planContext | plan_context | Solo en los bloqueos por plan: qué aplicativo o capacidad falta. Ver Errores de plan. |
Códigos y respuestas HTTP
ErrorCode | Cuándo aparece |
|---|---|
VALIDATION_ERROR | HTTP 400 o 422, o un dato que el SDK rechaza antes de enviar. |
UNAUTHORIZED | HTTP 401: clave no válida o ausente, o sesión OAuth que ya no se puede renovar. |
FORBIDDEN | HTTP 403: aplicación desactivada, origen no permitido, scope o permiso insuficiente, o bloqueo por plan. |
NOT_FOUND | HTTP 404. |
RATE_LIMITED | HTTP 429, o un 403 cuyo mensaje habla de «rate limit». |
NETWORK_ERROR | Sin respuesta del servidor: tiempo agotado, conexión rechazada o cortada. |
UNKNOWN | HTTP 500, 502, 503 o 504, y cualquier otro código no previsto. |
AUTH_EXPIRED | Declarado, pero el SDK no lo emite hoy: una sesión caducada llega como UNAUTHORIZED. |
Comprobaciones rápidas
El error trae comprobaciones ya hechas. En JavaScript son métodos (con paréntesis); en Python, propiedades (sin paréntesis).
| JavaScript | Python | Verdadero cuando |
|---|---|---|
isRetryable() | is_retryable | Tiene sentido volver a intentarlo: RATE_LIMITED o NETWORK_ERROR, salvo que el servidor diga expresamente lo contrario. |
isRateLimited() | is_rate_limited | Es un límite de peticiones. |
isUnauthorized() | is_unauthorized | La credencial no vale. |
isPlanError() | is_plan_error | Lo bloquea el plan de la organización, no un permiso. |
isModuleNotEnabled() | is_module_not_enabled | Falta el aplicativo entero. |
isCapabilityNotEnabled() | is_capability_not_enabled | El aplicativo está, pero falta una capacidad concreta. |
Para registrar el error en tus trazas, toJSON() (to_dict() en Python) lo convierte en un objeto plano con el código, el mensaje, la fecha, el estado HTTP, el identificador de petición y el contexto de plan.
Capturar y reaccionar
Mira primero si es un bloqueo de plan y después el code, nunca el texto del mensaje. Cuando el error te llega, el SDK ya ha reintentado lo que tenía sentido reintentar.
¿Qué hago con este error?
¿Lo bloquea el plan de la organización (isPlanError)?
Sí:
MODULE_NOT_ENABLED o CAPABILITY_NOT_ENABLED
Ni reintentar ni dar permisos lo arregla: planContext dice qué aplicativo o capacidad falta.No:
¿Qué code trae el error?
VALIDATION_ERROR:
Sin statusCode, lo rechazó el SDK antes de enviar; con 400 o 422, el servidor. Repetirlo igual da el mismo error.UNAUTHORIZED:
401
La clave no vale o falta, o la sesión OAuth ya no se puede renovar: pide a la persona que vuelva a autorizar.FORBIDDEN:
403
Con errorCode INSUFFICIENT_SCOPE falta un scope; si no, un permiso de la persona, o la aplicación está desactivada o el origen no está permitido.Otro código:
¿Cuál de los demás?
NOT_FOUND:
Comprueba el identificador
404
El errorCode dice qué no existe, por ejemplo TICKET_NOT_FOUND. Repetirlo igual no lo arregla.RATE_LIMITED:
429
El SDK ya agotó sus intentos. Baja el ritmo de peticiones antes de repetir.NETWORK_ERROR:
Mira si se hizo antes de repetir
No hubo respuesta. Crear o anular facturas, cobrar y devolver no se reintentan y pudieron hacerse: consulta su estado primero.UNKNOWN:
Fallo del servidor
5xx
El SDK ya lo reintentó. Guarda el requestId para soporte y, si era una escritura que mueve dinero, consulta su estado antes de repetirla.
El mismo criterio, en código:
import { ErrorCode, UtiliaSDKError } from '@utilia-os/sdk-js';
try {
await sdk.tickets.create({
user: { externalId: 'usr_123' },
title: 'Error en el formulario de pago',
description: 'Al enviar el formulario aparece un error 500 en la consola.',
});
} catch (error) {
if (!(error instanceof UtiliaSDKError)) throw error;
if (error.isPlanError()) {
// No se arregla reintentando ni dando permisos: hay que contratar.
return;
}
switch (error.code) {
case ErrorCode.VALIDATION_ERROR:
console.error('Datos no válidos:', error.message);
break;
case ErrorCode.UNAUTHORIZED:
console.error('Revisa la clave o la sesión OAuth.');
break;
case ErrorCode.FORBIDDEN:
console.error('Falta un scope o un permiso.');
break;
case ErrorCode.NOT_FOUND:
console.error('No existe:', error.errorCode);
break;
default:
console.error(`[${error.code}] ${error.message} (petición ${error.requestId})`);
}
}Reintentos
El SDK ya reintenta por su cuenta los errores de red, los 429 y los 5xx, con esperas de 0,5 s, 1 s, 2 s… hasta el número total de intentos de retryAttempts (retry_attempts), que por defecto es 3. Las escrituras que mueven dinero, como invoices.create e invoices.cancel, hacen un solo intento: si repites una a mano, conserva la clave de idempotencia y los datos originales. Los detalles están en Configurar el cliente.
Si prefieres controlar tú los reintentos, pon retryAttempts: 1 (o retry_attempts=1) y usa la comprobación isRetryable:
import { UtiliaSDKError } from '@utilia-os/sdk-js';
async function withRetry<T>(fn: () => Promise<T>, maxAttempts = 3): Promise<T> {
for (let attempt = 1; ; attempt++) {
try {
return await fn();
} catch (error) {
const retryable = error instanceof UtiliaSDKError && error.isRetryable();
if (!retryable || attempt === maxAttempts) throw error;
await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** (attempt - 1)));
}
}
}
const ticket = await withRetry(() => sdk.tickets.get('ticket-uuid', 'usr_456'));Errores antes de enviar la petición
El SDK comprueba algunos datos antes de llamar a la API. Cuando falla una de esas comprobaciones, el error no tiene código HTTP ni identificador de petición, porque la petición no llegó a salir.
Dónde salta un dato no válido
JavaScript
En el método del servicio
Python
Al construir el modelo
La lista completa de comprobaciones y sus límites está en Límites y validaciones.