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 |
SERVICE_UNAVAILABLE | 503 | No se pudo verificar al usuario en ese momento; el token sigue siendo válido |
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) - Un id de entidad trae algo que no son dígitos (ej.
?account_id=ACME,?task_ids=abc,123) - Un valor de
sort_byno está aceptado por ese reporte en particular
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.
Los ids de entidad son solo dígitos
Todo id de entidad de GeoTareas — tarea, cuenta, cliente, dispositivo, conductor, prestador, definición de workflow, tablero, tarjeta — es un string numérico. Se envía como string (son demasiado grandes para viajar como número JSON), pero ese string solo puede contener dígitos: "982710394857200005".
Cualquier parámetro de tipo id que reciba un valor no numérico se rechaza de entrada con 400 VALIDATION_ERROR. Aplica a valores únicos (?account_id=…), listas separadas por coma (?task_ids=…) y tuplas pareadas (?procedence_product_pairs=…).
GET /apidev/v1/tasks?account_id=ACME → 400 VALIDATION_ERROR
GET /apidev/v1/tasks?account_id=51204 → 200 OK
Los campos que no son ids de entidad siguen aceptando texto: códigos de estado (SA, FIN), tus propias referencias externas (external_ids, matrículas, números de documento), la búsqueda de texto libre y las opciones tipo enum.
Hasta el 2026-08-16 un id no numérico llegaba a la base de datos y salía como 500 INTERNAL_ERROR. Ahora es un 400 limpio, con la propiedad culpable nombrada en error.details.
sort_by se valida por reporte
Cada reporte de workflow acepta su propia lista de columnas ordenables. Un valor fuera de esa lista se rechaza con 400 VALIDATION_ERROR, y el mensaje enumera todos los valores aceptados por el reporte que llamaste:
Invalid sort_by 'ZZZ'. Allowed values for this report: totalTasks, slaMetCount, …
Antes, un sort_by no reconocido se ignoraba en silencio y el reporte volvía en su orden por defecto, así que una columna mal escrita parecía haber funcionado. Cada página de reporte lista sus valores aceptados.
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
- El usuario se desactivó, se le terminó la licencia o la fecha de vencimiento, o se quedó sin permisos (mensaje
User is not active) — se verifica en cada solicitud, no solo al iniciar sesión
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
- Se le quitó un permiso al usuario después de emitido el token — rige en segundos, sin volver a iniciar sesión
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."
}
}
SERVICE_UNAVAILABLE — 503
Se devuelve cuando el servidor no puede verificar al usuario en ese momento (el usuario y sus permisos se verifican en cada solicitud).
Cómo resolverlo: Reintentá la solicitud tras una breve espera usando la estrategia de reintentos que figura más abajo. No vuelvas a iniciar sesión: el token sigue siendo válido.
Respuesta de ejemplo:
{
"success": false,
"error": {
"code": "SERVICE_UNAVAILABLE",
"message": "Service temporarily unavailable. Please retry."
}
}
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 VALIDATION_ERROR en un filtro de id | El valor tiene que ser solo dígitos — puede que estés mandando un nombre en lugar de un id (account_id, country, definition_ids…) |
400 VALIDATION_ERROR en sort_by | Esa columna no es ordenable en ese reporte — el mensaje de error lista los valores aceptados |
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 |
503 SERVICE_UNAVAILABLE | Reintentá con backoff — el token sigue siendo válido, no vuelvas a iniciar sesión |
Estrategia de reintentos
Para errores transitorios (429, 500 y 503), 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)