Mejores prácticas para integrar APIs de terceros en aplicaciones de LATAM
Construir aplicaciones para Latinoamérica frecuentemente significa depender de APIs de terceros para datos que no existen en tu sistema — cotizaciones de cambio, validación de tax IDs, calendarios de datos geográficos y más. Si bien integrar un solo endpoint de API es sencillo, construir una integración de nivel producción que maneje errores con elegancia, rinda bien bajo carga y no desperdicie plata en llamadas innecesarias requiere ingeniería deliberada. En este artículo cubrimos las mejores prácticas clave que hemos visto adoptar a los equipos más exitosos enfocados en LATAM: rate limiting y estrategias de reintentos, patrones de caching, idempotencia, manejo de errores, manejo de respuestas multi-idioma, aislamiento del tráfico de pruebas respecto de producción, y monitoreo. Estos patrones aplican ya sea que estés integrando con Normadata, APIs gubernamentales como AFIP o SAT, procesadores de pagos o cualquier otro servicio externo.
Rate limiting y estrategias de reintentos
Toda API tiene rate limits, estén documentados o no. Alcanzar esos límites en producción significa que tus requests se throttlean o rechazan, lo que puede escalar en errores visibles para el usuario. La mejor defensa es una combinación de gestión proactiva de rate y reintentos inteligentes. Primero, trackeá tu uso contra los límites conocidos. Si una API permite 100 requests por minuto, construí un contador del lado del cliente que encole requests cuando te acerques al umbral en lugar de golpear el límite y manejar errores 429 reactivamente. Segundo, implementá backoff exponencial con jitter para reintentos. Cuando un request falla con un status 429 o 5xx, esperá antes de reintentar — y aumentá el tiempo de espera con cada fallo subsiguiente. Agregar jitter aleatorio previene el problema de thundering herd donde todos tus requests reintentados golpean la API al mismo tiempo. Acá va una implementación práctica:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) return response.json();
if (response.status === 429 || response.status >= 500) {
if (attempt === maxRetries) {
throw new Error(`Failed after ${maxRetries + 1} attempts`);
}
// Backoff exponencial: 1s, 2s, 4s + jitter aleatorio
const baseDelay = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
await new Promise((r) => setTimeout(r, baseDelay + jitter));
continue;
}
// Error no reintentable (4xx)
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
}Estrategias de caching para diferentes tipos de datos
No todos los datos de API tienen los mismos requisitos de frescura. Las cotizaciones cambian durante el día, pero la lista de estados de un país no cambia en años. Ajustar la duración de tu cache a la volatilidad de los datos ahorra llamadas a la API (y plata) sin sacrificar precisión. Para cotizaciones, un cache de 30-60 minutos es razonable para mostrar precios, pero las transacciones deberían usar la cotización más fresca disponible. Para resultados de validación de tax IDs, cacheá el resultado por 24 horas usando el input exacto como clave — un CUIT que era válido esta mañana sigue siendo válido esta noche. Para datos de referencia como países, estados, ciudades y listas de bancos, cacheá agresivamente — 24 horas o más. Estos datos cambian raramente, y podés invalidar en cada deploy. Para datos de cacheá por país por año. Los dentro de un año dado no cambian después de que el gobierno los pública (con raras excepciones de puente anunciados a mitad de año). El principio clave es: mientras más estáticos sean los datos, más tiempo deberías cachearlos. Usá los headers de cache devueltos por la API (Cache-Control, ETag) cuando estén disponibles, y recurrí a tu propia lógica de TTL cuando no lo estén.
Idempotencia para llamadas de validación
Los endpoints de validación como POST /v1/validate/tax-ids son inherentemente idempotentes — enviar el mismo CUIT dos veces siempre va a devolver el mismo resultado. Esto significa que podés reintentar de forma segura llamadas de validación fallidas sin efectos secundarios. Pero también significa que deberías deduplicar en la capa de aplicación. Si tu frontend envía dos requests rápidos de validación para el mismo CPF (por ejemplo, porque el usuario hizo doble clic en un botón), tu backend debería reconocer el duplicado y devolver el resultado cacheado en lugar de hacer dos llamadas a la API. Un cache simple en memoria usando el payload del request como clave funciona bien para esto. Acá va un patrón mínimo:
const validationCache = new Map();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 horas
async function validateTaxId(country, type, value) {
const cacheKey = `${country}:${type}:${value}`;
const cached = validationCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.result;
}
const response = await fetchWithRetry(
"https://api.normadata.io/v1/validate/tax-ids",
{
method: "POST",
headers: {
"X-API-Key": process.env.NORMADATA_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ value, country, type }),
}
);
validationCache.set(cacheKey, {
result: response,
timestamp: Date.now(),
});
return response;
}Manejo de respuestas multi-idioma
Las aplicaciones de LATAM típicamente necesitan soportar al menos español y portugués, y frecuentemente inglés también. Al integrar APIs, considerá cómo fluyen los datos localizados a través de tu sistema. Algunas APIs devuelven datos en un solo idioma — por ejemplo, los nombres de pueden venir en el idioma local del país (español para Argentina, portugués para Brasil). Tu capa de UI necesita manejar esto: mostrar el nombre localizado directamente, mapearlo a una clave de traducción, o usar una API que provea nombres tanto locales como en inglés. La API de Normadata, por ejemplo, devuelve tanto un nombre local como un campo name_en para cada feriado. Planificá tu modelo de datos para almacenar ambos cuando estén disponibles. Para mensajes de error de cara al usuario, nunca muestres strings de error crudos de la API a usuarios finales. Mapeá códigos de error a tus propios mensajes localizados. Un error como err_validation_failed debería traducirse a "El CUIT ingresado no es válido" en español o "The CUIT entered is not valid" en inglés, usando tu sistema de i18n.
Aislando el tráfico de pruebas del de producción
Un error común es usar una sola API key tanto para desarrollo como para producción. El patrón más limpio es aprovisionar keys separadas por entorno — una para desarrollo local, otra para staging, otra para producción — y cargarlas vía variables de entorno. Esto mantiene las llamadas de prueba visibles en los reportes de uso sin contaminar las métricas de producción, y te permite revocar una key de dev filtrada sin afectar a tus clientes. Para tests unitarios, no llames a la API en vivo: stubbeá el cliente HTTP y aseverá contra tus propias fixtures. Reservá las llamadas reales a la API para tests de integración, corrélos con una key dedicada, y gateálos para que se ejecuten solo on demand (por ejemplo, en CI nocturno en vez de en cada PR).
- Usá una key dedicada por entorno (dev, staging, prod) y cargala desde env vars.
- Stubbeá el cliente de API en tests unitarios para que corran offline y determinísticamente.
- Reservá las llamadas reales a la API para tests de integración, gateadas detrás de un flag y una key dedicada.
- Nunca commitees keys al repositorio — rotalas inmediatamente si una se expone.
Monitoreo y alertas
Una vez que tu integración está en producción, necesitás visibilidad sobre su salud. Trackeá cuatro métricas clave para cada API externa de la que dependés: tiempo de respuesta (p50 y p99), tasa de error (porcentaje de respuestas no-2xx), disponibilidad (¿podés llegar a la API?) y uso de cuota (qué tan cerca estás de tu rate limit). Configurá alertas sobre estas métricas para enterarte cuando algo se degrada antes de que tus usuarios lo reporten. Acá va un wrapper de monitoreo mínimo que loguea estas métricas:
async function monitoredFetch(name, url, options) {
const start = Date.now();
let status = "unknown";
try {
const response = await fetch(url, options);
status = response.status;
// Loguear métricas a tu sistema de monitoreo
logMetric({
api: name,
endpoint: url,
status,
latency_ms: Date.now() - start,
success: response.ok,
});
return response;
} catch (error) {
status = "network_error";
logMetric({
api: name,
endpoint: url,
status,
latency_ms: Date.now() - start,
success: false,
error: error.message,
});
throw error;
}
}Conclusión
Construir integraciones de API confiables en Latinoamérica requiere la misma disciplina de ingeniería que cualquier sistema de producción, con la complejidad adicional de datos volátiles, requisitos multi-idioma y sistemas gubernamentales diversos. Los patrones cubiertos acá — backoff exponencial, caching apropiado a los datos, validación idempotente, manejo de errores con awareness de localización, testing aislado por entorno y monitoreo de producción — forman una base sólida para cualquier aplicación enfocada en LATAM. Las APIs de Normadata están diseñadas con estos patrones en mente: códigos de error consistentes, respuestas amigables con cache y headers de rate limit completos. Visitá la documentación de la API para explorar la referencia completa, o consultá la página del producto Validate para ver qué podés validar en Sudamérica.