Saltar al contenido principal

Manejo de errores

Todos los errores de la API siguen una estructura de envoltorio consistente, lo que facilita detectar fallas y reaccionar programáticamente.

Envoltorio de error

Cada respuesta de error usa este formato:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable description of what went wrong"
}
}
CampoTipoDescripción
successbooleanSiempre false en respuestas de error
error.codestringCódigo de error legible por máquina (ver la referencia más abajo)
error.messagestringExplicación legible por humanos del error
error.detailsarrayOpcional. En VALIDATION_ERROR, lista cada propiedad inválida y las restricciones que no cumplió

Las respuestas de error nunca incluyen un campo data.

Referencia rápida

CódigoEstado HTTPCuándo ocurre
VALIDATION_ERROR400El cuerpo de la solicitud o los parámetros de consulta no pasan la validación a nivel de campo
INVALID_DATE_RANGE400El rango de fechas excede el máximo del endpoint, el fin es anterior al inicio, o una fecha no está en formato ISO
UNAUTHORIZED401JWT / clave de API ausente o inválido
TOKEN_EXPIRED401El JWT es válido pero expiró (vigencia de 1 hora)
FORBIDDEN403Autenticado pero con permisos insuficientes para este endpoint
NOT_FOUND404El ID del recurso no existe o el path de la URL es incorrecto
RATE_LIMITED429Demasiadas solicitudes dentro de la ventana del límite de solicitudes
INTERNAL_ERROR500Falla inesperada del lado del servidor

Referencia de códigos de error

VALIDATION_ERROR — 400

Se devuelve cuando el cuerpo de la solicitud o los parámetros de consulta no pasan la validación a nivel de campo.

Cuándo ocurre:

  • Falta un campo requerido
  • Un campo tiene el tipo incorrecto (ej. string en lugar de number)
  • Un valor está fuera del rango permitido (ej. limit mayor que 100)

Cómo resolverlo: Leé el array error.details — lista cada propiedad inválida y las restricciones que no cumplió, de modo que sabés exactamente qué corregir. Un filtro normalizador en el servidor garantiza que toda falla de validación de campo vuelva como VALIDATION_ERROR con este array details poblado.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "One or more fields failed validation.",
"details": [
{
"property": "limit",
"constraints": {
"max": "limit must not be greater than 100"
}
}
]
}
}

INVALID_DATE_RANGE — 400

Se devuelve cuando un rango de fechas en la solicitud no es aceptable.

Cuándo ocurre:

  • El rango entre las fechas de inicio y fin excede el máximo que permite el endpoint
  • La fecha de fin es anterior a la fecha de inicio
  • Una fecha no está en formato ISO (ej. 2026-06-13 o 2026-06-13T10:30:00Z)

Cómo resolverlo: Acortá el rango para que entre dentro del límite del endpoint, asegurate de que la fecha de fin sea igual o posterior a la de inicio, y enviá las fechas en formato ISO.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "INVALID_DATE_RANGE",
"message": "The selected date range is too wide. Choose a shorter period."
}
}

UNAUTHORIZED — 401

Se devuelve cuando la solicitud carece de credenciales de autenticación válidas.

Cuándo ocurre:

  • El encabezado Authorization está ausente o mal formado
  • El token JWT expiró (TTL de 1 hora)
  • El encabezado X-API-Key está ausente o es inválido
  • El encabezado tenant no coincide con el tenant del token
  • La clave de API está inactiva o fuera de su ventana de validez

Cómo resolverlo: Volvé a autenticarte mediante el endpoint de Login para obtener un token nuevo. Si la clave de API es rechazada, verificá su estado y ventana de validez con tu ejecutivo de cuenta.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Token has expired. Please re-authenticate."
}
}

FORBIDDEN — 403

Se devuelve cuando el usuario está autenticado pero no tiene permiso para acceder al recurso solicitado.

Cuándo ocurre:

  • El rol del usuario no incluye el permiso requerido para este endpoint
  • El recurso pertenece a un alcance al que el usuario no puede acceder

