Tres endpoints, una cabecera de autenticación, un cuerpo JSON. Una API de acortador de URL es una de las integraciones más sencillas de cualquier backlog, y el quickstart te da un enlace corto funcionando en pocos minutos. Lo que el quickstart omite es todo lo que ocurre cuando la integración funciona a volumen: el limitador de tasa empujando hacia atrás, un 503 transitorio en medio de un lote, una cola de trabajos que entrega el mismo mensaje dos veces. Gestionarlos mal produce enlaces duplicados, trabajo perdido y una tormenta de 429 que empeora las cosas.
Esta entrada es el complemento de endurecimiento en producción del quickstart de API. Cubre los tres mecanismos que separan una demo de una integración fiable: límites de tasa y cómo ajustar el ritmo contra ellos, qué errores reintentar y cómo hacer backoff, y claves de idempotencia que evitan que un reintento cree un segundo enlace. Los ejemplos usan la API de Elido, pero los patrones son los mismos con cualquier API de acortador de enlaces bien construida. Si tratas los enlaces cortos como infraestructura que gestionas desde código, el caso más amplio para eso está en enlaces cortos como Terraform.
Límites de tasa: un token bucket y una cabecera Retry-After
Elido mide la API con un token bucket, con alcance por espacio de trabajo. Las tasas sostenidas son 10 solicitudes por segundo en Free, 30 en Pro y 100 en Business, con capacidades de burst de 20, 60 y 200. En Pro, por ejemplo, un bucket lleno te deja lanzar 60 solicitudes de golpe antes de que la tasa vuelva a las 30 por segundo sostenidas. Encima hay un bucket aparte por clave de API, así que una clave ruidosa no se come todo el espacio de trabajo. La mayoría de los trabajos de creación de enlaces caben en el burst y nunca notan el límite.
No tienes que adivinar cuándo salta un límite. Una solicitud rechazada lleva tres señales:
Retry-After- cuántos segundos esperar antes de reintentar (ahora1).X-RateLimit-Scope- qué bucket saltó:workspaceoapi_key.- El cuerpo JSON -
{"error":"workspace rate limit exceeded"}, que nombra el límite.
No hay cabeceras con los tokens restantes, así que regula el ritmo en el cliente: mantén la concurrencia en o por debajo de la tasa sostenida de tu plan y, al primer 429, detente y espera Retry-After en lugar de correr contra un muro de rechazos. El ritmo previsor mantiene el rendimiento fluido; reintentar a ciegas tras cada rechazo desperdicia viajes y, si todos los clientes reintentan al mismo instante, fabrica una estampida.
Cuando necesites crear muchos enlaces, no hagas un bucle sobre el endpoint de creación individual. POST /v1/workspaces/{workspace_id}/links/bulk acepta hasta 100 enlaces en una solicitud. Una llamada bulk mueve cien enlaces en un solo viaje, mientras que cien llamadas individuales gastarían cien tokens y casi todo tu burst. Así es como la importación desde Google Sheets mueve los enlaces de una campaña sin activar el limitador.
Un 429 Too Many Requests - el estado que RFC 6585 reserva exactamente para esto - vuelve con una cabecera Retry-After que te dice cuántos segundos esperar. Respétala. Ese número es el limitador diciéndote cuándo habrá un token disponible, que es mejor información que cualquier suposición de tu backoff.
Reintentos: qué códigos y cómo hacer backoff
No todos los errores valen la pena reintentar, y reintentar el equivocado es cómo un fallo pequeño se convierte en una interrupción. Clasifica las respuestas en dos grupos.
Reintenta estos, porque son transitorios: 429 (fuiste demasiado rápido), y 500, 502, 503, 504 (un fallo del servidor o de la pasarela que puede resolverse por sí solo). No reintentes estos, porque la misma solicitud fallará igual: 400 (el payload no es válido), 401 (el token falta o es incorrecto), 403 (al token le falta el scope), 404 (el recurso no existe o no es tuyo), y 409 (conflicto de slug o una edición con versión obsoleta). El primer grupo es "espera e inténtalo de nuevo." El segundo es "arregla el código o la entrada." Reintentar un 400 en un bucle cerrado convierte un bug en un ataque de denegación de servicio contra ti mismo.
Para los códigos reintentables, el algoritmo que importa es el backoff exponencial con jitter. El backoff exponencial simple - duplicar la espera en cada intento - sigue sincronizando los clientes, porque todos los que fallaron en el mismo momento también reintentan en los mismos momentos. Añadir aleatoriedad los dispersa. El artículo de AWS sobre backoff exponencial y jitter es la referencia canónica y muestra por qué la versión con jitter reduce drásticamente la contención. Una versión compacta en TypeScript:
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
async function withRetry<T>(
call: () => Promise<Response>,
max = 5,
): Promise<Response> {
let attempt = 0;
while (true) {
const res = await call();
if (res.ok || !RETRYABLE.has(res.status) || attempt >= max) return res;
// Honor server guidance first; otherwise back off exponentially with full jitter.
const retryAfter = Number(res.headers.get("retry-after"));
const base =
Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: Math.min(1000 * 2 ** attempt, 20_000);
const wait = Math.random() * base; // full jitter
await new Promise((r) => setTimeout(r, wait));
attempt++;
}
}
Tres cosas hacen que esto sea seguro en lugar de peligroso. Limita los intentos, para que un fallo persistente falle visiblemente en lugar de girar para siempre. Respeta Retry-After cuando el servidor lo envía, recurriendo al backoff calculado solo cuando no lo hace. Y añade jitter, para que un grupo de workers recuperándose del mismo pico no estampe al unísono. Hasta ahora solo está publicado el SDK de Go; los SDK de TypeScript, Python y Ruby aún no están en sus registros de paquetes, así que en esos lenguajes este pequeño wrapper sobre HTTP simple es todo el cliente que necesitas.
Hay una regla que conecta los reintentos con la siguiente sección: un reintento de una creación solo es seguro si la creación es idempotente. De lo contrario, cada reintento arriesga crear un segundo enlace.
Idempotencia: cómo no crear enlaces duplicados
El fallo clásico es así. Tu worker crea un enlace corto, el enlace se crea, pero el 200 nunca llega de vuelta - la conexión se cae en el viaje de regreso. El worker ve un timeout, asume un fallo y reintenta. Ahora tienes dos enlaces para una campaña. A escala, el panel se llena de /foo, /foo-1, /foo-2, y los duplicados sesgán todos los informes downstream.
Las claves de idempotencia cierran esa brecha. Envía una cabecera Idempotency-Key en una solicitud mutante - cualquier cadena de hasta 255 caracteres - y el servidor almacena la respuesta contra ella. Presenta la misma clave de nuevo y obtienes la respuesta original, código de estado y cuerpo, sin que la operación se ejecute dos veces. El patrón es el mismo que Stripe documenta para solicitudes idempotentes, y es la forma estándar de hacer que una red poco fiable sea segura para las escrituras.
El detalle que lo hace funcionar o fracasar es de dónde viene la clave. No generes una clave aleatoria por intento - eso anula el propósito, porque entonces cada reintento parece una operación nueva. Derívala de un identificador de negocio estable para que la misma acción lógica produzca siempre la misma clave:
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({ destination_url: order.landingUrl }),
}),
);
const link = await res.json();
Ahora un reintento del mismo trabajo lleva order-12345-link de nuevo, encuentra la respuesta almacenada y devuelve el enlace que ya existe. Exactamente un enlace por pedido, sin importar cuántas veces la cola lo reentregue. Esto es lo que te permite combinar el bucle de backoff anterior con creaciones de forma segura: el reintento y la clave de idempotencia son dos mitades de la misma garantía.
Dos límites a tener en cuenta. La clave tiene alcance por espacio de trabajo: la misma clave en dos espacios de trabajo crea dos enlaces, lo cual es correcto para una API multi-tenant pero sorprende a los equipos que asumen que las claves son globales. Y la caché no es permanente - en Elido dura 24 horas con clave en (workspace, key). Un reintento dentro de la ventana deduplica; un reintento tres días después, de un trabajo bloqueado que finalmente se procesó, creará un enlace nuevo. Para lotes de varios días, no dependas solo de la clave. Persiste el ID del enlace devuelto en el primer éxito y consúltalo antes de volver a emitir la creación. La IETF ha estado estandarizando esta cabecera en el borrador de Idempotency-Key, y la advertencia de la ventana de 24 horas también se menciona allí.
Si estás conectando una integración de API hoy y quieres que sobreviva a sus propios reintentos, empieza con un espacio de trabajo gratuito, genera un token de cuenta de servicio, y pon una clave de idempotencia en tu primera creación en lugar de añadirla después de que aparezcan los duplicados.
Uniendo todo
Una llamada de creación lista para producción es los tres mecanismos apilados. Ajusta el ritmo del cliente a la tasa sostenida de tu plan para que rara vez alcances un 429. Envuelve la llamada en un backoff con jitter que reintente solo los códigos transitorios y respete Retry-After. Lleva una clave de idempotencia derivada de un ID de negocio para que el reintento sea seguro. Con fetch simple, el wrapper de arriba cubre los dos primeros y tú solo proporcionas la clave:
export async function shortenForOrder(order: Order) {
const res = await withRetry(() =>
fetch(`https://api.elido.app/v1/workspaces/${WORKSPACE_ID}/links`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `order-${order.id}-link`,
},
body: JSON.stringify({
destination_url: order.landingUrl,
tags: [`order:${order.id}`],
}),
}),
);
if (res.status === 429) {
// withRetry gave up and we are still limited. Defer the job.
throw new RetryableJobError(Number(res.headers.get("retry-after")) || 1);
}
if (!res.ok) throw new Error(`create failed: ${res.status}`); // non-retryable
return res.json();
}
Nada de esto es exótico. Es la misma disciplina que merece cualquier API con muchas escrituras, aplicada a enlaces. La recompensa es una integración que hace lo correcto bajo carga en lugar de corromper silenciosamente tu inventario de enlaces. Para el lado de lectura de la misma API - extraer datos de clics sin azotar el limitador - los compromisos están en webhooks versus polling para el rastreo de clics, y la superficie completa de endpoints está en la página de API y SDKs y el resumen de soluciones para desarrolladores.
Más en el blog
- API de acortador de URL: quickstart de 30 minutos en cinco lenguajes
- Enlaces cortos como Terraform: gestionar enlaces como código
- Webhooks vs polling para el rastreo de clics
- Importar campañas en masa desde Google Sheets
- Conectar la API a Claude y Cursor a través de MCP
- API gratuita de acortador de URL: ejemplos de código que funcionan
Preguntas frecuentes
¿Cuáles son los límites de tasa de una API de acortador de URL?
En Elido los límites son por espacio de trabajo y dependen del plan, con un token bucket: 10 solicitudes por segundo en Free (burst 20), 30 en Pro (burst 60) y 100 en Business (burst 200). Encima hay un bucket aparte por clave de API. Cuando se supera un límite, la API devuelve 429 con las cabeceras Retry-After y X-RateLimit-Scope, que indica qué bucket se alcanzó, así el cliente sabe cuánto esperar. Las creaciones en bloque admiten hasta 100 enlaces por solicitud, así que el endpoint bulk es la herramienta adecuada para trabajos grandes.
¿Qué errores de API debería reintentar?
Reintenta solo los fallos transitorios: 429 (límite de tasa alcanzado), 500, 502, 503 y 504. Usa backoff exponencial con jitter y un límite de reintentos. Nunca reintentes 400, 401, 403, 404 ni 409 - son fallos de validación, problemas de autenticación o conflictos, y la misma solicitud volverá a fallar. Combina los reintentos con una clave de idempotencia para que un reintento de creación no produzca un enlace duplicado.
¿Qué es una clave de idempotencia y cuándo la necesito?
Una clave de idempotencia es una cadena opaca que envías en una solicitud mutante para que el servidor pueda reconocer un reintento y devolver el resultado original en lugar de actuar dos veces. La necesitas siempre que una operación de creación o actualización pueda reintentarse - lo cual es siempre, porque las redes fallan y las colas de trabajos entregan al menos una vez. Deriva la clave de un identificador de negocio estable como un ID de pedido para que la misma operación lógica siempre lleve la misma clave.
¿Cómo evito crear enlaces cortos duplicados?
Envía una clave de idempotencia derivada de un identificador de negocio estable en cada creación. Si la llamada se reintenta, el servidor devuelve el enlace creado originalmente en lugar de generar uno nuevo. Para trabajos que pueden reintentarse días después - más allá de la ventana de caché de idempotencia - también almacena el ID del enlace devuelto en el primer éxito y consúltalo antes de volver a emitir la creación.
¿Cuánto dura una clave de idempotencia?
En Elido la caché de idempotencia dura 24 horas, con clave por espacio de trabajo más la clave. Un reintento dentro de esa ventana devuelve la respuesta almacenada; un reintento después se trata como una solicitud nueva y creará un nuevo recurso. Para trabajos en lote de varios días, persiste el ID del enlace resultante en el primer éxito en lugar de depender solo de la clave.
Prueba Elido
Pega una URL, obtén un enlace corto
Sin registro. El enlace vive 30 días. Crea una cuenta para conservarlo.
Sin registro. Crea una cuenta gratis y obtén 50 enlaces al mes.