La terza fonte di migrazione nel nostro rollout di Tier-3 è stata rilasciata oggi. Incolla una chiave API di Short.io, scegli il dominio di origine Short.io (es. example.short.gy), scegli il dominio di destinazione Elido, clicca su Avvia. Tre o sei minuti dopo, ogni link si trova sul tuo dominio Elido con lo slug preservato.
Questo post è il resoconto tecnico: cosa è specifico per Short.io, cosa ci ha sorpreso della loro API REST e perché abbiamo finito per esporre job per dominio invece di un batch per account.
Per dominio, non per account#
Il modello dati di Short.io ha una peculiarità che ha modellato l'intera UX del launcher: i link sono organizzati sotto domini e l'endpoint /links pagina per dominio. Non esiste una chiamata "dammi ogni link di ogni dominio in questo account".
Abbiamo considerato alcuni design:
- A. Iterare ogni dominio lato server, presentare un unico job all'utente. Più veloce dal punto di vista del numero di clic; più difficile esporre il progresso e la scelta della strategia di conflitto per dominio.
- B. Un job Elido per dominio di origine. Più lento nei clic (l'utente esegue N job per N domini), ma ogni job ha un contratto chiaro: un dominio di origine → un dominio di destinazione → una strategia di conflitto.
- C. Elencare ogni dominio, lasciare che l'utente effettui selezioni multiple, mettere in coda N job lato server.
Abbiamo rilasciato B e lasciato C per l'iterazione del piano di rollout. Il launcher richiede l'hostname del dominio di origine come campo di testo (nessun dropdown — l'elenco /domains di Short.io è economico da chiamare ma aggiunge un round-trip e l'utente conosce sempre l'hostname del proprio dominio). Un job per dominio, messo in coda dalla dashboard uno alla volta.
Il vantaggio della dimensione della pagina#
Short.io pagina a 150 link per chiamata per impostazione predefinita: la più generosa delle nostre cinque fonti di migrazione. Confronta:
- Bitly: 100 per pagina
- Rebrandly: 25 per pagina
- TinyURL: 100 per pagina (Pro/Bulk)
- Dub.co: 100 per pagina
- Short.io: 150 per pagina
Un dominio Short.io da 5.000 link richiede 34 round-trip. Un account Rebrandly da 5.000 link ne richiede 200. Il worker spende la maggior parte del suo tempo effettivo in attesa di risposte HTTP, quindi questo è importante: Short.io è empiricamente la fonte di migrazione più veloce che supportiamo.
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 è un booleano che Short.io restituisce esplicitamente: nessun parsing del cursore, nessun inseguimento dell'ultimo ID. La loro API è una delle meglio progettate tra i cinque vendor che supportiamo.
Link privati — cosa facciamo#
Short.io ha un flag "private" per link. Importiamo i link privati come link Elido con is_active=false in modo che lo slug non si risolva all'edge. L'utente li attiva selettivamente dalla dashboard dopo aver controllato a campione l'importazione.
La logica: se un link Short.io era privato alla fonte, l'intento dell'utente era di non farlo risolvere pubblicamente. Importarlo come is_active=true renderebbe visibili URL che erano deliberatamente limitati. Importarlo come is_active=false mantiene lo slug riservato ma irraggiungibile finché l'utente non decide: rigorosamente più sicuro dell'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,
})
Questa è una piccola differenza superficiale rispetto a Bitly (nessun flag equivalente) e Rebrandly (nessun flag equivalente). Vale la pena evidenziarlo nella procedura post-importazione in modo che l'utente capisca perché alcuni link importati non si risolvono immediatamente.
Cosa non migriamo#
Le configurazioni A/B split-test di Short.io non hanno un export pulito: sono un builder in-app che non espone una forma JSON deterministica tramite l'API REST. Ricostruisci come regole di smart-link di Elido post-importazione; la sintassi è più espressiva ma il modello mentale è lo stesso.
Lo storico per clic è il limite universale per ogni fonte di migrazione. I dati per-clic di Short.io vivono nel loro export di analisi, che è disponibile solo per il piano Team (consultato il 2026-05-22) ed emerge come contatori aggregati piuttosto che come eventi per-clic. I nuovi clic atterrano nelle analisi di Elido dal momento del cutover.
I design QR e i preset UTM per link — stessa storia di Bitly e Rebrandly. Taggo come imported:shortio, pronti per il follow-up in blocco tramite le campagne di Elido.
Passaggio del dominio#
L'interessante caso d'uso di Short.io è "Sto gestendo un dominio brandizzato su Short.io e voglio spostarlo su Elido senza cambiare l'URL". La migrazione gestisce il lato link in modo pulito; il lato DNS è una modifica CNAME.
Documentiamo la sequenza di passaggio sulla landing /migrate-from/shortio — mantieni entrambe le superfici in risoluzione in parallelo finché la tua iscrizione a Short.io non termina, quindi punta il DNS su Elido. Non c'è urgenza di disattivare Short.io il giorno in cui l'importazione termina.
I domini personalizzati in Elido usano il TLS on-demand di Caddy con domain-manager come fonte per la allow-list, quindi il cutover è una modifica CNAME più una chiamata API di verifica del dominio. Nessuna danza dei certificati da parte dell'utente.
Risoluzione dei conflitti e il contratto del worker#
Identico a Bitly e Rebrandly — il suffisso procede con mylink-2, mylink-3, … in caso di collisione; skip lascia intatto il link Elido esistente e registra la riga di origine; fail si interrompe al primo conflitto. La ricerca è una lettura indicizzata per riga.
Il contratto del worker — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000 — è condiviso tra tutti e cinque i vendor. Queste costanti fanno la maggior parte del lavoro e non sono manopole di configurazione. Sono il contratto che l'interfaccia di polling della dashboard assume.
Gestione dei token#
bgCtx := context.WithoutCancel(r.Context())
go h.shortio.Run(bgCtx, job.ID, imports.ShortioJobOptions{
Token: req.Token,
DomainID: req.DomainID,
})
source_token_id rimane NULL. Stessa semantica one-shot di Bitly e Rebrandly — l'utente incolla il token una volta, il worker viene eseguito, il token viene eliminato dalla memoria al completamento. Non lo persistiamo perché il valore della persistenza (uso ricorrente) non si applica alle migrazioni.
context.WithoutCancel mantiene il worker vivo oltre la richiesta HTTP che lo ha avviato. Stesso pattern di ogni altro vendor di migrazione in questo rollout.
Confronto con il percorso di export CSV#
Short.io espone un export CSV sui piani Team. Abbiamo scelto REST rispetto a CSV perché:
- REST preserva strutturalmente i tag di Short.io. CSV li appiattisce in una stringa separata da virgole che richiede uno splitting post-parsing.
- REST espone il flag
private. CSV non lo include in modo coerente. - REST ci fornisce un progresso deterministico (link visti / link rimanenti). CSV è un caricamento file one-shot senza segnale di progresso durante l'esecuzione.
- REST è piano-agnostico — ogni piano Short.io espone
/links. L'export CSV è solo per Team.
Il percorso CSV rimane nella nostra manica per gli utenti con account Short.io legacy il cui token API è stato revocato ma che hanno ancora un CSV dall'ultimo export.
Resumibilità e il problema del deploy#
Stesso compromesso delle prime due migrazioni. Il worker è in-process; un deploy a metà importazione uccide la goroutine. Il campo import_jobs.last_progress_at più il cron stuck-sweep di 5 minuti cambia qualsiasi riga running senza progressi negli ultimi 30 minuti a failed. La riesecuzione è idempotente sotto suffix e skip.
Per account sopra i 10.000 link su più domini Short.io, il design del job per dominio aiuta qui — ogni dominio è limitato dal budget di 30 minuti indipendentemente, quindi un deploy a metà del terzo dominio non perde il lavoro dai primi due.
Cosa c'è dopo#
Altri due vendor da aggiungere:
- Dub.co —
GET /api/links?projectSlug=…&limit=100. Le cartelle si appiattiscono in tag. L'API più pulita delle cinque. - TinyURL — Pro/Bulk REST API a 100 per pagina. TinyURL Free non ha API e non l'ha mai avuta; rimane un percorso manuale.
Dopo Dub e TinyURL, il rollout di Tier-3 è completato. Le cinque landing di migrazione (/migrate-from/bitly, /migrate-from/rebrandly, /migrate-from/shortio, /migrate-from/dub, /migrate-from/tinyurl) e i cinque post del blog tecnico coprono ogni query "vendor-da" su cui un cercatore di alternative a Bitly potrebbe atterrare.
Se hai rimandato un confronto con Short.io perché la storia della migrazione non era documentata, ora è documentata. Provalo — chiave API + dominio fino all'ultimo link importato in meno di sei minuti per account tipici.