Cómo resolverlo: Contactá a tu administrador para verificar que la cuenta de usuario tenga asignados los permisos requeridos. Cada endpoint documenta su permiso requerido en la referencia de la API.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "Insufficient permissions to access this resource."
}
}

NOT_FOUND — 404

Se devuelve cuando el recurso solicitado no existe.

Cuándo ocurre:

  • El ID en la URL no coincide con ningún registro existente
  • El recurso fue eliminado
  • El path de la URL es incorrecto

Cómo resolverlo: Verificá que el ID del recurso sea correcto y que el recurso no haya sido eliminado. Revisá nuevamente la URL del endpoint.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Device with ID 99999 not found"
}
}

RATE_LIMITED — 429

Se devuelve cuando la solicitud excede el límite de solicitudes permitido para el endpoint.

Cuándo ocurre:

  • Se enviaron demasiadas solicitudes dentro de la ventana del límite de solicitudes
  • El token bucket de telemetría está agotado

Cómo resolverlo: Esperá hasta el momento indicado por el encabezado de respuesta Retry-After antes de reintentar. Ver Límite de solicitudes para detalles sobre los límites por grupo de endpoints.

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Retry after 12 seconds."
}
}

INTERNAL_ERROR — 500

Se devuelve cuando ocurre un error inesperado del lado del servidor.

Cuándo ocurre:

  • Una excepción no manejada en el servidor
  • Un servicio dependiente está temporalmente no disponible

Cómo resolverlo: Reintentá la solicitud tras una breve espera usando la estrategia de reintentos que figura más abajo. Si el error persiste, contactá a soporte e incluí el valor X-Request-Id de los encabezados de la respuesta (ver más abajo).

Respuesta de ejemplo:

{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later."
}
}

X-Request-Id

Cada respuesta de la API incluye un encabezado X-Request-Id — un identificador único generado por el servidor para esa solicitud específica. No necesitás enviarlo; el servidor lo crea automáticamente.

HTTP/1.1 500 Internal Server Error
X-Request-Id: req_a1b2c3d4e5f6

Cuando contactes a soporte por errores persistentes, incluí siempre este valor. Le permite al equipo rastrear la solicitud exacta en los logs del servidor.


Resolución de problemas

Estoy recibiendo...Revisá primero
400 VALIDATION_ERRORLeé error.details — lista cada campo inválido y la restricción que no cumplió
400 INVALID_DATE_RANGE¿El rango está dentro del límite del endpoint? ¿La fecha de fin es igual o posterior a la de inicio? ¿Las fechas están en formato ISO?
401 TOKEN_EXPIRED después de llamadas que funcionabanEl token expiró (TTL de 1 h) — volvé a autenticarte mediante /apidev/v1/login
401 UNAUTHORIZED en la primera llamada¿Está presente X-API-Key? ¿tenant coincide con el tenant del JWT?
403 en un endpoint válidoEl rol del usuario carece del permiso requerido — verificá con tu administrador
404 con un ID correctoEl recurso pudo haber sido eliminado, o el path de la URL tiene un error de tipeo
429 en bucleDejá de reintentar — respetá el encabezado Retry-After, implementá backoff
500 una vezReintentá con backoff. Las fallas transitorias suceden
500 repetidamenteDejá de reintentar, contactá a soporte con X-Request-Id

Estrategia de reintentos

Para errores transitorios (429 y 500), implementá una estrategia de backoff exponencial:

  1. Primer reintento: esperá 1 segundo
  2. Segundo reintento: esperá 2 segundos
  3. Tercer reintento: esperá 4 segundos
  4. Cuarto reintento: esperá 8 segundos
  5. Rendite después de 4 reintentos y registrá el error

Para respuestas 429, preferí siempre el valor del encabezado Retry-After por sobre tu propio cálculo de backoff — te da el tiempo exacto de espera necesario.

async function requestWithRetry(fn, maxRetries = 4) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const status = error.response?.status;

if (status === 429 || status === 500) {
if (attempt === maxRetries) throw error;

const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.pow(2, attempt) * 1000;

await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}

throw error; // Non-retryable error
}
}
}