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"
}
}
| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | Siempre false en respuestas de error |
error.code | string | Código de error legible por máquina (ver la referencia más abajo) |
error.message | string | Explicación legible por humanos del error |
error.details | array | Opcional. 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ódigo | Estado HTTP | Cuándo ocurre |
|---|---|---|
VALIDATION_ERROR | 400 | El cuerpo de la solicitud o los parámetros de consulta no pasan la validación a nivel de campo |
INVALID_DATE_RANGE | 400 | El rango de fechas excede el máximo del endpoint, el fin es anterior al inicio, o una fecha no está en formato ISO |
UNAUTHORIZED | 401 | JWT / clave de API ausente o inválido |
TOKEN_EXPIRED | 401 | El JWT es válido pero expiró (vigencia de 1 hora) |
FORBIDDEN | 403 | Autenticado pero con permisos insuficientes para este endpoint |
NOT_FOUND | 404 | El ID del recurso no existe o el path de la URL es incorrecto |
RATE_LIMITED | 429 | Demasiadas solicitudes dentro de la ventana del límite de solicitudes |
INTERNAL_ERROR | 500 | Falla 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.
limitmayor 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-13o2026-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
Authorizationestá ausente o mal formado - El token JWT expiró (TTL de 1 hora)
- El encabezado
X-API-Keyestá ausente o es inválido - El encabezado
tenantno 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_ERROR | Leé 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 funcionaban | El 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álido | El rol del usuario carece del permiso requerido — verificá con tu administrador |
404 con un ID correcto | El recurso pudo haber sido eliminado, o el path de la URL tiene un error de tipeo |
429 en bucle | Dejá de reintentar — respetá el encabezado Retry-After, implementá backoff |
500 una vez | Reintentá con backoff. Las fallas transitorias suceden |
500 repetidamente | Dejá de reintentar, contactá a soporte con X-Request-Id |
Estrategia de reintentos
Para errores transitorios (429 y 500), implementá una estrategia de backoff exponencial:
- Primer reintento: esperá 1 segundo
- Segundo reintento: esperá 2 segundos
- Tercer reintento: esperá 4 segundos
- Cuarto reintento: esperá 8 segundos
- 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.
- JavaScript
- Python
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
}
}
}
import time
import requests
def request_with_retry(fn, max_retries=4):
for attempt in range(max_retries + 1):
response = fn()
if response.status_code in (429, 500):
if attempt == max_retries:
response.raise_for_status()
retry_after = response.headers.get("Retry-After")
delay = int(retry_after) if retry_after else 2 ** attempt
time.sleep(delay)
continue
return response
# Usage
def call_api():
return requests.get(
f"https://{TENANT}/apidev/v1/fleet/devices",
headers=headers,
params={"limit": 25},
)
response = request_with_retry(call_api)