Hoy se lanzó la tercera fuente de migración en nuestro despliegue de nivel 3. Pega una clave de API de Short.io, elige el dominio de origen de Short.io (ej. example.short.gy), elige el dominio de destino de Elido, haz clic en Start. De tres a seis minutos después, cada enlace reside en tu dominio de Elido con el slug preservado.
Esta publicación es el informe de ingeniería: qué es específico de Short.io, qué nos sorprendió de su API REST y por qué terminamos exponiendo trabajos por dominio en lugar de un lote por cuenta.
Por dominio, no por cuenta#
El modelo de datos de Short.io tiene un giro que dio forma a toda la UX del lanzador: los enlaces están organizados bajo dominios, y el endpoint /links pagina por dominio. No existe una llamada de "dame todos los enlaces de todos los dominios en esta cuenta".
Consideramos algunos diseños:
- A. Iterar cada dominio en el lado del servidor, presentar un trabajo al usuario. Más rápido desde la perspectiva de número de clics; más difícil exponer el progreso y la elección de estrategia de conflicto por dominio.
- B. Un trabajo de Elido por dominio de origen. Más lento en clics (el usuario ejecuta N trabajos para N dominios), pero cada trabajo tiene un contrato claro: un dominio de origen → un dominio de destino → una estrategia de conflicto.
- C. Listar cada dominio, permitir al usuario selección múltiple, poner en cola N trabajos en el lado del servidor.
Lanzamos B y dejamos C para la iteración del plan de despliegue. El lanzador pide el nombre de host del dominio de origen como un campo de texto (sin menú desplegable; la lista /domains de Short.io es barata de llamar pero añade un round-trip y el usuario siempre conoce su propio nombre de host de dominio). Un trabajo por dominio, puesto en cola desde el dashboard uno a la vez.
La ventaja del tamaño de página#
Short.io pagina a 150 enlaces por llamada por defecto, la más generosa de nuestras cinco fuentes de migración. Compara:
- Bitly: 100 por página
- Rebrandly: 25 por página
- TinyURL: 100 por página (Pro/Bulk)
- Dub.co: 100 por página
- Short.io: 150 por página
Un dominio de Short.io de 5,000 enlaces toma 34 round-trips. Una cuenta de Rebrandly de 5,000 enlaces toma 200. El worker pasa la mayor parte de su tiempo de ejecución esperando respuestas HTTP, así que esto importa: Short.io es empíricamente la fuente de migración más rápida que soportamos.
const shortioPageSize = 150
page := 1
for {
resp, err := w.fetchPage(ctx, opts.Token, opts.DomainID, page)
if err != nil { /* mark failed */ return }
if len(resp.Links) == 0 { break }
for _, link := range resp.Links { /* import */ }
if !resp.HasMore { break }
page++
}
HasMore es un booleano que Short.io devuelve explícitamente; sin análisis de cursor, sin persecución de last-id. Su API es una de las mejor diseñadas de los cinco proveedores que soportamos.
Enlaces privados: qué hacemos#
Short.io tiene un flag "private" por enlace. Importamos los enlaces privados como enlaces de Elido con is_active=false para que el slug no se resuelva en el edge. El usuario los activa selectivamente desde el dashboard después de verificar la importación.
El razonamiento: si un enlace de Short.io era privado en el origen, la intención del usuario era que no se resolviera públicamente. Importarlo como is_active=true mostraría URLs que estaban deliberadamente protegidas. Importarlo como is_active=false mantiene el slug reservado pero inaccesible hasta que el usuario decida; estrictamente más seguro que la alternativa.
isActive := !link.Private
linkID, err := w.links.InsertImported(ctx, sqldb.InsertImportedLinkParams{
WorkspaceID: job.WorkspaceID,
DomainID: job.TargetDomainID,
Slug: slug,
DestinationURL: link.OriginalURL,
Title: truncate(link.Title, 250),
Tags: append(link.Tags, "imported:shortio"),
IsActive: isActive,
CreatedByUserID: createdByUserID,
})
Esta es una pequeña diferencia de superficie respecto a Bitly (sin flag equivalente) y Rebrandly (sin flag equivalente). Vale la pena mencionarlo en la receta post-importación para que el usuario entienda por qué algunos enlaces importados no se resuelven inmediatamente.
Lo que no migramos#
Las configuraciones de prueba A/B de Short.io no tienen una exportación limpia; son un constructor dentro de la aplicación que no muestra una forma JSON determinista a través de la API REST. Reconstrúyelas como reglas de smart-link de Elido post-importación; la sintaxis es más expresiva pero el modelo mental es el mismo.
El historial por clic es el límite universal en todas las fuentes de migración. Los datos por clic de Short.io viven en su exportación de analíticas, que es solo para el plan Team (accedido el 2026-05-22) y se muestran como contadores agregados en lugar de eventos por clic. Los nuevos clics aterrizan en las analíticas de Elido desde el cambio.
Los diseños de QR y los presets de UTM por enlace: la misma historia que Bitly y Rebrandly. Etiquetados como imported:shortio, listos para seguimiento masivo a través de campañas de Elido.
Traspaso de dominio#
El caso de uso interesante de Short.io es "estoy ejecutando un dominio de marca en Short.io y quiero moverlo a Elido sin cambiar la URL". La migración maneja el lado del enlace limpiamente; el lado del DNS es un cambio de CNAME.
Documentamos la secuencia de traspaso en la landing /migrate-from/shortio: mantén ambas superficies resolviendo en paralelo hasta que termine tu suscripción a Short.io, luego apunta el DNS a Elido. No hay urgencia de dar de baja Short.io el día que termina la importación.
Los dominios personalizados en Elido utilizan TLS on-demand de Caddy con domain-manager como fuente de lista blanca, por lo que el cambio es un cambio de CNAME más una llamada de API de verificación de dominio. Sin baile de certificados por parte del usuario.
Resolución de conflictos y el contrato del worker#
Idéntico a Bitly y Rebrandly: el sufijo recorre mylink-2, mylink-3, … en caso de colisión; omitir deja el enlace existente de Elido solo y registra la fila de origen; fallar aborta al primer conflicto. La búsqueda es una lectura indexada por fila.
El contrato del worker ( MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000 ) se comparte entre los cinco proveedores. Estas constantes hacen la mayor parte del trabajo y no son parámetros de configuración. Son el contrato que asume la interfaz de usuario de polling del dashboard.
Manejo de tokens#
bgCtx := context.WithoutCancel(r.Context())
go h.shortio.Run(bgCtx, job.ID, imports.ShortioJobOptions{
Token: req.Token,
DomainID: req.DomainID,
})
source_token_id permanece NULL. La misma semántica de un solo uso que Bitly y Rebrandly: el usuario pega el token una vez, el worker se ejecuta, el token se elimina de la memoria al finalizar. No lo persistimos porque el valor de la persistencia (uso recurrente) no aplica a las migraciones.
context.WithoutCancel mantiene al worker vivo más allá de la solicitud HTTP que lo inició. El mismo patrón que cualquier otro proveedor de migración en este despliegue.
Comparación con la ruta de exportación CSV#
Short.io expone una exportación CSV en los planes Team. Elegimos REST sobre CSV porque:
- REST preserva las etiquetas de Short.io estructuralmente. CSV las aplana en una cadena separada por comas que requiere división post-análisis.
- REST expone el flag
private. CSV no lo incluye consistentemente. - REST nos da un progreso determinista (enlaces vistos / enlaces restantes). CSV es una carga de archivo de un solo uso sin señal de progreso durante el proceso.
- REST es agnóstico al plan: todos los planes de Short.io exponen
/links. La exportación CSV es solo para Team.
La ruta CSV permanece en nuestra reserva para usuarios con cuentas antiguas de Short.io cuyo token de API ha sido revocado pero que todavía tienen un CSV de la última exportación.
Resumibilidad y el problema del despliegue#
El mismo compromiso que las dos primeras migraciones. El worker está en proceso; un despliegue a mitad de la importación mata la goroutine. El campo import_jobs.last_progress_at más el cron de barrido de bloqueos de 5 minutos cambia cualquier fila running sin progreso en los últimos 30 minutos a failed. Re-ejecutar es idempotente bajo sufijo y omitir.
Para cuentas con más de 10,000 enlaces en múltiples dominios de Short.io, el diseño de trabajo por dominio ayuda aquí: cada dominio está limitado por el presupuesto de 30 minutos independientemente, por lo que un despliegue a mitad del tercer dominio no pierde el trabajo de los dos primeros.
¿Qué sigue?#
Dos proveedores más por aterrizar:
- Dub.co —
GET /api/links?projectSlug=…&limit=100. Las carpetas se aplanan en etiquetas. La API más limpia de las cinco. - TinyURL — API REST Pro/Bulk a 100 por página. TinyURL gratuito no tiene API y nunca la ha tenido; eso sigue siendo una ruta manual.
Después de Dub y TinyURL, el despliegue de nivel 3 está terminado. Las cinco landings de migración (/migrate-from/bitly, /migrate-from/rebrandly, /migrate-from/shortio, /migrate-from/dub, /migrate-from/tinyurl) y las cinco publicaciones de blog de ingeniería cubren cada consulta de proveedor de la que un buscador de alternativas a Bitly podría llegar.
Si has estado retrasando una comparación con Short.io porque la historia de migración no estaba documentada, ahora lo está. Pruébalo: clave de API + dominio hasta el último enlace importado en menos de seis minutos para cuentas típicas.