Una API de acortador de URL es una de las integraciones más pequeñas en el backlog típico de un equipo de ingeniería. Tres endpoints, una cabecera de autenticación, un payload JSON. La página de documentación promete la primera llamada en cinco minutos. Luego llega el tráfico de producción, la lógica de reintentos crea enlaces duplicados, el dashboard se llena de variantes /foo-1, /foo-2, /foo-3 del mismo destino, y alguien abre un ticket.
Este artículo recorre la integración real. Autenticación, la primera llamada, los cuatro endpoints que cubren la mayoría de los casos de uso, idempotencia, gestión de errores, límites de tasa y los detalles de producción que la guía rápida de cinco minutos se salta. Ejemplos de código en TypeScript, Python, Go, Ruby y PHP: los tres primeros a través de los SDKs oficiales (@elido/sdk, elido-python, github.com/elido/elido-go), los dos últimos a través de clientes HTTP simples.
Requisitos previos
Inicia sesión en el dashboard, navega a /dashboard/api-keys y crea una clave de API (empieza por elido_). Los tokens están limitados por workspace: un token emitido en el workspace A no puede crear enlaces en el workspace B. Los tokens de usuario-máquina (para sistemas de CI, herramientas internas, integración máquina a máquina) se crean en /dashboard/machine-users y rotan de forma independiente a las claves personales. Ambos tipos llevan un rol de workspace preestablecido (viewer, editor o admin) en lugar de permisos por endpoint, así que dale editor a un job de CI si solo crea enlaces. La guía sobre permisos de las claves de API explica a qué puede acceder cada rol, incluido por qué los cambios en webhooks requieren admin.
La URL base es https://api.elido.app/v1. Los dominios de redirección (f.elido.me, s.elido.me, b.elido.me) son independientes de la superficie de la API. Tus enlaces cortos se resuelven en el dominio de redirección; la API es para crearlos, modificarlos y leerlos.
La especificación OpenAPI se publica en https://elido.app/openapi.json y cumple con OpenAPI 3.1. Los SDKs oficiales se generan a partir de esa especificación y se vuelven a publicar con cada versión de la API; también puedes generar tu propio cliente en cualquier lenguaje compatible con OpenAPI.
La primera llamada
Crea un enlace corto a partir de la URL de destino. Cinco líneas en TypeScript:
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
const link = await elido.links.create({
destinationUrl: "https://shop.example.com/spring-sale",
});
console.log(link.shortUrl); // https://s.elido.me/abc123
Python:
from elido import Elido
client = Elido(token=os.environ["ELIDO_TOKEN"])
link = client.links.create(
destination_url="https://shop.example.com/spring-sale",
)
print(link.short_url) # https://s.elido.me/abc123
Go:
import "github.com/elido/elido-go/v2/elido"
client := elido.NewClient(elido.WithToken(os.Getenv("ELIDO_TOKEN")))
link, err := client.Links.Create(ctx, &elido.LinkCreateInput{
DestinationURL: "https://shop.example.com/spring-sale",
})
if err != nil {
return fmt.Errorf("create link: %w", err)
}
fmt.Println(link.ShortURL)
Ruby (sin SDK oficial, usando net/http):
require "net/http"
require "json"
uri = URI("https://api.elido.app/v1/links")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV['ELIDO_TOKEN']}"
req["Content-Type"] = "application/json"
req.body = { destination_url: "https://shop.example.com/spring-sale" }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
link = JSON.parse(res.body)
puts link["short_url"]
PHP (Guzzle):
$client = new GuzzleHttp\Client(['base_uri' => 'https://api.elido.app/v1/']);
$res = $client->post('links', [
'headers' => ['Authorization' => 'Bearer ' . getenv('ELIDO_TOKEN')],
'json' => ['destination_url' => 'https://shop.example.com/spring-sale'],
]);
$link = json_decode((string) $res->getBody(), true);
echo $link['short_url'];
Los cinco producen el mismo resultado. El cuerpo de la respuesta contiene la URL corta, el ID canónico del enlace, el ID del workspace y la marca de tiempo de creación. El slug (abc123 en el ejemplo anterior) lo genera el servidor a menos que pases slug en la solicitud. El alfabeto del slug es base62 ([0-9A-Za-z]); la longitud por defecto es de seis caracteres.
Los cuatro endpoints que realmente vas a usar
La API tiene más de cuatro endpoints, pero la mayoría de las integraciones se quedan dentro de este conjunto.
Crear un enlace
POST /v1/links acepta la URL de destino más campos opcionales:
slug: un slug que eliges (debe ser único en el dominio).domain_id: para enlaces de dominio personalizado; en/v1/linksse usa el dominio corto predeterminado de tu plan si se omite. La ruta con ámbito de workspace/v1/workspaces/{workspace_id}/linkslo requiere.title: una etiqueta que se muestra en el dashboard.tags: un array de cadenas libres para organización.expires_at: marca de tiempo RFC 3339 tras la cual el enlace devuelve 410 Gone.redirect_status:301,302(el predeterminado) o307.password: todavía no se acepta al crear; establécela con unPATCHjusto después, y la redirección mostrará una página con contraseña antes de reenviar.utmymetadata: previstas. Hoy, incluye los parámetros UTM directamente endestination_urly guarda tus propias claves de unión entags.
El slug personalizado es el campo que muerde a los equipos en producción. Si pasas un slug ya usado por otro enlace en el mismo dominio, la API devuelve 409 Conflict. El manejador de reintentos ingenuo que añade un contador (my-slug-1, my-slug-2) produce el problema de enlaces duplicados descrito en la introducción. El comportamiento de reintento correcto se describe en la sección de idempotencia más abajo.
Leer un enlace
GET /v1/links/{id} devuelve el registro completo del enlace, incluyendo short_url y toda la configuración. Los conteos de clics no están en el registro del enlace; proceden de los endpoints de analítica que aparecen más abajo. El ID del enlace es el identificador canónico: los slugs pueden cambiar, los IDs no.
GET /v1/links?host=…&tags=…&limit=… lista los enlaces del workspace con filtros. La paginación se basa en cursor; next_cursor en la respuesta es opaco y se pasa de vuelta como el parámetro de consulta cursor en la siguiente solicitud.
Actualizar un enlace
PATCH /v1/links/{id} acepta los mismos campos que la creación. Las actualizaciones más comunes: cambiar la URL de destino (útil para rotar campañas sin reimprimir códigos QR), cambiar etiquetas, extender expires_at. El slug se cambia mediante el mismo PATCH, enviando un nuevo slug. El slug antiguo deja de resolverse de inmediato; un endpoint de renombrado específico que mantenga una redirección 301 desde el slug antiguo durante un periodo de retención está previsto, pero aún no existe.
Eliminar un enlace
DELETE /v1/links/{id} realiza un borrado lógico y devuelve 204 No Content. El enlace deja de redirigir y desaparece de las llamadas de listado y lectura. Una vista de papelera con un endpoint de restauración y una ventana de 90 días antes de la eliminación definitiva están previstos; hoy no existe ninguna llamada de API que permita recuperar un enlace eliminado.
Claves de idempotencia
Cada solicitud mutante (POST, PATCH, DELETE) acepta una cabecera Idempotency-Key. El valor de la cabecera es una cadena opaca de hasta 255 caracteres; el servidor almacena el cuerpo de la respuesta y el código de estado durante 24 horas, indexados por (workspace_id, idempotency_key), y devuelve la respuesta almacenada si se presenta la misma clave otra vez.
Los SDKs oficiales generan claves de idempotencia automáticamente cuando no se proporcionan. Puedes anularlas:
const link = await elido.links.create(
{ destinationUrl: "https://shop.example.com/spring-sale" },
{ idempotencyKey: "order-12345-link" },
);
El caso de uso es un bucle de reintentos. Si tu job crea un enlace como parte del procesamiento de un pedido que viene antes en la cadena, genera la clave de idempotencia a partir del ID del pedido. Un reintento del mismo job ve la misma clave, encuentra la caché de idempotencia, y devuelve el enlace creado originalmente en vez de producir uno segundo.
El detalle clave: la caché de idempotencia vive durante 24 horas, no para siempre. Un reintento al tercer día de un job atascado creará un enlace nuevo. Si la integración se ejecuta en lotes de varios días, guarda el ID del enlace devuelto por la primera creación exitosa y búscalo antes de volver a emitir la solicitud.
Un segundo detalle: la idempotencia es por workspace. La misma clave en dos workspaces crea dos enlaces. Esta es la semántica correcta para una API multi-workspace, pero puede sorprender a los equipos que asumen que la clave es globalmente única.
Gestión de errores
La API devuelve códigos de estado HTTP estándar más un cuerpo de error estructurado:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Workspace rate limit of 100 req/s exceeded. Retry after 1 second.",
"request_id": "req_01HXYZAB123",
"retry_after": 1
}
}
Los códigos que verás con más frecuencia:
400 invalid_request: fallo de validación del payload. El campomessageenumera los campos específicos. No reintentes; corrige el payload.401 unauthorized: token ausente o inválido. No reintentes sin rotar el token.403 forbidden: el rol del token no permite la acción (una claveviewerno puede crear enlaces). Revisa el rol de la clave en/dashboard/api-keys.404 not_found: el recurso no existe o el token no tiene acceso a él (devolvemos 404 en vez de 403 para evitar filtrar la existencia del recurso a llamadores no autorizados).409 conflict: slug ya en uso, o edición simultánea detectada (PATCH sobre una versión desactualizada). Vuelve a obtener el recurso y reintenta.429 rate_limit_exceeded: espera según el valor deretry_after.500 internal_server_error: fallo del lado del servidor. Es seguro reintentar con la misma clave de idempotencia.502 bad_gateway,503 service_unavailable,504 gateway_timeout: problemas de infraestructura transitorios. Espera y reintenta.
Los SDKs oficiales implementan retroceso exponencial con jitter para 429, 500, 502, 503 y 504. No reintentan 400, 401, 403, 404 ni 409: esos son errores de programación o conflictos de lógica de negocio, no fallos transitorios. Los clientes HTTP personalizados deberían seguir el mismo patrón; reintentar un 400 con el mismo payload no producirá un resultado distinto.
El request_id en el cuerpo del error es el campo que hay que incluir en los tickets de soporte. Podemos rastrear cualquier solicitud a partir de ese ID a través del registro de auditoría, el log de la aplicación y las métricas de la plataforma, y no podemos rastrear una solicitud sin él.
Límites de tasa
Los límites de tasa publicados son 100 solicitudes por segundo por workspace en Pro, 500 en Business, y un límite negociado en Enterprise. El nivel gratuito es de 10 req/s.
El estado del límite de tasa se expone en tres cabeceras de respuesta en cada respuesta de la API:
X-RateLimit-Limit: el límite por segundo actual.X-RateLimit-Remaining: solicitudes restantes en el segundo actual.X-RateLimit-Reset: marca de tiempo Unix en la que se reinicia el cubo.
El límite de 100/s es una implementación de token bucket con una capacidad de ráfaga de 200, lo que significa que puedes emitir 200 solicitudes de golpe si el cubo está lleno, y luego asentarte en la tasa sostenida de 100/s. La mayoría de los jobs de creación de enlaces cortos caben cómodamente en la ráfaga; las integraciones intensivas en analítica que paginan a través de eventos de clic históricos se benefician del margen del nivel Pro.
Para operaciones masivas, el endpoint POST /v1/links/bulk acepta hasta 100 enlaces por solicitud y cuenta como una sola unidad de límite de tasa. Este es el endpoint correcto para cualquier job que cree más de un centenar de enlaces a la vez. Para el tratamiento más profundo sobre cómo marcar el ritmo contra el token bucket, qué códigos de estado reintentar, y cómo las claves de idempotencia evitan que los reintentos dupliquen enlaces, consulta límites de tasa, reintentos e idempotencia en producción.
Lo que hacen los SDKs que el HTTP simple no hace
Los SDKs oficiales incluyen cuatro cosas que se amortizan rápido:
- Reintento automático con retroceso para los códigos de estado reintentables.
- Generación de claves de idempotencia cuando no se proporcionan explícitamente.
- Errores tipados para que puedas hacer
catch (err) { if (err instanceof ElidoRateLimitError) { … } }en vez de analizar JSON en los bloques catch. - Iteradores de paginación para que los endpoints de listado expongan iteradores asíncronos o generadores en vez de exigir manejo manual del cursor.
El SDK de Go además expone el cliente HTTP subyacente para instrumentación, útil si quieres conectarlo a tu configuración de trazado existente. La página de la función API + SDKs del repositorio cubre la superficie completa; la referencia de la API se publica en /docs/api-reference.
Acceso a analítica
Los endpoints de analítica son de solo lectura y viven bajo /v1/workspaces/{id}/analytics/; la guía de la API de analítica de enlaces enumera todos los informes, sus parámetros y la forma de sus respuestas. Las consultas más comunes:
GET .../clicks/recent?from=…&to=…: clics individuales, del más reciente al más antiguo, paginados connext_cursor. Útil para pipelines de exportación.GET .../timeseries?from=…&to=…&interval=day: conteos de clics agrupados para un rango de tiempo;intervalpuede serhouroday, ytzestablece la zona horaria de las agrupaciones.GET .../breakdown/country?from=…&to=…: desglose geográfico.GET .../breakdown/referrer?from=…&to=…: desglose por referrer.
Los demás informes son summary, links/top, los desgloses restantes (host, device, browser, destination) y las listas principales (top-countries, top-regions, top-cities, top-referrers, top-destinations). from y to son fechas YYYY-MM-DD y to es exclusivo; añade link_id para limitar cualquier informe a un solo enlace, y limit para dimensionar los desgloses y las listas principales.
El feed de eventos de clic sin procesar es el más grande. Un workspace con 10 millones de clics al mes produce unos 600 MB de datos de eventos en JSON al mes. Para exportaciones a esta escala, la guía de exportación de analítica cubre el mecanismo de exportación masiva que evita el envoltorio JSON y transmite directamente desde el almacén de analítica.
Webhooks para eventos de enlaces
Los webhooks son el inverso del sondeo: en vez de que tú le preguntes a la API qué cambió, la API entrega los eventos de enlaces y dominios a tu endpoint. Configúralo en /dashboard/webhooks:
await elido.webhooks.create({
url: "https://your-app.example/webhooks/elido",
events: ["link.created", "link.updated", "link.expired"],
secret: process.env.WEBHOOK_SIGNING_SECRET,
});
Un evento click.created por cada clic está en la hoja de ruta pero todavía no disponible, así que hoy los datos de clics vienen de los endpoints de analítica. Cada entrega incluye una cabecera X-Elido-Signature (también enviada como X-Webhook-Signature) con el valor v1=<hex>: un HMAC-SHA256, con clave de tu secreto de endpoint, sobre el valor X-Webhook-Timestamp, un punto y el cuerpo de la solicitud sin procesar. Verifica la firma antes de procesar; sin ella, cualquier llamador puede enviar a tu endpoint de webhook y suplantar a Elido.
La semántica de entrega es "al menos una vez": una entrega fallida se reintenta con un retroceso de minutos, con tres intentos en total por defecto. Para la forma detallada y el comportamiento de reintento, el artículo webhooks frente a sondeo compara los dos patrones de integración.
Un ejemplo trabajado: automatización de campañas
La integración que motiva la mayoría de las adopciones de la API se ve así. Tu automatización de marketing crea una campaña en Customer.io o HubSpot. Un hook se dispara cuando se publica la campaña. Tu manejador crea el enlace corto, lo adjunta al registro de la campaña, y lo devuelve a la herramienta de gestión de campañas para sustituirlo en la plantilla de email.
En TypeScript:
import { Elido } from "@elido/sdk";
const elido = new Elido({ token: process.env.ELIDO_TOKEN! });
export async function onCampaignPublished(campaign: Campaign) {
const link = await elido.links.create(
{
destinationUrl: campaign.destinationUrl,
tags: [
"campaign",
`campaign:${campaign.id}`,
`batch:${campaign.batchId}`,
campaign.channel,
],
},
{
idempotencyKey: `campaign-${campaign.id}-link`,
},
);
await campaignStore.update(campaign.id, { shortUrl: link.shortUrl });
return link;
}
La clave de idempotencia se deriva del ID de la campaña. Si el hook de campaña publicada se dispara dos veces (ocurre: las entregas de webhooks son al menos una vez), la segunda llamada devuelve el mismo enlace sin crear un duplicado. Las etiquetas campaign: y batch: contienen tus propias claves de unión para que puedas correlacionar los eventos de clic de Elido con la campaña; está previsto añadir un campo metadata específico para esto. Los parámetros UTM deben estar en campaign.destinationUrl mientras el campo utm no esté disponible.
Para la atribución de campaña de principio a fin con plantillas UTM y reenvío de conversiones, la referencia de seguimiento de UTM recorre el pipeline completo.
Lo que todavía no está en la API
Dos cosas que se preguntan con frecuencia, actualmente no disponibles:
- Un único GET de analítica por enlace que devuelva todos los desgloses en una sola llamada. El modelo actual requiere llamadas separadas para clics, país, referrer, dispositivo y serie temporal. La agregación está en la hoja de ruta; por ahora, ejecuta las solicitudes en paralelo desde tu propio código.
- Reenvío (replay) de webhooks desde la API. El dashboard expone el historial de entregas de webhooks y admite el reenvío; la API todavía no. Esto también está en la hoja de ruta.
Si una funcionalidad está en la especificación OpenAPI, está soportada. Si está en este artículo pero no en la especificación, trátala como prevista en vez de garantizada.
Lecturas relacionadas
- Smart links explicados: la referencia principal del cluster de funciones; cubre cómo el motor de redirección resuelve un enlace en el edge.
- Webhooks frente a sondeo para seguimiento de clics: cuándo usar qué patrón de integración.
- Seguimiento de conversiones del lado del servidor vía enlaces cortos: extendiendo la API al flujo de reenvío de conversiones.
- Importación masiva de campañas desde Google Sheets: un ejemplo trabajado del endpoint masivo.
- API de acortador de URL: límites de tasa, reintentos, idempotencia: reforzando la integración para tráfico de producción.
- Permisos de las claves de API para herramientas de enlaces: claves vinculadas al workspace, límites de los roles y rotación.
- API gratuita de acortador de URL: ejemplos de código que funcionan: la llamada de creación en curl, JavaScript, Python y Go, y lo que limitan los niveles gratuitos.
- API de analítica de enlaces: extrae estadísticas de clics con una clave de API: todos los informes, sus parámetros de consulta y un script diario para Slack.
- Recorrido operativo: la guía del servidor MCP para conectar la superficie de la API de Elido a Claude, Cursor y otros clientes compatibles con MCP.
- Superficie del producto:
/features/api-sdksy/solutions/developers.
Prueba Elido
Pega una URL, obtén un enlace corto
Sin registro. El enlace vive 30 días. Crea una cuenta para conservarlo.
Gratis, sin registro · 2 por día